Put a label on a worker pool, and every node that pool creates carries it. Pods then use `nodeSelector` or [node affinity](/docs/hetzner/apalla/servers-and-nodes/scheduling/node-affinity-and-selectors) (a rule that attracts pods to nodes with a specific label) to target that pool. ## Prerequisites - Access to the management cluster. See [Access the management cluster](/docs/hetzner/apalla/getting-started/access-the-management-cluster). - A `Cluster` object already applied. ## Add labels to a worker pool Each worker pool is one entry under `spec.topology.workers.machineDeployments`. Add a metadata.labels block Add it to the pool, at the same level as `name`, `replicas`, and `failureDomain`: ```yaml vars title="cluster.yaml" apiVersion: cluster.x-k8s.io/v1beta2 kind: Cluster metadata: name: mycluster namespace: my-org spec: topology: classRef: name: hetzner-apalla-1-36-v4 version: v1.36.4 controlPlane: replicas: 3 workers: machineDeployments: - class: workeramd64hcloud name: md-0 replicas: 3 failureDomain: fsn1 metadata: labels: node-role.kubernetes.io/backend: "" node.cluster.x-k8s.io/environment: production variables: overrides: - name: workerMachineTypeHcloud value: cpx42 ``` > [!NOTE] > Only add a `metadata:` block when you actually have labels to set. In v1beta2, do not add an empty `metadata: {}` placeholder. Apply the Cluster resource Apply it directly, or let GitOps sync it: ```console $ kubectl apply -f cluster.yaml ``` Syself reads `metadata.labels` from each pool and writes them onto the matching node objects in the workload cluster, on top of the labels it sets on every node automatically. See [Node labels and annotations](/docs/hetzner/apalla/reference/node-labels-and-annotations) for those system labels. Confirm the label landed ```console $ kubectl get nodes -l node-role.kubernetes.io/backend ``` This lists every node that carries the label, so you can check the pool reached the nodes you expect. ## Which labels reach the node Only labels in these domains reach the node: | Domain | Example | | -------------------------------- | ----------------------------------------------- | | `node-role.kubernetes.io` | `node-role.kubernetes.io/backend: ""` | | `node-restriction.kubernetes.io` | `node-restriction.kubernetes.io/location: fsn1` | | `node.cluster.x-k8s.io` | `node.cluster.x-k8s.io/environment: production` | A label in any other domain, for example `mycompany.com/tier: critical`, does not reach the node. Syself does not report an error; the label is silently dropped. > [!WARNING] > Role labels under `node-role.kubernetes.io/` use an **empty-string value** (`""`), not `"true"`. A `nodeSelector` or affinity rule on a pod must match the exact key and value. A pod selecting `node-role.kubernetes.io/backend: "true"` does **not** match a node labelled `node-role.kubernetes.io/backend: ""`. ## System labels Syself already sets You do not have to label a node to describe what it is. Syself Autopilot already tags every node with a handful of labels you can select on directly: | Label | Value | On | | --------------------------------------- | --------------------------------------------- | ------------------------ | | `autopilot.syself.com/machine-type` | `hcloud` or `baremetal` | every node | | `node.kubernetes.io/instance-type` | the server type, for example `cpx42` | every node | | `node-role.kubernetes.io/control-plane` | empty | control-plane nodes | | `node.kubernetes.io/worker` | `true` | worker nodes | | `instance.hetzner.cloud/is-root-server` | `true` | bare-metal nodes | | `topology.kubernetes.io/region` | location on cloud, network zone on bare metal | every node | | `topology.kubernetes.io/zone` | datacenter on cloud, location on bare metal | every node | | `autopilot.syself.com/gpu` | `true` | nodes with a working GPU | So before you invent a label, check whether one of these already says what you mean. The full list, and what sets each one, is in [Node labels and annotations](/docs/hetzner/apalla/reference/node-labels-and-annotations). ## Bare-metal pools also use host labels A bare-metal pool uses a separate set of labels on `HetznerBareMetalHost` resources to determine which physical servers the pool can claim. The pool uses `workerHostSelectorBareMetal` to select worker hosts, or `controlPlaneHostSelectorBareMetal` when selecting hosts for a bare-metal control plane. See [Add bare-metal servers](/docs/hetzner/apalla/servers-and-nodes/provision/add-bare-metal-servers) for instructions on labelling and selecting hosts. For an example of using both host and node labels in the same pool, see [Mix cloud and bare-metal pools](/docs/hetzner/apalla/servers-and-nodes/pools/mix-cloud-and-bare-metal). ## Multiple pools with different roles Each `machineDeployments` entry is a separate pool with its own labels. Add several entries to give different groups of nodes different roles. Machine type and labels are independent settings, so a staging pool can use a smaller machine type while carrying its own label. This manifest declares a production pool and a staging pool side by side: ```yaml workers: machineDeployments: - class: workeramd64hcloud name: md-production replicas: 3 failureDomain: fsn1 metadata: labels: node-role.kubernetes.io/backend: "" node.cluster.x-k8s.io/environment: production variables: overrides: - name: workerMachineTypeHcloud value: cpx42 - class: workeramd64hcloud name: md-staging replicas: 2 failureDomain: fsn1 metadata: labels: node-role.kubernetes.io/backend: "" node.cluster.x-k8s.io/environment: staging variables: overrides: - name: workerMachineTypeHcloud value: cpx32 ``` Each pool needs a unique `name`. > [!TIP] > You can mix cloud and bare-metal pools in the same cluster: `class: workeramd64hcloud` for cloud, `class: workeramd64baremetal` for bare metal. Each pool gets its own `metadata.labels`. See [Mix cloud and bare-metal pools](/docs/hetzner/apalla/servers-and-nodes/pools/mix-cloud-and-bare-metal). ## Related - [Node affinity and selectors in practice](/docs/hetzner/apalla/servers-and-nodes/scheduling/node-affinity-and-selectors): match pods to the labels you just set. - [Node taints are not supported](/docs/hetzner/apalla/servers-and-nodes/scheduling/apply-taints): why to use labels, not taints.