すべてが 1.37 の新しい変更ではない。
Kubernetes v1.37 は 2026年8月26日にリリースされた。このリリースのせいだとされている変更の多くは v1.34 と v1.35 で入ったものであり、ひとつはまだ 1 リリース先の v1.38 の話だ。そして本当に Pod を ContainerCreating のまま止めうる変更は、ほとんど話題になっていない。何がどこで起きたのかの帳簿、アップグレード前に走らせるべき監査、そして手順書をまとめる。
- Kubernetes
- SELinux
- アップグレード
- プラットフォーム
Kubernetes v1.37 は 2026年8月26日にリリースされた。そしてそれをめぐる記事群には注目に値する形がある。このリリースのものだとされているが実際にはそうでない変更がいくつもある。cgroup v1 のノードで kubelet が起動を拒むこと(このデフォルトが裏返ったのは v1.35)、Static Pod が Secret と ConfigMap の参照を失うこと(デフォルトで有効になったのは v1.34)、そして kube-proxy の ipvs モードが削除されること(v1.35 以来ずっと非推奨の告知が付いているだけで、削除の目標は v1.43)。ひとつは逆方向に誤って報じられている。containerd 1.x は v1.37 の kubelet に対して今も動き、崖があるのは v1.38 だ。その一方で、このリリースの中で本当にアップグレード当日に Pod を ContainerCreating のまま止めうる変更——SELinuxMount が GA になりデフォルトで有効になること——は段落ひとつで済まされている。SELinux を動かしていないクラスタの持ち主にとっては、それが見えないからだ。

この区別は細かすぎる話ではない。列が違えば必要な作業も違うからだ。cgroup の変更に 1.37 で驚かされたのなら、あなたはすでに 2 リリース分遅れていて、対処は切り戻しではなくノードごとの再起動だ。containerd 1.x はもう動かないと誰かに言われて慌ててコンテナランタイムを作り直したのなら、まだ 1 リリース先にある締め切りのためにメンテナンス枠を使ったことになる——本物の締め切りではあるが、今日のものではない。そして SELinux の変更に 1.37 で驚かされたのなら、起動しない Pod がすでに存在していて、しかもそれを見つけられたはずの監査は、アップグレードを済ませたあとでは実行が目に見えて難しくなる。だからこの記事は 2 つのことをやる。帳簿を正直に仕分けたうえで、作業が必要な部分——SELinux の監査とオプトアウトの全体、ipvs と containerd の実際のタイムライン、そして不可逆な工程を正しい順番に置いた手順書——を掘り下げる。
障害の出方と、それぞれがどのリリースのものか
まず実際に目にするものから始める。ここで挙げる障害はどれも、自分の原因を名乗らないからだ。あるノードで Pod が ContainerCreating のまま永遠に止まる一方、同一の Pod が別のノードでは正常に動く——これは SELinux のラベル競合で、決め手はどちらの Pod が先にボリュームに到達したかだ。アップグレードから戻ってきたノードで kubelet がそもそも起動しない——これはほぼ確実に cgroup v1 であり、v1.35 以来ずっとそうだった。2021年から Static Pod として動いてきた監視エージェントが、ちょうど 1 台——たった今アップグレードしたノード——だけで失敗する——これは、そもそも使えるはずのなかった Secret 参照であり、それを取り上げたデフォルトが入ったのは v1.34 まで遡る。[sneak]
| 見えている現象 | たいていの意味 | どこで扱うか |
|---|---|---|
あるノードで 1 つの Pod が ContainerCreating で止まり、同一の Pod は別の場所で動いている | 共有ボリューム上の SELinux ラベル競合。先にボリュームに到達した Pod が、そのマウント唯一のコンテキストを握っている | SELinuxMount |
| アップグレード後に kubelet がそもそも起動しない | failCgroupV1: false の上書きが無い cgroup v1。これは v1.35 以来ずっとそうだ | cgroup v1 |
| 何年も Static Pod だった監視エージェントが、アップグレードしたノードでだけ失敗する | Static Pod のマニフェスト内の secretRef または configMapRef。v1.34 以来デフォルトで禁止されており、リリースを飛ばしたクラスタに刺さる | Static Pod |
| kube-proxy が非推奨の警告をログに出し、あとはすべて動き続けている | ipvs モード、v1.35 以来の非推奨。v1.37 は KubeProxyIPVS ゲートを追加するだけで、削除の目標は v1.43 | ipvs |
Pod は動いていて、一部のノードで cri_losing_support メトリクスが非ゼロになっている | CRI の cgroup ドライバのフォールバックの上にいる containerd 1.x。v1.37 では今もサポート対象で、フォールバックが削除されるのは v1.38 | containerd |
externalIPs を持つ Service を適用するたびに API サーバが警告を出す | v1.37 の話ですらない——Service の ExternalIPs は v1.36 で非推奨になった。まだ何も削除されておらず、動かなくなるものも無い | 帳簿 |
アップグレード後も kubectl top と HPA が動き続けている | 正常。metrics.k8s.io が v1 に昇格し、両バージョンとも提供され続ける | metrics.k8s.io |
身につけておく価値のある考え方は、バージョンアップグレードは「移行先のリリースの変更」ではなく「最後に自信を持てていたリリース以降のすべての変更」を表面化させる、ということだ。実際のクラスタはマイナー 1 つずつ上がったりしない。1.34 に 1 年据え置いてから動く。Service の ExternalIPs は今まさにその例だ。これは v1.36 で非推奨になり、API サーバが使用のたびに警告を出すようになった。以前のバージョンから v1.37 へ来た人たちは、その警告に初めて出会っている。まだ何も削除されていない——kube-proxy 側のサポートがデフォルトで無効になるのは早くても v1.40、削除は早くても v1.43 の見込みだ——そして、3 リリース先の変更が障害に化けるのはまさにこの経路である。誰もリリースノートを読まなかったリリースで警告が到着するのだ。この記事の 2 番目の節がリリースノートの要約ではなく帳簿になっているのはそのためだ。[extip]
帳簿:1.37 の新しい変更と、すでに入っていたもの
正直な会計をここに示す。左の列はリリースチーム自身の sneak peek が v1.37 として挙げているもの、右側の列はその変更が実際にどこから来ているか、あるいはどこへ向かっているかだ。「それ以前」の行にあるものはすでに片付いているべきもので、片付いていなければアップグレードの作業枠がそれを知る場所になる。「それ以降」の行にあるものは、今週の作業ではなくカレンダーに入れるべき日付だ。[sneak]
| 変更 | よく帰属される先 | 実際に起きるのは | 1.37 のアップグレード当日に何が起きるか |
|---|---|---|---|
SELinuxMount が GA、デフォルトで有効 | 1.37 | 1.37 | Pod を ContainerCreating のまま止めうる。異なるラベルを持つ 2 つの Pod が 1 つのボリュームを共有し、CSI ドライバがオプトインしている場合。作業が要るのはこれだ |
KubeProxyIPVS フィーチャーゲートが追加され、非推奨と記される | 1.37 | 1.37 | 何も起きない。このゲートは、後で ipvs をデフォルト無効にするために存在する |
kubectl run --filename/-f が非推奨 | 1.37 | 1.37 | 警告のみ。しかも元から無視されていたフラグの話だ。影響するのはスクリプトであってクラスタではない |
metrics.k8s.io が v1 に昇格 | 1.37 | 1.37 | 壊れるものは無い。移行期間中は v1 と v1beta1 の両方が提供される |
| Static Pod は Secret も ConfigMap も参照できない | 1.37 | 1.34 | 新しいことは何も無い——ただしリリースを飛ばしてきた場合は別で、たった今アップグレードしたノードで該当の Static Pod が端的に壊れる |
| kubelet が cgroup v1 で起動を拒む | 1.37 | 1.35 | 新しいことは何も無い。ここで刺さったのなら、そのノードは v1.35 から failCgroupV1: false を抱えてきたということだ |
kube-proxy の ipvs モードが非推奨 | 「1.37 で削除」 | 1.35(警告のみ) | 起動時のログ 1 行。2 リリース分ずっとそうだった。ゲートのデフォルトが false になるのは 1.40、削除は 1.43 |
Service の ExternalIPs が非推奨 | 「1.36 で削除」 | 1.36(警告のみ) | API サーバの警告。デフォルト無効は早くても 1.40、削除は早くても 1.43 |
| containerd 1.x の CRI フォールバックが削除 | 1.36、ときに 1.37 | 1.38 | まだ何も起きない。 containerd 1.x はフォールバックの上で 1.37 でも動き、cri_losing_support がそれに頼っているノードを数えている |
この記事で最も役に立つ一文はこれだ。ノードが SELinux を enforcing で動かしていないなら、ここで最も長い節はまるごとあなたには関係しない。 SELinux が利用できないかカーネルで無効になっている場合、kubelet は SELinux のコードパスを丸ごと飛ばす。まずそこを確認すること——コマンド 1 つで済む——それが、この記事が半日の監査作業になるのか 10 分の読み物になるのかを決める。
このうち 2 つについては、誰かの言葉を信じるのではなく自分で検証する方法を書いておく価値がある。フィーチャーゲートのリファレンスは SELinuxMount、SELinuxChangePolicy、KubeProxyIPVS のリリースごとの状態を公開していて、「どのリリースがどのデフォルトを裏返したか」という主張を確認するにはこれが最速だ。そして各バージョンのリリースチームのブログには、それぞれの非推奨一覧が載っている。3 つ読んでも 20 分で、アップグレードの作業枠に投じられるものの大半よりは良い使い道になる。[gates]
自分が実際に立っている場所
これが実際に行動につながる前に、4 つの数字が必要だ。しかもそれらは互いに独立している。ノードごとの kubelet バージョン、そのノードで SELinux が enforcing かどうか、動いている cgroup のバージョン、そして kube-proxy がどのモードにいるか。ある程度の規模のフリートでは答えは揃わない。ノードプールはそれぞれの周期で進むし、バージョンスキューポリシーは kubelet が API サーバより遅れることを明示的に許している。つまり混在したフリートは怠慢の証拠ではなく、サポートされた構成だ。[skew]
# Four numbers decide how much of this article applies to you, and they are
# independent of each other. Ask for all four rather than assuming.
# 1. Control plane and kubelet versions. Node pools drift; this is normal.
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'RUNTIME:.status.nodeInfo.containerRuntimeVersion,'\
'KERNEL:.status.nodeInfo.kernelVersion,OS:.status.nodeInfo.osImage'
# NODE KUBELET RUNTIME KERNEL OS
# node-01 v1.36.4 containerd://2.3.2 6.8.0-51 Ubuntu 24.04.3 LTS
# node-02 v1.35.9 containerd://1.7.28 5.15.0-118 Ubuntu 22.04.5 LTS <- two problems
# 2. Is SELinux actually in play? If the answer is "no" on every node, the
# largest section of this article does not apply to you at all.
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -q -it --image=busybox --profile=general -- \
chroot /host sh -c 'getenforce 2>/dev/null || echo "not installed"' 2>/dev/null
done
# node-01 Enforcing <- section "SELinux" applies
# node-02 not installed <- it does not
# 3. Which cgroup version. v2 shows cgroup2fs; v1 shows tmpfs.
kubectl debug node/node-01 -q -it --image=busybox --profile=general -- \
chroot /host stat -fc %T /sys/fs/cgroup
# cgroup2fs
# 4. Which kube-proxy mode. This is the one people are most often wrong about,
# because the answer usually predates everyone currently on the team.
kubectl -n kube-system get configmap kube-proxy \
-o jsonpath='{.data.config\.conf}' | grep -E '^\s*mode:'
# mode: "ipvs"
# Clean up the debug pods. `kubectl debug node/...` names them
# node-debugger-<node>-<suffix> and applies no label, so there is nothing to
# select on - match the name, or they accumulate silently.
kubectl get pods -o name | grep '^pod/node-debugger-' | xargs -r kubectl deleteもうひとつ重要な数字は、今いるバージョンに実際どれだけ滑走路が残っているかだ。Kubernetes は直近 3 つのマイナーリリースをサポートし、それぞれおよそ 14 か月、最後の 2 か月はメンテナンスモードで重大なセキュリティ修正しか入らない。アップグレードが任意でない理由はこのスケジュールにあり、誰かが今回の見送りを提案してきたときに手元に置いておく価値がある。[k8srel]
| リリース | リリース日 | メンテナンスモード開始 | サポート終了 |
|---|---|---|---|
| 1.34 | 2025年8月27日 | 2026年8月27日 | 2026年10月27日——残り 2 か月 |
| 1.35 | 2025年12月17日 | 2026年12月28日 | 2027年2月28日 |
| 1.36 | 2026年4月22日 | 2027年4月28日 | 2027年6月28日 |
| 1.37 | 2026年8月26日 | 2027年8月ごろ | 2027年10月ごろ |
SELinuxMount が GA へ:Pod を止める変更
この記事が存在する理由がこの節だ。SELinuxMount は v1.37 で GA に到達し、デフォルトで有効になる。この変更は性能面の勝利であり、仕組みも見事だ。コンテナランタイムがボリュームを歩いてすべての inode にラベルを付け直す——大きなファイルシステムやリモートのファイルシステムでは本当に遅い——のではなく、kubelet が -o context=<label> を付けてボリュームをマウントし、カーネルがそのマウント上のすべての inode に定数時間でラベルを適用する。問題はこの仕組みの直接の帰結だ。1 つのマウントが保持できる SELinux コンテキストはちょうど 1 つである。 再帰的なラベル付けのもとでは、異なるラベルを持つ 2 つの Pod が 1 つのボリュームを共有できた。コンテキストマウントのもとではできない。どちらか一方は、もう一方が消えるまで ContainerCreating に座り続ける。[selblog][kep1710]
| 条件 | どこで確認するか | 成立しない場合 |
|---|---|---|
| ノードの OS が SELinux をサポートし、enforcing である | ノード上で getenforce | 何も変わらない。 kubelet は SELinux のパスを丸ごと飛ばす |
SELinuxMountReadWriteOncePod が有効 | v1.36 以降は GA で無条件 | サポート対象のバージョンでは該当しない |
SELinuxMount と SELinuxChangePolicy が有効 | フィーチャーゲート。SELinuxMount は 1.36 ではベータかつ無効、1.37 では GA かつ有効 | 従来どおり、ランタイムが再帰的にラベルを適用する |
Pod が少なくとも seLinuxOptions.level を指定している | Pod またはコンテナの securityContext | ランタイムがマウント後にランダムな level を割り当て、結局は再帰的にラベルを付け直す |
CSI ドライバが seLinuxMount: true を設定している | kubectl get csidrivers -o custom-columns=… | 変化なし。 未設定は true ではない。in-tree では fc、iscsi、rbd だけがこのオプションに対応 |
spec.securityContext.seLinuxChangePolicy が未設定または MountOption | Pod の spec | Recursive が明示的なオプトアウトであり、従来の挙動を保つ |
影響範囲は聞こえるほど広くない。最悪を想定するのではなく、正確に割り出す価値がある。1 つのボリュームの挙動が変わるまでには 5 つの条件がすべて成立する必要があり、見落とされがちなのは CSI ドライバだ。kubelet がコンテキストマウントを使うのは、ドライバが自身の CSIDriver オブジェクトに seLinuxMount: true を設定して「受け取れる」と宣言している場合だけである。このフィールドを未設定のままにしているドライバは——未設定は true ではない——従来の再帰的な挙動を保ち、デフォルトの裏返りの影響をまったく受けない。マウントオプションに対応している in-tree のボリュームタイプは fc、iscsi、rbd の 3 つで、それ以外の in-tree のものはいずれにせよ再帰的にラベルを付け直す。[csidriver]
# The blast radius of the SELinuxMount change is not "clusters with SELinux".
# It is the intersection of three things, and all three have to be true before
# a single pod is at risk.
# (a) SELinux enforcing on the node. Checked above. If not, stop here.
# (b) A CSI driver that has opted in. The kubelet only uses the mount option
# when the driver declares it can take one. Drivers that do not set this
# keep the old recursive relabel and are unaffected by the flip.
kubectl get csidrivers \
-o custom-columns='DRIVER:.metadata.name,SELINUXMOUNT:.spec.seLinuxMount'
# DRIVER SELINUXMOUNT
# ebs.csi.aws.com true <- volumes on this driver change behaviour
# efs.csi.aws.com false <- unchanged
# csi.trident.netapp.io <none> <- unset is not true; unchanged
# The in-tree volume types that support the mount option are fc, iscsi and rbd.
# Everything else in tree relabels recursively regardless.
# (c) Two pods with different SELinux labels sharing one volume. This is the
# part you cannot infer from a manifest, because the label is often
# assigned by the runtime rather than written down. The cheapest proxy is
# to find the volumes that more than one workload mounts at all:
kubectl get pods -A \
-o jsonpath='{range .items[*]}{range .spec.volumes[?(@.persistentVolumeClaim)]}'\
'{.persistentVolumeClaim.claimName}{"\t"}{end}{.metadata.namespace}{"\n"}{end}' \
| awk -F'\t' 'NF>1' | sort | uniq -c | sort -rn | awk '$1>1'
# 3 shared-media default
# 2 build-cache ci
# That list is a starting point, not an answer. The answer comes from the
# controller below, which knows the labels.
# One more thing worth knowing before you panic: a pod that mounts a volume
# through different subPaths used to be able to share it across labels too.
# That case also stops working, and it is rare enough that upstream says it has
# never been seen in practice.壊れる共有パターンは 2 つあり、現実に起きるのはそのうち 1 つだけだ。1 つ目は、2 つの Pod が異なる subPath を通じて異なるラベルで 1 つのボリュームを共有する形で、上流はこれを非常にニッチだと説明し、実際に見たことがないと述べている。2 つ目は特権 Pod と非特権 Pod が 1 つのボリュームを共有する形で、こちらもよくあるわけではないが実アプリケーションで観測されており、探しにいくべきなのはこちらだ。この件に承認を出す立場にいるなら、KEP 自身のアップグレードに関する記述を読む価値がある。公式の手順書に最も近いものがそこにある。[kepstory3]
アップグレードの前に済ませるべき監査
Kubernetes v1.36 はまさにこの問題のためのコントローラを出荷したが、ほとんど誰も有効にしていない。オプトインであり、しかも警告する対象の事態がまだ起きていなかったからだ。selinux-warning-controller は --controllers=*,selinux-warning-controller の背後で kube-controller-manager の中で動き、クラスタ内のすべての Pod を監視し、SELinuxMount が許さない形でボリュームを共有している Pod のペアを 1 組ずつ報告する。Pod が別々のノードにあっても報告する——スケジューラが明日それらを同居させるかもしれない、という正しい理屈による。[k8s136]
# Kubernetes v1.36 shipped a controller whose entire job is to find these
# conflicts before the upgrade turns them into stuck pods. It is off by
# default and it is the single most useful thing in this article.
#
# It runs inside kube-controller-manager. `*` keeps every default controller
# and adds this one; listing it alone would disable all the others.
# kubeadm clusters: edit the static pod manifest on each control plane node.
sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml
# spec:
# containers:
# - command:
# - kube-controller-manager
# - --controllers=*,bootstrapsigner,tokencleaner,selinux-warning-controller
# ^^^^^^^^^^^^^^^^^^^^^^^^^
# The kubelet restarts the pod when the file changes. Give it a minute.
# Note the second requirement, which is easy to miss: you must NOT have
# explicitly disabled the SELinuxChangePolicy feature gate. It is GA and on by
# default, so this only bites clusters carrying an old --feature-gates line.
kubectl -n kube-system get pod -l component=kube-controller-manager \
-o jsonpath='{.items[*].spec.containers[*].command}' | tr ',' '\n' \
| grep -E 'feature-gates|controllers='
# Confirm it is running before you trust its silence:
kubectl -n kube-system logs -l component=kube-controller-manager --tail=200 \
| grep -i 'selinux'
# ... "Starting controller" controller="selinux-warning-controller"
# Enabling it has one privacy consequence worth stating: the metric it emits
# carries namespace names as labels, so it can leak namespace names to anyone
# who can read kube-controller-manager metrics. Upstream's assumption is that
# only cluster administrators can.次に、メトリクスを両方読む。答えている問いが違い、どちらか一方では足りないからだ。コントローラの selinux_warning_controller_selinux_volume_conflict は競合する Pod 名と名前空間をラベルとして持つ——これが作業リストになる。kubelet の volume_manager_selinux_volume_context_mismatch_warnings_total には Pod 名のラベルが一切ないが、実際に失敗することになる Pod の正直な件数であり、SELinuxMount がまだ無効なうちに出力される。この最後の一節こそが、これを v1.36 でやることの意味そのものだ。すべてがまだ動いている状態で警告カウンタが非ゼロになる。アップグレード後には同じ測定が ..._errors_total として現れ、そのときには Pod は危険な状態ではなく、すでに止まっている。[selblog]
# There are two metrics and they answer two different questions. You need both:
# one tells you WHICH pods, the other tells you HOW MANY will actually fail.
# --- (1) From kube-controller-manager: which pods conflict, by name ---------
# Reported even when the pods are on different nodes, because the scheduler
# may put them together tomorrow.
kubectl get --raw /metrics 2>/dev/null | true # via your metrics stack, or:
kubectl -n kube-system exec -it \
"$(kubectl -n kube-system get pod -l component=kube-controller-manager \
-o name | head -1)" -- \
curl -sk https://127.0.0.1:10257/metrics \
| grep '^selinux_warning_controller_selinux_volume_conflict'
# selinux_warning_controller_selinux_volume_conflict{
# pod1_name="my-other-pod",pod1_namespace="default",
# pod1_value="system_u:object_r:container_file_t:s0:c0,c1",
# pod2_name="my-pod",pod2_namespace="default",
# pod2_value="system_u:object_r:container_file_t:s0:c0,c2",
# property="SELinuxLabel"} 1
# --- (2) From the kubelet: how many pods would actually fail ---------------
# Emitted while SELinuxMount is still DISABLED, which is exactly the window
# you are in before the upgrade. It has no pod-name label - that is what the
# controller above is for - but it is the honest count.
kubectl get --raw "/api/v1/nodes/node-01/proxy/metrics" \
| grep '^volume_manager_selinux_volume_context_mismatch_warnings_total'
# volume_manager_selinux_volume_context_mismatch_warnings_total{...} 2
# After the upgrade the same measurement lives under a different name, and by
# then the pods are already stuck rather than merely at risk:
# volume_manager_selinux_volume_context_mismatch_errors_total
# The whole point of doing this on v1.36 is that the warnings metric is
# non-zero while everything still works. Read it while it is still cheap.メトリクスが名指ししたワークロードごとに、選択肢は 2 つある。共有をやめるか、その Pod をオプトアウトさせるかだ。オプトアウトは Pod のフィールド spec.securityContext.seLinuxChangePolicy で、v1.36 以降は安定版 API になっている——つまり今、今のバージョンのまま、挙動を何も変えずに適用でき、アップグレードが着地した瞬間に正しく働く。このリリース全体で最も安上がりな保険がこれだ。Recursive を設定するとその Pod は従来の挙動を保ち、性能面の恩恵を失う。ラベルをまたいでボリュームを共有していたワークロードにとっては、もともと払っていた代償にすぎない。[seccontext][mutadm]
# For every workload the metrics named, you have two options: fix the sharing,
# or opt that pod out of the mount-option path. The opt-out is a Pod field and
# it is stable API as of Kubernetes v1.36.
apiVersion: apps/v1
kind: Deployment
metadata:
name: legacy-shared-cache
spec:
template:
spec:
securityContext:
# Three values:
# unset - follow the cluster default. On v1.37 with SELinuxMount
# enabled, that means mount options.
# MountOption - use mount options explicitly. Only valid while the
# SELinuxMount feature gate is on.
# Recursive - the pre-1.37 behaviour: the runtime relabels every
# file. Slower on large volumes, and it is what lets two
# differently labelled pods share one volume.
seLinuxChangePolicy: Recursive
containers:
- name: app
image: registry.internal.example.com/app:1.4.2
---
# Applying this to every affected workload by hand does not scale, and both
# the SELinux blog and the KEP say so. Prefer a policy. In-tree:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingAdmissionPolicy
metadata:
name: selinux-recursive-optout
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
# Scope this to the namespaces the metrics actually named. A cluster-wide
# opt-out works, but it also throws away the performance win for every
# workload that was never at risk.
matchConditions:
- name: only-flagged-namespaces
expression: "request.namespace in ['default', 'ci']"
failurePolicy: Fail
reinvocationPolicy: IfNeeded
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{ spec: Object.spec{
securityContext: Object.spec.securityContext{
seLinuxChangePolicy: "Recursive" } } }seLinuxChangePolicy | 1.36 での挙動 | 1.37 での挙動 | 使いどころ |
|---|---|---|---|
| 未設定(デフォルト) | 再帰的なラベル付け——SELinuxMount はデフォルトで無効 | マウントオプション。他の条件がすべて成立する場合 | ラベルをまたいでボリュームを共有していないものすべてにとってのデフォルト |
MountOption | フィーチャーゲートが有効なときだけ有効な値 | マウントオプション、明示的に | 必要になることはまれ。1.37 ではデフォルトがすでにこの挙動をする |
Recursive | 再帰的なラベル付け | 再帰的なラベル付け——オプトアウト | これが対処。 競合メトリクスが名指ししたすべてのワークロードに、アップグレード前に適用する |
影響を受けるすべての Deployment と StatefulSet に手作業でこのフィールドを付けていくのは、小規模なクラスタを超えるとスケールしない。SELinux のブログもそう明言し、一括で行う手段として MutatingAdmissionPolicy、Mutating Webhook、Kyverno、Gatekeeper を挙げている。適用範囲について助言がひとつ。念のためにと Recursive をクラスタ全体へ一律に適用したくなる誘惑には抗うこと。それは動くし、そして一度も危険でなかったすべてのワークロード——たいていのクラスタではほぼ全部だ——から性能改善を捨てることになる。メトリクスが実際に名指しした名前空間に絞ること。[kyverno]
Static Pod と Secret の参照:これは 1.34 の出来事
1.33 からアップグレードしてきた人によって、最も 1.37 のせいにされやすい障害がこれだ。Static Pod は API サーバ経由で作られるのではなくディスク上のディレクトリから kubelet が管理するものなので、そもそも API オブジェクトを読めるはずがなかった。ところが不具合により、configMapRef や secretRef といったフィールドを通じて Secret と ConfigMap を参照できてしまっていた。それを塞ぐ制限が PreventStaticPodAPIReferences フィーチャーゲートであり、v1.34 以来デフォルトで有効だ——つまり 1.34、1.35、1.36 を実際に踏んできたクラスタでは、これはすでに起きていて、該当する Pod はすでに壊れている。v1.37 の sneak peek はこのゲート自体が削除され、オプトアウトも一緒に無くなると告知した。しかし v1.37 とともに公開されたフィーチャーゲートのリファレンスは、PreventStaticPodAPIReferences をデフォルト true のベータゲートとして今も載せている。つまり逃げ道は技術的にはまだ残っているかもしれない。だが、無いものとして計画すること。これは機能ではなく不具合だったのであり、戻ってくることはない。そして Static Pod のディレクトリを grep するコストは、ノードごとにこれを発見するより安い。[staticpod][iss140226]
# Static pods are managed by the kubelet from a directory on disk, not by the
# API server. They were never supposed to be able to read API objects; a bug
# let them, through envFrom.configMapRef, envFrom.secretRef, valueFrom and
# volume references. The gate that closes it - PreventStaticPodAPIReferences -
# has defaulted to true since v1.34, so this is only "new in 1.37" for a
# cluster that skipped releases. The v1.37 sneak peek says the gate was
# removed in this release; the shipped v1.37 feature-gates reference still
# lists it as Beta/true. Plan as if the opt-out is gone: it closed a defect,
# and it is not coming back. Find the references before the upgrade.
# Where the manifests live. Do not assume /etc/kubernetes/manifests: read it
# from the kubelet's own configuration.
sudo grep -E '^staticPodPath:' /var/lib/kubelet/config.yaml
# staticPodPath: /etc/kubernetes/manifests
# The audit, per node. Any hit is a pod that will fail to start on v1.37.
sudo grep -rnE 'configMapRef|secretRef|configMapKeyRef|secretKeyRef|(configMap|secret):' \
/etc/kubernetes/manifests/
# /etc/kubernetes/manifests/node-exporter.yaml:24: secretRef:
# /etc/kubernetes/manifests/node-exporter.yaml:25: name: scrape-creds
# Fleet-wide, without logging into every box. Note that the control plane's
# own static pods (kube-apiserver, etcd, kube-scheduler,
# kube-controller-manager) are generated by kubeadm and do not use these
# references, so a clean result there is expected rather than reassuring.
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -q -it --image=busybox --profile=general -- \
chroot /host sh -c \
'grep -rlE "configMapRef|secretRef|configMapKeyRef|secretKeyRef" \
/etc/kubernetes/manifests/ 2>/dev/null | tr "\n" " " || true' 2>/dev/null
echo
done
# The fix is to stop being a static pod, or stop needing the reference:
# * A DaemonSet is the right answer for almost everything that is a static
# pod today for historical reasons. It can read Secrets normally.
# * If it has to stay static, put the value in the manifest, or bind-mount a
# file from the host and read it from there. Both are worse than a
# DaemonSet, and both work.対処はほぼ常に「Static Pod をやめる」ことだ。Static Pod の多くは歴史的な理由で存在している——DaemonSet が今ほど有能になる前、ノードレベルのエージェントを動かす手段がそれだった——のであり、DaemonSet なら Secret を普通に読めるし、ローリングアップデートも効き、人が見る場所に現れる。どうしても Static Pod のまま残す必要があるなら、選択肢は値をマニフェストに直書きするか、ホストからファイルを bind mount してそこから読むかだ。どちらも DaemonSet より劣り、どちらも動く。別件として、kubectl run --filename/-f もこのリリースで非推奨になったことを記しておく。生成される Pod はいずれにせよコマンドライン引数だけから組み立てられていた、というのが理由だ。こちらが影響するのはスクリプトであってクラスタではない。[iss138671]
kube-proxy の ipvs:1.35 以来の非推奨であって削除ではない
次は最も過剰に報じられている変更だ。kube-proxy の ipvs モードは v1.35 以来非推奨の告知を抱えている。v1.37 からではない。v1.37 が加えるのは、それ自体が非推奨と記された KubeProxyIPVS フィーチャーゲートであり、kube-proxy が起動時に出す警告ログはそれ以前からのものだ。動かなくなるものは何もなく、フィーチャーゲートも裏返らず、トラフィックにも影響しない。非推奨の理由は理解しておく価値がある。なぜ修正されなかったのかがそれで説明できるからだ。カーネルの ipvs API は Kubernetes の Service が必要とするものすべてを表現できないので、ipvs モードは仕事の一部について常に裏で iptables にフォールバックしてきた。KEP-3866 は節の見出しでこれを率直に書いている——kube-proxy の ipvs モードが我々を救うことはない、と。[kep5495][kep3866]
| リリース | ipvs モードに何が起きるか | やるべきこと |
|---|---|---|
| 1.35 | 非推奨。kube-proxy が起動時に警告をログに出す | 何も無い——ただし時計が動き出したのはここだ |
| 1.37 | KubeProxyIPVS フィーチャーゲートが追加され、それ自体が非推奨と記される | 何も無い。移行を計画する。急がない |
| 1.38 – 1.39 | 動き続け、警告も出続ける | 都合の良い作業枠で nftables モードへ移行する |
| 1.40 | KubeProxyIPVS ゲートのデフォルトが false になる見込み | ゲートで戻すか、そのときまでに終えているか |
| 1.43 | サポートが完全に削除 | もう何も残っていない——これが締め切り |
行き先は nftables モードで、v1.33 以降 GA であり、Linux ノードでの推奨モードだ。クラスタが勝手に移行することはない。mode: "nftables" を明示的に設定する必要がある。カーネル要件は本物だが重荷ではない——nftables モードをサポートできないほど古いカーネルはいずれも 2026年末までに LTS を離れる——し、nftables のバグ修正は、古いリリースにいる ipvs のユーザーが Kubernetes を先に上げなくても移行できるように、意図的に 1.33 と 1.34 のブランチへバックポートされている。[nftblog]
# What actually happens on v1.37 if you run ipvs mode: kube-proxy logs the
# same deprecation warning it has logged since v1.35. That is all. Nothing
# stops working, no feature gate flips, and no traffic is affected. What v1.37
# adds is the KubeProxyIPVS gate, itself marked deprecated - the switch that
# will later be used to turn the mode off by default.
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=50 | grep -i deprecat
# W0826 ... "ipvs mode of kube-proxy is deprecated and will be removed in a
# future release; see KEP-5495"
# The reason is worth knowing, because it explains why there is no fixing it:
# the kernel's ipvs API cannot express everything a Kubernetes Service needs,
# so ipvs mode has always fallen back to iptables underneath for parts of the
# job. It was never the clean escape from iptables it was sold as.
# The destination is nftables mode, GA since v1.33. Clusters never migrate on
# their own - you have to set it.
# --- migrating, on a kubeadm cluster ---------------------------------------
# 1. Check the kernel. nftables mode wants a reasonably modern kernel; every
# kernel too old for it leaves LTS by the end of 2026.
kubectl get nodes -o jsonpath='{range .items[*]}{.status.nodeInfo.kernelVersion}{"\n"}{end}' \
| sort -u
# 2. Change the mode in the ConfigMap.
kubectl -n kube-system get configmap kube-proxy -o yaml > /tmp/kube-proxy.bak.yaml
kubectl -n kube-system patch configmap kube-proxy --type merge -p \
"$(printf '{"data":{"config.conf":%s}}' \
"$(kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' \
| sed 's/^\(\s*mode:\).*/\1 "nftables"/' | jq -Rs .)")"
# 3. Roll the DaemonSet one node at a time and watch, rather than all at once.
kubectl -n kube-system rollout restart daemonset/kube-proxy
kubectl -n kube-system rollout status daemonset/kube-proxy --timeout=10m
# 4. Verify from the data plane, not the control plane. A Service that
# resolves but does not connect is the failure mode here.
kubectl run nft-check --rm -it --restart=Never --image=busybox -- \
sh -c 'wget -qO- --timeout=5 http://kubernetes.default.svc/healthz || echo FAILED'
# Rolling back is the same edit in reverse; keep /tmp/kube-proxy.bak.yaml.
# Do this as its own change, on its own day. Bundling a proxy-mode migration
# into a version upgrade means that when connectivity breaks you will not know
# which one did it.順序についてひとつ、実感を込めて助言しておく。プロキシモードの移行は、バージョンアップグレードに抱き合わせるのではなく、独立したメンテナンス枠で、独立した日にやること。どちらの変更もデータプレーンに触れるので、両方を含む作業枠で疎通が壊れたら、直すのではなく「どちらのせいか」を割り出すことに障害時間を費やすことになる。まとめる理由になるほどの時間的圧力はここには無い——非推奨ポリシーはベータ以上の機能に長い滑走路を保証しているし、KEP 自身の卒業基準はフィーチャーゲートのデフォルトが false になるのを v1.40、削除を v1.43 としている。[kubeproxycfg][deprecpolicy]
cgroup v1:これは 1.35 の出来事
cgroup v1 の話は最も帰属を取り違えられており、そして正しく理解すると打つ手が変わる。kubelet の failCgroupV1 設定は Kubernetes v1.35 以来 true がデフォルトだ。したがって cgroup v1 のままのノードは、誰かが failCgroupV1: false を足していない限り、そのときからずっと kubelet の起動を拒んできた——そして v1.35 のアップグレードの最中に、多くの人が急いでそれを足し、その後一度も見直していない。だから v1.37 へ向かう道で有用な問いは「これは壊れるか」ではなく「誰がまだ上書き設定を抱えていて、それをいつまで続けるのか」だ。[kep5573][kubeletcfg]
# This is the change most often misattributed to v1.37. The kubelet setting
# `failCgroupV1` has defaulted to TRUE since Kubernetes v1.35. A node still on
# cgroup v1 has therefore been refusing to start its kubelet since v1.35,
# unless somebody added the override - which many people did, in a hurry, and
# then forgot.
# So the useful question on the way to v1.37 is not "will this break" but
# "who is still carrying the override?"
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -q -it --image=busybox --profile=general -- \
chroot /host sh -c \
'printf "cgroup=%s override=%s\n" \
"$(stat -fc %T /sys/fs/cgroup)" \
"$(grep -c failCgroupV1 /var/lib/kubelet/config.yaml 2>/dev/null)"' 2>/dev/null
done
# node-01 cgroup=cgroup2fs override=0 <- fine
# node-02 cgroup=tmpfs override=1 <- v1, running on borrowed time
# The override itself, for reference. It is a stopgap and upstream says so:
# apiVersion: kubelet.config.k8s.io/v1beta1
# kind: KubeletConfiguration
# failCgroupV1: false
# v1.37 still honours it. KEP-5573 removes cgroup v1 support outright in a
# later release, and no date has been committed to, so the honest planning
# assumption is "the next one that suits SIG Node" rather than a fixed month.
# What you lose in the meantime is not theoretical. In-place pod resizing and
# tiered memory protection - both features people are actively asking for -
# depend on cgroup v2 and simply do not work on a v1 node.
# Switching the node is a kernel command line change and a reboot:
# systemd.unified_cgroup_hierarchy=1
# ...and then the container runtime's cgroup driver has to agree with the
# kubelet's. That is a longer job than it looks, which is why it has its own
# article.v1.37 はこの上書き設定を今も尊重する。そして KEP-5573 は後のリリースで cgroup v1 のサポートを完全に削除するが、その時期は確約されていない——だから正直な計画上の前提は、計画書に書ける月ではなく「SIG Node の都合の良いリリース」だ。その間に手放しているものは理屈の上の話ではない。In-place の Pod リサイズと階層化されたメモリ保護はどちらも cgroup v2 に依存していて、v1 のノードでは単に機能しない。しかもこれらは、現にチームから要望が上がる類の機能だ。切り替え自体はカーネルコマンドラインの変更と再起動、そしてコンテナランタイムの cgroup ドライバを kubelet のそれと一致させることであり——これは聞こえるより手間がかかるので、別記事にしてある。[cgroups]
containerd 1.x:崖は 1.38 であって今ではない
こちらは逆方向に誤って報じられており、取り違えると必要のなかったメンテナンス枠を 1 つ失うことになる。containerd 1.x のサポートは v1.36 で削除されていないし、v1.37 でも削除されていない。v1.37 の kubelet は今もそれに対して動く。KubeletCgroupDriverFromCRI が有効なとき、kubelet は RuntimeConfig の CRI RPC でランタイムに cgroup ドライバを問い合わせる。containerd 1.y はその RPC を実装していないので、kubelet は黙って自分の --cgroup-driver の値にフォールバックし、その際に cri_losing_support メトリクスを増やす。このフォールバックはもともと v1.37 で消える予定だったが、containerd v1.7 自身のサポート期間に合わせるという明示的な理由で 1 リリース先送りされた。Kubernetes のランタイムのドキュメントは今やそれを明言している。v1.38 でフォールバックは削除され、古い containerd は新しい kubelet に対して失敗する。[k8s134][ctrrel]
# The change most often stated backwards. containerd 1.x was NOT removed in
# v1.36, and it still runs against a v1.37 kubelet. What it runs on is a
# fallback: the kubelet asks the runtime for its cgroup driver over the
# RuntimeConfig CRI RPC, containerd 1.y does not implement that RPC, and the
# kubelet falls back to its own --cgroup-driver value. That fallback was
# scheduled to go in v1.37 and was deferred one release to align with
# containerd v1.7's support window, so it disappears in v1.38.
# The audit is a metric, not a spreadsheet. Every node relying on the fallback
# increments this, so scrape it rather than walking nodes by hand.
kubectl get --raw /metrics | grep '^cri_losing_support'
# cri_losing_support{version="1.38.0"} 1
# Then confirm which nodes, and with what:
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,'\
'RUNTIME:.status.nodeInfo.containerRuntimeVersion' | sort -k3
# NODE KUBELET RUNTIME
# node-02 v1.37.0 containerd://1.7.28 <- works today, fails on 1.38
# node-01 v1.37.0 containerd://2.3.2
crictl version
# RuntimeName: containerd
# RuntimeVersion: v2.3.2
# RuntimeApiVersion: v1
# Two things make this less comfortable than "one release of runway" sounds:
# containerd 1.7's own extended support ends in September 2026, and
# containerd's published Kubernetes support matrix has no row for 1.37 yet -
# the last row is 1.36 (2.3.0+, 2.2.0+). Book the migration before 1.38, in
# its own window. Landing two runtime-level changes together means that when a
# node comes back wrong you will be bisecting instead of fixing.つまり締め切りは本物で、ちょうど 1 リリース先にある——騒がれているほど悪い位置ではないが、何もしないでよいというほど良い位置でもない。これを鋭くする事情が 2 つある。まず、時計はランタイム側のほうが先に切れる。ここで頼りになる LTS ブランチは containerd 1.7 であり、その延長サポートは 2026年9月に終わる——つまり今だ。そして containerd 自身の Kubernetes サポートマトリクスには、そもそも Kubernetes 1.37 の行がまだ存在しない——最後に公開されている行は 1.36 で、2.3.0+ と 2.2.0+ が挙がっている——ので、1.37 向けに containerd 公認のバージョンを提示してくる人は、引用しているのではなく外挿している。実務としては、cri_losing_support をスクレイプすること。これはフォールバックを必要としているノードがすでに出力しているもので、ノードを手で回るより良い監査になる。そのうえで containerRuntimeVersion で裏を取る。ランタイムの移行は 1.38 より前に、専用の作業枠でやること。ランタイムレベルの変更を 1 つの枠にまとめるということは、ノードがおかしな状態で戻ってきたときに、直すのではなく二分探索をするということだ。[runtimes]
metrics.k8s.io がようやく v1 に到達する
このリリースの朗報であり、しかも本当に良い話だ。metrics.k8s.io がおよそ 9 年のベータを経て v1 に昇格する。これは kubectl top と HorizontalPodAutoscaler の CPU / メモリメトリクスの裏にある API であり、Kubernetes の中でも最も広く使われているインターフェースのひとつだ。これほど長くベータに置かれていたこと自体が奇妙とも言える。この昇格は変更を持ち込むものではなく安定性を追認するものだ。機能上の差異は想定されておらず、移行期間中は v1 と v1beta1 の両方が提供され続ける。[kep5207][metricspipe]
# The good news in this release. metrics.k8s.io graduates to v1 after nearly
# nine years in beta. Both versions stay served during the transition, so
# there is nothing to do on upgrade day - this is a thing you can adopt on
# your own schedule rather than a thing that happens to you.
kubectl get --raw /apis/metrics.k8s.io | jq -r '.versions[].groupVersion'
# metrics.k8s.io/v1
# metrics.k8s.io/v1beta1
# What consumes it: `kubectl top`, and the HorizontalPodAutoscaler's cpu and
# memory metrics. Both keep working without changes.
kubectl top nodes
kubectl top pods -A --sort-by=memory | head
# Where it matters is code you own. Anything that talks to the API directly -
# a custom autoscaler, a capacity report, a dashboard backend - should move
# off v1beta1 while both are available rather than after one is not.
kubectl get --raw /apis/metrics.k8s.io/v1/nodes | jq '.items[0]'
# Find the clients still on the beta path, from the API server's own counters:
kubectl get --raw /metrics \
| grep 'apiserver_requested_deprecated_apis\|metrics.k8s.io.*v1beta1' | head
# There is no removal date for v1beta1 yet. Kubernetes' deprecation policy
# guarantees a beta API at least nine months or three releases after
# deprecation, so this is a housekeeping item, not a deadline.つまりアップグレード当日にやることは何もない。そこが肝心で、これは自分に降りかかってくるものではなく、自分の都合で採用するものだ。効いてくるのは自分が持っているコードのほうで、カスタムのオートスケーラ、キャパシティレポート、ダッシュボードのバックエンドなど API を直接叩くものは、片方が無くなってからではなく両方あるうちにベータのパスから離れておくべきだ。v1beta1 の削除予定日はまだ無いし、非推奨ポリシーはベータ API に少なくとも 9 か月または 3 リリースを保証しているので、締め切りではなく片付け仕事として扱えばいい。[hpa]
リリースの残りを手短に
このリリースには、アップグレードに影響しないとはいえ知っておく価値のあることがあと 3 つある。3 つとも削除ではなく昇格であり、注目すべきは 1 つ目だ。[kep4960]
- ユーザー名前空間内の kubelet——rootless モード——がベータに到達。 ノードのコンポーネントは伝統的にホスト上で root として動いてきた。この機能は、Linux のユーザー名前空間の中では root に見えたまま、ホスト上では非特権ユーザーとして動かすことを可能にし、ノードコンポーネントの脆弱性の影響範囲を狭める。ここでのベータは、ゲートがデフォルトで有効になるという意味だが、有効にしただけで kubelet がユーザー名前空間の中で動きはじめるわけではない——その背後にホスト側の設定がある——し、この変更はリリースの告知にも載らなかった。自分の身に起きたことではなく、テスト用のノードで試してみる類のものとして扱うこと。なお、Pod のためのユーザー名前空間とは別の機能でもある。そちらは v1.35 でベータ、v1.36 で GA になっている。
- ReadWriteOncePod ボリュームの SELinux ラベル付けは v1.36 ですでに GA になっている。 これは
SELinuxMountReadWriteOncePodであり、この記事が扱うSELinuxMountより狭いゲートだ。一部のクラスタがしばらく前からコンテキストオプション付きでマウントしていながら何も壊れていないのはこのためである。RWOP のボリュームは定義上共有できないので、この記事が述べる競合はそこでは起こりえない。 - ボリュームのヘルスモニタリングがアルファからやり直しになる。 最初の実装は v1.21 で入ったまま昇格しなかった。KEP-1432 は
CSIVolumeHealthゲートの背後でこれをリセットし、4 つの CSI RPC——ControllerListVolumeHealth、ControllerGetVolumeHealth、NodeGetVolumeHealth、NodeGetStorageHealth——を導入して、PersistentVolumeClaim.status.healthStatus、Pod.status.volumeHealth、CSINode.status.storageHealthへ報告する。語彙は意図的に小さく機械可読で、Inaccessible、DataLoss、Degradedの 3 つと、ドライバ固有の詳細のために condition に付くreasonとmessageだけだ。
アルファはデフォルト無効で本番向けではないという意味だが、ハングしたマウントをストレージベンダーのダッシュボードと突き合わせて原因を割り出す作業をしたことがあるなら、このヘルスモニタリングの作業は追いかける価値がある。その問いに対して Kubernetes が初めて提示した機械可読な答えがこれだ。[kep1432][userns]
アップグレード、順番どおりに
コマンドよりも順番のほうが重要だ。このアップグレードで本当に難しいことはすべて、アップグレードの前にある。そしてあとになると目に見えて割高になる唯一の工程が SELinux の監査だ——だからこれを最初に、分単位ではなく週単位で前倒しして置く。[kubeadmup]
# The order matters more than the commands, and the SELinux audit has to come
# first because it is the only step that is materially harder after the
# upgrade than before it.
# --- WEEKS BEFORE, on v1.36 ------------------------------------------------
# 1. Turn on selinux-warning-controller, read both metrics, apply the opt-outs.
# 2. Audit static pods for Secret and ConfigMap references. Move them to
# DaemonSets where you can.
# 3. Get every node onto containerd 2.x and cgroup v2, if any are not.
# Both of these are already overdue rather than upcoming.
# 4. Decide about ipvs - and then do it in a DIFFERENT window.
# --- THE DAY ---------------------------------------------------------------
# Control plane first, one node at a time. Nothing here is 1.37-specific;
# it is the standard kubeadm sequence and it is standard because it works.
sudo apt-mark unhold kubeadm && sudo apt-get update \
&& sudo apt-get install -y kubeadm='1.37.0-*' && sudo apt-mark hold kubeadm
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.37.0 # first control plane node
# sudo kubeadm upgrade node # every other control plane node
# Then the kubelet and kubectl on that same node:
sudo apt-mark unhold kubelet kubectl && sudo apt-get update \
&& sudo apt-get install -y kubelet='1.37.0-*' kubectl='1.37.0-*' \
&& sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet
# --- WORKER NODES, ONE AT A TIME -------------------------------------------
NODE=node-02
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --timeout=15m
# ... upgrade kubeadm, run `kubeadm upgrade node`, upgrade kubelet, restart ...
kubectl uncordon "$NODE"
# Then STOP and look, before the next node. The failure this release can
# produce is a pod stuck in ContainerCreating, and it is per-node:
kubectl get pods -A --field-selector spec.nodeName="$NODE" \
-o wide | grep -v Running | grep -v Completed
# The version skew policy is what makes the staged rollout legal: a kubelet
# may be up to three minor versions behind the API server, so a fleet halfway
# through this is a supported configuration rather than a risk in itself.段階的に進めることについてひとつ安心材料がある。バージョンスキューポリシーは kubelet が API サーバより最大 3 マイナー遅れることを許しているので、このロールアウトを半分まで進めたフリートはそれ自体がリスクなのではなく、サポートされた構成だ。時間をかけること。1 台アップグレードし、きちんと見て、それから続ける——このリリースが生みうる障害はノード単位で起き、誰かが呼び出しをかけるエラーとしてではなく「決して起動しない Pod」として現れるからだ。[skew]
切り戻しと、切り戻せないもの
切り戻しについては、安心させる答えではなく率直な答えを出したい。そしてこのアップグレードでは答えが 3 つに分かれる。可逆な変更は本当に可逆だ。kube-proxy のモードは ConfigMap と DaemonSet の再起動であり、seLinuxChangePolicy は v1.36 と v1.37 でまったく同じ挙動をする Pod のフィールドだ——だからこそ、アップグレード前に適用しておくことにコストが無く、切り戻しの筋書き全体をそれで買えることになる。[kubeadmup]
# Rollback deserves a straight answer. Most of this release rolls back; one
# part of it does not roll back in the way people assume.
# --- What rolls back cleanly ----------------------------------------------
# The kube-proxy mode change: it is a ConfigMap and a DaemonSet restart.
kubectl -n kube-system apply -f /tmp/kube-proxy.bak.yaml
kubectl -n kube-system rollout restart daemonset/kube-proxy
# The seLinuxChangePolicy opt-out: it is a Pod field. Setting it to Recursive
# is safe on v1.36 and v1.37 alike, which is why applying it BEFORE the
# upgrade costs nothing and buys the whole rollback story.
# --- What does not ---------------------------------------------------------
# The control plane. `kubeadm upgrade` has no downgrade path: going back means
# restoring the etcd snapshot you took before you started, which means losing
# everything written to the cluster since. If you did not take one, you do not
# have a rollback - you have a forward fix.
sudo ETCDCTL_API=3 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.37-$(date +%F).db"
# The static pod references. If a static pod was relying on a secretRef, the
# reference is gone on 1.37 and comes back on a downgrade - but the downgrade
# is the control plane operation above, so in practice the fix is forward:
# move it to a DaemonSet.
# --- The one that surprises people ----------------------------------------
# Downgrading the kubelet does NOT un-stick a pod that failed to mount because
# of an SELinux label conflict, because the pod that WON the volume is still
# holding it with its own context. Terminate one of the two, or set
# seLinuxChangePolicy: Recursive on both and let them share again. Version
# numbers are not the lever here; the Pod field is.切り戻せない部分はコントロールプレーンだ。kubeadm upgrade にダウングレードの経路は無い。後戻りするということは、開始前に取得した etcd のスナップショットを復元し、それ以降クラスタに書かれたすべてを失うということだ。取っていなければ、あるのは切り戻しではなく前進での修正であり、それは午前 2 時にするには別種の会話になる。そして人が驚く障害がひとつある。kubelet をダウングレードしても、SELinux のラベル競合でマウントに失敗した Pod は解放されない。ボリュームを勝ち取った Pod が自分のコンテキストでそれを握り続けているからだ。ここでのレバーはバージョン番号ではなく Pod のフィールドである。[selblog]
祈るのではなく検証する
このアップグレードの検証には特有の形がある。特徴的な障害では、すべてが緑のまま残るからだ。ノードは Ready。kubelet は健全。コントロールプレーンも問題なし。ただ、あるノードの上で、あるチームの、ある Pod だけが作成を終えない。だから「クラスタが立っている」ことを確認しても何も証明されない。静かに間違っていられるもののほうを確認する必要がある。[chlog]
#!/usr/bin/env bash
# Post-upgrade verification. Exits non-zero when something is wrong, so it can
# run between nodes in a pipeline rather than being read by a person at 3am.
set -uo pipefail
rc=0
fail() { printf ' FAIL %s\n' "$*"; rc=1; }
pass() { printf ' ok %s\n' "$*"; }
echo '== versions =='
kubectl version -o json | jq -r '.serverVersion.gitVersion'
echo '== every node Ready and on the expected version =='
bad=$(kubectl get nodes --no-headers | awk '$2!="Ready"{print $1}')
[ -z "$bad" ] && pass 'all nodes Ready' || fail "not Ready: $bad"
echo '== no pod stuck creating (the SELinux failure mode) =='
stuck=$(kubectl get pods -A --no-headers \
| awk '$4=="ContainerCreating"{print $1"/"$2}')
[ -z "$stuck" ] && pass 'nothing in ContainerCreating' || fail "stuck: $stuck"
echo '== SELinux context mismatches that became real failures =='
for n in $(kubectl get nodes -o name); do
v=$(kubectl get --raw "/api/v1/nodes/${n#node/}/proxy/metrics" 2>/dev/null \
| awk '/^volume_manager_selinux_volume_context_mismatch_errors_total/{s+=$2} END{print s+0}')
[ "$v" = "0" ] && pass "${n#node/}: 0 mismatch errors" \
|| fail "${n#node/}: $v SELinux mismatch errors"
done
echo '== static pods all running =='
sp=$(kubectl get pods -A -o json \
| jq -r '.items[] | select(.metadata.annotations["kubernetes.io/config.source"]=="file")
| select(.status.phase!="Running") | .metadata.namespace+"/"+.metadata.name')
[ -z "$sp" ] && pass 'static pods Running' || fail "static pods not Running: $sp"
echo '== the resource metrics API answers on both versions =='
kubectl get --raw /apis/metrics.k8s.io | jq -e \
'[.versions[].groupVersion] | index("metrics.k8s.io/v1")' >/dev/null \
&& pass 'metrics.k8s.io/v1 served' || fail 'metrics.k8s.io/v1 missing'
kubectl top nodes >/dev/null 2>&1 && pass 'kubectl top works' || fail 'kubectl top broken'
echo '== service traffic actually flows =='
kubectl run verify-net --rm -i --restart=Never --image=busybox --timeout=60s -- \
sh -c 'wget -qO- --timeout=5 http://kubernetes.default.svc/healthz' >/dev/null 2>&1 \
&& pass 'in-cluster Service reachable' || fail 'in-cluster Service unreachable'
exit $rc最後にまとめてではなく、ノードとノードの間で走らせること。このスクリプトは問題があれば非ゼロで終了するので、午前 3 時に人が読むのではなくパイプラインの 1 工程に置ける。そしてアップグレードが終わったあとも残す価値がある確認が 2 つあり、それが ContainerCreating の掃き出しと SELinux の不一致カウンタだ。どちらも安上がりで、どちらも「誰かが自分のワークロードが戻っていないことに気づくまで待つ」種類の問題を捕まえる。[k8spatch]
どの順番でやるか
圧縮すれば、これはこの記事が匂わせるより小さなアップグレードだ——ただし、その 2 つ前のリリースに属する作業を済ませてあれば、の話だが。下の判断表は実のところ、そのどれを飛ばしたのかを問うている。[sneak]
| あなたの状況が… | なら 1.37 は… | そして作業は… |
|---|---|---|
| 1.36 上、SELinux はどこにも無く、containerd 2.x、cgroup v2 | 通常のアップグレード | 標準的な kubeadm の手順。Static Pod のマニフェストを grep して進める |
1.36 上、SELinux は enforcing、seLinuxMount: true の CSI ドライバあり | 監査が必要になるほうのアップグレード | 今すぐ警告コントローラを有効にし、メトリクスを両方読み、指し示された先に Recursive を適用してからアップグレードする |
| 1.34 または 1.35 上、1.37 へ一気に上げる計画 | 2~3 リリース分の変更が同時に来る | 飛び越えるすべてのバージョンのリリースノートを読むこと。Static Pod の制限、cgroup のデフォルト、ExternalIPs の警告はどれもそこにある |
| まだ cgroup v1 のノードがある | 最も急ぐべき問題ではない | そのノードは 1.35 から上書き設定を抱えている。アップグレードの前に、別途変換すること |
| まだ containerd 1.x のノードがある | 今日は問題ない。1.38 で壊れる | アップグレード当日の作業ではない。cri_losing_support をスクレイプし、1.38 を取る前にランタイムの移行を単独の枠で予約すること |
kube-proxy を ipvs モードで動かしている | ログ 1 行、それ以上ではない | nftables への移行は別の日に。1.43 までの猶予があり、抱き合わせれば障害の原因が見えなくなる |
| マネージドサービス(GKE、EKS、AKS) | プロバイダが決めるスケジュールどおり | SELinux の監査は依然としてあなたのもの——ノードが相手のものでも、ワークロードと CSI ドライバはあなたのものだ |
- まず SELinux の問いに、コマンド 1 つで答える。 どのノードも SELinux を enforcing で動かしていないなら、ここで最も長い節をまるごと飛ばし、1.37 を通常のアップグレードとして扱ってよい。1 台でも該当するなら、以下はすべて当てはまる。
- v1.36 の時点で
selinux-warning-controllerを有効にし、メトリクスを両方読む。 コントローラは競合する Pod を名指しし、kubelet は実際に失敗するものを数える。これを何週間も前にやること。アップグレード後では目に見えて難しくなるうえ、そのときには Pod は危険な状態ではなくすでに止まっている。 - メトリクスが名指ししたワークロードに
seLinuxChangePolicy: Recursiveを、手作業ではなくポリシーで適用する。 v1.36 では安定版 API であり、今日は何も変えず、アップグレードが着地した瞬間に正しく働く。クラスタ全体ではなく、関係する名前空間に絞ること。 - 全ノードの Static Pod ディレクトリを
secretRefとconfigMapRefで grep する。 これは v1.34 以来のデフォルトなので、リリースを順に踏んできたクラスタではすでに起きている。飛ばしてきたクラスタでは、まだ待ち構えている。見つかったものは DaemonSet へ移すこと。ほぼ常に、最初からそうあるべきだったものだ。 - 1.35 由来の cgroup v1 の負債を清算し、containerd の移行は 1.38 より前にカレンダーへ入れる。 cgroup v1 のノードは 2 リリース分ずっと上書き設定を抱えてきた。containerd 1.x のノードは今日はまだ動くが、1 リリース先で動かなくなる。どちらもそれぞれの作業枠を取ること——そして ipvs から nftables への移行は、まったく別の日に回すこと。
上の負債のうち 2 つには専用の手順書がある。どちらも 5 分で済む作業ではないからだ。containerd 1.7 から 2.x への移行 はランタイム側から見た containerd 1.x の削除であり、cgroup v1 から cgroup v2 への移行 は failCgroupV1 の上書き設定の裏にある監査と変換だ。同じメンテナンス枠で入口レイヤも作り直すなら、ingress-nginx から Gateway API への移行 がその移行を扱っている。そして、そもそもこれ全体に見合う価値があるのかと部屋の誰かが問い始めたなら、Kubernetes を使うべきでないとき がその議論のもう一方の側を、正直に述べたものだ。
よくある質問
Kubernetes 1.37 はいつリリースされ、実際に何が壊れますか?
v1.37 は 2026年8月26日にリリースされました。この中でワークロードを止めうる変更はひとつだけです。SELinuxMount が GA になりデフォルトで有効になるため、SELinux が enforcing のノードで、オプトインした CSI ドライバを使い、異なるラベルを持つ 2 つの Pod が 1 つのボリュームを共有している場合、Pod が ContainerCreating のまま止まりうります。本当に新しい残りのものは静かです。KubeProxyIPVS フィーチャーゲートが追加されて即座に非推奨と記され、metrics.k8s.io が v1 に昇格して両バージョンとも提供され続け、kubectl run --filename/-f が非推奨になりますが、これが影響するのはスクリプトであってクラスタではありません。このリリースのせいだとされている残りのものは、それより前に入ったか、まだ入っていないかのどちらかです。cgroup v1 での kubelet 起動失敗は v1.35、Static Pod の Secret 制限は v1.34、ipvs モードは v1.35 以来の非推奨で削除は v1.43、そして containerd 1.x は今も動きます——その CRI フォールバックが削除されるのは v1.38 です。
Kubernetes 1.37 は kube-proxy の ipvs モードを削除しますか?
いいえ。そして非推奨にしたのもこのリリースではありません。それは v1.35 の出来事で、kube-proxy はそれ以来ずっと起動時に警告をログに出しています。v1.37 が加えるのは、それ自体が非推奨と記された KubeProxyIPVS フィーチャーゲート——後にこのモードを無効化するためのスイッチ——です。動かなくなるものはなく、トラフィックにも影響しません。KEP-5495 がタイムラインを定めています。このゲートは v1.40 でデフォルトが false になる見込みで、サポートは v1.43 で完全に削除されます。非推奨の理由は、カーネルの ipvs API が Kubernetes の Service に必要なものすべてを表現できず、ipvs モードが仕事の一部について常に裏で iptables にフォールバックしてきたことです。移行先は v1.33 以降 GA の nftables モードで、クラスタが勝手に切り替わることはないので、mode: "nftables" は自分で設定する必要があります。バージョンアップグレードとは別のメンテナンス枠でやってください。
1.37 にアップグレードしたら Pod が ContainerCreating で止まったままです。なぜですか?
そのノードが SELinux を enforcing で動かしているなら、原因は SELinuxMount の GA 昇格が持ち込んだボリューム共有の競合である可能性が高いです。ボリュームは再帰的にラベルを付け直されるのではなく -o context=<label> でマウントされるようになり、1 つのマウントが保持できる SELinux コンテキストはちょうど 1 つなので、異なるラベルを持つ 2 つの Pod が 1 台のノードで 1 つのボリュームを共有することはもうできません。片方は、もう片方が終了するまで ContainerCreating に座り続けます。典型的なのは特権 Pod と非特権 Pod がボリュームを共有しているケースです。そのノードで kubelet のメトリクス volume_manager_selinux_volume_context_mismatch_errors_total を読んで確認してください。対処は該当する Pod に spec.securityContext.seLinuxChangePolicy: Recursive を設定することです。なお、kubelet をダウングレードしてもボリュームは解放されません。勝ち取った側の Pod がコンテキストを握り続けているからです。
アップグレード前に SELinux のボリューム競合を見つけるにはどうしますか?
v1.36 で出荷された selinux-warning-controller を、kube-controller-manager に --controllers=*,selinux-warning-controller を渡して有効にし、SELinuxChangePolicy フィーチャーゲートを明示的に無効化していないことを確認してください。そのうえでメトリクスを 2 つ読みます。selinux_warning_controller_selinux_volume_conflict は競合する Pod 名と名前空間をラベルとして持ちます——これが作業リストで、Pod が別々のノードにあっても報告されます。スケジューラが後で同居させるかもしれないからです。volume_manager_selinux_volume_context_mismatch_warnings_total は SELinuxMount がまだ無効なうちに kubelet が出力するもので、Pod 名のラベルはありませんが、実際に失敗する Pod の正直な件数を示します。両方が必要です。これは v1.36 のうちに、警告カウンタが非ゼロでありながらすべてがまだ動いている状態でやってください。
seLinuxChangePolicy: Recursive は実際に何をしますか、今設定しても安全ですか?
その Pod をマウントオプションの経路から外し、1.37 より前の挙動を保ちます。つまりコンテナランタイムがボリュームを歩いてすべてのファイルにラベルを付け直します。代償は性能面の恩恵で——大きなボリュームやリモートのボリュームでは、再帰的なラベル付けは本当に遅いです——見返りは、異なるラベルを持つ 2 つの Pod が再びそのボリュームを共有できることです。v1.36 の時点で安定版の Pod API なので、今日設定してもクラスタの現在の挙動は何も変わらず、アップグレードが着地した瞬間に正しく働きます。だからこれがこのリリースで最も安上がりな保険です。すべての Deployment を編集するのではなく MutatingAdmissionPolicy かポリシーエンジンで適用し、クラスタ全体ではなく競合メトリクスが実際に名指しした名前空間に絞ってください——一律の Recursive は、一度も危険でなかったすべてのワークロードから改善を捨ててしまいます。
うちのクラスタは SELinux を使っていません。この話は関係ありますか?
ほとんど関係ありません。SELinux が利用できないかカーネルで無効になっている場合、kubelet は SELinux のコードパスを丸ごと飛ばすので、SELinuxMount の変更はあなたにとって何もしないのと同じであり、この記事で最も長い節は無視できます。残るのは Static Pod の制限です——全ノードの Static Pod ディレクトリを secretRef と configMapRef で grep してください。これは v1.34 以来のデフォルトなので、刺さるのはリリースを飛ばしたクラスタだけです。加えて、まだ清算していなければ v1.35 由来の cgroup v2 の負債と、containerd 2.x への移行です。後者は 1.37 では急ぎませんが、1.38 より前には済ませる必要があります。ただし想定で済ませず、全ノードで getenforce を確認してください。あるノードプールだけ SELinux 有効のイメージを使っている混在フリートは、思っているより一般的です。
1.37 は cgroup v1 で kubelet が起動しないようにしますか?
その変更は 1.37 のものではありません。kubelet の failCgroupV1 設定は v1.35 以来 true がデフォルトなので、cgroup v1 のノードは、誰かが failCgroupV1: false を足していない限り、そのときからずっと kubelet の起動に失敗してきました。v1.37 はこの上書きを今も尊重します。KEP-5573 は後のリリースで cgroup v1 のサポートを完全に削除しますが、時期は確約されていないので、計画上の前提は特定の月ではなく「SIG Node の都合の良いリリース」とすべきです。上書きに寄りかかり続けないほうがよい理由は、In-place の Pod リサイズと階層化されたメモリ保護がどちらも cgroup v2 を必要とし、それなしでは単に動かないことです。ノードの切り替えはカーネルコマンドラインの変更(systemd.unified_cgroup_hierarchy=1)、再起動、そしてランタイムの cgroup ドライバを kubelet のそれと一致させることを意味します。
Kubernetes 1.37 に containerd 2.0 は必要ですか?
いいえ——そしてこれは、最も頻繁に逆向きに語られている主張です。containerd 1.x は v1.37 の kubelet に対して今も動きます。それはフォールバックによるものです。kubelet は RuntimeConfig の CRI RPC でランタイムに cgroup ドライバを問い合わせますが、containerd 1.y はその RPC を実装していないので、kubelet は自分の --cgroup-driver の値にフォールバックし、その際に cri_losing_support メトリクスを増やします。このフォールバックは v1.37 での削除が予定されていましたが、containerd v1.7 のサポート期間に合わせて 1 リリース先送りされたため、消えるのは v1.38 です。その時点で、古い containerd は新しい kubelet に対して実際に失敗します。ただし、聞こえるほど安心できない事情が 2 つあります。containerd 1.7 自身の延長サポートは 2026年9月に終わること、そして containerd が公開している Kubernetes サポートマトリクスには 1.37 の行がまだ無く、公式に保証された組み合わせを現時点で誰も引用できないことです。cri_losing_support と containerRuntimeVersion で監査し、1.38 を取る前に移行を予約してください——バージョンアップグレードに抱き合わせず、専用の作業枠で。
Secret を読んでいる Static Pod は何に置き換えればよいですか?
ほぼすべての場合、DaemonSet です。Static Pod は API サーバ経由で作られるのではなくディスク上のディレクトリから kubelet が管理するものなので、API オブジェクトを読むことはそもそも想定されていませんでした——不具合により configMapRef や secretRef といったフィールドを通じてそれが通ってしまっていました。この不具合を塞ぐ PreventStaticPodAPIReferences ゲートは v1.34 以来デフォルトで有効なので、これは 1.37 の新機能ではありません——複数のリリースを飛ばしたクラスタにとって新しく感じられるだけです。v1.37 の sneak peek はこのゲート自体が削除されると述べましたが、v1.37 とともに公開されたフィーチャーゲートのリファレンスは、今もそれをデフォルト true のベータゲートとして載せています。いずれにせよ、オプトアウトを前提に計画しないでください。Static Pod の多くは、DaemonSet が今ほど有能になる前の歴史的な理由で存在しています。DaemonSet なら Secret を普通に読め、ローリングアップデートも効き、人が探す場所に現れます。どうしても Static Pod のまま残す必要があるなら、値をマニフェストに直書きするか、ホストからファイルを bind mount してそこから読んでください。どちらも DaemonSet より劣り、どちらも動きます。ノードごとに 1 台ずつ発見するのではなく、アップグレードの前に見つけてください。
1.34 や 1.35 から 1.37 へ一気に上げてよいですか?
上げられます——バージョンスキューポリシーは kubelet が API サーバより最大 3 マイナー遅れることを許していますし、kubeadm はコントロールプレーンを 1 バージョンずつ扱います——が、リスクは仕組みではなく読み物のほうにあります。リリースを飛ばすということは、飛ばしたバージョンのすべての非推奨が一度に到着するということで、それらはまとめてではなくリリースごとに文書化されています。1.35 から来るなら v1.36 の変更も引き継ぎます。Service の ExternalIPs の非推奨警告、SELinuxMountReadWriteOncePod と SELinuxChangePolicy の GA 到達、そして Pod のユーザー名前空間の安定化です。1.34 から来るなら、さらに v1.35 の failCgroupV1 のデフォルト裏返りも加わります——これは cgroup v1 のノードで kubelet を端的に止めます。飛び越えるすべてのバージョンについて、リリースチームのブログ記事を読んでください。3 つ読んでも 20 分ほどで、アップグレードの作業枠に投じられるものの大半よりは良い使い道になります。
1.37 のアップグレードは切り戻せますか?
部分的に、そしてその内訳が重要です。kube-proxy のモード変更はきれいに戻ります——ConfigMap と DaemonSet の再起動です。seLinuxChangePolicy は v1.36 と v1.37 で同じ挙動をする Pod のフィールドで、だからこそ事前に適用しておくことにコストがありません。コントロールプレーンは戻りません。kubeadm upgrade にダウングレードの経路は無いので、後戻りするということは開始前に取得した etcd のスナップショットを復元し、それ以降に書かれたすべてを失うということです。そのスナップショットは取っておいてください。そして人が驚く障害がひとつあります。kubelet をダウングレードしても、SELinux の競合でマウントに失敗した Pod は解放されません。ボリュームを勝ち取った Pod がマウントのコンテキストを握り続けているからです。レバーはバージョン番号ではなく、両方の Pod の seLinuxChangePolicy です。
metrics.k8s.io の v1beta1 は無くなりますか?
まだですし、予告なく無くなることもありません。metrics.k8s.io はおよそ 9 年のベータを経て v1.37 で v1 に昇格しますが、採用を自分の都合で進められるように、移行期間中は v1 と v1beta1 の両方が提供され続けます。機能上の変更は想定されていません——この昇格は安定性を持ち込むのではなく追認するものです。kubectl top と HorizontalPodAutoscaler は何もしなくても動き続けます。やる価値があるのは、自分が持っているコード——カスタムのオートスケーラ、キャパシティレポート、ダッシュボードのバックエンド——を、両方が提供されているうちにベータのパスから離すことです。Kubernetes の非推奨ポリシーはベータ API に非推奨後 少なくとも 9 か月または 3 リリースを保証していますし、v1beta1 には削除日がまったく与えられていないので、締め切りではなく片付け仕事として扱ってください。
参考資料
まず一次資料から。このリリースに何が入っているかについて権威があるのは、リリースチームの sneak peek と v1.37 の CHANGELOG だけだ。そして複数リリースにまたがるタイムラインが書き下されている唯一の場所が KEP であり、だからこそ「1.43 で削除されるもの」を「1.37 で削除」と書く要約に対する解毒剤になる。この記事が何かを訂正している箇所——cgroup の失敗は v1.35 の変更であること、Static Pod の制限は v1.34 の変更であること、ipvs は v1.37 で削除されるのではなく v1.35 以来非推奨であること、そして containerd の崖は v1.36 で背後に過ぎ去ったのではなく v1.38 で前方に控えていること——で対立しているのは二次的な記事群であって、プロジェクトではない。ひとつだけプロジェクト内部の食い違いがあり、これは名指ししておく価値がある。sneak peek は PreventStaticPodAPIReferences ゲートがこのリリースで削除されたと述べているが、v1.37 とともに公開されたフィーチャーゲートのリファレンスは今もそれを載せている。実際に出荷されたものについては、リファレンスのページのほうを信じるべきだ。
- Kubernetes v1.37 Sneak Peek - the release team's own list of what is deprecated, removed and breaking in this release, published 31 July 2026. This is the document that separates "new in 1.37" from "still in progress", and it carries its own caveat that the information reflects the state of the release before the release date
- Kubernetes CHANGELOG-1.37.md - the authoritative record once the release is cut on 26 August 2026. Where this article and the changelog disagree after that date, the changelog is right and this page is a snapshot of the plan
- SELinux Volume Label Changes goes GA (and likely implications in v1.37) - the pre-announcement by the feature's own authors. It contains the five conditions for a mount-option relabel, the two conflict scenarios, the seLinuxChangePolicy opt-out, the selinux-warning-controller and the recommended upgrade path. This is the single most important source for this article
- KEP-1710: Speed up SELinux volume relabeling using mounts. The enhancement proposal behind SELinuxMount, including why a mount can hold only one context and therefore why volume sharing across differently labelled pods stops working
- KEP-1710, "Story 3: cluster upgrade" - the upgrade scenario written by the authors, which is the closest thing to an official runbook for this change
- Kubernetes - Configure a Security Context for a Pod or Container: seLinuxOptions, the seLinuxChangePolicy field, efficient SELinux volume relabeling and the selinux-warning-controller
- Kubernetes API reference - CSIDriver: the seLinuxMount field a driver has to set to true before the kubelet will mount its volumes with a context option. Drivers that do not set it keep the old recursive behaviour, which is why the blast radius of this change is driver-specific
- Kubernetes - Feature Gates: the per-release state of SELinuxMount, SELinuxChangePolicy, SELinuxMountReadWriteOncePod and KubeProxyIPVS. The table is the fastest way to check a claim about which release flipped which default
- KEP-5495: Deprecate ipvs mode in kube-proxy. The deprecation timeline this article quotes - warning now, feature gate defaulting to false by v1.40, removal by v1.43 - comes from its graduation criteria and nowhere else
- KEP-5495 README on GitHub: the same document at its source, including the graduation criteria table with the release numbers
- KEP-3866: nftables kube-proxy backend, including the section titled "The ipvs mode of kube-proxy will not save us" - the technical argument that ipvs mode never stopped depending on iptables underneath, which is the reason for the deprecation
- NFTables mode for kube-proxy - the introduction to the mode that replaces both iptables and ipvs, and the migration notes for moving to it
- Kubernetes - kube-proxy configuration (v1alpha1) reference: the `mode` field this article tells you to read and change, and the rest of the KubeProxyConfiguration surface
- KEP-5573: Remove CGroup v1 support. The staged removal plan behind the kubelet's failCgroupV1 setting, and the statement that the override is temporary
- Kubernetes - About cgroup v2: how to check which version a node is on, the requirements for cgroup v2, and the features that depend on it
- Kubernetes - kubelet configuration (v1beta1) reference: failCgroupV1 and the rest of the KubeletConfiguration fields this article edits
- Kubernetes - Static Pods: what they are, why the kubelet manages them directly rather than through the API server, and therefore why referencing API objects from one was never supposed to work
- kubernetes/kubernetes issue 140226 - the discussion behind prohibiting Secret and ConfigMap references from static pods, and the fate of the PreventStaticPodAPIReferences feature gate that allowed an opt-out. The gate has defaulted to on since v1.34; the v1.37 sneak peek says it was removed in this release while the shipped v1.37 feature-gates reference still lists it, so the reference page is the one to trust
- kubernetes/kubernetes issue 138671 - the deprecation of `kubectl run --filename/-f`, on the grounds that the pod it produces is always built purely from the command-line arguments
- KEP-5207: metrics.k8s.io API definition. The enhancement that graduates the resource metrics API to stable after nearly nine years in beta, and the statement that v1 and v1beta1 both remain available during the transition
- Kubernetes - Resource metrics pipeline: what metrics.k8s.io actually serves, who serves it, and how `kubectl top` and the HorizontalPodAutoscaler consume it
- Kubernetes - Horizontal Pod Autoscaling: the largest consumer of the resource metrics API, and the reason its graduation matters beyond `kubectl top`
- KEP-2033 / KEP-4960: Kubelet in UserNS, also known as rootless mode. Graduating to beta in v1.37, which lets node components run as an unprivileged user on the host while still appearing as root inside the namespace
- Kubernetes - User namespaces for pods: the workload-level feature that reached GA in v1.36, distinct from the rootless kubelet but built on the same kernel mechanism
- KEP-1432: Volume health monitoring. Reset to alpha in v1.37 with four new CSI RPCs and three new status fields, after an initial implementation in v1.21 that never graduated
- Kubernetes v1.34: Of Wind & Will - the release that opened the containerd 1.x end-of-support discussion and the CRI cgroup-driver work behind it. Read alongside the container runtimes page, which records where that timeline actually ended up: the fallback is dropped in v1.38, not v1.36
- Kubernetes v1.36 release announcement - the release that made user namespaces GA, graduated SELinuxMountReadWriteOncePod, and shipped the selinux-warning-controller that this article tells you to switch on
- Kubernetes v1.36: Deprecation and removal of Service ExternalIPs - note that despite the title, v1.36 only deprecates the field and starts emitting warnings; kube-proxy support goes off by default no earlier than v1.40 and removal is no earlier than v1.43. A good illustration both of why reading one release's notes is not enough, and of how a headline turns into a rumour
- Kubernetes - Releases: the supported branches and their end-of-life dates. The source for the support window this article uses to argue about how much runway a cluster actually has
- Kubernetes - Patch releases: the cadence, the support period and the maintenance-mode window at the end of each minor release's life
- Kubernetes - Version skew policy: how far the kubelet may lag the API server, which is what makes a staged node upgrade legal in the first place
- Kubernetes - Upgrading kubeadm clusters: the control plane first, then one node at a time, with drain and uncordon around each. The sequence the runbook in this article slots into
- Kubernetes - Deprecation policy: the rules that govern how long a deprecated feature must survive before removal, which is why an ipvs deprecation in v1.37 cannot become a removal before v1.43
- Kubernetes - Container runtimes: installing and configuring containerd or CRI-O, including the cgroup driver requirement that ties this article to the cgroup v2 migration. It is also the page that settles the containerd question, stating that older containerd versions still work today through the kubelet's cgroup-driver fallback and that in Kubernetes 1.38 that fallback is dropped and they will fail with newer kubelets
- containerd - Versioning and release: the release-status table and the Kubernetes/containerd support matrix. As of September 2026 the matrix stops at Kubernetes 1.36 (2.3.0+, 2.2.0+) and has no row for 1.37, and containerd 1.7's extended LTS support ends in September 2026
- Kubernetes - Releases: the release and support dates quoted in this article, including v1.35 on 17 December 2025, v1.36 on 22 April 2026 and v1.37 on 26 August 2026
- Kubernetes - MutatingAdmissionPolicy: the in-tree way to apply the seLinuxChangePolicy opt-out across a namespace or a cluster without editing every workload by hand
- Kyverno: a policy engine the SELinux blog names explicitly as a way to apply the opt-out fleet-wide. Listed because the alternative - patching every Deployment and StatefulSet individually - does not scale past a small cluster
- Gateway API v1.6: TCPRoute and UDPRoute graduate to Standard. Out of scope for this article but on the same upgrade window for most clusters, and the reason the ingress migration keeps appearing in the same maintenance plan
Was this useful?