
<!-- Generated from cluster/manifests/clusters-crd.yaml by docs/crdref. Do not edit. -->

A Cluster declares the fleet in one document: the release every
machine runs, the network, the shared runtime settings, and the
disruption budget for changes. [Upgrade the
fleet](/docs/guides/upgrade/) moves every machine with one edit to
`spec.version`.

A Cluster is the group that liken machines form together. It holds the topology, which is the set of machines that run control planes, and the network facts that every node must agree on.

## spec

The declared state of this cluster: the release the fleet runs, which machines lead it, the network and the time sources that every node agrees on, the limits a rollout keeps to, the optional features, the registries, and the k3s runtime settings. One document serves every machine, so each field here states a fact that the whole fleet shares. A fact about one machine belongs on that Machine instead.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spec--version"></span>`version` | string | no | The liken release that the fleet moves toward. An upgrade edits this one field. A Machine spec holds no version of its own. Each machine's operator compares its running version (status.version.liken) with this target, and moves toward the target with the same staged change and granted reboot that every other change uses. This value must name an entry in releases.catalog. Pattern: `^\d{4}\.\d{2}\.\d{2}-\d{3}$`. |
| <span id="spec--releases"></span>`releases` | [object](#specreleases) | no | Where release artifacts come from, and which releases exist. The operator reads this section live on every pass, as it reads version. The cluster document that machines stage and reboot into does not include it. A published release must move machines through downloads and sequenced reboots. It must never stage a configuration reboot of the whole fleet by itself. The fleet observer polls the channel's root document at a slow interval. To request an immediate poll, set the Cluster's liken.sh/check-releases annotation to any new value, for example a timestamp. The value itself means nothing: the edit is the request. This is an annotation and not a spec field, because it asks for one action and does not declare a standing state. kubectl uses the same shape to request a Deployment rollout. |
| <span id="spec--origin"></span>`origin` | string | no | How the cluster's datastore came to exist. Founded, the default when the field is unset, means that liken created the datastore, through the founding leader's cluster-init. Adopted means that the datastore is already in a cluster that liken did not create. Machines built with that cluster's identity join it as members, and this includes the founding leader. No machine initializes a new datastore. A deployment declares adoption when the document is created. The only legal edit is the promotion from Adopted to Founded, made after the last foreign member is gone. Promotion changes nothing on a running fleet, because k3s ignores cluster-init when the datastore already exists. Promotion is important when the cluster is built again from nothing: the founder of a founded cluster can create the datastore again. Only a boot reads this field, so the edit costs no reboot and no restart. Each machine holds the new document and applies it at its next boot, and reports StagedForNextBoot on its ClusterConverged condition until then. One of: `Founded`, `Adopted`. |
| <span id="spec--leaders"></span>`leaders` | []string | no | The machines that run control planes, by Machine name. A machine gets its role from this list. A machine that is named here is a leader, and a machine that is absent is a follower. The first entry is the founding leader, and the other leaders join through its address. |
| <span id="spec--endpoint"></span>`endpoint` | string | no | The URL that followers join the cluster through, for example https://10.10.0.1:6443. If the cluster has one leader, this is that leader's address on the node network. Only a boot reads this field: a machine that has joined keeps the address of every leader and never asks the endpoint again. So an edit costs no reboot and no restart. Each machine holds the new document and applies it at its next boot, and reports StagedForNextBoot on its ClusterConverged condition until then. Two things still hold the old address until a machine boots. A follower keeps the endpoint's host in its time sources, as the last resort behind each leader's own address. And a machine that was down during the edit has not staged the new document, so it boots with the old endpoint. Keep the old address reachable until every machine reports Converged. Pattern: `^https://.+$`. |
| <span id="spec--network"></span>`network` | [object](#specnetwork) | no | The cluster's address plan: the facts that k3s requires every node to agree on, declared once here and not repeated on each machine. An unset field keeps k3s's own default. Every field here is immutable after it is set, except nodePortCIDRs. k3s cannot change these values on a live cluster, so an edit would change nothing, and the difference would appear only at the next reboot. The CEL transition rules (self == oldSelf) run only when the old value exists, which is what "immutable after it is set" means: a cluster that starts without a field can still get the field, and from then on the field holds. These rules compare with oldSelf. The Machine's grow-only storage rules compare with status instead, so that a declared size the machine cannot satisfy can be edited back down to the real size. These fields need no such escape, because their real values never change, so there is never a failed declaration to reverse. |
| <span id="spec--time"></span>`time` | [object](#spectime) | no | The cluster's time hierarchy: where the leaders get their time. Only the leaders read this section. The followers sync from the leaders themselves, so time moves from the upstreams to the leaders, and then to every other machine. |
| <span id="spec--disruption"></span>`disruption` | [object](#specdisruption) | no | How much of the fleet can be down at the same time when the cluster sequences reboots. This is the machine-level equivalent of a workload's PodDisruptionBudget, as one number. A staged change that needs a reboot waits for the cluster to grant the machine a turn, which is a RebootApproved condition written onto the Machine. This budget sets how many turns can be open at once. The count includes the machines that are down for any other reason, so a fleet that already has machines down pauses its own rollout. |
| <span id="spec--features"></span>`features` | map[string]map[string]string | no | The cluster's opt-ins from liken's feature vocabulary: optional capabilities that the fleet as a whole offers, beyond the minimum cluster. The map is keyed by feature slug. The presence of a key is the opt-in, and a feature that takes no parameters has the configuration {}. traefik is k3s's bundled ingress controller. servicelb is klipper-lb, which serves Services of type LoadBalancer on the nodes' own addresses. metrics-server is the aggregated API behind `kubectl top`. helm is the Helm controller inside the k3s server process, which turns HelmChart resources into workloads. A declared traefik enables helm automatically, because k3s deploys Traefik through such a resource. network-policy is the controller that turns NetworkPolicy resources into packet filtering. Without it, the API accepts those resources, but nothing enforces them, which is the behavior of the flannel CNI itself. iscsi is the host-side iSCSI initiator: static open-iscsi binaries, the iscsi_tcp kernel module, an initiator name made from each machine's name, and an iscsid DaemonSet. Every machine also publishes the stable name of each LUN it reaches under /dev/disk/by-path, in the form ip-address:port-iscsi-target-lun-number, which is the name a driver resolves a volume through. This is the host contract that CSI drivers such as synology-csi need. nfs is the NFSv4 client: a static mount.nfs and the nfsv4 kernel module, with no daemons, and version 4 only. flux is GitOps sync through Flux: the fleet's declared state is in a git repository, and the cluster applies what the repository holds. flux is the first feature in the vocabulary that takes parameters. Its repository parameter is required: the git URL the fleet syncs from, for example ssh://git@forge.example/fleet.git. Its path parameter (default ".") and its branch parameter (default "main") select the part of the repository that this cluster syncs. Its knownHosts parameter holds the forge's SSH host keys in known_hosts form, one line for each key. These keys are public material that lets the first clone verify the forge, so they go in the spec and not in a Secret. Its prune parameter (default "true") says whether the sync deletes an object that the repository no longer produces. Pruning is right for a cluster that liken founds, because every object in it came from the repository. A repository that carries its own copy of the flux-system Kustomization must declare prune: "false". That Kustomization appears in its own inventory, so the first build that stops producing it marks it for deletion, and the deletion removes everything the repository ever applied. The repository is expected to be private, and the cluster makes its own SSH deploy key. The private half never leaves the cluster, and status.flux.publicKey carries the half to register at the forge. If you remove flux from this object, the cluster retracts the whole feature: the sync stops, the engine and its namespace go, and the deploy key goes with them, so a new opt-in makes a new key to register. What the repository deployed stays in operation. Retraction stops the sync; it does not remove the workloads. The first three features (traefik, servicelb, and metrics-server) are components that the k3s binary already includes, and liken disables them by default: anything more than the control plane must be a declared, visible workload, and this field is that declaration. Adding a feature converges when k3s restarts in place, one leader at a time; the machine and its pods stay up. Removing network-policy, iscsi, or nfs reboots the machine instead, because each leaves kernel state that only a boot clears. Removing helm waits until no HelmChart remains, and removing servicelb waits until no LoadBalancer Service remains; until then the feature keeps running and the machine reports RetractionBlocked with the objects to remove. Removing traefik removes helm with it, in that order. |
| <span id="spec--registries"></span>`registries` | [object](#specregistries) | no | How container images arrive on the fleet's machines: the mirror endpoints that containerd pulls through, and k3s's embedded peer-to-peer registry (Spegel). These are cluster facts, because any node can be asked to pull any image. Credentials are deliberately not here. A spec is public, so credentials enter through the registry-credentials Secret in liken-system, which is the shape that `kubectl create secret docker-registry` produces. The machine operator reads that Secret and delivers it to each machine. As with the features above, the system reads the registries only when the k3s process starts, so an edit here converges when k3s restarts in place on each machine. The restart is sequenced through the same turns as a reboot, one leader at a time, but the machine, and its pods, stay up. |
| <span id="spec--runtime"></span>`runtime` | [object](#specruntime) | no | The discipline that the cluster applies to the k3s process, to the components inside it, and to containerd beside it. The section has one subsection for each thing that reads a setting: k3s for the process itself, kubelet for the configuration of the kubelet component that runs inside it, and containerd for the container runtime that k3s starts. The whole section is an opt-in. An unset field applies nothing, so the reader keeps its own default, and a cluster that names nothing here renders the same configuration it rendered before. Every value here is read only when the k3s process starts, so an edit converges when k3s restarts in place on each machine, sequenced through the same turns as a reboot, one leader at a time, with the machine and its pods up. containerd follows the same rule, because k3s starts containerd and stops it again. |

### spec.releases

Where release artifacts come from, and which releases exist. The operator reads this section live on every pass, as it reads version. The cluster document that machines stage and reboot into does not include it. A published release must move machines through downloads and sequenced reboots. It must never stage a configuration reboot of the whole fleet by itself. The fleet observer polls the channel's root document at a slow interval. To request an immediate poll, set the Cluster's liken.sh/check-releases annotation to any new value, for example a timestamp. The value itself means nothing: the edit is the request. This is an annotation and not a spec field, because it asks for one action and does not declare a standing state. kubectl uses the same shape to request a Deployment rollout.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specreleases--source"></span>`source` | string | no | The base URL that serves the releases. A release's artifacts are at `<source>/<version>/`. The first of them is release.yaml, the document that names every artifact by its digest. Pattern: `^https?://.+$`. |
| <span id="specreleases--catalog"></span>`catalog` | [\[\]object](#specreleasescatalog) | no | The releases that machines can be asked to run. Each entry's digest is the sha256 of the bytes of that release's release.yaml, and it is the start of the trust chain. This API names the document, the document names the artifacts, and the system compares every downloaded byte with one of the two. |

#### spec.releases.catalog[]

The releases that machines can be asked to run. Each entry's digest is the sha256 of the bytes of that release's release.yaml, and it is the start of the trust chain. This API names the document, the document names the artifacts, and the system compares every downloaded byte with one of the two.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specreleasescatalog--version"></span>`version` | string | yes | The release that this entry catalogs, as a calendar date and a serial. This is the name of the directory under releases.source that holds the release, and the value that spec.version names to move the fleet onto the release. Pattern: `^\d{4}\.\d{2}\.\d{2}-\d{3}$`. |
| <span id="specreleasescatalog--digest"></span>`digest` | string | yes | The sha256 of the bytes of this release's release.yaml, as sha256: and 64 hex digits. A machine compares the document it downloads with this digest. If the two do not agree, the machine stays on the release it runs and reports the phase Blocked with the reason DigestMismatch. Read the digest from the release channel when you add the entry. Pattern: `^sha256:[0-9a-f]{64}$`. |

### spec.network

The cluster's address plan: the facts that k3s requires every node to agree on, declared once here and not repeated on each machine. An unset field keeps k3s's own default. Every field here is immutable after it is set, except nodePortCIDRs. k3s cannot change these values on a live cluster, so an edit would change nothing, and the difference would appear only at the next reboot. The CEL transition rules (self == oldSelf) run only when the old value exists, which is what "immutable after it is set" means: a cluster that starts without a field can still get the field, and from then on the field holds. These rules compare with oldSelf. The Machine's grow-only storage rules compare with status instead, so that a declared size the machine cannot satisfy can be edited back down to the real size. These fields need no such escape, because their real values never change, so there is never a failed declaration to reverse.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specnetwork--nodecidr"></span>`nodeCIDR` | string | no | The subnet that the nodes use to address each other. On each machine, the interface with an address inside this subnet carries the cluster traffic, and that address becomes the machine's node IP. Pattern: `^[0-9a-fA-F:.]+/[0-9]{1,3}$`. |
| <span id="specnetwork--clustercidr"></span>`clusterCIDR` | string | no | The range that pod addresses come from. The k3s default is 10.42.0.0/16. Pattern: `^[0-9a-fA-F:.]+/[0-9]{1,3}$`. |
| <span id="specnetwork--servicecidr"></span>`serviceCIDR` | string | no | The range that service addresses come from. The k3s default is 10.43.0.0/16. Pattern: `^[0-9a-fA-F:.]+/[0-9]{1,3}$`. |
| <span id="specnetwork--clusterdns"></span>`clusterDNS` | string | no | The service address of the cluster's DNS resolver. The address is inside serviceCIDR. The k3s default is 10.43.0.10. Pattern: `^[0-9a-fA-F:.]+$`. |
| <span id="specnetwork--clusterdomain"></span>`clusterDomain` | string | no | The DNS suffix for the names inside the cluster. The k3s default is cluster.local. |
| <span id="specnetwork--nodeportcidrs"></span>`nodePortCIDRs` | []string | no | The networks that a NodePort service answers on. A NodePort is a port that the machines themselves open, and the machine addresses it answers on are a choice. If this list is unset, a NodePort answers on the node IP only: the address on nodeCIDR, where the rest of the cluster expects to find this machine. Set this list when the traffic arrives at a different address, which is the usual case for a cluster that is reached over a tunnel or a second segment. Each entry is a subnet, and a NodePort answers on every local address inside any of them. The list replaces the default and does not add to it, so the document states the whole answer: a list without nodeCIDR closes the NodePorts on the node network, also for the other machines in the cluster. The other fields here are immutable, because a change to one of them renumbers the cluster. This field is not immutable. It only increases or decreases the set of local addresses that answer, and the change reverses. k3s reads this list when its process starts, so an edit converges when k3s restarts in place on each machine, with no reboot. |

### spec.time

The cluster's time hierarchy: where the leaders get their time. Only the leaders read this section. The followers sync from the leaders themselves, so time moves from the upstreams to the leaders, and then to every other machine.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="spectime--upstreams"></span>`upstreams` | []string | no | The NTP servers that the leaders sync from, as hostnames or addresses. There is no default, and that is deliberate: the person who runs the deployment chooses whether to give the machines to a public pool. An empty list makes the fleet free-run. The machines stay consistent with each other, but they are correct only if the hardware clocks are also correct. |

### spec.disruption

How much of the fleet can be down at the same time when the cluster sequences reboots. This is the machine-level equivalent of a workload's PodDisruptionBudget, as one number. A staged change that needs a reboot waits for the cluster to grant the machine a turn, which is a RebootApproved condition written onto the Machine. This budget sets how many turns can be open at once. The count includes the machines that are down for any other reason, so a fleet that already has machines down pauses its own rollout.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specdisruption--maxunavailable"></span>`maxUnavailable` | integer | no | How many machines can be unavailable at the same time, planned and unplanned together. The default is one, the safest rollout. The leaders keep a stricter automatic floor that this number cannot raise: only one leader is down at a time. The datastore keeps quorum only while a majority of the leaders is up, and no policy setting changes that arithmetic. Default: `1`. |

### spec.registries

How container images arrive on the fleet's machines: the mirror endpoints that containerd pulls through, and k3s's embedded peer-to-peer registry (Spegel). These are cluster facts, because any node can be asked to pull any image. Credentials are deliberately not here. A spec is public, so credentials enter through the registry-credentials Secret in liken-system, which is the shape that `kubectl create secret docker-registry` produces. The machine operator reads that Secret and delivers it to each machine. As with the features above, the system reads the registries only when the k3s process starts, so an edit here converges when k3s restarts in place on each machine. The restart is sequenced through the same turns as a reboot, one leader at a time, but the machine, and its pods, stay up.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specregistries--mirrors"></span>`mirrors` | map[string][]string | no | A registry host, mapped to the endpoint URLs that containerd tries, in order of preference, before it falls back to the registry itself. The key is the host as an image reference names it (docker.io, registry.example:5000). |
| <span id="specregistries--embedded"></span>`embedded` | boolean | no | Turns on k3s's embedded registry mirror (Spegel). Each node serves the images it already holds to its peers, so the fleet pulls each image over the uplink one time, and not one time for each machine. This setting shares the images of every registry, through the wildcard mirror entry in registries.yaml. The mirrors declared above keep their own entries in both cases. |

### spec.runtime

The discipline that the cluster applies to the k3s process, to the components inside it, and to containerd beside it. The section has one subsection for each thing that reads a setting: k3s for the process itself, kubelet for the configuration of the kubelet component that runs inside it, and containerd for the container runtime that k3s starts. The whole section is an opt-in. An unset field applies nothing, so the reader keeps its own default, and a cluster that names nothing here renders the same configuration it rendered before. Every value here is read only when the k3s process starts, so an edit converges when k3s restarts in place on each machine, sequenced through the same turns as a reboot, one leader at a time, with the machine and its pods up. containerd follows the same rule, because k3s starts containerd and stops it again.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specruntime--k3s"></span>`k3s` | [object](#specruntimek3s) | no | The runtime discipline of the k3s process itself: the Go environment that init gives it, and how much it prints. An unset field applies nothing, so k3s keeps its own default for it. The two Go fields shape only the environment that init gives to the k3s process it starts, where k3s keeps Go's own defaults: no memory ceiling, and a heap that grows to twice its live data before the collector runs. Set a field to change that trade on a small machine, where k3s is the largest resident process. containerd and the shims that k3s starts inherit that environment, because k3s is their parent. No other process reads it: not init, not the operators, and not the workloads, which get their environment from their own pod specs. |
| <span id="specruntime--kubelet"></span>`kubelet` | [object](#specruntimekubelet) | no | The configuration of the kubelet. The kubelet is a component inside the k3s process, not a program of its own, and it runs on every machine of the cluster. An unset field applies nothing, so the kubelet keeps its own default for it. |
| <span id="specruntime--containerd"></span>`containerd` | [object](#specruntimecontainerd) | no | The configuration of containerd, the container runtime that runs beside the k3s process. containerd is a program of its own that k3s starts, not a component compiled into k3s, so it keeps its own configuration file and its own log level. Nothing in the k3s section reaches it. |

#### spec.runtime.k3s

The runtime discipline of the k3s process itself: the Go environment that init gives it, and how much it prints. An unset field applies nothing, so k3s keeps its own default for it. The two Go fields shape only the environment that init gives to the k3s process it starts, where k3s keeps Go's own defaults: no memory ceiling, and a heap that grows to twice its live data before the collector runs. Set a field to change that trade on a small machine, where k3s is the largest resident process. containerd and the shims that k3s starts inherit that environment, because k3s is their parent. No other process reads it: not init, not the operators, and not the workloads, which get their environment from their own pod specs.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specruntimek3s--gomemorylimit"></span>`goMemoryLimit` | string | no | The soft ceiling on everything that the k3s Go runtime manages: the heap, the stacks, and its own metadata (Go's GOMEMLIMIT). As the memory in use gets near the ceiling, the collector works harder instead of letting the heap grow. Above the ceiling, the runtime limits collection to half of the CPU and lets the heap grow, so a spike becomes slowness and not a crash. The field accepts three forms. "off" removes the ceiling. A percent such as "25%" is that share of each machine's memory, so one setting applies across a fleet of different sizes. An absolute quantity such as "448Mi" is the same ceiling on every machine. If the field is unset, k3s runs with no ceiling, the same as "off". The two wrong directions have different symptoms. With no ceiling, the kernel's OOM killer is the only protection, so the process dies under a spike. With a ceiling that is too tight, the collector runs against a limit it cannot clear and burns CPU on collection; the symptom is high user-time CPU on k3s with no matching workload. To find a good value, run k3s with no ceiling, read its resting size, then set the ceiling above that size with headroom for the node. Pattern: `^(off\|[0-9]{1,3}%\|[0-9]+(Ki\|Mi\|Gi\|Ti)?)$`. |
| <span id="specruntimek3s--gogc"></span>`goGC` | integer | no | The everyday pace of the collector, as a percent of heap growth between collections (Go's GOGC). If the field is unset, init sets no GOGC, so k3s keeps Go's own pace of one hundred percent: the heap grows to twice its live data before the collector runs. A value of 50 collects at fifty percent heap growth, and trades a little CPU all the time to keep the process resting near the size of its live data. A higher value collects less often and rests larger. A value below 1 is refused, because it would make the collector run without pause. |
| <span id="specruntimek3s--debug"></span>`debug` | boolean | no | Raises the k3s process to debug logging, the same thing that k3s's own --debug flag does. It reaches every Kubernetes component compiled into the process: the API server, the scheduler, the controllers, and the kubelet. It does not reach containerd's level, which the containerd section below sets. It does reach containerd's volume: k3s writes containerd's output to its own stream as well as to containerd's log file, and liken ships both files, so this field roughly doubles the containerd lines that leave the machine. If the field is unset, k3s logs at info. Turn it on to read a decision that the info lines do not explain, and turn it off again, because debug multiplies the volume of a stream that a small machine has to store and ship off itself. |

#### spec.runtime.kubelet

The configuration of the kubelet. The kubelet is a component inside the k3s process, not a program of its own, and it runs on every machine of the cluster. An unset field applies nothing, so the kubelet keeps its own default for it.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specruntimekubelet--imagegc"></span>`imageGC` | [object](#specruntimekubeletimagegc) | no | The policy for the kubelet's image collector. containerd's image store grows with every image that a node pulls, and nothing leaves the store while a container uses it. The kubelet is the only thing that prunes the store, and it prunes on two triggers: how full the disk is, and how long an image has gone unused. Use this section when the clusterState filesystem grows more than you want, or when a node keeps images that no workload names any more. |

#### spec.runtime.kubelet.imageGC

The policy for the kubelet's image collector. containerd's image store grows with every image that a node pulls, and nothing leaves the store while a container uses it. The kubelet is the only thing that prunes the store, and it prunes on two triggers: how full the disk is, and how long an image has gone unused. Use this section when the clusterState filesystem grows more than you want, or when a node keeps images that no workload names any more.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specruntimekubeletimagegc--highthresholdpercent"></span>`highThresholdPercent` | integer | no | The disk usage that starts collection, as a percent of the filesystem that holds containerd's image store. On liken that filesystem is clusterState. The kubelet measures the filesystem every five minutes. When usage passes this threshold, the kubelet removes unused images, the least recently used first, until usage falls under lowThresholdPercent. If the field is unset, the threshold is 85. A percent rather than a byte count is what lets one setting serve machines with different disk sizes. |
| <span id="specruntimekubeletimagegc--lowthresholdpercent"></span>`lowThresholdPercent` | integer | no | The disk usage that stops collection, as a percent of the same filesystem. It must be below highThresholdPercent, so collection has a range to work in. If the field is unset, the threshold is 80. The distance between the two thresholds sets how much each round of collection removes: a narrow gap collects a little, often, and a wide gap collects a lot, rarely. |
| <span id="specruntimekubeletimagegc--maximumage"></span>`maximumAge` | string | no | How long an unused image may stay in the store before the kubelet removes it, whatever the disk usage is, as a Go duration such as "168h". This is the trigger that keeps a store from carrying years of tags that no workload names any more. If the field is unset, the kubelet does no age check at all, and only the disk thresholds prune the store. The value must be greater than minimumAge. Pattern: `^[0-9]+(\.[0-9]+)?(ns\|us\|ms\|s\|m\|h)([0-9]+(\.[0-9]+)?(ns\|us\|ms\|s\|m\|h))*$`. |
| <span id="specruntimekubeletimagegc--minimumage"></span>`minimumAge` | string | no | How long an unused image is kept before the kubelet may remove it, as a Go duration such as "5m". This is what stops a node from removing an image it just stopped using and pulling the same image again minutes later. If the field is unset, the age is two minutes. Raise it on a node that cycles through a few large images. Pattern: `^[0-9]+(\.[0-9]+)?(ns\|us\|ms\|s\|m\|h)([0-9]+(\.[0-9]+)?(ns\|us\|ms\|s\|m\|h))*$`. |

#### spec.runtime.containerd

The configuration of containerd, the container runtime that runs beside the k3s process. containerd is a program of its own that k3s starts, not a component compiled into k3s, so it keeps its own configuration file and its own log level. Nothing in the k3s section reaches it.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="specruntimecontainerd--loglevel"></span>`logLevel` | string | no | How much containerd prints. containerd is the loudest writer on a liken machine. At info it prints a line for each step of every pod's life, on every node, for as long as the node runs. If the field is unset, containerd keeps its own default of info. Set "warn" to keep the failures and drop the pod lifecycle lines, once the workloads on this cluster run without your attention. Set "debug" while you follow an image pull or a container start that the info lines do not explain. containerd takes three more levels that this field does not offer: trace above debug, which is a volume that no machine should write to the disk it also runs on, and fatal and panic below error, which drop the error lines written before a crash, the lines that explain it. One of: `debug`, `info`, `warn`, `error`. |

## status

What a reader can observe about the cluster as a whole. The leaders write it. Each leader's operator sweeps the Machine list on every pass and publishes what it found. Only the leaders can do this, because a follower that reaches the API is reaching a leader.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="status--phase"></span>`phase` | string | no | The conditions below, as one word. The system derives this value on every sweep, and never stores it as a truth of its own. Ready means that every machine is Ready. Updating means that the machines which are not Ready are all in a transition: rebooting into a change, waiting for one, or booting. This is the expected shape of a Cluster edit as it moves through the fleet. Degraded means that at least one machine is Lost, Blocked, or unhealthy in another way. This field can never truthfully show one state: lost quorum. The loss of a majority of the leaders takes the API server down with it, so no writer is left. When quorum is lost, the signal is that this status does not change any more. One of: `Ready`, `Updating`, `Degraded`. |
| <span id="status--observedgeneration"></span>`observedGeneration` | integer | no | The metadata.generation of the spec that this status judged. The sweep stamps it on every write. Each condition carries the same stamp, but a client that only asks whether the sweep has seen its edit reads the stamp here, at the top of status, where Kubernetes controllers conventionally publish it. |
| <span id="status--conditions"></span>`conditions` | [\[\]object](#statusconditions) | no | The sweep's observations about the fleet, one entry for each type. The sweep maintains two types. MachinesReady reports the headcount and names each machine that is not Ready. Progressing reports the reboot rollout, and is False when the rollout stops. The phase above is these conditions in one word. |
| <span id="status--machines"></span>`machines` | [object](#statusmachines) | no | How many machines are fully healthy, out of how many exist. A fully healthy machine is in phase Ready, with a heartbeat recent enough to count as current. The summary field holds the same two numbers in the form 4/5. The status stores the summary because a printer column can read one field, but cannot combine two. |
| <span id="status--flux"></span>`flux` | [object](#statusflux) | no | The observable half of the flux feature. publicKey is the fleet's deploy key: the public half, in the authorized_keys form that a forge accepts. The private half never appears where a person can read it. The cluster operator makes the pair in the flux-system Secret when the feature is declared, and publishes the public half here. Register it at the forge as a deploy key with write access, and the fleet syncs from then on. To rotate the key, delete the flux-system Secret in the flux-system namespace. The next sweep makes a new pair, and this field shows the new half to register. |
| <span id="status--releases"></span>`releases` | [object](#statusreleases) | no | What the sweep observes about the releases: the catalog it was given, and the channel it polls. |

### status.conditions[]

The sweep's observations about the fleet, one entry for each type. The sweep maintains two types. MachinesReady reports the headcount and names each machine that is not Ready. Progressing reports the reboot rollout, and is False when the rollout stops. The phase above is these conditions in one word.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusconditions--type"></span>`type` | string | yes | The name of the observation, and the key of this entry in the list. The sweep writes MachinesReady, the fleet headcount, and Progressing, the state of the reboot rollout. Pattern: `^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$`. |
| <span id="statusconditions--status"></span>`status` | string | yes | The verdict for this observation: True, False, or Unknown. A True MachinesReady means that every machine is Ready. A False Progressing means that the rollout no longer makes progress. Unknown means that the sweep cannot tell. One of: `True`, `False`, `Unknown`. |
| <span id="statusconditions--observedgeneration"></span>`observedGeneration` | integer | no | The metadata.generation of the Cluster spec that this condition judged. The generation counts spec edits, so this field lets a client tell a verdict on the current spec from a verdict on an earlier spec. That difference is important here, because an edit can wait for a reboot. |
| <span id="statusconditions--reason"></span>`reason` | string | yes | One CamelCase word for the cause of the current status, for a program to match on. MachinesReady uses AllMachinesReady, MachinesUpdating, and MachinesDegraded. Progressing uses RolloutComplete, RollingOut, and RolloutStalled. Pattern: `^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$`. |
| <span id="statusconditions--message"></span>`message` | string | no | The detail behind the reason, for a person to read. The MachinesReady message gives the tally and names each machine that is not Ready. The Progressing message names each machine that holds a reboot turn, and each machine that waits for one. |
| <span id="statusconditions--lasttransitiontime"></span>`lastTransitionTime` | string | yes | When the status field last changed value. The sweep writes this condition on every pass, but moves this time only when the status changes, so the value gives the age of the current verdict. |

### status.machines

How many machines are fully healthy, out of how many exist. A fully healthy machine is in phase Ready, with a heartbeat recent enough to count as current. The summary field holds the same two numbers in the form 4/5. The status stores the summary because a printer column can read one field, but cannot combine two.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusmachines--ready"></span>`ready` | integer | no | How many machines are fully healthy: in phase Ready, with a heartbeat recent enough to count as current. |
| <span id="statusmachines--total"></span>`total` | integer | no | How many Machine resources the sweep found. A machine counts here from the moment its Machine resource exists, whether or not the machine is up. |
| <span id="statusmachines--summary"></span>`summary` | string | no | The two counts above in the form 4/5, ready first. The MACHINES column of `kubectl get clusters` reads this field. |

### status.flux

The observable half of the flux feature. publicKey is the fleet's deploy key: the public half, in the authorized_keys form that a forge accepts. The private half never appears where a person can read it. The cluster operator makes the pair in the flux-system Secret when the feature is declared, and publishes the public half here. Register it at the forge as a deploy key with write access, and the fleet syncs from then on. To rotate the key, delete the flux-system Secret in the flux-system namespace. The next sweep makes a new pair, and this field shows the new half to register.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusflux--publickey"></span>`publicKey` | string | no | The public half of the fleet's deploy key, on one line, in the authorized_keys form that a forge accepts. Register this value at the forge as a deploy key with write access. An empty value means that the spec does not declare the flux feature, or that the cluster operator has not made the key pair yet. |

### status.releases

What the sweep observes about the releases: the catalog it was given, and the channel it polls.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| <span id="statusreleases--newest"></span>`newest` | string | no | The highest version in the catalog. The sweep derives it, so the NEWEST printer column can sit next to VERSION and nobody compares versions at the terminal. |
| <span id="statusreleases--available"></span>`available` | string | no | The latest version that the release channel itself announces: the channel.yaml document at spec.releases.source, which the fleet observer polls at a slow interval. The liken.sh/check-releases annotation forces the next poll. This value is advisory. It can name a version that the catalog does not hold yet, and to adopt that version you still commit a catalog entry with its digest. |

