Skip to content
Install the CLI

How-to guide Operate a cluster

Bind a cluster definition to its Kubernetes cluster

kix deploy <cluster> --context <ctx> applies the build to whatever API server the context points at. Two commands run one after the other, or a default context that still points at the last cluster you worked on, are enough to deploy an example or test definition onto a live cluster.

Binding a definition closes that gap. The definition names the Kubernetes cluster it belongs to, and Kix refuses to apply it anywhere else. Bind every definition that deploys to a live cluster.

Kix identifies a Kubernetes cluster by the UID of its kube-system Namespace. That Namespace is created when the cluster is bootstrapped and cannot be deleted, so its UID stays the same for the life of the cluster, and every cluster has its own: a kind cluster, a k3s cluster, an EKS cluster, and a vCluster all differ.

kix status prints it in its header:

$ kix status production --context prod
Kubernetes cluster ID: 4d7b1c2e-9f3a-4b8e-a1c5-6d2e8f0a9b3c
...

kubectl gives the same value:

$ kubectl --context prod get namespace kube-system -o jsonpath='{.metadata.uid}'
4d7b1c2e-9f3a-4b8e-a1c5-6d2e8f0a9b3c

The UID is known as soon as the cluster exists, so you can bind a definition before its first deploy.

Set cluster.kubernetes.uid in a cluster module:

{ kix, packages }:
kix.buildCluster {
name = "production";
modules = [
kix.flavors.gke
{
cluster.kubernetes.uid = "4d7b1c2e-9f3a-4b8e-a1c5-6d2e8f0a9b3c";
# ...
}
];
}

The value must be a lowercase UUID; anything else fails evaluation. Several Kix clusters on one Kubernetes cluster all set the same UID.

The build carries the UID, and the Activation record of each deploy of a bound build carries it as the label kix.run/kubernetes-uid.

Every command that applies a build of the definition checks the binding after it connects and before it reads or writes anything else: kix deploy in all its modes (including --plan, --bundle, --current-activation, and --operator), kix plan, kix gc (soft and --hard), and the operator when it picks up a Queued record.

  • The definition names another Kubernetes cluster. The command stops and names both IDs and the context. Nothing is applied.
  • The definition names none, but a bound definition has deployed here. Once an Activation record that carries the label has finished a deploy on the Kubernetes cluster, that cluster accepts only definitions that name it. A record whose deploy never finished, such as one the operator refused because its build names another Kubernetes cluster, does not lock it. The command stops and tells you which Kix cluster bound it and which UID to add. This is what keeps an unbound example from landing on a live cluster. If the records carry a UID that is not this cluster’s (they were restored from another cluster), the message says so and names the live UID instead.
  • The definition names none and nothing on the cluster is bound. The command runs as before.

kix rollback applies a build this Kix cluster deployed here before, so it refuses only a target that names another Kubernetes cluster. A target built before the definition set cluster.kubernetes.uid rolls back as usual.

Read-only commands such as kix diff and kix status do not check the binding. The operator marks a refused record Failed with the message and emits a Warning event on it.

The check reads the kube-system Namespace. An account that may not get namespaces stops with a message that names that permission. The operator leaves such a record Queued, emits a Warning event, and retries once its service account is granted get on namespaces.

A definition may also set k8sVersion, the Kubernetes version the build checks every package’s meta.k8s.minVersion and maxVersion against. The build carries the value, and kix deploy, kix plan, and the operator compare it with the version the API server reports, right after the binding check and before anything is read or written. The comparison is as precise as you write the value: k8sVersion = "1.31" matches any 1.31.x, and "1.31.4" matches only 1.31.4. A vendor suffix such as +k3s1 or -gke.1209000 on either side is ignored. Write the minor version. A value with a patch number stops the deploy after every patch upgrade, and managed clusters (GKE, EKS, AKS) apply those on their own.

kix status prints the live version under the cluster ID:

$ kix status production --context prod
Kubernetes cluster ID: 4d7b1c2e-9f3a-4b8e-a1c5-6d2e8f0a9b3c
Kubernetes version: v1.31.4+k3s1
...

When the cluster runs another version, kix deploy and kix plan stop, and the operator marks a Queued record Failed with a Warning event. The message names both versions and the value to set, written as precisely as the definition writes it:

Kubernetes version mismatch for cluster 'production' on context prod (https://prod.example:6443)
Nothing was applied to the cluster.
`k8sVersion` in 'production': 1.30
Live cluster version: v1.31.4+k3s1
Why:
Package version checks (`meta.k8s.minVersion` and `maxVersion`) ran against Kubernetes 1.30 when 'production' was built, so some packages in this build may not work on v1.31.4+k3s1.
Next:
- If context prod (https://prod.example:6443) is the right cluster, set `k8sVersion = "1.31";` in 'production's definition and build again (if a Kubernetes upgrade is in progress, make this change after the upgrade finishes).
- If Kix is connected to the wrong cluster, pass the right `--context` for 'production' and run the command again.

After a Kubernetes upgrade, update k8sVersion, build again, and deploy. The new build runs the package checks against the version the cluster now runs.

A consumer cluster built with kix.interface gets k8sVersion from the platform’s interface file unless its own definition sets one. The build records where the value came from, and the message then names the platform and gives both fixes: export a new interface file from the platform after its upgrade and build against it, or set k8sVersion in the consumer’s definition, which overrides the file.

Two things go ahead on a mismatch, so that a Kubernetes upgrade never blocks them: kix rollback prints one line saying the target build was made for another version and rolls back anyway, and the operator keeps repairing an Active record. kix diff does not check. A definition that leaves k8sVersion unset is not compared, and nothing is printed.

When a Kubernetes cluster is recreated, its kube-system UID changes. Read the new ID as above and update cluster.kubernetes.uid in the definition. The commit records the move.

The lock on a Kubernetes cluster lives in the kix.run/kubernetes-uid label of its Activation records, so it outlives the Kix cluster that set it. When a bound Kix cluster is removed and its records stay behind, an unbound definition is still refused. To let unbound definitions deploy again, remove the label from every record that carries it:

kubectl label activations.kix.run -l kix.run/kubernetes-uid kix.run/kubernetes-uid-

Deleting the dead Kix cluster’s records also lifts the lock. Do not delete an Active or Degraded record while the operator runs: its finalizer tears down everything that record deployed.

A later deploy of a bound definition writes the label again and locks the cluster again.