0052. TalosMachine.spec.nodeLabels is rendered, wins over the TalosStorage preset, and a kubelet-forbidden label is a render error

Status: Accepted · Date: 2026-10-26 · Design section: Storage, Patch layers Decided: Render spec.nodeLabels into KubeNodeConfig (contract 1.14+) or machine.nodeLabels (older) right after layer 1, so the operator's label beats a TalosStorage preset label on the same key, and fail the render for labels the kubelet's NodeRestriction would refuse

Context

Approval sets spec.nodeLabels (ADR 0004, 0011), but nothing rendered them, so an approved label never reached the Node. ADR 0050 made TalosStorage selection match on spec.nodeLabels at render time and on the live Node's labels at evacuation time; with the labels never applied, layer 1 and layer 4 disagreed. That gap, noted in ADR 0050 decision 2, is closed here.

Decisions

1. Where. injectNodeLabels (pkg/render/nodelabels.go) merges the labels with the same version-contract branch as nodeRegistrationDocs: KubeNodeConfig.labels from Talos 1.14, legacy machine.nodeLabels below it (a legacy block next to the multi-document base fails validation).

2. Precedence. It runs after injectStorage and before every TalosConfigPatch. spec.nodeLabels is the operator's intent (machine layer); the preset's labels are system layer. On a shared key the machine's value wins (the strategic merge applies it last), so an operator can set node.longhorn.io/create-default-disk: "false" on one node. A patch still beats both.

3. Forbidden labels. Talos v1.14.3 validates only syntax (pkg/machinery/labels: qualified name, value of at most 63 characters) and has no reserved list of its own; its own field comment points at the kube-apiserver NodeRestriction plugin. A kubelet started with node-role.kubernetes.io/... or another kubernetes.io / k8s.io label outside the allowed set refuses to start. Render therefore returns an error naming the label (allowed: kubernetes.io/{hostname,arch,os}, the topology / instance-type / failure-domain keys, and anything under kubelet.kubernetes.io or node.kubernetes.io). Not a CEL rule: the allowed set is Kubernetes', not ours, and duplicating it into the CRD would drift; the error surfaces as the machine's render failure condition.

Search the docs

Search pages and headings