Перейти к содержимому
← Блог

Не всё это появилось в 1.37.

Kubernetes v1.37 вышел 26 августа 2026 года. Большая часть изменений, которые ему приписывают, случилась в v1.34 и v1.35, ещё одно наступит только в v1.38, а то единственное, из-за которого поды действительно могут зависнуть в ContainerCreating, почти не упоминают. Здесь — честная сводка, аудит, который нужно провести до обновления, и пошаговый регламент.

·24 мин чтения
  • Kubernetes
  • SELinux
  • Обновления
  • Платформа

Kubernetes v1.37 вышел 26 августа 2026 года, и у обзоров вокруг него есть форма, которую стоит заметить. Этому релизу приписывают несколько изменений, которые к нему не относятся: отказ kubelet стартовать на cgroup v1 (это значение по умолчанию перевернулось в v1.35), потерю статическими подами ссылок на Secret и ConfigMap (включено по умолчанию с v1.34) и удаление режима ipvs у kube-proxy (предупреждение об устаревании он несёт с v1.35, а удаление намечено на v1.43). Ещё одно изменение перевирают в обратную сторону: containerd 1.x по-прежнему работает с kubelet версии v1.37, а обрыв наступает в v1.38. При этом единственное изменение этого релиза, которое в день обновления действительно способно оставить поды в ContainerCreating — переход SELinuxMount в GA и включение по умолчанию, — получает один абзац, потому что оно невидимо для всех, у кого на узлах нет SELinux.

Обложка из трёх колонок под заголовком «Kubernetes 1.37: разложить по релизам». Первая колонка с подписью «уже случилось» перечисляет kubelet, отказывающийся стартовать на узлах с cgroup v1 начиная с 1.35, статические поды, потерявшие ссылки на Secret и ConfigMap с 1.34, и режим ipvs у kube-proxy с предупреждением об устаревании с 1.35. Вторая колонка с подписью «действительно новое в 1.37» перечисляет SELinuxMount, переходящий в GA и включённый по умолчанию, с предупреждающим значком и текстом «поды могут зависнуть в ContainerCreating», feature gate KubeProxyIPVS с пометкой «устаревший» и metrics.k8s.io, дорастающий до v1. Третья колонка с подписью «ещё впереди» перечисляет отключение запасного пути CRI для containerd 1.x в 1.38, переход feature gate ipvs в false по умолчанию в 1.40 и полное удаление поддержки ipvs в 1.43. Нижняя полоса гласит «выпущен 26 августа 2026 года».
Разложить по релизам: что случилось ещё до 26 августа 2026 года, что действительно новое в 1.37 и что ещё впереди — в 1.38 и дальше.

Это различие — не педантизм, потому что за каждой колонкой стоит своя работа. Если изменение по cgroup застанет вас врасплох на 1.37, вы опоздали на два релиза, и лечится это перезагрузкой узла, а не откатом. Если вы в панике пересоберёте среду выполнения контейнеров, потому что вам сказали, будто containerd 1.x перестал работать, вы потратите окно обслуживания на дедлайн, до которого остаётся ещё целый релиз, — настоящий, но не сегодняшний. А если врасплох застанет изменение по SELinux, у вас будут поды, которые не запускаются, а аудит, который нашёл бы их заранее, после обновления провести заметно сложнее. Поэтому статья делает две вещи: честно разводит изменения по релизам, а затем подробно разбирает то, где действительно нужна работа, — аудит SELinux и отказ от нового поведения целиком, настоящие сроки по ipvs и containerd и регламент, расставляющий необратимые шаги в правильном порядке.

Как это выглядит и к какому релизу относится

Начните с того, что вы на самом деле увидите, потому что ни один из этих отказов не называет свою причину сам. Под бесконечно висит в ContainerCreating на одном узле, а точно такой же под на другом узле работает — это конфликт меток SELinux, и решает всё то, какой под добрался до тома первым. Узел возвращается после обновления, и kubelet на нём вообще не стартует — это почти наверняка cgroup v1, и так обстоят дела с v1.35. Агент мониторинга, живущий статическим подом с 2021 года, падает ровно на одном узле — на том, который вы только что обновили, — и виновата ссылка на Secret, которой у него никогда не должно было быть, а значение по умолчанию, которое её отняло, приехало ещё в v1.34.[sneak]

Что вы видитеЧто это обычно значитГде разбирается
Один под завис в ContainerCreating на одном узле; точно такой же работает на другомКонфликт меток SELinux на общем томе. Под, добравшийся до тома первым, держит единственный контекст монтированияSELinuxMount
Kubelet после обновления вообще не стартуетcgroup v1 без обхода failCgroupV1: false. Так обстоят дела с v1.35cgroup v1
Агент мониторинга, годами живший статическим подом, падает только на обновлённом узлеsecretRef или configMapRef в манифесте статического пода. Запрещено по умолчанию с v1.34, так что кусает кластеры, пропускавшие релизыСтатические поды
kube-proxy пишет предупреждение об устаревании, и всё продолжает работатьРежим ipvs, устаревший с v1.35. В v1.37 добавлен feature gate KubeProxyIPVS; удаление намечено на v1.43ipvs
Поды работают, а метрика cri_losing_support на части узлов ненулеваяcontainerd 1.x на запасном пути CRI для драйвера cgroup. На v1.37 всё ещё поддерживается; запасной путь убирают в v1.38containerd
API-сервер пишет предупреждение каждый раз, когда применяют Service с externalIPsЭто вообще не v1.37 — поле ExternalIPs у Service объявили устаревшим в v1.36. Пока ничего не удалено и ничего не перестаёт работатьСводка
kubectl top и HPA после обновления продолжают работатьТак и должно быть. metrics.k8s.io выпускается в v1, и обе версии остаются обслуживаемымиmetrics.k8s.io

Общий принцип, который стоит усвоить: обновление версии вскрывает все изменения с того релиза, в котором вы в последний раз были уверены, а не только изменения того релиза, куда вы переезжаете. На практике кластеры не идут по одному минору за раз — они сидят на 1.34 год, а потом прыгают. Поле ExternalIPs у Service — свежий пример: в v1.36 оно объявлено устаревшим, API-сервер начал предупреждать на каждое его использование, и те, кто приходит на v1.37 с более ранних версий, встречают это предупреждение впервые. Ничего пока не удалено: ожидается, что поддержка в kube-proxy будет выключена по умолчанию не раньше v1.40, а удаление — не раньше v1.43. Именно так изменение с горизонтом в три релиза превращается в аварию: предупреждение приезжает в релизе, заметки к которому никто не прочитал. Поэтому второй раздел статьи — сводка по релизам, а не пересказ release notes.[extip]

Сводка: что новое в 1.37, а что уже случилось

Вот честная бухгалтерия. Левая колонка — то, что в анонсе релизной команды числится за v1.37; правые — релиз, в котором изменение появилось на самом деле или в который оно ещё только направляется. Всё, что попало в строки «раньше», вы должны были разобрать давно, а если не разобрали, окно обновления и есть тот момент, когда вы об этом узнаете; всё, что попало в строки «позже», — дата в календарь, а не работа на эту неделю.[sneak]

ИзменениеКому обычно приписываютГде случается на самом делеЧто делает в день обновления на 1.37
SELinuxMount в GA, включён по умолчанию1.371.37Может оставить поды в ContainerCreating там, где два по-разному помеченных пода делят один том, а драйвер CSI согласился на монтирование с контекстом. Вот здесь и нужна работа
Добавлен feature gate KubeProxyIPVS, помеченный как устаревший1.371.37Ничего. Gate существует затем, чтобы позже можно было выключить ipvs по умолчанию
kubectl run --filename/-f объявлен устаревшим1.371.37Только предупреждение, и на флаге, который и так игнорировался. Касается скриптов, а не кластеров
metrics.k8s.io выпускается в v11.371.37Ничего не ломается. На время перехода обслуживаются и v1, и v1beta1
Статическим подам нельзя ссылаться на Secret и ConfigMap1.371.34Ничего нового — если только вы не пропускали релизы: тогда эти статические поды сломаются сразу на только что обновлённом узле
Kubelet отказывается стартовать на cgroup v11.371.35Ничего нового. Если укусит здесь — значит, узел носит failCgroupV1: false с v1.35
Режим ipvs у kube-proxy объявлен устаревшим«удалён в 1.37»1.35 (только предупреждение)Строка в журнале при старте, как и последние два релиза. Значение по умолчанию станет false в 1.40; удаление в 1.43
Поле ExternalIPs у Service объявлено устаревшим«удалено в 1.36»1.36 (только предупреждение)Предупреждение API-сервера. Выключение по умолчанию не раньше 1.40; удаление не раньше 1.43
Запасной путь CRI для containerd 1.x убран1.36, иногда 1.371.38Пока ничего. containerd 1.x на 1.37 по-прежнему работает через запасной путь, а cri_losing_support считает узлы, которые на него опираются

Самое полезное предложение во всей статье: если на ваших узлах SELinux не работает в режиме enforcing, самый большой раздел здесь к вам не относится вообще. Когда SELinux недоступен или отключён в ядре, kubelet пропускает весь связанный с ним код целиком. Проверьте это первым делом — одна команда, — потому что от ответа зависит, полдня перед вами работы по аудиту или десять минут чтения.

Два пункта заслуживают отдельного слова о том, как проверить их самому, а не верить на слово. Справочник по feature gates публикует состояние SELinuxMount, SELinuxChangePolicy и KubeProxyIPVS в каждом релизе — это самый быстрый способ проверить утверждение о том, где какое значение по умолчанию перевернулось. А блог релизной команды к каждой версии несёт собственный список устаревшего: прочитать три таких поста — двадцать минут, и это лучшее применение окна обновления, чем большая часть того, что в него обычно попадает.[gates]

Где вы находитесь на самом деле

Прежде чем всё это станет применимым, нужны четыре величины, и они независимы друг от друга: версия 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 поддерживает три последних минорных релиза, примерно по четырнадцать месяцев каждый, и последние два месяца проходят в режиме сопровождения, куда приезжают только критические исправления безопасности. Именно этот график делает обновления не опциональными, и его стоит держать перед глазами, когда кто-то предлагает это обновление отложить.[k8srel]

РелизВыпущенРежим сопровождения сКонец поддержки
1.3427 августа 202527 августа 202627 октября 2026 — осталось два месяца
1.3517 декабря 202528 декабря 202628 февраля 2027
1.3622 апреля 202628 апреля 202728 июня 2027
1.3726 августа 2026≈ август 2027≈ октябрь 2027

SELinuxMount переходит в GA: изменение, которое останавливает поды

Это тот раздел, ради которого написана статья. SELinuxMount переходит в GA в v1.37 и включён по умолчанию. Изменение выигрывает в производительности, и механизм у него изящный: вместо того чтобы среда выполнения обходила том и переставляла метку каждому inode — а на большой или сетевой файловой системе это по-настоящему медленно, — kubelet монтирует том с -o context=<label>, и ядро применяет метку ко всем inode этого монтирования за постоянное время. Проблема — прямое следствие механизма. У одного монтирования может быть ровно один контекст SELinux. При рекурсивной смене меток два пода с разными метками могли делить том; при монтировании с контекстом — уже нет, и один из них будет висеть в ContainerCreating, пока второй не исчезнет.[selblog][kep1710]

УсловиеГде проверитьЕсли не выполняется
ОС узла поддерживает SELinux, и он в режиме enforcinggetenforce на узлеНичего не меняется. Kubelet пропускает весь путь SELinux целиком
SELinuxMountReadWriteOncePod включёнGA и безусловно с v1.36Неприменимо ни на одной поддерживаемой версии
SELinuxMount и SELinuxChangePolicy включеныFeature gates. SELinuxMount в 1.36 — бета и выключен, в 1.37 — GA и включёнМетки расставляются рекурсивно средой выполнения, как раньше
Под задаёт хотя бы seLinuxOptions.levelsecurityContext пода или контейнераСреда выполнения назначает случайный level после монтирования и всё равно меняет метки рекурсивно
Драйвер CSI выставляет seLinuxMount: truekubectl get csidrivers -o custom-columns=…Без изменений. Незаданное не равно true. В дереве опцию поддерживают только fc, iscsi и rbd
spec.securityContext.seLinuxChangePolicy не задан или равен MountOptionSpec подаRecursive — явный отказ, сохраняющий старое поведение

Радиус поражения уже, чем это звучит, и его стоит посчитать точно, а не предполагать худшее. Чтобы поведение изменилось хотя бы у одного тома, должны сойтись пять условий, и то, о котором забывают, — драйвер CSI: kubelet использует монтирование с контекстом только тогда, когда драйвер заявил, что умеет его принимать, выставив seLinuxMount: true в своём объекте CSIDriver. Драйвер, оставивший поле незаданным — а незаданное не равно true, — сохраняет прежнее рекурсивное поведение, и переключение его не касается вовсе. Из встроенных типов томов опцию монтирования поддерживают fc, iscsi и rbd; всё остальное в дереве переставляет метки рекурсивно в любом случае.[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.

Ломаются два сценария совместного доступа, и в дикой природе встречается только один. Первый — два пода делят том через разные subPath с разными метками; апстрим называет это очень узким случаем и говорит, что на практике его не видел. Второй — привилегированный и непривилегированный поды делят один том: тоже нечасто, но в реальных приложениях наблюдается, и искать надо именно его. Раздел KEP про сценарий обновления стоит прочитать, если подписывать это решение придётся вам, — ближе к официальному регламенту здесь ничего нет.[kepstory3]

Аудит, который обязан случиться до обновления

В Kubernetes v1.36 приехал контроллер ровно под эту задачу, и почти никто его не включил: он выключен по умолчанию, а то, о чём он предупреждает, тогда ещё не наступило. selinux-warning-controller работает внутри kube-controller-manager за флагом --controllers=*,selinux-warning-controller, следит за всеми подами кластера и сообщает о каждой паре подов, которая делит том так, как SELinuxMount не позволит. Он сообщает о них даже тогда, когда поды на разных узлах, — на верном основании, что завтра планировщик может свести их вместе.[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 имена и пространства имён конфликтующих подов лежат в лейблах — это ваш список работ. У kubelet'овой volume_manager_selinux_volume_context_mismatch_warnings_total лейбла с именем пода нет вовсе, зато это честный счёт подов, которые действительно упадут, и снимается он, пока SELinuxMount ещё выключен. Последняя оговорка — весь смысл того, чтобы делать это на v1.36: счётчик предупреждений уже ненулевой, а всё пока работает. После обновления то же измерение появляется как ..._errors_total, и к тому моменту поды уже не под угрозой, а зависли.[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.

Для каждой нагрузки, которую назвали метрики, есть два пути: починить совместный доступ или вывести этот под из-под нового поведения. Отказ — это поле пода, spec.securityContext.seLinuxChangePolicy, и оно стабильное API с v1.36. То есть применить его можно прямо сейчас, на текущей версии, без единого изменения в поведении, а в момент обновления оно сделает правильную вещь. Это самая дешёвая страховка во всём релизе. Значение Recursive сохраняет для этого пода старое поведение и стоит вам выигрыша в производительности — но для нагрузки, делившей том между разными метками, это ровно тот размен, на который вы шли и раньше.[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Допустим только при включённом feature gateОпция монтирования, явноНужно редко. На 1.37 это и так делает значение по умолчанию
RecursiveРекурсивная смена метокРекурсивная смена меток — отказ от нового поведенияЛечение. Применить ко всем нагрузкам, названным метриками конфликтов, до обновления

Проставлять это поле руками в каждом Deployment и StatefulSet перестаёт работать сразу за пределами маленького кластера, и блог про SELinux говорит об этом прямо, называя MutatingAdmissionPolicy, мутирующие webhook'и, Kyverno и Gatekeeper как способы сделать это массово. Один совет про охват: не поддавайтесь искушению выкатить Recursive на весь кластер на всякий случай. Так тоже работает — и выбрасывает прирост производительности у каждой нагрузки, которой ничего не грозило, а таких в большинстве кластеров почти все. Ограничьте охват теми пространствами имён, которые действительно назвали метрики.[kyverno]

Статические поды и их ссылки на Secret: это случилось в 1.34

Это тот отказ, который вероятнее всего припишут 1.37 — те, кто обновлялся с 1.33. Статическими подами управляет kubelet, читая каталог на диске, а не создавая их через API-сервер, — то есть читать объекты API они изначально были не должны; из-за дефекта они всё же могли ссылаться на Secret и ConfigMap через поля вроде configMapRef и secretRef. Ограничение, которое это закрывает, — feature gate PreventStaticPodAPIReferences, и он включён по умолчанию с v1.34: значит, на любом кластере, который реально прошёл через 1.34, 1.35 и 1.36, всё это уже случилось, и затронутые поды уже сломались. Анонс v1.37 сообщал, что сам gate удаляют вместе с возможностью отказа; при этом справочник по feature gates, вышедший с v1.37, по-прежнему числит PreventStaticPodAPIReferences как бета-gate со значением true по умолчанию, так что запасной выход технически может быть ещё на месте. Планируйте так, будто его нет. Это был дефект, а не возможность, он не вернётся, а пройтись 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.

Лечится это почти всегда тем, что под перестаёт быть статическим. Огромная часть статических подов существует по историческим причинам — так запускали узловой агент до того, как DaemonSet стал таким, какой он сейчас, — а DaemonSet спокойно читает Secret, получает раскатку по одному и виден там, где люди смотрят. Если что-то действительно обязано остаться статическим, остаётся либо вписать значение прямо в манифест, либо примонтировать файл с хоста и читать его оттуда. Оба варианта хуже DaemonSet, и оба работают. Отдельно отметим, что kubectl run --filename/-f в этом релизе тоже объявлен устаревшим — на том основании, что получавшийся под всё равно всегда собирался исключительно из аргументов командной строки; это касается скриптов, а не кластеров.[iss138671]

kube-proxy ipvs: устарел с 1.35, но не удалён

Теперь изменение, о котором пишут больше всего лишнего. Режим ipvs у kube-proxy несёт предупреждение об устаревании с v1.35, а не с v1.37. Что добавляет v1.37 — это feature gate KubeProxyIPVS, сам по себе помеченный как устаревший, вдобавок к предупреждению, которое kube-proxy пишет при старте. Ничего не перестаёт работать, ни один feature gate не переворачивается, трафик не затронут. Обоснование стоит понимать, потому что оно объясняет, почему это никогда и не чинили: API ipvs в ядре не способно выразить всё, что нужно Service в Kubernetes, поэтому режим ipvs всегда частично опирался на iptables под собой. KEP-3866 формулирует это прямо в заголовке раздела: режим ipvs у kube-proxy нас не спасёт.[kep5495][kep3866]

РелизЧто происходит с режимом ipvsЧто нужно делать
1.35Объявлен устаревшим. kube-proxy пишет предупреждение при стартеНичего — но именно тогда пошёл отсчёт
1.37Добавлен feature gate KubeProxyIPVS, сам помеченный как устаревшийНичего. Спланируйте переход, но не спешите с ним
1.38 – 1.39Работает, продолжает предупреждатьПерейти на режим nftables в удобное вам окно
1.40Ожидается, что KubeProxyIPVS станет false по умолчаниюВключить обратно через feature gate — или к этому моменту уже закончить
1.43Поддержка удалена полностьюДелать больше нечего — это и есть дедлайн

Точка назначения — режим nftables, он GA с v1.33 и рекомендован для узлов на Linux. Кластеры не переезжают сами: mode: "nftables" нужно выставить явно. Требование к ядру реальное, но не обременительное — все ядра, слишком старые для режима nftables, покидают LTS к концу 2026 года, — а исправления по nftables специально бэкпортировали в ветки 1.33 и 1.34, чтобы пользователи ipvs на старых релизах могли переехать, не обновляя сначала Kubernetes.[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.

Один совет по последовательности, высказанный с чувством: делайте смену режима прокси в отдельном окне обслуживания, в отдельный день, а не одним пакетом с обновлением версии. Оба изменения трогают data plane, и, когда связность сломается в окне, где было и то и другое, вы потратите аварию на выяснение того, что именно её вызвало, вместо того чтобы её чинить. Спешить здесь не с чего: политика устаревания гарантирует функции уровня бета и выше длинную дорожку, а собственные критерии перевода из KEP ставят переключение значения по умолчанию в false на 1.40, а удаление — на 1.43.[kubeproxycfg][deprecpolicy]

cgroup v1: это случилось в 1.35

Историю с cgroup v1 путают чаще всего, и правильная атрибуция меняет то, что с ней делать. Настройка kubelet failCgroupV1 имеет значение по умолчанию true начиная с Kubernetes v1.35. Значит, узел, всё ещё живущий на cgroup v1, с тех пор и отказывается запускать kubelet — если только кто-то не добавил failCgroupV1: false, а это в спешке сделали многие во время обновления на 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», а не как месяц, который можно вписать в план. То, чем вы платите всё это время, вполне осязаемо: изменение ресурсов пода на лету и многоуровневая защита памяти опираются на cgroup v2 и на узле с v1 просто не работают, а этих возможностей команды уже просят. Само переключение — это правка командной строки ядра и перезагрузка плюс приведение драйвера cgroup у среды выполнения в согласие с kubelet, что сложнее, чем звучит, и заслуживает отдельной статьи.[cgroups]

containerd 1.x: обрыв в 1.38, а не сейчас

Здесь ошибаются в обратную сторону, и цена ошибки — окно обслуживания, которое тратить было незачем. Поддержку containerd 1.x не удаляли в v1.36 и не удаляют в v1.37. Kubelet версии v1.37 по-прежнему с ним работает: при включённом KubeletCgroupDriverFromCRI kubelet спрашивает у среды выполнения её драйвер cgroup через CRI-вызов RuntimeConfig, containerd 1.y этот RPC не реализует, и kubelet тихо откатывается к собственному значению --cgroup-driver, увеличивая метрику cri_losing_support. Изначально этот запасной путь собирались убрать в v1.37, но отложили на релиз — специально, чтобы совпасть с окном поддержки containerd v1.7. Документация 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.

То есть дедлайн реальный и до него ровно один релиз — положение лучше, чем подсказывает паника, и хуже, чем подсказывает бездействие. Обостряют его две вещи. Со стороны среды выполнения часы кончаются раньше: containerd 1.7 — та самая LTS-ветка, на которой всё держится, и её расширенная поддержка заканчивается в сентябре 2026 года, то есть сейчас. И в матрице поддержки Kubernetes у самого containerd строки для Kubernetes 1.37 нет вовсе — последняя опубликованная строка это 1.36 с версиями 2.3.0+ и 2.2.0+, — так что всякий, кто называет вам благословлённую containerd версию под 1.37, экстраполирует, а не цитирует. На практике: снимайте cri_losing_support — её уже отдаёт каждый узел, которому нужен запасной путь, и это лучший аудит, чем ручной обход узлов, — затем подтвердите через containerRuntimeVersion. Сделайте миграцию среды выполнения до 1.38 и в своём отдельном окне. Два изменения уровня среды выполнения вместе означают, что вернувшийся неисправным узел вы будете бисектить, а не чинить.[runtimes]

metrics.k8s.io наконец доходит до v1

Хорошая новость этого релиза, и она действительно хорошая. metrics.k8s.io выпускается в v1 после почти девяти лет в бете. Это API за kubectl top и за метриками CPU и памяти у HorizontalPodAutoscaler, то есть один из самых используемых интерфейсов 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 минимум девять месяцев или три релиза, так что относитесь к этому как к уборке, а не как к дедлайну.[hpa]

Остальной релиз, коротко

Ещё три вещи в этом релизе стоит знать, хотя ни одна из них на обновление не повлияет. Все три — переводы на следующую стадию, а не удаления, и следить стоит за первой.[kep4960]

  • Kubelet в пользовательском пространстве имён — rootless-режим — доходит до беты. Компоненты узла традиционно работали на хосте от root. Здесь они работают на хосте от непривилегированного пользователя, оставаясь root внутри пользовательского пространства имён Linux, что ограничивает радиус поражения при уязвимости в компоненте узла. Бета здесь означает, что feature gate включён по умолчанию, но само по себе его включение ещё не запускает kubelet в пользовательском пространстве имён — за этим стоит настройка хоста, — и в анонс релиза это изменение не попало. Относитесь к нему как к тому, что стоит попробовать на тестовом узле, а не как к тому, что уже с вами случилось. И это не то же самое, что пользовательские пространства имён для подов, которые дошли до беты в v1.35 и до GA в v1.36.
  • Смена меток SELinux для томов ReadWriteOncePod стала GA ещё в v1.36. Это SELinuxMountReadWriteOncePod, feature gate более узкий, чем SELinuxMount, о котором эта статья, — и именно поэтому часть кластеров уже какое-то время монтирует с опцией контекста, и ничего не ломается: тома RWOP по определению нельзя делить, так что описанный здесь конфликт там возникнуть не может.
  • Мониторинг здоровья томов начинается заново, с альфы. Первая реализация приехала в v1.21 и дальше так и не пошла; KEP-1432 обнуляет её за feature gate CSIVolumeHealth и вводит четыре RPC для CSI — ControllerListVolumeHealth, ControllerGetVolumeHealth, NodeGetVolumeHealth и NodeGetStorageHealth, — которые отчитываются в PersistentVolumeClaim.status.healthStatus, Pod.status.volumeHealth и CSINode.status.storageHealth. Словарь намеренно маленький и машиночитаемый: Inaccessible, DataLoss и Degraded, плюс 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-сервера на три минорные версии, так что парк, застрявший на середине раскатки, — поддерживаемая конфигурация, а не риск сам по себе. Не торопитесь. Обновите один узел, посмотрите на него как следует и только потом продолжайте, потому что отказ, который способен породить этот релиз, живёт на отдельном узле и выглядит как под, который так и не стартовал, а не как ошибка, по которой кто-то поднимет вас звонком.[skew]

Откат — и то, что откатить нельзя

Откат заслуживает прямого ответа, а не успокаивающего, и в этом обновлении у ответа три части. Обратимое обратимо по-настоящему: режим kube-proxy — это ConfigMap и перезапуск DaemonSet, а seLinuxChangePolicy — поле пода, которое ведёт себя одинаково на v1.36 и v1.37, и именно поэтому применить его до обновления ничего не стоит, а покупает вам весь сценарий отката.[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.

Не откатывается control plane. У kubeadm upgrade нет пути вниз; вернуться назад — значит восстановить снапшот etcd, снятый до начала, и потерять всё, что записалось в кластер с тех пор. Если вы его не снимали, отката у вас нет — у вас есть починка вперёд, а это другой разговор, и вести его придётся в два часа ночи. И есть один отказ, который людей удивляет: понижение версии kubelet не расклинивает под, который не смог примонтировать том из-за конфликта меток SELinux, потому что выигравший том под всё ещё держит его своим контекстом. Рычаг здесь — поле пода, а не номер версии.[selblog]

Проверять, а не надеяться

У проверки в этом обновлении особая форма, потому что характерный отказ оставляет всё зелёным. Узел в состоянии Ready. Kubelet здоров. Control plane в порядке. Просто есть под, который никогда не дойдёт до создания, — на одном узле, у одной команды. Поэтому проверка «кластер жив» не доказывает ровным счётом ничего: проверять надо то, что может быть неправильным молча.[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

Запускайте это между узлами, а не в конце. Скрипт возвращает ненулевой код, то есть он может стоять шагом в пайплайне, а не читаться человеком в три часа ночи, и две проверки, которые стоит оставить даже после того, как обновление позади, — обход подов в ContainerCreating и счётчик несовпадений SELinux. Обе дешёвые, и обе ловят класс проблем, который иначе ждёт, пока кто-нибудь не заметит, что его нагрузка так и не вернулась.[k8spatch]

В каком порядке всё это делать

В сжатом виде это обновление меньше, чем можно решить по статье, — при условии, что вы сделали работу, относившуюся к двум предыдущим релизам. Таблица решений ниже на самом деле спрашивает, что именно из них вы пропустили.[sneak]

Если ваша ситуация…то 1.37 — это…а работа…
На 1.36, SELinux нигде нет, containerd 2.x, cgroup v2Рутинное обновлениеОбычная последовательность kubeadm. Пройтись grep по манифестам статических подов — и вперёд
На 1.36, SELinux в enforcing, драйверы CSI с seLinuxMount: trueТот случай, где нужен аудитВключить контроллер предупреждений сейчас, прочитать обе метрики, применить Recursive там, куда они указывают, и только потом обновляться
На 1.34 или 1.35, планируете прыгнуть сразу на 1.37Два-три релиза изменений разомЧитать заметки к каждой версии, через которую перешагиваете. Запрет для статических подов, значение по умолчанию для cgroup и предупреждение про ExternalIPs — всё там
Есть узлы, всё ещё на cgroup v1Не самая срочная ваша проблемаТакой узел носит обход с 1.35. Переведите его отдельно, до обновления
Есть узлы, всё ещё на containerd 1.xСегодня нормально, в 1.38 сломаетсяЭто не работа на день обновления. Снимите cri_losing_support, а потом отдельно запланируйте миграцию среды выполнения до перехода на 1.38
kube-proxy работает в режиме ipvsСтрока в журнале, не болееПереезжайте на nftables в другой день. У вас есть время до 1.43, а склейка окон прячет причину любой аварии
Управляемый сервис (GKE, EKS, AKS)То, что запланирует провайдерАудит SELinux всё равно ваш — нагрузки и драйверы CSI ваши, даже когда узлы не ваши
  1. Ответьте на вопрос про SELinux первым делом, одной командой. Если ни на одном узле SELinux не работает в режиме enforcing, пропустите самый большой раздел здесь целиком и считайте 1.37 рутинным обновлением. Если хоть на одном работает — всё, что ниже, к вам относится.
  2. Включите selinux-warning-controller на v1.36 и прочитайте обе метрики. Контроллер называет конфликтующие поды; kubelet считает те, что действительно упадут. Сделайте это за недели до обновления, потому что после него это заметно сложнее, а поды к тому моменту уже зависли, а не находятся под угрозой.
  3. Примените seLinuxChangePolicy: Recursive к нагрузкам, которые назвали метрики, — политикой, а не руками. На v1.36 это стабильное API, сегодня оно ничего не меняет, а в момент обновления делает правильную вещь. Ограничьте охват затронутыми пространствами имён, а не всем кластером.
  4. Пройдитесь grep по каталогу статических подов на каждом узле в поисках secretRef и configMapRef. Это поведение по умолчанию с v1.34, так что на кластере, шедшем по релизам подряд, всё уже случилось; на том, который прыгал, — ещё ждёт. Переносите найденное в DaemonSet — почти всегда им это и должно было быть с самого начала.
  5. Закройте долг по cgroup v1 из 1.35 и поставьте миграцию containerd в календарь до 1.38. Узел на cgroup v1 носит обход уже два релиза. Узел на containerd 1.x сегодня работает, а через релиз перестанет. У каждого своё окно — а переход с ipvs на nftables оставьте совсем на другой день.

У двух долгов выше есть собственные регламенты, потому что ни один из них не пятиминутное дело: переход с containerd 1.7 на 2.x — это удаление containerd 1.x со стороны среды выполнения, и переход с cgroup v1 на cgroup v2 — аудит и конвертация, которые стоят за обходом failCgroupV1. Если в том же окне обслуживания вы заодно пересобираете входной слой, переход с ingress-nginx на Gateway API разбирает этот переход. А если это тот момент, когда кто-то в комнате спрашивает, стоит ли всё это того, когда не стоит использовать Kubernetes — другая сторона того же спора, изложенная честно.

Частые вопросы

Когда вышел Kubernetes 1.37 и что на самом деле ломается?

v1.37 вышел 26 августа 2026 года. Остановить нагрузку в нём способно только одно изменение: SELinuxMount переходит в GA и включён по умолчанию — из-за этого поды могут зависнуть в ContainerCreating там, где два по-разному помеченных пода делят том на узле с SELinux в режиме enforcing, а драйвер CSI согласился на монтирование с контекстом. Остальное по-настоящему новое — тихое: добавлен и сразу помечен устаревшим feature gate KubeProxyIPVS, metrics.k8s.io выпускается в v1 с обеими версиями в обслуживании, а kubectl run --filename/-f объявлен устаревшим, что касается скриптов, а не кластеров. Всё остальное, что приписывают этому релизу, случилось раньше или ещё не случилось: отказ kubelet на cgroup v1 — это v1.35, запрет на Secret у статических подов — v1.34, режим ipvs устарел ещё в v1.35 и удаляется в v1.43, а containerd 1.x по-прежнему работает — его запасной путь CRI убирают в v1.38.

Удаляет ли Kubernetes 1.37 режим ipvs у kube-proxy?

Нет — и устаревшим он его тоже не объявлял: это случилось в v1.35, и с тех пор kube-proxy пишет предупреждение при старте. Что добавляет v1.37 — это feature gate KubeProxyIPVS, сам по себе помеченный как устаревший; именно им режим позже и выключат. Ничего не перестаёт работать, трафик не затронут. KEP-5495 задаёт график: ожидается, что gate станет false по умолчанию к v1.40, а поддержка будет удалена полностью к v1.43. Причина устаревания в том, что API ipvs в ядре не способно выразить всё, что нужно Service в Kubernetes, поэтому режим ipvs всегда частично опирался на iptables под собой. Цель перехода — режим nftables, GA с v1.33; кластеры сами никогда не переключаются, так что mode: "nftables" придётся выставить самому. Делайте это в окне обслуживания, отдельном от обновления версии.

Почему поды зависли в ContainerCreating после обновления до 1.37?

Если на узле SELinux работает в режиме enforcing, вероятная причина — конфликт совместного доступа к тому, который принёс переход SELinuxMount в GA. Тома теперь монтируются с -o context=<label>, а не переразмечаются рекурсивно, и у одного монтирования может быть ровно один контекст SELinux — то есть два пода с разными метками, делящие один том на одном узле, больше не уживаются. Один висит в ContainerCreating, пока второй не завершится. Классический случай — привилегированный и непривилегированный поды с общим томом. Подтвердить можно, прочитав на этом узле метрику kubelet volume_manager_selinux_volume_context_mismatch_errors_total. Лечение — выставить затронутым подам spec.securityContext.seLinuxChangePolicy: Recursive; учтите, что понижение версии kubelet том не освободит, потому что выигравший его под всё ещё держит контекст.

Как найти конфликты томов SELinux до обновления?

Включите selinux-warning-controller, приехавший в v1.36, передав kube-controller-manager --controllers=*,selinux-warning-controller, и убедитесь, что вы не выключали явно feature gate SELinuxChangePolicy. Затем читайте две метрики. У selinux_warning_controller_selinux_volume_conflict имена и пространства имён конфликтующих подов лежат в лейблах — это ваш список работ, и конфликты она показывает даже тогда, когда поды на разных узлах, потому что планировщик может свести их позже. У volume_manager_selinux_volume_context_mismatch_warnings_total, которую отдаёт kubelet, пока SELinuxMount ещё выключен, лейбла с именем пода нет, зато она даёт честный счёт подов, которые действительно упадут. Нужны обе. Делайте это на v1.36, пока счётчик предупреждений уже ненулевой, а всё ещё работает.

Что на самом деле делает seLinuxChangePolicy: Recursive и безопасно ли выставить его сейчас?

Он выводит этот под с пути опции монтирования и сохраняет поведение до 1.37: среда выполнения обходит том и переставляет метку каждому файлу. Цена — потерянный выигрыш в производительности: на большом или сетевом томе рекурсивная переразметка по-настоящему медленная. Выгода — два по-разному помеченных пода снова могут делить том. Это стабильное API пода с v1.36, поэтому сегодня его установка не меняет в поведении кластера ничего, а в момент обновления делает правильную вещь. Отсюда и звание самой дешёвой страховки в релизе. Применяйте его через MutatingAdmissionPolicy или движок политик, а не правкой каждого Deployment, и ограничивайте охват теми пространствами имён, которые действительно назвали метрики конфликтов, а не всем кластером: сплошной Recursive выбрасывает улучшение у каждой нагрузки, которой ничего не грозило.

У нас в кластере нет SELinux. Что-то из этого нас касается?

Почти ничего. Когда SELinux недоступен или отключён в ядре, kubelet пропускает весь связанный с ним код, так что изменение SELinuxMount для вас — пустая операция, а самый большой раздел этой статьи можно не читать. Остаётся ограничение на статические поды: пройдитесь grep по каталогу статических подов на каждом узле в поисках secretRef и configMapRef — это поведение по умолчанию с v1.34, поэтому кусает только кластеры, пропускавшие релизы, — плюс долг по cgroup v2 из v1.35, если вы его ещё не закрыли, и миграция на containerd 2.x, которая на 1.37 не срочна, но должна быть сделана до 1.38. И всё же проверьте getenforce на каждом узле, а не полагайтесь на память: разнородный парк, где один пул узлов приезжает с образом с включённым SELinux, встречается чаще, чем принято думать.

Правда ли, что 1.37 не даёт kubelet стартовать на cgroup v1?

Это изменение не из 1.37. Настройка kubelet failCgroupV1 имеет значение по умолчанию true начиная с v1.35, так что узел на cgroup v1 с тех пор и не может запустить kubelet, если кто-то не добавил failCgroupV1: false. v1.37 этот обход по-прежнему уважает. KEP-5573 вырезает поддержку cgroup v1 целиком в одном из будущих релизов, но дата не зафиксирована, поэтому планировочное допущение должно звучать как «в том релизе, который подойдёт SIG Node», а не как конкретный месяц. Не сидеть на обходе стоит потому, что изменение ресурсов пода на лету и многоуровневая защита памяти требуют cgroup v2 и без него просто не работают. Перевод узла — это правка командной строки ядра (systemd.unified_cgroup_hierarchy=1), перезагрузка и приведение драйвера cgroup у среды выполнения в согласие с kubelet.

Нужен ли containerd 2.0 для Kubernetes 1.37?

Нет — и именно это утверждение чаще всего переворачивают. containerd 1.x по-прежнему работает с kubelet версии v1.37. Работает он через запасной путь: kubelet спрашивает у среды выполнения драйвер cgroup через CRI-вызов RuntimeConfig, containerd 1.y этот RPC не реализует, и kubelet откатывается к собственному значению --cgroup-driver, увеличивая метрику cri_losing_support. Убрать этот путь планировали в v1.37, но отложили на релиз, чтобы совпасть с окном поддержки containerd v1.7, так что исчезает он в v1.38 — и вот тогда старые версии containerd действительно перестают работать с новыми kubelet. Две оговорки, из-за которых всё не так уютно: расширенная поддержка самого containerd 1.7 заканчивается в сентябре 2026 года, а в опубликованной матрице поддержки Kubernetes у containerd строки для 1.37 пока нет вовсе, так что официально благословлённую пару сейчас никто процитировать не может. Аудит — через cri_losing_support и containerRuntimeVersion, а миграцию запланируйте до перехода на 1.38, в собственном окне, а не вместе с обновлением версии.

Чем заменить статический под, который читает Secret?

DaemonSet — почти в любом случае. Статическими подами управляет kubelet, читая каталог на диске, а не создавая их через API-сервер, поэтому чтение объектов API работать было не должно: дефект пропускал его через поля вроде configMapRef и secretRef. Feature gate PreventStaticPodAPIReferences, который этот дефект закрывает, включён по умолчанию с v1.34, так что в 1.37 это не новость — новым оно кажется только кластерам, перепрыгнувшим несколько релизов. Анонс v1.37 говорил, что gate удаляют совсем; справочник по feature gates, вышедший с v1.37, по-прежнему числит его бета-gate'ом со значением true по умолчанию. Так или иначе, не стройте план вокруг возможности отказа. Большинство статических подов существует по историческим причинам — с тех времён, когда DaemonSet был не таким, какой он сейчас; DaemonSet спокойно читает Secret, получает раскатку по одному и появляется там, где его ищут. Если что-то действительно обязано остаться статическим, впишите значение прямо в манифест или примонтируйте файл с хоста и читайте оттуда. Оба варианта хуже DaemonSet, и оба работают. Найдите их до обновления, а не по одному узлу за раз.

Стоит ли обновляться сразу с 1.34 или 1.35 до 1.37?

Можно: политика допустимого расхождения версий разрешает kubelet отставать от API-сервера на три минорные версии, а kubeadm ведёт control plane по одной версии за раз. Риск не в механике, а в чтении. Пропуская релизы, вы получаете все устаревания пропущенных версий разом, а документированы они по релизам, а не накопительно. Приходя с 1.35, вы наследуете ещё и изменения v1.36: предупреждение об устаревании поля ExternalIPs у Service, доросшие до GA SELinuxMountReadWriteOncePod и SELinuxChangePolicy и ставшие стабильными пользовательские пространства имён для подов. Приходя с 1.34, вы дополнительно наследуете переворот значения по умолчанию у failCgroupV1 из v1.35 — а он остановит kubelet на узле с cgroup v1 наглухо. Прочитайте пост релизной команды по каждой версии, через которую перешагиваете. Три таких поста — примерно двадцать минут, и это лучшее применение окна обновления, чем большая часть того, что в него обычно попадает.

Можно ли откатить обновление до 1.37?

Частично, и части здесь важны. Смена режима kube-proxy откатывается чисто — это ConfigMap и перезапуск DaemonSet. seLinuxChangePolicy — поле пода, которое ведёт себя одинаково на v1.36 и v1.37, и именно поэтому применить его заранее ничего не стоит. Control plane не откатывается: у kubeadm upgrade нет пути вниз, так что вернуться назад — значит восстановить снапшот etcd, снятый до начала, и потерять всё, что записалось с тех пор. Снимите этот снапшот. И один отказ людей удивляет: понижение версии kubelet не расклинивает под, который не смог примонтировать том из-за конфликта SELinux, потому что выигравший том под всё ещё держит контекст монтирования. Рычаг — seLinuxChangePolicy на обоих подах, а не номер версии.

Уходит ли metrics.k8s.io v1beta1?

Пока нет, и без предупреждения не уйдёт. metrics.k8s.io выпускается в v1 в v1.37 после почти девяти лет в бете, и на время перехода обслуживаются обе версии — v1 и v1beta1, — именно для того, чтобы переходить можно было в своём темпе. Функциональных изменений не ожидается: выпуск фиксирует стабильность, а не вводит её. kubectl top и HorizontalPodAutoscaler продолжают работать без каких-либо действий с вашей стороны. Что действительно стоит сделать — увести с бета-пути код, который ваш: собственный автоскейлер, отчёт по ёмкости, бэкенд дашборда, — пока доступны обе версии. Политика устаревания Kubernetes гарантирует бета-API минимум девять месяцев или три релиза после объявления устаревшим, а v1beta1 даты удаления не назначали вовсе, так что это уборка, а не дедлайн.

Источники

Сначала первичные источники. Анонс релизной команды и changelog v1.37 — единственные авторитетные утверждения о том, что вошло в этот релиз; KEP — единственное место, где записаны графики длиной в несколько релизов, и именно это делает их противоядием от пересказа, который пишет «удалено в 1.37» про то, что удаляют в 1.43. Там, где эта статья что-то поправляет — что отказ по cgroup относится к v1.35, что запрет для статических подов относится к v1.34, что ipvs объявлен устаревшим ещё в v1.35, а не удалён в v1.37, и что обрыв по containerd впереди, в v1.38, а не позади, в v1.36, — расхождение со вторичными обзорами, а не с проектом. Одно расхождение внутреннее для проекта, и его стоит назвать: анонс говорит, что feature gate PreventStaticPodAPIReferences в этом релизе удалён, а справочник по feature gates, опубликованный вместе с v1.37, его по-прежнему перечисляет. Доверять о том, что реально вышло, стоит справочнику.

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. Kubernetes - Configure a Security Context for a Pod or Container: seLinuxOptions, the seLinuxChangePolicy field, efficient SELinux volume relabeling and the selinux-warning-controller
  7. 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
  8. 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
  9. 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
  10. KEP-5495 README on GitHub: the same document at its source, including the graduation criteria table with the release numbers
  11. 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
  12. NFTables mode for kube-proxy - the introduction to the mode that replaces both iptables and ipvs, and the migration notes for moving to it
  13. Kubernetes - kube-proxy configuration (v1alpha1) reference: the `mode` field this article tells you to read and change, and the rest of the KubeProxyConfiguration surface
  14. KEP-5573: Remove CGroup v1 support. The staged removal plan behind the kubelet's failCgroupV1 setting, and the statement that the override is temporary
  15. 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
  16. Kubernetes - kubelet configuration (v1beta1) reference: failCgroupV1 and the rest of the KubeletConfiguration fields this article edits
  17. 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
  18. 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
  19. 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
  20. 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
  21. Kubernetes - Resource metrics pipeline: what metrics.k8s.io actually serves, who serves it, and how `kubectl top` and the HorizontalPodAutoscaler consume it
  22. Kubernetes - Horizontal Pod Autoscaling: the largest consumer of the resource metrics API, and the reason its graduation matters beyond `kubectl top`
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. Kubernetes - Patch releases: the cadence, the support period and the maintenance-mode window at the end of each minor release's life
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. Kubernetes - MutatingAdmissionPolicy: the in-tree way to apply the seLinuxChangePolicy opt-out across a namespace or a cluster without editing every workload by hand
  38. 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
  39. 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

Было полезно?