Патч-релизы редко попадают в заголовки, и в этом их главная особенность: пока всё работает, о них не вспоминают, а когда что-то ломается, выясняется, что исправление лежало на полке уже месяц. Октябрь принёс сразу два таких релиза. У системы управления контейнерами Kubernetes вышел патч 1.37.2 в ветке Garhwal, а у Docker Engine ветка 29.9 получила версию 29.9.2, а вслед за ней и 29.9.3. Новых возможностей в них нет, и это нормально: задача патча состоит в том, чтобы убрать известные дефекты, не меняя поведение системы. Ниже собран порядок работы для тех, кто отвечает за кластеры, хосты с контейнерами и раннеры сборки. Что изменилось, в какой последовательности обновлять, какие команды запускать и как убедиться, что всё встало на место.
Kubernetes 1.37.2 закрывает очередной месяц ветки Garhwal без новых возможностей
Ветка 1.37 с кодовым названием Garhwal вышла в конце августа 2026 года. Главной её темой стал стабильный формат KYAML, более строгое и менее двусмысленное подмножество YAML для конфигураций. Вместе с ним статус Stable получил и вывод kubectl get -o kyaml. Рядом идёт расширение динамического распределения ресурсов (DRA): часть его возможностей стала стабильной, а ещё появилась поддержка заявок на ресурсы на уровне рабочей нагрузки. Больше всего это интересно задачам машинного обучения и высокопроизводительных вычислений. Отдельной строкой идёт постепенный отказ от cgroup v1: проект настойчиво советует переходить на cgroup v2 до того, как поддержку старой версии уберут совсем.
Патчи на этом фоне выглядят скромно. Первый, 1.37.1, вышел 15 сентября. Второй, 1.37.2, по графику выпусков назначался на 13 октября, а крайний срок для включения исправлений приходился на 9 октября. Интервал в четыре недели считается обычным ритмом проекта: исправления сначала попадают в основную ветку разработки, затем переносятся в поддерживаемые ветки, и этот перенос называют бэкпортом. Всё, что не успело к дедлайну, ждёт следующего патча. Новые функции в такие релизы не пускают по правилам проекта, поэтому риск обновления с 1.37.1 на 1.37.2 заметно ниже, чем при переходе между минорными версиями.
Срок жизни ветки известен заранее. Режим сопровождения у 1.37 начнётся 28 августа 2027 года, а конец поддержки назначен на 28 октября 2027 года. Это примерно четырнадцать месяцев с момента выпуска, и этот отрезок стоит держать в календаре: на нём строится план перехода на следующие версии. Патчи внутри ветки позволяют ехать по этой дороге без ям, но они не отменяют крупного обновления, а только помогают до него добраться в хорошем состоянии.
Ветка Docker Engine 29.9 от 29.9.0 до 29.9.3 приносит исправления безопасности и сети
Docker Engine 29.9.0 вышел 8 октября и сразу оказался релизом с повышенным вниманием к безопасности: в нём исправлены пять уязвимостей. Основная часть исправлений пришла с обновлением среды выполнения Go, на которой написан движок. Самая заметная проблема касалась HTTP/2. Клиент мог вызвать сбой службы Docker, изменив размер таблицы заголовков HPACK прямо во время отправки запросов. Для хоста, к которому обращаются по сети или через автоматизацию, это прямой путь к простою. Ещё одна проблема затрагивала только Windows и позволяла при определённых условиях создать каталог за пределами корневой папки данных Docker. Сетевые компоненты HTTP/2 в BuildKit тоже получили обновление.
Рядом с уязвимостями в 29.9.0 лежит длинный список обычных исправлений, и некоторые из них неприятны именно в эксплуатации. Команда docker ps и запрос GET /containers/json пропускали опубликованные порты у сетей в режиме routed gateway, из-за чего скрипты и мониторинг, читающие этот вывод, видели неполную картину. В Swarm задачи в оверлейных сетях отклонялись, когда служба не могла писать в каталог /var/lib/docker, а опубликованные порты сервисов Swarm при включённом userland-прокси стали доступны на IPv6-адресах хоста. Наборы параметров защиты IPsec для шифруемой оверлейной сети раньше оставались в системе до перезапуска службы, теперь они освобождаются, когда из сети уходит последний контейнер. Заодно вернули очистку пустой цепочки DOCKER-INGRESS в iptables и правила FORWARD, которые остались от Docker Engine 28.0.0 и более ранних версий. Ещё появилась возможность lazy-pull в хранилище образов containerd: она управляет тем, пропускает ли docker pull загрузку слоёв, которые снапшоттер умеет отдавать сам.
Версия 29.9.1 вышла 14 октября и принесла обновление containerd до 2.4.2, которое закрывает три уязвимости. Одна позволяла уронить containerd при загрузке или импорте образа, если в описании дочернего элемента была аннотация с испорченным дайджестом. Вторая срабатывала при параллельной загрузке слоёв: недобросовестный реестр мог прислать подделанный заголовок Content-Length и вызвать сбой. Третья давала возможность исчерпать место на диске хоста образом или реестром, потому что объём входящих данных слоя не сверялся с заявленным размером до проверки. Обычные исправления в 29.9.1 тоже ощутимы: docker ps -s перестал целиком отказывать, когда размер одного контейнера вычислить не удаётся, а docker save перестал оставлять слои на диске после удаления образа, если один слой встречается в нём дважды. Параметр --link-local-ip больше не принимает адреса, которые не являются link-local, и контейнер из-за них не падает при старте. Сети-мосты стали восстанавливаться при запуске службы, если мост привязан к зоне firewalld, отличной от docker, а интерфейсы мостов не добавляются в зону docker, когда iptables и ip6tables выключены. RootlessKit обновился до 3.3.0.
Версия 29.9.2 от 20 октября выглядит как маленький патч: в ней только обновление среды Go до 1.26.10. Но последствия у него вполне практические. Новая среда отвергает цепочки сертификатов, в которых используются восемь отозванных промежуточных сертификатов удостоверяющего центра eMudhra, ключи которых были скомпрометированы. Если реестр образов опирается на одну из таких цепочек, docker pull и docker push перестанут проходить проверку TLS, и такому реестру понадобится новая цепочка сертификатов. Для раннеров сборки это возможный источник внезапно покрасневших заданий сразу после обновления.
А 3 ноября вышла версия 29.9.3, и она тоже про безопасность. В ней исправлены чрезмерная нагрузка на процессор при разборе специально составленных ключей RSA, паника при распаковке данных zstd и потеря защиты шифрования и подмены для оверлейных сетей Swarm после того, как firewalld сбрасывает правила iptables Docker. Обновление SwarmKit закрывает уязвимость, при которой владелец действующего сертификата рабочего узла мог вскоре после смены лидера получить сертификат управляющего узла. BuildKit поднялся до 0.34.2 с двумя исправлениями безопасности, а зависание службы при высокой нагрузке на запросы к контейнерам, образам и системе устранено. Поэтому ставить стоит сразу последний патч ветки, а не останавливаться на 29.9.2. Ветка 29 существует с 10 ноября 2025 года, и на момент написания её актуальный патч это 29.9.3.
Обновление кластера с 1.37.1 до 1.37.2 по шагам kubeadm с копией данных etcd
Порядок для кластеров на kubeadm не меняется от патча к патчу: сначала управляющий слой, потом рабочие узлы, и всё по одному узлу за раз. Начинать стоит не с обновления, а с подушки безопасности. Копия данных etcd занимает несколько секунд, а при неудаче экономит часы. Заодно полезно взглянуть на бюджеты прерываний, потому что команда drain их уважает и будет ждать, если выселение пода нарушает заданный минимум живых реплик.
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-before-1.37.2.db
kubectl get pdb -A
Первым делом обновляется сам kubeadm на первом узле управляющего слоя. Репозиторий пакетов у Kubernetes делится по минорным версиям, поэтому для патча внутри 1.37 его адрес менять не нужно. После установки нужной версии команда kubeadm upgrade plan покажет, до чего можно подняться, и только потом запускается применение.
sudo apt-mark unhold kubeadm
sudo apt-get update
sudo apt-get install -y kubeadm='1.37.2-*'
sudo apt-mark hold kubeadm
kubeadm version -o short
sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.37.2
На остальных узлах управляющего слоя и на рабочих узлах вместо apply запускается sudo kubeadm upgrade node. После этого каждый узел проходит через одну и ту же короткую процедуру: выселение нагрузки, обновление kubelet и kubectl, перезапуск службы, возврат в работу.
kubectl drain NODE --ignore-daemonsets --delete-emptydir-data
sudo apt-mark unhold kubelet kubectl
sudo apt-get install -y kubelet='1.37.2-*' kubectl='1.37.2-*'
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet
kubectl uncordon NODE
Правила совместимости версий внутри патча не мешают, но их полезно помнить. Kubelet не должен быть новее сервера API, при этом он может отставать от него вплоть до трёх минорных версий. Утилита kubectl допускает разницу в одну минорную версию в любую сторону. Порядок "сначала управляющий слой" поэтому сохраняется всегда, даже когда разница в одну цифру после последней точки. В управляемых кластерах облачных провайдеров патч приходит по собственному графику, и там задача сводится к проверке версии и выбору окна, а не к ручному набору этих команд.
Отдельная мелочь, которая выпадает из внимания: на каждом узле перед окном обновления стоит проверить, какая версия cgroup используется. Ветка 1.37 прямо советует уходить с cgroup v1, и проще выяснить это заранее, чем в момент, когда поддержку уберут.
stat -fc %T /sys/fs/cgroup/
Ответ cgroup2fs означает вторую версию, а tmpfs говорит о том, что узел всё ещё живёт на первой.
Обновление Docker Engine на хостах и раннерах сборки с включённым режимом live restore
На хостах с Docker процедура выглядит проще, но подвохов здесь не меньше. Первым шагом идёт проверка текущей версии, вторым выбор того, что именно ставить. Если репозиторий отстаёт, команда apt-cache madison docker-ce покажет, какие сборки доступны, и нужную можно зафиксировать явно. Обновляются вместе все пакеты семейства, чтобы клиент, служба и плагины не разошлись по версиям.
docker version --format '{{.Server.Version}}'
apt-cache madison docker-ce | head -n 5
sudo apt-get update
sudo apt-get install -y --only-upgrade \
docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
Главная неприятность любого обновления движка в том, что вместе со службой перезапускаются контейнеры. Режим live restore частично снимает эту проблему: служба останавливается и поднимается, а контейнеры продолжают работать. Включать его нужно заранее, до окна обновления, чтобы к моменту установки пакета уже работала служба с этим параметром.
{
"live-restore": true
}
Настройка живёт в файле /etc/docker/daemon.json, а проверить её можно командой docker info --format '{{.LiveRestoreEnabled}}'. Есть два ограничения, о которых нельзя забывать. Режим не работает вместе с режимом Swarm, поэтому узлы кластера Swarm сначала переводят в состояние drain командой docker node update --availability drain NODE. Второе ограничение касается масштаба обновления: режим рассчитан на небольшие шаги, и патч внутри ветки 29.9 как раз такой случай.
С раннерами сборки история отдельная. Это самые шумные потребители Docker: образы там меняются каждый час, кэш слоёв растёт, а задания приходят в любое время суток. Спокойный порядок такой: вывести раннер из пула, дождаться окончания текущих заданий, обновить пакеты, проверить версию и вернуть раннер в работу. Если сборка идёт через Docker в Docker, то версию службы внутри конвейера стоит закрепить явно, а не оставлять на усмотрение плавающего тега. Иначе одни и те же задания в разные дни пойдут на разных версиях движка, и разбираться с расхождениями придётся уже по факту.
image: docker:29.9.3
services:
- docker:29.9.3-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
Пример написан в синтаксисе GitLab CI, но смысл переносится на любую систему сборки: версия клиента и версия службы в образах должны совпадать и меняться только осознанно, вместе с хостами.
Проверка версий и поведения после обновления конкретными командами
Обновление без проверки остаётся полдела. Для Kubernetes проверка занимает пару минут, и каждая команда отвечает на свой вопрос. Все ли узлы показывают v1.37.2, готов ли сервер API, нет ли подов, которые застряли после выселения, и что говорят свежие события.
kubectl get nodes -o wide
kubectl version
kubectl get --raw='/readyz?verbose'
kubectl get pods -A --field-selector=status.phase!=Running
kubectl get events -A --sort-by=.lastTimestamp | tail -n 20
Колонка VERSION в первой команде должна показать одинаковые значения у всех обновлённых узлов. Если один узел отстал, значит, на нём что-то прервалось между установкой пакета и перезапуском kubelet. Вторая и третья команды подтверждают, что управляющий слой живёт и принимает запросы. Четвёртая выводит всё, что не находится в состоянии Running, а в этот список попадают и завершённые задания, поэтому пару строк с готовыми задачами можно смело пропустить. Пятая показывает, не накопились ли предупреждения за время окна.
На хостах с Docker проверка строится вокруг самой службы и сети. Версия сервера должна совпасть с целевой, служба не должна пестрить ошибками в журнале, а тестовый контейнер должен запускаться и останавливаться без возражений. Отдельно стоит проверить вывод опубликованных портов, ведь именно здесь в 29.9.0 исправлен пропуск портов в выводе для сетей в режиме routed gateway.
docker version --format '{{.Server.Version}}'
docker info --format '{{.LiveRestoreEnabled}}'
docker run --rm hello-world
docker ps --format 'table {{.Names}}\t{{.Ports}}'
journalctl -u docker --since "10 min ago" --no-pager
Отдельный пункт для всех, кто ставит 29.9.2 и новее: загрузку образов нужно проверить из каждого реестра, которым пользуются кластеры и конвейеры, включая внутренние. Из-за отозванных промежуточных сертификатов проблема проявляется не у всех, а только у реестров с определённой цепочкой, поэтому пробный docker pull REGISTRY/IMAGE:TAG на обновлённом раннере дешевле, чем разбор упавшей сборки.
Если после обновления контейнеры с сетевыми правилами через firewalld ведут себя странно, первым делом нужно сравнить зону моста с той, что указана в конфигурации. Две из исправленных ошибок ветки 29.9 касаются именно этого сочетания, и знакомо оно тем, кто настраивал фильтрацию трафика для контейнеров вручную.
Чек-лист недели для администратора кластера и владельца конвейера сборки
Всё выше можно свести к короткому плану на одну рабочую неделю. Он подходит и команде из одного человека, и группе, где за кластер, хосты и конвейер отвечают разные люди. Порядок пунктов не случаен: каждый следующий опирается на результат предыдущего.
Чек-лист состоит из следующих шагов:
- Снять текущие версии:
kubectl get nodesдля кластеров,docker versionдля каждого хоста и раннера, результаты записать в таблицу; - Создать копию etcd, убедиться, что файл не пустой и читается, и убрать её в место за пределами самого кластера;
- Проверить PodDisruptionBudget и запас мощности, чтобы выселение одного узла не загнало оставшиеся в нехватку памяти;
- Обновить управляющий слой по одному узлу, сверяя вывод
kubeadm upgrade planс целевой версией 1.37.2; - Обновить рабочие узлы небольшими партиями и после каждой партии смотреть на состояние подов и события;
- Заранее включить live restore и обновить Docker Engine до актуального патча ветки 29.9 (сейчас это 29.9.3) на хостах, начиная с наименее критичных;
- Вывести раннеры из пула, обновить, закрепить теги образов с версией движка и вернуть в работу только после пробной сборки;
- Записать в журнал изменений дату, версии и результаты проверок, а следующий просмотр новых патчей поставить на ту же неделю месяца.
Первые два пункта выглядят скучно, и именно на них чаще всего экономят. Таблица с версиями спасает от самой неприятной ситуации: когда на одном хосте стоит 29.9.2, на другом 29.8, а в журнале нет ни слова о том, почему так вышло.
Как не отстать от графика патчей и что записать на следующую неделю
Ритм у обоих проектов примерно месячный. Kubernetes выпускает патчи ветки раз в четыре недели, Docker Engine с 8 октября по 3 ноября выпустил четыре версии, 29.9.0, 29.9.1, 29.9.2 и 29.9.3. По графику проекта очередной плановый патч Kubernetes приходится на 10 ноября, а крайний срок для включения исправлений назначен на 6 ноября, так что на следующую неделю в плане уже есть готовая точка отсчёта. Это значит, что обновление нужно закладывать в рабочий календарь так же, как резервное копирование: не по настроению и не после инцидента, а по расписанию. Удобнее всего фиксировать один и тот же день недели и одно и то же окно, чтобы команда привыкла и не воспринимала каждое обновление как событие.
Дальше работает простая последовательность: сначала тестовый кластер и один раннер, затем меньшая часть боевых узлов, затем остальное. Если на тестовом контуре что-то идёт не так, в журнале остаётся запись о версии и симптоме, а боевой контур остаётся нетронутым. Это дешевле любой попытки угадать заранее, где именно проявится проблема.
Отдельная строка в плане нужна для крупных вех. До конца поддержки ветки 1.37 остаётся время до 28 октября 2027 года, но подготовка к переходу на следующую версию начинается раньше: с проверки cgroup v2 на узлах, ревизии устаревших API и чистки образов от слоёв, которые давно никто не использует. Патч 1.37.2 и Docker 29.9.2 в этом плане служат разминкой, а не самоцелью. Тот, кто стабильно проходит такие обновления без шума, обычно спокойно проходит и большие.
Рутинный патч-день получается скучным, если всё сделано правильно. Узлы встают в строй, версии совпадают, журнал короткий, и ни одно из исправлений в заметках к выпуску не успело стать чьей-то проблемой. Для инфраструктуры это лучший итог из возможных.