Node affinity and selectors in practice
Once your pools carry labels , you can steer pods onto them. Use nodeSelector for the simple case, and node affinity when you need conditions. The one rule that trips people up: the pod's selector must match the label's exact key and value.
nodeSelector for the simple case#
nodeSelector pins a pod to nodes that carry every label listed:
spec:
nodeSelector:
node-role.kubernetes.io/backend: ""
spec:
nodeSelector:
autopilot.syself.com/machine-type: baremetal
Match the exact key and empty-string value#
Role labels under node-role.kubernetes.io/ use an empty-string value (""), not "true". A selector for "true" does not match a node labelled "":
# Node label set by the pool: node-role.kubernetes.io/backend: ""
nodeSelector:
node-role.kubernetes.io/backend: "true"
node-role.kubernetes.io/backend: ""
Note
Only labels in the node-role.kubernetes.io, node-restriction.kubernetes.io, and node.cluster.x-k8s.io domains reach the node from a pool definition. Any other label is silently dropped, so a selector for it never matches. See Label nodes and assign roles for the full rule.
Combine pool labels with system labels#
Syself also sets labels on every node before your pool's labels land. You can select on these without declaring them anywhere:
| Label | Value | Where |
|---|---|---|
autopilot.syself.com/machine-type | baremetal or hcloud | every node |
instance.hetzner.cloud/is-root-server | "true" | bare-metal nodes |
node.kubernetes.io/instance-type | for example cpx42 | every node |
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" | GPU nodes |
kubernetes.io/hostname | the node's hostname | every node |
nodeSelector requires every listed label to match. Combine one of your pool's labels with a system label to narrow further. This pod schedules only on a node that is in the backend pool and bare metal:
spec:
nodeSelector:
node-role.kubernetes.io/backend: ""
autopilot.syself.com/machine-type: baremetal
See Node labels and annotations for the full list.
Need rules, not just labels#
nodeSelector matches labels and stops there. When you need a hard-or-soft choice, matching operators like In or Exists, or placement relative to other pods, use node affinity and pod anti-affinity. Both live on Set affinity and anti-affinity , which owns required-versus-preferred node affinity and one-replica-per-node anti-affinity for databases and quorum members. To spread replicas evenly as your node count changes, reach for topology spread constraints instead.
The one thing that stays on this page: affinity rules read the same node labels a nodeSelector does, so the exact-key gotcha above and the system-label table apply to them too.
Why unmatched pods go elsewhere#
A selector or affinity rule attracts. It does not repel. A pod that sets no selector can still land on your labelled nodes. A pod whose preferred affinity cannot be met just schedules somewhere else.
A label alone does not reserve a pool. The supported way to reserve one is to give every other workload a different pool to select, so nothing else lands there. Syself Autopilot does not support node taints; see Node taints are not supported .