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 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 Autopilot also sets labels on every node before your pool's labels land, and you can select on those without declaring them anywhere. See Platform labels and annotations for the full list.
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
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, see Set affinity and anti-affinity . To spread replicas evenly as your node count changes, see topology spread constraints .
Affinity rules read the same node labels a nodeSelector does, so the exact-key rule above applies 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.
Reserve a pool for one workload
To hold a class of hardware such as a GPU, a bare-metal server, or a client's dedicated capacity for one workload, use a pool label instead of a taint:
- Put the dedicated servers in their own worker pool and set a label in the pool's
metadata. A pool label persists across node replacement; akubectllabel does not. - Point the workload at that pool with a
nodeSelector, or with node affinity when you need a rule rather than a plain match. - Do not deploy anything else that selects the pool. You control what runs there.
# The dedicated workload targets the pool's label
spec:
nodeSelector:
autopilot.syself.com/gpu: "true"
Note
A label reliably pulls the wanted pod onto the dedicated nodes and survives node replacement, but it cannot keep other pods off, which is something a taint would do. In practice this rarely matters: you control what you deploy, and scarce hardware is guarded by its own resource accounting (a GPU through nvidia.com/gpu , a secure-runtime pod through its runtime class ), so an unmarked pod that lands there only uses CPU and memory, never the GPU or secure runtime you had reserved for other workloads.
For a boundary that does not depend on every pod carrying the right selector, a dedicated bare-metal pool is the hard separation; see Dedicate node pools per client . For a hard, enforced guarantee that nothing else can schedule onto a set of nodes, contact Syself.
Related
Steer workload placement
A decision table that routes you to the right placement control, plus the default node and zone spread that already runs before you set anything.
Set affinity and anti-affinity
Require or prefer certain nodes, and co-locate or separate pods from each other, when a plain node selector is not enough.