Skip to main content
Version: Next 🚧

Node labels and annotations

The operator runs a node-labeler on every node in the cluster. On a Kairos node it reads /etc/kairos-release and /proc/cmdline and writes the result as node labels and node annotations, all under the kairos.io/ prefix.

These are the labels you select on in the nodeSelector of a NodeOp or a NodeOpUpgrade. Read them from a running cluster with:

kubectl get nodes -o json | jq '.items[].metadata.labels | with_entries(select(.key | startswith("kairos.io/")))'

Labels​

kairos.io/managed is always "true" on a Kairos node. Every other label is written only when the matching key in /etc/kairos-release has a value, so a selector must tolerate an absent label rather than assume one.

LabelSource keyExample valueWhat it is
kairos.io/managednonetrueThe node is a Kairos node. Set on every labeled node.
kairos.io/idKAIROS_IDkairosAlways kairos on an image built by kairos-init.
kairos.io/familyKAIROS_FAMILYdebian, redhat, alpineThe distribution family of the base image.
kairos.io/flavorKAIROS_FLAVORubuntu, fedoraThe base distribution.
kairos.io/flavor-releaseKAIROS_FLAVOR_RELEASE24.04, 42The version of that distribution.
kairos.io/variantKAIROS_VARIANTcore, standardstandard carries a Kubernetes distribution, core does not.
kairos.io/releaseKAIROS_RELEASEv3.5.2The Kairos version, with a leading v.
kairos.io/modelKAIROS_MODELgeneric, rpi4The hardware model the image was built for.
kairos.io/archKAIROS_ARCHamd64, arm64The image architecture.
kairos.io/trusted-bootKAIROS_TRUSTED_BOOTtrue, falseWhether the image was built for Trusted Boot.
kairos.io/fipsKAIROS_FIPStrue, falseWhether the image was built with FIPS support.
kairos.io/software-version-prefixKAIROS_SOFTWARE_VERSION_PREFIXk3s, k0sThe Kubernetes distribution in the image. Absent on a core image.
kairos.io/software-versionKAIROS_SOFTWARE_VERSIONv1.32.1-k3s1The version of that distribution. Absent on a core image.

Boot state​

kairos.io/boot-state does not come from /etc/kairos-release. The labeler reads /proc/cmdline and reports which image the node booted:

ValueThe node booted
activeThe active image, which is the normal case.
passiveThe passive image, which is the previous active image after an upgrade.
recoveryThe recovery image.
livecdLive media, an ISO or a netboot, so there is no installed system in use.
unknownThe command line carries none of the tokens above.

A node showing passive has fallen back, so it is running the image it ran before its last upgrade. A node showing recovery or livecd is not running an installed system, and an upgrade targeted at it does not do what you mean.

On a Trusted Boot node the value is always unknown. The labeler reads the kernel command line, and a UKI node carries no boot state token there: the agent copies one UKI artifact into the active, passive and recovery roles, so all of them boot with the same embedded command line. The role the node actually booted is recorded by systemd-boot in an EFI variable, which the labeler does not read. So a selector on kairos.io/boot-state matches no Trusted Boot node at all, whichever image it booted. Tracked in kairos-io/kairos#5206.

Annotations​

Values that are too long, or that hold characters a Kubernetes label value cannot carry, are written as annotations instead. You cannot select on an annotation.

AnnotationSource keyExample value
kairos.io/nameKAIROS_NAMEkairos-standard-ubuntu-24.04
kairos.io/id-likeKAIROS_ID_LIKEkairos-standard-ubuntu-24.04
kairos.io/versionKAIROS_VERSIONv3.5.2
kairos.io/init-versionKAIROS_INIT_VERSIONv0.5.17
kairos.io/bug-report-urlKAIROS_BUG_REPORT_URLhttps://github.com/kairos-io/kairos/issues
kairos.io/home-urlKAIROS_HOME_URLhttps://github.com/kairos-io/kairos

kairos.io/name and kairos.io/id-like carry the same string, because kairos-init writes kairos-<variant>-<flavor>-<flavor-release> into both KAIROS_NAME and KAIROS_ID_LIKE. Neither of them names the base distribution, so read kairos.io/flavor and kairos.io/variant for that.

Three behaviours to know before you write a selector​

Label values are sanitized, so the value is not always the value in /etc/kairos-release. A Kubernetes label value accepts only [a-zA-Z0-9-_.]. The labeler replaces every other character with -, trims leading and trailing -, _ and ., and truncates to 63 characters. A flavour recorded as quay.io/centos/centos:stream9 becomes quay.io-centos-centos-stream9. Annotations are written unchanged. When a selector does not match, compare it against the label on the node, not against the release file.

Stale keys are removed. Each run replaces the whole kairos.io/ set: any kairos.io/ label or annotation on the node that the current read does not produce is deleted. A label does not survive an upgrade to an image that no longer sets it, and you cannot add your own label under the kairos.io/ prefix, because the next run deletes it. Use a prefix of your own.

A non-Kairos node is left alone. If the node has no /etc/kairos-release, and its /etc/os-release ID does not contain kairos, the labeler writes nothing at all, not even kairos.io/managed. That is what makes kairos.io/managed: "true" mean "Kairos nodes only" in the examples on the other pages.

Selector examples​

Every Kairos node, which is the selector used throughout these docs:

nodeSelector:
matchLabels:
kairos.io/managed: "true"

Only the FIPS nodes on arm64:

nodeSelector:
matchLabels:
kairos.io/fips: "true"
kairos.io/arch: "arm64"

Every Kairos node that booted its active image, which skips the nodes sitting in recovery or on live media:

nodeSelector:
matchLabels:
kairos.io/managed: "true"
kairos.io/boot-state: "active"

This one also skips every Trusted Boot node, for the reason given under Boot state.

Every node that runs a Kubernetes distribution, whichever one it is:

nodeSelector:
matchExpressions:
- key: kairos.io/software-version-prefix
operator: Exists

Every Kairos node except the Trusted Boot ones:

nodeSelector:
matchExpressions:
- key: kairos.io/managed
operator: In
values: ["true"]
- key: kairos.io/trusted-boot
operator: NotIn
values: ["true"]