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-v6 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 [Platform labels and annotations](/docs/hetzner/apalla/servers-and-nodes/labels/platform-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: ""`. ## 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/bare-metal/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.