Skip to main content

Node affinity and selectors in practice

Inspect 1.36

Once your pools carry , 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:

yaml
		spec:
  nodeSelector:
    node-role.kubernetes.io/backend: ""
	
yaml
		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 "":

yaml
		# 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 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 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:

yaml
		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 . To spread replicas evenly as your node count changes, see .

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:

  1. Put the dedicated servers in their own and set a label in the pool's metadata. A pool label persists across node replacement; a kubectl label does not.
  2. Point the workload at that pool with a nodeSelector, or with when you need a rule rather than a plain match.
  3. Do not deploy anything else that selects the pool. You control what runs there.
yaml
		# 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 , a secure-runtime pod through its ), 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 . For a hard, enforced guarantee that nothing else can schedule onto a set of nodes, contact Syself.