Your next Kubernetes upgrade has a quiet variable, and it is not the Kubernetes minor. It is the container runtime underneath the kubelet: containerd 1.7's extended support window closed this month, and the kubelet release that drops compatibility with it is already on the calendar. If you run your own nodes — Talos, kubeadm, or Cluster API machine images on Hetzner — now is the moment to find out exactly which runtime every node is running, what the jump to 2.x breaks, and in what order to do the work.
The good news: the whole audit takes about fifteen minutes, and the fix is a routine rolling upgrade, not a migration project. Here is the timeline, the audit, the break table, and the sequencing.
The support window already closed
Get the dates precise, because two different "end of support" moments get conflated in upgrade-guide writeups. Upstream support for the containerd 1.7 branch — patches from the committers team — ended March 10, 2026. What kept 1.7 alive after that was a narrowly scoped extended support arrangement: two individual maintainers carrying security fixes through September 2026, explicitly focused on Google Kubernetes Engine's usage with Kubernetes 1.30 through 1.32.
That extension is now over. If your nodes report containerd://1.7.x today, the runtime your kubelet talks to receives no fixes from anyone — not for CVEs, not for the CRI incompatibilities that are about to matter. The supported targets are the 2.x LTS lines: 2.0 LTS is supported into March 2027, and 2.3 LTS stretches to April 2028.
The second deadline is the one with teeth. The kubelet has carried a compatibility fallback for old runtimes for years: containerd 1.x does not implement the RuntimeConfig CRI RPC, so when a kubelet asks the runtime how cgroups are configured, a 1.x runtime cannot answer correctly, and the kubelet falls back to its own --cgroup-driver flag. Kubernetes planned to remove that fallback, then deliberately deferred the removal from 1.37 to 1.38 to align with the end of containerd 1.7's support window. The message from upstream could not be clearer: 1.38 is the release where the training wheels come off, and 1.7 nodes are expected to be gone by then.
So the calendar reads: runtime EOL now, kubelet enforcement in 1.38. Every self-managed fleet's next upgrade plan sits between those two dates.
The 15-minute fleet audit
This is the core deliverable: three checks that tell you whether you are exposed, and where.
Check 1: inventory every node's runtime. The kubelet already reports this; you just have to ask for all nodes at once instead of eyeballing kubectl get nodes -o wide one screen at a time:
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}' | sort | uniq -c | sort -rnAny line containing containerd://1.7 is a node running an unsupported runtime. The pass criterion is blunt: zero lines with 1.7. Note the failure mode this catches that dashboards miss — a fleet that is 90 percent migrated still has the problem, because the kubelet/runtime contract is per-node, not per-fleet. One forgotten static worker or an old machine deployment still scaling from a stale image template is enough to ruin upgrade day.
Check 2: trace each 1.7 back to the image that installed it. Nodes do not pick runtimes; images do. If you run Talos, the mapping is mechanical: Talos 1.7 ships containerd 1.7, Talos 1.9 moved to containerd 2.0, and current Talos 1.14 release candidates bundle containerd 2.3. So a Talos fleet reports its exposure through its Talos version — anything on the 1.7 line or older is the audit finding, and the remediation is a Talos upgrade, not a containerd surgery. For kubeadm or image-builder fleets, check the pinned version in your image definition (the Kubernetes image-builder default, your Packer template, or your cloud-image build script) rather than what happens to be installed on a node today; the installed version tells you the symptom, the image definition tells you whether the next scaled-up node reintroduces it.
Check 3: read your containerd config the way 2.x will. On a representative 1.7 node, look at the top of the effective config:
containerd config dump | head -5
grep -n 'io.containerd.grpc.v1.cri' /etc/containerd/config.toml | head -20If the dump opens with version = 2, you are on the 1.x config schema, and if the grep hits, your config addresses the old unified CRI plugin. Neither is fatal — 2.x auto-converts v2 configs — but both feed directly into the break table below. Record them now so the upgrade does not surprise you later.
Fifteen minutes, three commands, and you know exactly where you stand. The rest of this post is for fleets that found a 1.7 in check 1.
What the 1.7 to 2.x jump actually breaks
containerd 2.0 is a real major release, not a renumbered minor. The installer project kURL treated it that way: it hard-blocked any containerd major-version jump until 1.x's approaching EOL forced it to build a guarded 1.7-to-2.x path. Here is what guards like that one exist for, one row per incompatibility, each with its symptom and fix.
| Change | What happens | What to do |
|---|---|---|
| Config schema v2 → v3 | 2.x auto-converts a v2 config, so old files load. The trap runs the other way: if you write v3 plugin IDs into a file still headed version = 2, those stanzas are silently ignored and images unpack into the wrong snapshotter. | After upgrading, regenerate with containerd config default (emits v3) and re-apply your deltas, or at minimum flip the header and validate with containerd config dump. |
| CRI plugin split | The single io.containerd.grpc.v1.cri plugin becomes two plugins, io.containerd.cri.v1.runtime and io.containerd.cri.v1.images. Config addressing the old ID still works through conversion, but anything that parses or templates plugin paths — config management, image-builder overlays — must learn the new IDs. | Grep your config generation for the old plugin ID and update templates before rollout. |
| Registry TLS config removed | registry.configs.tls was removed in 2.0 outright; the sibling registry.auths, registry.configs, and registry.mirrors keys are deprecated with removal slated for 2.2. | Move mirror and TLS configuration to the hosts-directory layout (/etc/containerd/certs.d) now, while both forms still parse. |
| Schema 1 images refused | Pulling ancient Docker Schema 1 images is disabled; the escape hatch is the CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1 environment variable, and even that is documented as going away. | Find Schema 1 images in your registries (anything pushed by a pre-2017 Docker) and re-push them. Do not standardize on the env var. |
| NRI enabled by default | The Node Resource Interface, alpha and off in 1.7, is on by default in 2.0. Mostly this just works, but any device-plugin or resource-management agent that assumed NRI was absent can behave differently. | Test GPU and device-plugin workloads on a 2.x canary node before fleet-wide rollout. |
| Go module path change | Libraries moved to github.com/containerd/containerd/v2. This bites only fleets with custom Go tooling linked against containerd client packages. | Bump imports in your own tooling; k3s-derived distributions already went through this migration for Kubernetes 1.35. |
Two reassuring notes. First, none of these rows requires rethinking your architecture: they are config and plumbing changes, which is why the remediation is a rolling machine rollout rather than a redesign. Second, the managed world has already walked this path — GKE moved to containerd 2.0 with Kubernetes 1.33 and publishes a migration guide covering the same deprecations — so the failure modes above are observed, not theoretical.
Sequence it: runtime before kubelet
The ordering rule is simple — upgrade the runtime before you upgrade the kubelet past the fallback removal — and the reason is the per-node contract from the audit section. A kubelet that no longer carries the CRI fallback asks the runtime for its RuntimeConfig and expects an answer; containerd 1.7 cannot give one. Runtime-first means every node can answer before any kubelet starts asking.
For Cluster API fleets, the mechanics fall out of the platform's own primitives. The runtime lives in the machine image, so the upgrade is a new image (new Talos version, rebuilt image-builder AMI, refreshed cloud image) referenced by an updated machine deployment, rolled out with the usual surge-and-replace semantics. Roll the control plane's machines first, watch the new nodes report containerd://2.x in nodeInfo, then roll the workers. Because this is a machine replacement and not an in-place package swap, rollback is the previous image template — keep it until the new runtime has survived a full workload cycle.
For kubeadm fleets, one gotcha deserves its own callout: kubeadm has been known to write the deprecated --pod-infra-container-image flag into /var/lib/kubelet/kubeadm-flags.env, and a kubelet that has dropped its deprecated flags fails to start when that file still passes them. When you upgrade either side, scrub that file for flags the new kubelet no longer accepts; a kubelet that will not start looks exactly like a runtime incompatibility from the outside, and you do not want to debug both at once.
And mind the version skew in the other direction too. Kubernetes 1.35-era guidance treated 1.35 as the last release comfortable with containerd 1.7 or earlier, and the fallback's deferral to 1.38 extends the grace period — it does not extend support. Running a supported kubelet against an unsupported runtime is exactly the configuration where a CVE lands with no patch. The supported matrix is the goal: modern kubelet, 2.x runtime, both inside their windows.
Put it in the runbook
Here is the literal line to add next to the Kubernetes minor in your upgrade runbook:
Verify zero nodes report
containerd://1.x(and no image template pins 1.x) before upgrading the kubelet past the CRI-fallback removal; roll the runtime via machine-image replacement first.
That one line would have prevented nearly every bad containerd upgrade story in the managed-provider migration guides: the fleet that upgraded Kubernetes and discovered the runtime afterward, the config that silently ignored its new stanzas, the single stale machine deployment that reintroduced 1.7 two weeks later. The runtime is infrastructure you chose when you chose your node image; the runbook should treat it with the same seriousness as the Kubernetes version it sits under.
The deeper lesson is about what self-hosting actually obligates you to track. Managed Kubernetes moved its fleets to containerd 2.0 a year ago without most tenants noticing — GKE tenants on 1.33 simply woke up on the new runtime. When you own the machines, nobody moves you; the support window closes whether or not your runbook mentions it. That is the honest price of owned infrastructure, and it is payable in fifteen-minute audits exactly like this one, run ahead of every upgrade rather than during it.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with node lifecycle and upgrades handled by the platform instead of your runbook. Star the repo on GitHub or deploy your first app today.



