Node taints
Syself Autopilot does not support durable node taints. A taint you set by hand does not survive, and there is no supported field to set one that sticks. Use labels and node affinity to control where pods run.
Why a taint does not stick#
A taint on a Node repels pods: only a pod with a matching toleration (a pod-side field that says "I accept this taint") can schedule there. It is the inverse of a label, which attracts pods that opt in.
Syself Autopilot builds on Cluster API (CAPI), which creates and replaces nodes from templates, declarative blueprints describing everything a new node should be. The limitation sits in Cluster API itself: its templates carry labels but have no taint field, so a taint can never be part of the blueprint a node is rebuilt from. A node is replaced on a cluster upgrade, a health-check recovery, or a manual removal (see Self-healing and node replacement for what triggers each). Each new node starts clean, so a taint you add with kubectl taint is gone after the next replacement, and there is no supported way to make it persist.
Warning
Do not use kubectl taint to control placement. It lasts only for the current node's lifetime and disappears the next time that node is replaced. Use labels and node affinity instead.
Note
This is being worked on upstream. Cluster API has an experimental proposal to propagate taints from the pool template to the node, so a taint would survive replacement. Syself is following that work closely and will support durable taints once the feature is tested and proven stable.
The control-plane taint is the one exception#
Control-plane nodes carry node-role.kubernetes.io/control-plane:NoSchedule. Syself Autopilot sets it at registration, and it is built into the control-plane template, so it persists across every replacement. It comes from the platform, not from a setting you apply, which is why ordinary workloads never land on control-plane nodes. It is the only taint on the cluster, and you do not manage it.
Use labels and node affinity instead#
Labels set in the Cluster manifest propagate to the Node object and survive replacement. Combine a label with a nodeSelector or node affinity on your pods, and you place pods exactly where you want, durably.
Label the pool #
Add the label under metadata.labels on the machineDeployments entry:
workers:
machineDeployments:
- class: workeramd64hcloud
name: md-gpu
replicas: 2
failureDomain: fsn1
metadata:
labels:
node-role.kubernetes.io/gpu-only: ""
Target it from a pod #
Use nodeSelector to pin the pod to nodes with that label:
spec:
nodeSelector:
node-role.kubernetes.io/gpu-only: ""
Only labels in the node-role.kubernetes.io, node-restriction.kubernetes.io, and node.cluster.x-k8s.io domains propagate from the Cluster manifest; anything outside those is silently dropped. See Label nodes and assign roles for the full rules, and Node affinity and selectors for required-vs-preferred affinity.
Tip
Syself Autopilot already sets some labels for you, so check whether one already describes the pool you want before adding your own: autopilot.syself.com/machine-type (baremetal or hcloud), autopilot.syself.com/gpu=true on nodes with an NVIDIA GPU, and instance.hetzner.cloud/is-root-server=true on bare-metal workers.
Keeping other pods off a pool#
Labels attract, they do not repel: a pod that sets no selector can still land on a labelled node. To reserve a pool, give it a role label, put a nodeSelector on the pods that belong there, and give every other workload its own pool to select, so nothing lands where it should not. For a boundary that does not depend on every pod being labelled correctly, a dedicated bare-metal pool is the hard separation; see Dedicate node pools per client .