Skip to content

feat(kubernetes): add labels and taints to worker pools - #4927

Draft
Aleksei Sviridkin (lexfrei) wants to merge 1 commit into
feat/kubernetes-kairos-poolsfrom
feat/kubernetes-nodes-labels-taints
Draft

Aleksei Sviridkin (lexfrei) wants to merge 1 commit into
feat/kubernetes-kairos-poolsfrom
feat/kubernetes-nodes-labels-taints

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Worker pools can now carry their own node labels and taints, for Talos and Kairos workers alike. Without a taint, GPU workers take ordinary workloads too. A nvidia.com/gpu:NoSchedule taint keeps them off and is tolerated by the GPU Operator's own DaemonSets by default; any other taint, or HAMi, needs the add-on tolerations set through addons.gpuOperator.valuesOverride or addons.hami.valuesOverride, or the pool is left without GPUs. The override replaces the whole tolerations list, so it has to keep the nvidia.com/gpu entry too. KubernetesNodes gets two fields, labels and taints.

Stacked on #4911 (Kairos worker pools), the base branch is feat/kubernetes-kairos-pools.

Labels in the domains Cluster API syncs to Nodes (node-restriction.kubernetes.io, node.cluster.x-k8s.io) go on the MachineDeployment template. Cluster API syncs those to the Nodes, so they change in place. Every other label and every taint goes through the pool's bootstrap config, so changing them rolls the pool. Taints use kubelet registration because Cluster API's own taint propagation is still behind an alpha feature gate.

Some values are refused before anything is applied. A malformed taint effect or key fails when Helm loads the values, against the field; other keys and values Kubernetes would reject fail at render time instead of in the reconcile Job. A node-role.kubernetes.io/<role> label is refused with a pointer to roles, which already sets node roles. Hostname, os and arch describe the node itself, so a pool can't override them. The deprecated beta zone and instance-type labels are refused too, and on Proxmox the zone and instance-type labels belong to its cloud controller manager. Taints in Kubernetes or Cluster API namespaces are refused. A pool without labels or taints renders byte for byte as before.

I tested it on a test cluster with one Talos and one Kairos pool. On both, a label in a Cluster API domain reached the Node without a rollout. A kubelet label and a taint rolled the pool, and the new Node carried both. A pod without the toleration stayed Pending, a pod with it ran on that Node. The Kairos node was also recreated once before the test, because the default worker disk changed in the test overlay. That rollout is unrelated to this change.

Reviewed by Opus. The last two passes on the same commit found nothing.

A decision record for the kubernetes-nodes-split proposal explains why this replaces the proposal's Talos-only userMachineConfig layer. It goes to cozystack/community separately.

Screenshots

No UI changes.

Downstream repositories

The trigger map reaches two repositories. Follow-ups are not opened yet, they are listed here for a maintainer to decide.

Release note

feat(kubernetes): tenant worker pools take `labels` and `taints`, for Talos and Kairos workers alike, so for example a `nvidia.com/gpu:NoSchedule` taint keeps ordinary workloads off GPU workers.

@coderabbitai

coderabbitai Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added area/kubernetes Issues or PRs related to the tenant Kubernetes app kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files labels Oct 14, 2026
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/kubernetes-nodes-labels-taints branch 2 times, most recently from 8e4cc75 to af8fd13 Compare October 18, 2026 21:19
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/kubernetes-nodes-labels-taints branch 2 times, most recently from 700b686 to edb15a6 Compare October 27, 2026 21:57
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/kubernetes-kairos-pools branch 2 times, most recently from 5b53c4f to 7d309f6 Compare October 29, 2026 13:55
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/kubernetes-nodes-labels-taints branch from edb15a6 to 66c6a82 Compare October 29, 2026 13:55
A pool can now carry its own node labels and taints, for Talos and for
Kairos workers alike, for example to keep ordinary workloads off GPU
workers with an nvidia.com/gpu NoSchedule taint, which the GPU
operator's daemonsets tolerate by default. A taint the GPU addons do
not tolerate keeps their device plugin off the pool and leaves it
without GPUs, so the taints description says what they tolerate.

Labels go where something will apply them. Cluster API syncs labels from
a Machine to its Node only under node-role.kubernetes.io,
node-restriction.kubernetes.io and node.cluster.x-k8s.io. Node roles
already have their own field, roles, so a node-role.kubernetes.io label
is refused rather than giving a pool two ways to set one; labels in the
other two domains go on the MachineDeployment template and change in
place. Every other label goes through the bootstrap config: on Talos,
machine.nodeLabels, which machined writes to the Node with the kubelet's
credentials; on Kairos, kubeadm's node-labels, where the kubelet admits
no other kubernetes.io or k8s.io label than the kubelet.kubernetes.io
and node.kubernetes.io domains and a few well-known keys. The chart
applies that rule on both OSes. Of the well-known keys it refuses
hostname, os and arch, which describe the node itself, and the
deprecated beta zone, region and instance-type keys, which the
cloud-node controller copies over the current ones. The current zone,
region and instance-type keys are accepted on KubeVirt and refused on
Proxmox, whose cloud controller manager sets them.

Taints are registered by the kubelet when the node joins: Talos
through the kubelet's registerWithTaints, Kairos through kubeadm's
nodeRegistration.taints. Cluster API v1.13 can also manage Machine
taints, but behind an alpha feature gate that is off. Both are part of
the bootstrap config, so a change rolls the pool and every new machine
carries them.

The MachineDeployment also announces the pool's labels and taints to
the cluster-autoscaler through its capacity labels and taints
annotations. A pool scaled to zero has no Node to read them from, and
without them a pod selecting the pool's label never scales it up, and a
tainted pool is scaled up for pods that cannot tolerate it.

Keys and values are held to the label and taint character sets, which
also keeps them safe in the Talos reconcile script, and tenant label
keys are quoted there so a key such as null stays a string. The values
schema also declares the taint key format and the three effects, so
Helm reports a malformed taint against its field before any template
renders. A pool without labels or taints renders as before.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kubernetes Issues or PRs related to the tenant Kubernetes app kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant