|
| 1 | +# Upgrades |
| 2 | + |
| 3 | +The provider upgrades a running cluster between supported Kubernetes minors by |
| 4 | +driving `kubeadm upgrade`. Upgrades are **image-driven and explicitly pinned**: |
| 5 | +you boot a newer provider-kubernetes image and bump the version pin; on the next |
| 6 | +boot the provider detects the delta and converges the cluster. |
| 7 | + |
| 8 | +> Validated live for 1.34 -> 1.35 on a single control plane. Multi-node/HA upgrade |
| 9 | +> ordering is implemented (see below) but exercise it carefully. As always, this |
| 10 | +> provider is under active development and not field-ready. |
| 11 | +
|
| 12 | +## The model |
| 13 | + |
| 14 | +- The **target version is the bundled `kubeadm` binary** in the image you boot. |
| 15 | + By ADR-3 an operator `kubernetesVersion` pin must equal the bundled minor, so the |
| 16 | + pin and the binary always agree. |
| 17 | +- An upgrade runs **only when you pin** `clusterConfiguration.kubernetesVersion` to |
| 18 | + the new minor. A newer image **without** a matching pin bump does **not** |
| 19 | + auto-upgrade - it is a no-op (so a routine image refresh never silently |
| 20 | + re-versions your cluster). |
| 21 | +- Only **+1 minor** at a time (kubeadm's rule). Skipping a minor (1.34 -> 1.36), |
| 22 | + downgrades, and out-of-window targets are **refused loudly** before anything |
| 23 | + destructive runs. |
| 24 | + |
| 25 | +## Supported edges |
| 26 | + |
| 27 | +Within the current window: **1.34 -> 1.35** and **1.35 -> 1.36**. To go 1.34 -> |
| 28 | +1.36, step through 1.35 (boot the 1.35 image first, let it converge, then the 1.36 |
| 29 | +image). |
| 30 | + |
| 31 | +## Upgrading a single control plane |
| 32 | + |
| 33 | +1. Make sure the cluster is healthy (`kubectl get nodes`, control-plane pods |
| 34 | + Running). Take an **etcd backup** first - see "etcd snapshots" below. |
| 35 | +2. Bump the pin in the node's cloud-config (`/oem` on an installed node) to the new |
| 36 | + minor, e.g. `kubernetesVersion: v1.35.5`. |
| 37 | +3. Upgrade the OS image to the matching minor and reboot. Either: |
| 38 | + - `kairos-agent upgrade --source oci:<registry>/provider-kubernetes:<tag>-k8s1.35` |
| 39 | + (then reboot), or |
| 40 | + - boot the new image via your provisioning flow. |
| 41 | +4. On reboot the provider's reconcile detects that the bundled binary is a minor |
| 42 | + ahead of the cluster and runs `kubeadm upgrade apply <version>`. When the new |
| 43 | + kubelet cannot start against the old node config (a flag removed across the |
| 44 | + minor), the provider first repairs the kubelet config (see below), brings the |
| 45 | + control plane back, then applies. The kubelet is restarted onto the new version. |
| 46 | +5. Verify: `kubectl version` (server), `kubectl get nodes` (node at the new |
| 47 | + version, Ready). |
| 48 | + |
| 49 | +## Upgrading multiple control planes / workers (HA) |
| 50 | + |
| 51 | +Upgrade **one node at a time**, control planes first, then workers: |
| 52 | + |
| 53 | +1. Upgrade the first control plane as above; wait until the cluster version has |
| 54 | + flipped and it is Ready. |
| 55 | +2. Upgrade each remaining control plane one at a time. A follower runs |
| 56 | + `kubeadm upgrade node` (the provider detects the cluster already advanced and |
| 57 | + does not re-apply); it waits, bounded, for the control plane to be healthy first. |
| 58 | +3. Upgrade workers last, one at a time; each runs `kubeadm upgrade node`. |
| 59 | + |
| 60 | +This sequencing is the operator's responsibility (the same one-at-a-time delivery |
| 61 | +contract as HA join); the provider adds bounded health gates but builds no |
| 62 | +cross-node lock. |
| 63 | + |
| 64 | +## The kubelet-config repair (why an upgrade "just works" after an image swap) |
| 65 | + |
| 66 | +Kubernetes removes/renames kubelet flags and config keys across minors. Because a |
| 67 | +Kairos A/B image swap replaces the kubelet binary *before* any upgrade runs, the |
| 68 | +new kubelet can crashloop on a flag the old kubeadm wrote (for example |
| 69 | +`--pod-infra-container-image`, removed in 1.35), which would leave the control |
| 70 | +plane down. The provider handles this automatically: when an upgrade is due and the |
| 71 | +local API is unreachable, it runs `kubeadm init phase kubelet-start` to regenerate |
| 72 | +the kubelet config with the new kubeadm (no API, no secrets), which lets the |
| 73 | +kubelet start and the existing control plane return before `kubeadm upgrade apply` |
| 74 | +runs. You do not need to do anything for this. |
| 75 | + |
| 76 | +## etcd snapshots |
| 77 | + |
| 78 | +`kubeadm upgrade apply` mutates etcd and is largely forward-only. The provider |
| 79 | +takes a **best-effort** etcd snapshot before applying, but **only** onto an |
| 80 | +encrypted persistent partition (an etcd snapshot is a full plaintext dump of every |
| 81 | +cluster secret). If it cannot confirm the partition is encrypted, it **refuses to |
| 82 | +write a plaintext snapshot** and logs a warning - so on an unencrypted node you |
| 83 | +must take your own snapshot before upgrading. The provider never copies the |
| 84 | +snapshot off the node; backup custody is yours. |
| 85 | + |
| 86 | +## Rollback |
| 87 | + |
| 88 | +kubeadm upgrades (especially etcd) are forward-only; there is no automatic |
| 89 | +rollback. To recover, restore your etcd snapshot and boot the previous image. A |
| 90 | +node wedged mid-upgrade can be recovered with the [reset](./lifecycle.md) flow and |
| 91 | +re-joined. The provider never auto-resets a control plane on upgrade failure - it |
| 92 | +fails loud and leaves the node for you to inspect. |
| 93 | + |
| 94 | +## What can go wrong |
| 95 | + |
| 96 | +| Symptom | Cause / fix | |
| 97 | +|---------|-------------| |
| 98 | +| Reconcile logs `refuse-upgrade` | Skip-level, downgrade, or out-of-window pin. Pin only +1 minor within the window. | |
| 99 | +| Upgrade doesn't start | No version pin bump (a newer binary alone won't upgrade). Bump `clusterConfiguration.kubernetesVersion`. | |
| 100 | +| Pin/binary mismatch hard error | The pin minor must equal the bundled image's minor. | |
| 101 | +| Snapshot skipped warning | Persistent partition encryption unconfirmed - take a manual etcd backup. | |
| 102 | + |
| 103 | +See also [Lifecycle and reset](./lifecycle.md) and [Troubleshooting](./troubleshooting.md). |
0 commit comments