Kubernetes 1.34 end of support: a pre-flight checklist for 1.35
---
title: "Kubernetes 1.34 end of support: a pre-flight checklist for 1.35"
published: true
description: "Kubernetes 1.34 goes end of life on 27 October 2026. A practical pre-flight checklist for 1.35 and 1.36: cgroup v2, containerd 2.x, APIs, ingress, skew policy and etcd backups."
tags: kubernetes, devops, containers, sre
canonical_url: agency42.se
---
By Johan Kuylenstierna, Agency 42. Commands verified on a kind lab cluster (Kubernetes v1.34.0) on 11 Oct 2026. Drafted with AI assistance; reviewed and approved by Johan Kuylenstierna.
On 27 October 2026, Kubernetes 1.34 reaches end of life. It has been in maintenance mode since 27 August, which already meant CVE fixes only. After the 27th it gets nothing at all.
Unlike the Vogons' demolition notice, this one wasn't hidden in a filing cabinet on Alpha Centauri. The 1.35 release notes spelled out the hard parts back in December. In practice, the minor version bump itself is rarely what hurts. What hurts is everything around it that nobody has looked at since the cluster was built: a node image from 2021, a pinned containerd, an ingress controller that was "temporary".
One more thing before the checklist. Kubernetes 1.35 is itself end of life on 28 February 2027. Treat 1.35 as a waypoint and 1.36 (supported until 28 June 2027) as the destination. That matters, because 1.36 is where containerd 1.x support ends.
Pre-flight 1: know what your nodes actually run
Agent Le'man doesn't trust inventory spreadsheets. He reads nodeInfo:
kubectl get nodes -o custom-columns=NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,RUNTIME:.status.nodeInfo.containerRuntimeVersion,KERNEL:.status.nodeInfo.kernelVersion
Every row tells you something. containerd://1.7.x means a runtime upgrade belongs in the plan. Kubelets more than one minor apart mean someone stopped halfway last time. And a 4.x kernel is a hint for the next check.
On the kind lab cluster used for this article it looks like this:
NODE KUBELET RUNTIME KERNEL
lab-control-plane v1.34.0 containerd://2.1.3 6.12.94+
Pre-flight 2: cgroup v2 on every Linux node
On each node:
stat -fc %T /sys/fs/cgroup/
cgroup2fs means cgroup v2. tmpfs means cgroup v1. On the lab node:
$ stat -fc %T /sys/fs/cgroup/
cgroup2fs
cgroup v1 is deprecated as of 1.35, and the kubelet's failCgroupV1 setting now defaults to true. With default config, a 1.35 kubelet will not start on a cgroup v1 node. You can set failCgroupV1: false in the kubelet config to buy time, but it is a temporary escape hatch and full removal follows the deprecation policy. kubeadm is stricter still: its SystemVerification preflight check errors out on cgroup v1 during init, join and upgrade with a 1.35 kubelet.
Moving to cgroup v2 needs kernel 5.8 or later and usually a newer OS image. Also check the software that reads cgroups directly. Old JVMs, Node.js before 20.3.0 and standalone cAdvisor before v0.43.0 can misread memory limits under v2. The upstream cgroup v2 page has the version list.
Pre-flight 3: containerd 2.x before you go past 1.35
SIG Node designated 1.35 as the last Kubernetes release to support containerd 1.x. containerd 1.7 has also reached its own end of life (30 September 2026), so it no longer gets patches either.
To see which nodes are affected, look for the kubelet_cri_losing_support metric. Alert on it existing at all, not on a specific version label:
kubectl get --raw "/api/v1/nodes/<node>/proxy/metrics" | grep kubelet_cri_losing_support
On a containerd 2.x node this returns nothing (grep exits 1). Empty output is the result you want.
According to containerd's Kubernetes support matrix, Kubernetes 1.35 is tested with containerd 2.2.0+, 2.1.5+ and 1.7.28+, and 1.36 with 2.3.0+ and 2.2.0+. Note that containerd 2.2 goes end of life on 6 November 2026, while 2.3 is an LTS release supported until April 2028. If you're touching containerd anyway, 2.3 is the longer-lived target.
Things that bite during the containerd 2 move:
- Config. Old config.toml files are migrated automatically on startup. containerd config migrate writes the new format, but don't commit to it until you're sure you won't roll back.
- Removed features. CRI v1alpha2, the AUFS snapshotter and the runc v1 shims are gone. Pulling Docker Schema 1 images is disabled by default.
- A free warning list. On containerd 1.7.12 and later, ctr deprecations list tells you what the node uses that 2.x will complain about. Run it before the upgrade, not after.
One more trap: a 1.36 or 1.37 kubelet will still boot on containerd 1.7, because the kubelet falls back to its own cgroup driver setting. That fallback is scheduled for removal in 1.38. "It still boots" is not the same as "supported".
Pre-flight 4: deprecated APIs, mostly the ones you didn't write
Good news: the official deprecated API migration guide lists no built-in API removals after 1.32. For a 1.34 → 1.35 → 1.36 path, the risk sits in CRDs, operators and old Helm charts.
The API server tells you who is still calling deprecated APIs:
kubectl get --raw /metrics | prom2json | jq '.[] | select(.name=="apiserver_requested_deprecated_apis").metrics[].labels'
On a fresh 1.34 lab cluster with nothing but Calico installed, it already found something:
{
"group": "",
"removed_release": "",
"resource": "endpoints",
"subresource": "",
"version": "v1"
}
That's core v1 Endpoints, deprecated since 1.33 in favour of EndpointSlices. No removal release is set, so it isn't an upgrade blocker, but it shows how the metric works: some client in your cluster is still calling it, and the metric tells you before a removal does. This covers only the API server instance you hit and only since its last restart, so check every instance and let it run for a while. For manifests and Helm releases, Pluto is actively maintained. kubent still works, but its last release was in 2024, so its rules may lag.
Pre-flight 5: kube-proxy, ingress and the rest of the add-ons
kube-proxy in ipvs mode is deprecated as of 1.35. It is scheduled to be disabled by default in 1.40 and removed in 1.43, so this isn't a fire, but start testing nftables mode now (it needs kernel 5.13+). On kubeadm clusters:
kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep '^mode'
Ingress NGINX (the Kubernetes community controller) was retired in March 2026. The repository is archived and the last release was controller-v1.15.1. It keeps working, but nobody will patch the next CVE. Check whether you run it:
kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx
If you do, Gateway API is the recommended path and Ingress2Gateway will convert most of your Ingress objects for you.
Then go through the rest of the list: CNI, CSI drivers, cert-manager, service mesh, monitoring agents. Each has a compatibility matrix, and the target Kubernetes version should be in it before you start.
Pre-flight 6: PodDisruptionBudgets that block drains
A PDB with zero allowed disruptions will hang kubectl drain indefinitely, usually at 23:40 on a Friday:
kubectl get pdb -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,ALLOWED:.status.disruptionsAllowed
Fix the zeros (single replicas, minAvailable: 100%) before the maintenance window, not during it.
Managed and distribution clusters
On managed Kubernetes, the upstream date isn't your deadline. Your vendor's is.
| Platform | 1.34 standard support ends | Extended support |
|---|---|---|
| Amazon EKS | 2 Dec 2026 | until 2 Dec 2027, at extra cost |
| Azure AKS | Nov 2026 | LTS until Nov 2027, if you enable it |
| Google GKE | 25 Jan 2027 | until 25 Nov 2027 |
| RKE2 (SUSE) | maintenance ended 27 Aug 2026 | EOL 27 Feb 2027 |
Extended support is a bridge, not a destination. The cgroup, containerd and ingress work above still has to happen. It just happens later, and you pay for the wait.
OpenShift 4.21 is built on Kubernetes 1.34 and uses CRI-O, so the containerd section doesn't apply. cgroup v1 has been gone since OpenShift 4.19, and Red Hat's lifecycle policy (including EUS on even-numbered releases) sets your calendar. RKE2 bundles its own containerd: v1.34.12+rke2r1 already ships containerd 2.2.7, so check the bundled versions in the release notes rather than the host packages.
Upgrade order: the skew policy is the map
The version skew policy sets the order:
1. kube-apiserver first. In HA setups, all instances must stay within one minor version of each other.
2. kube-controller-manager, kube-scheduler, cloud-controller-manager next. They must never be newer than the API server.
3. kubelets, node by node: drain, upgrade, uncordon. A kubelet must never be newer than the API server, and may be up to three minors older.
4. kube-proxy last, under the same rule.
The control plane moves one minor at a time. kubeadm doesn't support skipping minor versions, so 1.34 → 1.36 means two upgrades. Upstream recommends going to the latest patch of your current minor first, then to the latest patch of the target. On kubeadm with pkgs.k8s.io, the package repository is per minor version, so switch the repo before kubeadm upgrade plan. And upgrade the CNI by hand. kubeadm won't do it for you.
Backup and rollback: be honest about "rollback"
Kubernetes doesn't really do in-place minor downgrades. In practice, rollback means a restore. So take a snapshot you have actually tested:
sudo mkdir -p /var/backups
sudo etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
snapshot save /var/backups/etcd-pre-1.35.db
sudo etcdutl --write-out=table snapshot status /var/backups/etcd-pre-1.35.db
# "Tested" means restored: restore into a scratch dir and check it opens
sudo etcdutl snapshot restore /var/backups/etcd-pre-1.35.db --data-dir /tmp/etcd-restore-test
The status check on the lab snapshot:
+----------+----------+------------+------------+---------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE | VERSION |
+----------+----------+------------+------------+---------+
| 13f43fbd | 1636 | 457 | 6.1 MB | 3.6.0 |
+----------+----------+------------+------------+---------+
ETCDCTL_API=3 is no longer needed: v3 has been the default since etcd 3.4, and etcdctl 3.6 warns unrecognized environment variable if you set it.
kubeadm also writes etcd and manifest backups to /etc/kubernetes/tmp during an upgrade. That's a nice extra, but it's not a backup strategy. If your etcd is still on 3.5, move every member to 3.5.26 or later before going to 3.6 to avoid the zombie-member bug. On EKS, an in-place upgrade can be rolled back within 7 days. Elsewhere, staging is your rollback rehearsal.
And if you're in NIS2 scope, "we ran an unsupported Kubernetes version" is not a sentence you want in an incident report.
The copy-paste checklist
[ ] Inventory: kubelet, runtime and kernel version per node
[ ] cgroup v2 on every Linux node (stat -fc %T /sys/fs/cgroup/ = cgroup2fs)
[ ] cgroup-aware app versions (JVM, Node.js >= 20.3.0, cAdvisor >= 0.43.0)
[ ] containerd 2.x planned before 1.36 (2.3 LTS recommended); ctr deprecations list run
[ ] kubelet_cri_losing_support absent on all nodes
[ ] Deprecated API calls checked (apiserver metric + Pluto on manifests/Helm)
[ ] kube-proxy mode known; nftables tested if you're on ipvs
[ ] Ingress NGINX found and migration planned (Gateway API / Ingress2Gateway)
[ ] CNI, CSI, cert-manager, mesh, agents: target version in their compatibility matrix
[ ] PDBs with 0 allowed disruptions fixed
[ ] Vendor support dates checked (EKS/AKS/GKE/OpenShift/RKE2)
[ ] etcd snapshot taken AND test-restored
[ ] Staging upgraded first; control plane -> nodes, one minor at a time
[ ] Plan 1.35 -> 1.36 before 28 Feb 2027
Don't panic. Under three weeks is plenty, if you start today. More checklists live at Agency-42/kubernetes-best-practices, and if you'd rather not do this alone, Agency 42 can help with Kubernetes operations and upgrades.
Sources
- Kubernetes patch releases and EOL dates: kubernetes.io
- Kubernetes v1.35 release blog (cgroup v1, containerd 1.x, ipvs, Ingress NGINX): kubernetes.io
- Kubernetes v1.35 sneak peek (containerd 1.x final call): kubernetes.io
- Cgroup driver autoconfiguration GA and containerd 1.y timeline: kubernetes.io
- About cgroup v2 (stat check, failCgroupV1, app versions): kubernetes.io
- The shift to cgroup v2 (kubeadm preflight, 1.38 fallback): kubernetes.io
- Container runtimes (1.38 fallback removal): kubernetes.io
- containerd releases and Kubernetes support matrix: github.com/containerd/containerd/blob/main/RELEAS…
- containerd 2.0 changes: github.com/containerd/containerd/blob/main/docs/c…
- Deprecated API migration guide: kubernetes.io
- Deprecated API metric: kubernetes.io
- kube-proxy modes (ipvs timeline, nftables): kubernetes.io
- Ingress NGINX retirement: kubernetes.io
- Version skew policy: kubernetes.io
- Upgrading kubeadm clusters: kubernetes.io
- Operating etcd (snapshot and verify): kubernetes.io
- etcd v3.6 zombie members: kubernetes.io
- Amazon EKS version calendar: docs.aws.amazon.com
- AKS supported versions: learn.microsoft.com
- GKE release schedule: cloud.google.com
- OpenShift 4.21 release notes: docs.redhat.com
- OpenShift 4.19 release notes (cgroup v1 removed): docs.redhat.com
- SUSE product lifecycle (RKE2): suse.com
- RKE2 v1.34 support matrix: suse.com
---
Originally published at agency42.se. Want to automate the API-visible parts of this checklist? k8s-preflight is a small read-only script that runs them for you.
Mentions
ingress-nginx 退役之後:在臨時叢集實測 Traefik 的 ingress-nginx 相容模式與 Gateway API > 一句話結論:用 ingress-nginx 的小叢集,不必急著重寫所有 Ingress。實測下來,Traefik 的 ingress-nginx 相容模式可以原封不動吃下常見的 annotation;想一步到位改 Gateway API 也行,但轉址狀態碼、IP 白名單這類細節要自己補。這篇是評估,不是遷移紀錄。 遇到的問題 單機叢集常見的配置是:k3s 的內建 Traefik 關掉,自己用 Helm 裝 ingress-nginx,所有服務掛 ingressClassName: nginx,再用幾個 nginx.ingress.kubernetes.io/... annotation 處理 HTTPS 轉址、逾時、IP 白名單。 這個專案已經退役了。官方 repo 的說明寫得很直接: - 2026 年 3 月之後,不再有新版本、bug 修正,也不再修補新發現的安全漏洞。 - 已經部署的不會壞,Helm chart 和映像也還在。 - 沒有安裝過的人,建議直接找一個 Gateway API 的實作。 也就是說現有的叢集不會馬上出事,但每一天都在累積「沒人修」的風險。接下來有兩種做法: 1. 換 controller,Ingress 不動。 讓別的 controller 認得 nginx 的 annotation。 2. 換 API。 改寫成 Gateway API 的 Gateway 與 HTTPRoute。 這篇不下結論要選哪個,而是在隔離的臨時叢集把兩條路都實際跑一遍,記下哪些順利、哪些要注意。 測試環境 整個實驗用一個臨時的 k3d 單節點叢集,不佔主機的埠,測完就刪。 | 項目 | 版本 | |---|---| | k3s(k3d 預設) | v1.31.5 | | Traefik(Helm chart 42.2.0) | v3.7.16 | | Gateway API CRD(standard) | v1.7.0 | | cert-manager | v1.21.3 | | ingress2gateway | v1.2.0 | 測試用的後端是 traefik/whoami,它會回傳請求的標頭,還有 ?wait=25s 這種可以讓後端故意慢回應的參數,以及 /echo 的 WebSocket 端點,很適合拿來測逾時。 要先講清楚的限制: - 叢集裡用自簽憑證,沒有真的向 Let's Encrypt 申請。 - 從叢集內的 Pod 發請求,沒有經過 NodePort 或 LoadBalancer,所以「來源 IP 會不會被改掉」沒有驗證。 - 單節點,沒做效能比較。 手把手步驟 前置:k3s 內建的 Traefik 不能直接用 先查了 k3s 自帶的 Traefik。k3d 預設的 k3s v1.31.5,內建的是 Traefik 2.11.18。 Traefik 的 ingress-nginx 相容 provider,官方文件寫明需要 v3.6.2 以上。所以 v1.31.5 附的版本沒有這個功能(用 traefik --help 也確實只看到 kubernetesgateway,而且還需要 --experimental.kubernetesgateway)。 比較新的 k3s 會附 Traefik 3.7.x:查 k3s v1.37.1 的內建 manifest,chart 是 41.4.0,對應 Traefik v3.7.12。但舊版 k3s 的叢集,得自己用 Helm 裝新版 Traefik。 下面的實驗都是這樣做:建叢集時停用內建 Traefik,再用 Helm 裝。 k3d cluster create demo --servers 1 --agents 0 --no-lb \ --k3s-arg "--disable=traefik@server:0" 路線一:讓 Traefik 直接吃 nginx 的 Ingress Helm values 只要打開 provider: providers: kubernetesIngressNGINX: enabled: true # 預設 ingressClass 是 nginx 原本的 Ingress 完全不用改。測試用的是三種最常見的寫法,原封不動貼進去: # 1. HTTPS 轉址 metadata: annotations: nginx.ingress.kubernetes.io/force-ssl-redirect: "true" nginx.ingress.kubernetes.io/ssl-redirect: "true" # 2. 長時間逾時 nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" # 3. IP 白名單 nginx.ingress.kubernetes.io/whitelist-source-range: "192.0.2.0/24" 實測結果: | 項目 | 結果 | |---|---| | HTTP → HTTPS 轉址 | 308 Permanent Redirect,Location 正確帶回原本的路徑,和 ingress-nginx 的預設一致 | | whitelist-source-range | 名單外的來源收到 403 Forbidden | | proxy-read-timeout | 有生效。為了方便驗證,改成 5 秒、後端 25 秒才回應,結果在 5.0 秒收到 504;改成 60 秒就正常通過 | | backend-protocol: "HTTPS" | 後端用自簽憑證的 HTTPS,收到 200;拿掉 annotation 就變成 400 | | WebSocket | 透過這個 Ingress 連線、閒置 30 秒後仍可收發 | 這個 provider 可以和 Gateway API 的 provider 同時開著。Traefik 官方文件沒有說明兩者能不能並存,所以實測了:同一個 Traefik 裡,Ingress 和 HTTPRoute 都正常回應。 有一個小地方:Ingress 的 ADDRESS 欄位是空的。這個 provider 預設沒有回填 status.loadBalancer,chart 有 publishService 這個選項,我沒有測它有沒有用。依賴 Ingress status 的工具(例如自動建立 DNS 記錄的)要留意。 另外,如果打算讓 ingress-nginx 和 Traefik 並行一段時間再切換,官方文件提醒兩個 controller 會互相覆寫 Ingress 的 status,路由不受影響,但監看 status 的工具會被干擾。這一點我沒有實測。 路線二:改寫成 Gateway API 先裝 Gateway API 的標準 CRD,再裝 Traefik 並打開 Gateway provider: kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.7.0/standard-install.yaml providers: kubernetesGateway: enabled: true gateway: enabled: false # 自己建 Gateway Traefik 的 chart 輸出訊息建議安裝 v1.5.1,我裝的是 v1.7.0,GatewayClass 仍然正常被接受。 Gateway 的 listener 埠必須對應 Traefik 的 entrypoint(預設是 HTTP 8000、HTTPS 8443),不是對外的 80 和 443: apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: {name: main, namespace: traefik} spec: gatewayClassName: traefik listeners: - name: web port: 8000 protocol: HTTP allowedRoutes: {namespaces: {from: All}} - name: websecure port: 8443 protocol: HTTPS tls: mode: Terminate certificateRefs: [{name: demo-tls}] allowedRoutes: {namespaces: {from: All}} HTTP 轉 HTTPS apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: {name: app-redirect, namespace: demo} spec: parentRefs: [{name: main, namespace: traefik, sectionName: web}] hostnames: [app.example.test] rules: - filters: - type: RequestRedirect requestRedirect: {scheme: https, statusCode: 308} 這一段有三個發現: - 不要寫 port。 一開始寫了 port: 443,Location 變成 https://app.example.test:443/x。拿掉之後才是乾淨的 https://app.example.test/x。 - statusCode 預設是 302。 沒寫的話,原本 ingress-nginx 的 308 會變成臨時轉址。 - 308 可以用,但屬於 Extended。 CRD 的說明是「301 與 302 是 Core,其餘是 Extended」。實測 Traefik 3.7.16 接受並回傳 308 Permanent Redirect。換別的 Gateway 實作就不一定支援,要看該實作的文件。 IP 白名單 Gateway API 標準沒有 IP 白名單的欄位。Traefik 的做法是建一個 Middleware,再用 ExtensionRef 掛到 HTTPRoute: apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: {name: only-office, namespace: demo} spec: ipAllowList: sourceRange: ["192.0.2.0/24"] --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: {name: internal, namespace: demo} spec: parentRefs: [{name: main, namespace: traefik, sectionName: websecure}] hostnames: [internal.example.test] rules: - filters: - type: ExtensionRef extensionRef: {group: traefik.io, kind: Middleware, name: only-office} backendRefs: [{name: whoami, port: 80}] 名單外的來源收到 403 Forbidden,把 sourceRange 換成 Pod 網段就通過。這是 Traefik 專屬的寫法,換 Gateway 實作就得重寫。 cert-manager 整合 憑證的位置從 Ingress 的 annotation,搬到 Gateway 的 listener。cert-manager 要打開 Gateway 支援: config: apiVersion: controller.config.cert-manager.io/v1alpha1 kind: ControllerConfiguration enableGatewayAPI: true Gateway 加上 issuer 的 annotation,listener 指向一個「還不存在」的 Secret: metadata: annotations: cert-manager.io/cluster-issuer: selfsigned spec: listeners: - name: auto hostname: auto.example.test port: 8443 protocol: HTTPS tls: mode: Terminate certificateRefs: [{name: auto-tls}] 大約 12 秒後,cert-manager 自動建出 Certificate(READY=True)和 auto-tls 這個 Secret,Traefik 開始用它提供 HTTPS,請求回 200。 這裡用的是自簽的 issuer。真正用 Let's Encrypt 時,DNS-01 或 HTTP-01 的流程這次沒有驗證。 長連線與逾時 原本 annotation 裡的 proxy-read-timeout: 3600,主要是為了 WebSocket 和長時間回應。到了 Traefik,要分三種情況看。 預設值。 不調任何逾時: - WebSocket 連上之後閒置 400 秒,再送一次訊息,仍然收到回應。 - 後端 100 秒才回應的 HTTP 請求,等到 100.1 秒成功收到 200。 3600 秒本身沒有實際等待測試,只能說預設值下,閒置幾百秒不會被切斷。 調得很短的 entrypoint 逾時。 為了確認哪些設定會影響,把 HTTPS entrypoint 的 readTimeout、writeTimeout、idleTimeout 都設成 10 秒: ports: websecure: transport: respondingTimeouts: readTimeout: 10s writeTimeout: 10s idleTimeout: 10s - WebSocket 閒置 30 秒,仍然正常。升級成 WebSocket 之後,連線不受這幾個逾時影響。 - 一般 HTTP 請求,後端 25 秒才回應,在第 10 秒被切斷(客戶端看到連線被中斷,沒有收到 HTTP 狀態)。 也就是說,要讓「長時間才回應」的請求(例如串流輸出)不被切掉,要看的是 entrypoint 的 writeTimeout。預設值下 100 秒的回應沒有被切斷(前面實測過),所以一般不用動,但一旦被調過就會是問題。 nginx annotation 不能蓋過 entrypoint。 在 entrypoint 設了 10 秒的情況下,即使 Ingress 寫了 proxy-read-timeout: "3600",後端 25 秒的請求還是在 10 秒被切斷。annotation 控制的是後端那一段的逾時,不會改寫 entrypoint 的設定。 ingress2gateway 轉出來的東西 官方的轉換工具 ingress2gateway 可以把現有的 Ingress 轉成 Gateway API。拿上面的三個 Ingress 餵給 v1.2.0: ingress2gateway print --input-file ing.yaml --providers ingress-nginx 轉換器會印出警告,值得逐條看: - force-ssl-redirect 被標為 Unsupported annotation。不過因為 Ingress 有 TLS,工具仍然自動產生了 308 的 HTTP → HTTPS 轉址路由,結果是對的,警告看起來只是沒有逐個 annotation 處理。 - whitelist-source-range 轉換失敗:IP-based authorization is not supported。白名單會直接掉,轉完的路由是沒有限制的。 - 逾時 annotation 會做「best-effort」轉成 timeouts.request,但數字不對。 最後一點值得單獨講。同樣的 annotation,轉出來的結果: | proxy-read-timeout(秒) | 轉出的 timeouts.request | |---|---| | 60 | 10m0s(應該是 1 分鐘) | | 100 | 16m40s(應該是 1 分 40 秒) | | 3600 | 10h0m0s(應該是 1 小時) | 每一筆都是預期的 10 倍。從原始碼看,解析 annotation 的那一步是正確的(秒數轉成 Duration),所以問題出在後面哪一步,這裡沒有追到,也沒找到對應的 issue。這是 v1.2.0 的觀察,之後版本可能已經修掉,重點是轉換結果一定要人工逐項核對,不要直接 apply。 踩到的坑 整理一下實測中遇到的: 1. k3s 內建的 Traefik 可能太舊。 v1.31.5 附的是 2.11.18,沒有 ingress-nginx 相容 provider,要自己用 Helm 裝 3.6.2 以上。 2. Gateway 的 listener 埠是 entrypoint 的埠。 寫成 80、443 會對不上。 3. RequestRedirect 不要寫 port,也別忘了 statusCode。 前者會讓網址多出 :443,後者預設是 302,不是 ingress-nginx 的 308。 4. IP 白名單在 Gateway API 沒有標準寫法。 得靠實作專屬的 Middleware,轉換工具也不會幫你處理。 5. 轉換工具的輸出要核對。 至少逾時數字要自己重算。 為什麼這樣評估 如果現有叢集的 Ingress 很多,而且多半只用到轉址、逾時、白名單這幾個 annotation,路線一的成本最低:換掉 controller 就好,YAML 一行不改,風險也最小。缺點是 annotation 的相容只涵蓋「一部分」。Traefik 官方文件也寫了某些 annotation 的行為有差異,所以還是要逐項驗證。 路線二是長期比較乾淨的方向,因為 Kubernetes 官方文件說 Ingress API 已經凍結,並建議改用 Gateway。但每個服務都得重寫,白名單、逾時這類能力要換成實作專屬的寫法,等於把鎖定換了一個地方。 所以比較務實的順序是:先用路線一把 controller 換掉,擺脫退役的專案;再依需求,一個服務一個服務搬到 Gateway API。這篇只是把可行性驗過一遍,哪一天真的動手,仍然要在自己的叢集重新驗證,尤其是真實憑證、來源 IP 與多節點的情境。 延伸閱讀 - Ingress NGINX 退役說明(官方 repo) - Kubernetes:Ingress(含 API 已凍結的說明) - Traefik:從 ingress-nginx 遷移 - Traefik:Kubernetes Ingress NGINX provider - ingress2gateway - cert-manager:Gateway API 支援
Kubernetes 1.34 end of support: a pre-flight checklist for 1.35 --- title: "Kubernetes 1.34 end of support: a pre-flight checklist for 1.35" published: true description: "Kubernetes 1.34 goes end of life on 27 October 2026. A practical pre-flight checklist for 1.35 and 1.36: cgroup v2, containerd 2.x, APIs, ingress, skew policy and etcd backups." tags: kubernetes, devops, containers, sre canonical_url: agency42.se --- By Johan Kuylenstierna, Agency 42. Commands verified on a kind lab cluster (Kubernetes v1.34.0) on 11 Oct 2026. Drafted with AI assistance; reviewed and approved by Johan Kuylenstierna. On 27 October 2026, Kubernetes 1.34 reaches end of life. It has been in maintenance mode since 27 August, which already meant CVE fixes only. After the 27th it gets nothing at all. Unlike the Vogons' demolition notice, this one wasn't hidden in a filing cabinet on Alpha Centauri. The 1.35 release notes spelled out the hard parts back in December. In practice, the minor version bump itself is rarely what hurts. What hurts is everything around it that nobody has looked at since the cluster was built: a node image from 2021, a pinned containerd, an ingress controller that was "temporary". One more thing before the checklist. Kubernetes 1.35 is itself end of life on 28 February 2027. Treat 1.35 as a waypoint and 1.36 (supported until 28 June 2027) as the destination. That matters, because 1.36 is where containerd 1.x support ends. Pre-flight 1: know what your nodes actually run Agent Le'man doesn't trust inventory spreadsheets. He reads nodeInfo: kubectl get nodes -o custom-columns=NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,RUNTIME:.status.nodeInfo.containerRuntimeVersion,KERNEL:.status.nodeInfo.kernelVersion Every row tells you something. containerd://1.7.x means a runtime upgrade belongs in the plan. Kubelets more than one minor apart mean someone stopped halfway last time. And a 4.x kernel is a hint for the next check. On the kind lab cluster used for this article it looks like this: NODE KUBELET RUNTIME KERNEL lab-control-plane v1.34.0 containerd://2.1.3 6.12.94+ Pre-flight 2: cgroup v2 on every Linux node On each node: stat -fc %T /sys/fs/cgroup/ cgroup2fs means cgroup v2. tmpfs means cgroup v1. On the lab node: $ stat -fc %T /sys/fs/cgroup/ cgroup2fs cgroup v1 is deprecated as of 1.35, and the kubelet's failCgroupV1 setting now defaults to true. With default config, a 1.35 kubelet will not start on a cgroup v1 node. You can set failCgroupV1: false in the kubelet config to buy time, but it is a temporary escape hatch and full removal follows the deprecation policy. kubeadm is stricter still: its SystemVerification preflight check errors out on cgroup v1 during init, join and upgrade with a 1.35 kubelet. Moving to cgroup v2 needs kernel 5.8 or later and usually a newer OS image. Also check the software that reads cgroups directly. Old JVMs, Node.js before 20.3.0 and standalone cAdvisor before v0.43.0 can misread memory limits under v2. The upstream cgroup v2 page has the version list. Pre-flight 3: containerd 2.x before you go past 1.35 SIG Node designated 1.35 as the last Kubernetes release to support containerd 1.x. containerd 1.7 has also reached its own end of life (30 September 2026), so it no longer gets patches either. To see which nodes are affected, look for the kubelet_cri_losing_support metric. Alert on it existing at all, not on a specific version label: kubectl get --raw "/api/v1/nodes/<node>/proxy/metrics" | grep kubelet_cri_losing_support On a containerd 2.x node this returns nothing (grep exits 1). Empty output is the result you want. According to containerd's Kubernetes support matrix, Kubernetes 1.35 is tested with containerd 2.2.0+, 2.1.5+ and 1.7.28+, and 1.36 with 2.3.0+ and 2.2.0+. Note that containerd 2.2 goes end of life on 6 November 2026, while 2.3 is an LTS release supported until April 2028. If you're touching containerd anyway, 2.3 is the longer-lived target. Things that bite during the containerd 2 move: - Config. Old config.toml files are migrated automatically on startup. containerd config migrate writes the new format, but don't commit to it until you're sure you won't roll back. - Removed features. CRI v1alpha2, the AUFS snapshotter and the runc v1 shims are gone. Pulling Docker Schema 1 images is disabled by default. - A free warning list. On containerd 1.7.12 and later, ctr deprecations list tells you what the node uses that 2.x will complain about. Run it before the upgrade, not after. One more trap: a 1.36 or 1.37 kubelet will still boot on containerd 1.7, because the kubelet falls back to its own cgroup driver setting. That fallback is scheduled for removal in 1.38. "It still boots" is not the same as "supported". Pre-flight 4: deprecated APIs, mostly the ones you didn't write Good news: the official deprecated API migration guide lists no built-in API removals after 1.32. For a 1.34 → 1.35 → 1.36 path, the risk sits in CRDs, operators and old Helm charts. The API server tells you who is still calling deprecated APIs: kubectl get --raw /metrics | prom2json | jq '.[] | select(.name=="apiserver_requested_deprecated_apis").metrics[].labels' On a fresh 1.34 lab cluster with nothing but Calico installed, it already found something: { "group": "", "removed_release": "", "resource": "endpoints", "subresource": "", "version": "v1" } That's core v1 Endpoints, deprecated since 1.33 in favour of EndpointSlices. No removal release is set, so it isn't an upgrade blocker, but it shows how the metric works: some client in your cluster is still calling it, and the metric tells you before a removal does. This covers only the API server instance you hit and only since its last restart, so check every instance and let it run for a while. For manifests and Helm releases, Pluto is actively maintained. kubent still works, but its last release was in 2024, so its rules may lag. Pre-flight 5: kube-proxy, ingress and the rest of the add-ons kube-proxy in ipvs mode is deprecated as of 1.35. It is scheduled to be disabled by default in 1.40 and removed in 1.43, so this isn't a fire, but start testing nftables mode now (it needs kernel 5.13+). On kubeadm clusters: kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep '^mode' Ingress NGINX (the Kubernetes community controller) was retired in March 2026. The repository is archived and the last release was controller-v1.15.1. It keeps working, but nobody will patch the next CVE. Check whether you run it: kubectl get pods --all-namespaces --selector app.kubernetes.io/name=ingress-nginx If you do, Gateway API is the recommended path and Ingress2Gateway will convert most of your Ingress objects for you. Then go through the rest of the list: CNI, CSI drivers, cert-manager, service mesh, monitoring agents. Each has a compatibility matrix, and the target Kubernetes version should be in it before you start. Pre-flight 6: PodDisruptionBudgets that block drains A PDB with zero allowed disruptions will hang kubectl drain indefinitely, usually at 23:40 on a Friday: kubectl get pdb -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,ALLOWED:.status.disruptionsAllowed Fix the zeros (single replicas, minAvailable: 100%) before the maintenance window, not during it. Managed and distribution clusters On managed Kubernetes, the upstream date isn't your deadline. Your vendor's is. | Platform | 1.34 standard support ends | Extended support | |---|---|---| | Amazon EKS | 2 Dec 2026 | until 2 Dec 2027, at extra cost | | Azure AKS | Nov 2026 | LTS until Nov 2027, if you enable it | | Google GKE | 25 Jan 2027 | until 25 Nov 2027 | | RKE2 (SUSE) | maintenance ended 27 Aug 2026 | EOL 27 Feb 2027 | Extended support is a bridge, not a destination. The cgroup, containerd and ingress work above still has to happen. It just happens later, and you pay for the wait. OpenShift 4.21 is built on Kubernetes 1.34 and uses CRI-O, so the containerd section doesn't apply. cgroup v1 has been gone since OpenShift 4.19, and Red Hat's lifecycle policy (including EUS on even-numbered releases) sets your calendar. RKE2 bundles its own containerd: v1.34.12+rke2r1 already ships containerd 2.2.7, so check the bundled versions in the release notes rather than the host packages. Upgrade order: the skew policy is the map The version skew policy sets the order: 1. kube-apiserver first. In HA setups, all instances must stay within one minor version of each other. 2. kube-controller-manager, kube-scheduler, cloud-controller-manager next. They must never be newer than the API server. 3. kubelets, node by node: drain, upgrade, uncordon. A kubelet must never be newer than the API server, and may be up to three minors older. 4. kube-proxy last, under the same rule. The control plane moves one minor at a time. kubeadm doesn't support skipping minor versions, so 1.34 → 1.36 means two upgrades. Upstream recommends going to the latest patch of your current minor first, then to the latest patch of the target. On kubeadm with pkgs.k8s.io, the package repository is per minor version, so switch the repo before kubeadm upgrade plan. And upgrade the CNI by hand. kubeadm won't do it for you. Backup and rollback: be honest about "rollback" Kubernetes doesn't really do in-place minor downgrades. In practice, rollback means a restore. So take a snapshot you have actually tested: sudo mkdir -p /var/backups sudo etcdctl --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save /var/backups/etcd-pre-1.35.db sudo etcdutl --write-out=table snapshot status /var/backups/etcd-pre-1.35.db # "Tested" means restored: restore into a scratch dir and check it opens sudo etcdutl snapshot restore /var/backups/etcd-pre-1.35.db --data-dir /tmp/etcd-restore-test The status check on the lab snapshot: +----------+----------+------------+------------+---------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | VERSION | +----------+----------+------------+------------+---------+ | 13f43fbd | 1636 | 457 | 6.1 MB | 3.6.0 | +----------+----------+------------+------------+---------+ ETCDCTL_API=3 is no longer needed: v3 has been the default since etcd 3.4, and etcdctl 3.6 warns unrecognized environment variable if you set it. kubeadm also writes etcd and manifest backups to /etc/kubernetes/tmp during an upgrade. That's a nice extra, but it's not a backup strategy. If your etcd is still on 3.5, move every member to 3.5.26 or later before going to 3.6 to avoid the zombie-member bug. On EKS, an in-place upgrade can be rolled back within 7 days. Elsewhere, staging is your rollback rehearsal. And if you're in NIS2 scope, "we ran an unsupported Kubernetes version" is not a sentence you want in an incident report. The copy-paste checklist [ ] Inventory: kubelet, runtime and kernel version per node [ ] cgroup v2 on every Linux node (stat -fc %T /sys/fs/cgroup/ = cgroup2fs) [ ] cgroup-aware app versions (JVM, Node.js >= 20.3.0, cAdvisor >= 0.43.0) [ ] containerd 2.x planned before 1.36 (2.3 LTS recommended); ctr deprecations list run [ ] kubelet_cri_losing_support absent on all nodes [ ] Deprecated API calls checked (apiserver metric + Pluto on manifests/Helm) [ ] kube-proxy mode known; nftables tested if you're on ipvs [ ] Ingress NGINX found and migration planned (Gateway API / Ingress2Gateway) [ ] CNI, CSI, cert-manager, mesh, agents: target version in their compatibility matrix [ ] PDBs with 0 allowed disruptions fixed [ ] Vendor support dates checked (EKS/AKS/GKE/OpenShift/RKE2) [ ] etcd snapshot taken AND test-restored [ ] Staging upgraded first; control plane -> nodes, one minor at a time [ ] Plan 1.35 -> 1.36 before 28 Feb 2027 Don't panic. Under three weeks is plenty, if you start today. More checklists live at Agency-42/kubernetes-best-practices, and if you'd rather not do this alone, Agency 42 can help with Kubernetes operations and upgrades. Sources - Kubernetes patch releases and EOL dates: kubernetes.io - Kubernetes v1.35 release blog (cgroup v1, containerd 1.x, ipvs, Ingress NGINX): kubernetes.io - Kubernetes v1.35 sneak peek (containerd 1.x final call): kubernetes.io - Cgroup driver autoconfiguration GA and containerd 1.y timeline: kubernetes.io - About cgroup v2 (stat check, failCgroupV1, app versions): kubernetes.io - The shift to cgroup v2 (kubeadm preflight, 1.38 fallback): kubernetes.io - Container runtimes (1.38 fallback removal): kubernetes.io - containerd releases and Kubernetes support matrix: github.com/containerd/containerd/blob/main/RELEAS… - containerd 2.0 changes: github.com/containerd/containerd/blob/main/docs/c… - Deprecated API migration guide: kubernetes.io - Deprecated API metric: kubernetes.io - kube-proxy modes (ipvs timeline, nftables): kubernetes.io - Ingress NGINX retirement: kubernetes.io - Version skew policy: kubernetes.io - Upgrading kubeadm clusters: kubernetes.io - Operating etcd (snapshot and verify): kubernetes.io - etcd v3.6 zombie members: kubernetes.io - Amazon EKS version calendar: docs.aws.amazon.com - AKS supported versions: learn.microsoft.com - GKE release schedule: cloud.google.com - OpenShift 4.21 release notes: docs.redhat.com - OpenShift 4.19 release notes (cgroup v1 removed): docs.redhat.com - SUSE product lifecycle (RKE2): suse.com - RKE2 v1.34 support matrix: suse.com --- Originally published at agency42.se. Want to automate the API-visible parts of this checklist? k8s-preflight is a small read-only script that runs them for you.