containerd 1.x: время вышло.
Расширенная поддержка containerd 1.7 заканчивается в сентябре 2026 года, и она изначально относилась только к версиям Kubernetes, которые уже сняты с поддержки. Здесь — переписывание конфигурации на версию 3, конвертация реестров, кладущая кластеры, и единственная ветка, на которую стоит переходить.
- containerd
- Kubernetes
- Контейнеры
- Linux
У этого перехода есть две версии: одна читается как рутинная задача, другая — как дедлайн, и в какой из них находитесь вы, решает сноска. Таблица релизов containerd показывает ветку 1.7 как LTS до сентября 2026 года, и это выглядит как запас. Сноска под таблицей уточняет, что с марта 2026 года эту поддержку обеспечивают два поимённо названных мейнтейнера и что она ориентирована на использование с Kubernetes 1.32, 1.31 и 1.30 в Google Kubernetes Engine, а изменения, не нужные для этого сценария, могут быть отклонены. Kubernetes 1.32 снят с поддержки в феврале 2026 года. Если вы не держите на GKE Kubernetes без поддержки, этот спасательный круг бросали не вам.

То есть это переход с реальной датой, и он заслуживает большего, чем увеличенный номер версии в регламенте. Дальше — вся картина: как читать релизную политику containerd и не попасться на слово LTS, почему 2.1 — худшее место для посадки, переписывание конфигурации с версии 2 на версию 3 с переехавшими идентификаторами плагинов, конвертация реестров со своим собственным багом и собственным классом аварий, путь загрузки образов, тихо изменившийся в 2.1, всё, что удалили в 2.0, пошаговый регламент для узла, честный разбор того, что откат возвращает, а что нет, и скрипт проверки, возвращающий ненулевой код при любой проблеме.
Как это выглядит и почему нигде не звучит слово containerd
Ни один из этих отказов не заявляет о себе как о проблеме версии среды выполнения, и именно поэтому его диагностируют поздно. Узел возвращается после обновления, а CRI-плагина попросту нет: демон работает, systemctl status зелёный, и все поды на узле висят в ContainerCreating. Образ, четыре года приходивший с внутреннего зеркала, начинает приходить с Docker Hub, и первым признаком становится счёт за исходящий трафик. RuntimeClass, добавленный командой вручную два года назад, перестаёт разрешаться — и падают только те нагрузки, которые им пользуются. В каждом случае среда выполнения работает, и среда выполнения не та.[ctrrel]
| Что вы видите | Что это обычно значит | Где разбирается |
|---|---|---|
Все поды на узле висят в ContainerCreating, демон при этом здоров | CRI-плагин не загрузился. containerd стартует всё равно и сообщает об отказе только в собственном журнале | Реестры |
| Образы вдруг тянутся из публичного реестра, а не с внутреннего зеркала | Блок registry.mirrors не пережил переписывание в config_path | Реестры |
Контейнер pause тянется с registry.k8s.io на узле без выхода в интернет | sandbox_image не перенесли в pinned_images.sandbox | Разделение плагина |
| Падают только нагрузки на gVisor или Kata, всё остальное работает | В новой конфигурации отсутствует обработчик среды выполнения, названный в RuntimeClass | Kubernetes |
| Старый образ, тянувшийся на прошлой неделе, теперь падает с ошибкой манифеста | Загрузка образов Docker schema 1: отключена по умолчанию в containerd 2.0, полностью удалена в 2.1 | Удаления |
| Загрузка образов ведёт себя иначе после изменения, которое никто не связал с образами | Настройка, которую Transfer Service не может выполнить, вернула узел к локальной загрузке | Загрузка образов |
| Демон при каждом старте пишет в журнал «Configuration migrated from version 2» | Файл так и не переписали. Его тянет слой совместимости, а именно там живёт баг с реестрами | Конфигурация |
Общая нить в том, что containerd 2.x сознательно терпим к конфигурации версии 2: читает её, преобразует в памяти и стартует. В момент обновления это любезность, а после — обуза, потому что переход может бесконечно оставаться наполовину сделанным, и ничто не заставит его завершить. Демон записывает свои возражения в журнал и стартует всё равно; плагин, который не загрузился, выставляет статус ошибки, который вам никто не покажет. Вся эта статья в некотором смысле — аргумент в пользу того, чтобы довести переход до конца, а не оставлять его на слое совместимости.[ctr20]
Прочитать таблицу поддержки как следует
Начните с таблицы релизов, потому что это единственный документ, который что-то решает, и его систематически читают неправильно. У containerd два типа веток. Обычный релиз поддерживается восемь месяцев. Один релиз в год объявляется LTS и поддерживается минимум два года. Поверх этого отдельная ветка может получить расширенную поддержку от поимённо названных мейнтейнеров после закрытия общего окна — и это другая сущность под той же меткой в той же колонке.[ctrrel]
| Ветка | Статус | Конец поддержки | Что это значит для вас |
|---|---|---|---|
| 1.6 | Снята с поддержки | 23 августа 2025 | Без поддержки уже год. Больше ничего не будет, включая исправления безопасности |
| 1.7 | LTS, расширенная | сентябрь 2026 | Только расширенная поддержка от двух названных мейнтейнеров, скроенная под Kubernetes 1.30–1.32 в GKE |
| 2.0 | LTS, расширенная | март 2027 | Та же форма: расширенная поддержка под Kubernetes 1.33 в GKE, который сам снят с поддержки |
| 2.1 | Снята с поддержки | 3 июля 2026 | Уже позади. Версия, на которую многие обновились первой, и худшее место для остановки |
| 2.2 | Активная | 6 ноября 2026 | Получает исправления, но запаса десять недель. Годится как пересадка, не годится как пункт назначения |
| 2.3 | LTS | 30 апреля 2028 | Цель. Текущая долгоживущая ветка, впереди почти два года поддержки |
| 2.4 | Будущая | предварительно апрель 2027 | Обычный восьмимесячный релиз. Не замена LTS |
Теперь наложите матрицу поддержки Kubernetes — место, где два проекта встречаются. containerd публикует список рекомендованных версий для каждого минорного релиза Kubernetes. Для Kubernetes 1.36 этот список выглядит как 2.3.0+, 2.2.0+ — и записи 1.x в нём нет вовсе. В kubelet ничто этого не проверяет: неподдерживаемая пара запустится, будет работать и выглядеть исправной ровно до момента, когда перестанет, и дальше вы отлаживаете в одиночку. Матрица — это утверждение о том, что было протестировано, а тестирование — единственное, что стоит между вами и багом среды выполнения, которого больше никто не видел.[k8srel]
| Kubernetes | Версии containerd в списке рекомендованных | Конец поддержки Kubernetes | Как читать |
|---|---|---|---|
| 1.33 | 2.1.0+, 2.0.4+, 1.7.24+, 1.6.36+ | 28 июня 2026 | Уже без поддержки. Именно эту пару называет расширение containerd 2.0 |
| 1.34 | 2.1.3+, 2.0.6+, 1.7.28+, 1.6.39+ | 27 октября 2026 | Осталось два месяца, и два из четырёх вариантов containerd — 2.1 и 1.6 — сами сняты с поддержки |
| 1.35 | 2.2.0+, 2.1.5+, 1.7.28+ | 28 февраля 2027 | Последняя строка, где вообще появляется среда выполнения 1.x |
| 1.36 | 2.3.0+, 2.2.0+ | 28 июня 2027 | Записи 1.x нет. С этой строки переход перестаёт быть необязательным |
Ловушка внутри ловушки — containerd 2.1. Для всех, кто обновлялся во второй половине 2025 года, это было очевидное место посадки, во многих внутренних документах так написано до сих пор, — и эта ветка снята с поддержки 3 июля 2026 года: раньше, чем 2.2, которая идёт до ноября 2026, и намного раньше 2.3, текущей LTS с поддержкой до апреля 2028. «Перейти на 2.x» — это не план. С чистого листа целевая ветка ровно одна, и это 2.3.
Что на этих узлах установлено на самом деле
Прежде чем что-либо трогать, установите, что установлено на самом деле: на парке любого размера ответом не будет одна версия. Важны три вещи, и они независимы: версия демона, версия файла конфигурации и версия CRI API, которую в действительности получает kubelet. Про версию конфигурации забывают, и именно она может отсутствовать: файл без строки version считается файлом версии 1. И вот здесь документы самого проекта расходятся между собой — стоит знать, как именно: руководство по конфигурации CRI утверждает, что версию 1 удалили в containerd 2.0, тогда как RELEASES.md говорит, что отсутствующая версия разбирается как версия 1 и что все предыдущие версии поддерживаются через миграцию, а в исходниках до сих пор лежит функция миграции с v1. Считайте файл версии 1 тем, что надо исправить при первом же обнаружении, а не тем, о чём можно уверенно рассуждать.[cfgtoml]
# The daemon, the client and the shim are three separate versions and they are
# allowed to disagree. Ask all three rather than assuming.
containerd --version
# containerd github.com/containerd/containerd/v2 v2.3.2 <revision>
ctr version # client and server, side by side
runc --version # the OCI runtime is a separate install now
# The configuration version is the single most useful number here. There is no
# `version` line in very old files: absent means version 1, which containerd
# 2.0 removed outright rather than migrating.
head -1 /etc/containerd/config.toml
# version = 2
# What the plugins are doing. A plugin in state "error" is the daemon telling
# you a migration went wrong; it does not stop the daemon from starting.
ctr plugins ls | awk '$4!="ok"'
# TYPE ID PLATFORMS STATUS
# From the Kubernetes side, which is what actually matters:
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,RUNTIME:.status.nodeInfo.containerRuntimeVersion,'\
'KUBELET:.status.nodeInfo.kubeletVersion,OS:.status.nodeInfo.osImage'
# NODE RUNTIME KUBELET OS
# node-01 containerd://1.7.28 v1.34.9 Ubuntu 24.04.3 LTS
# And the CRI API version the kubelet is really getting. containerd 2.0 removed
# v1alpha2; if anything on this node still speaks it, it stops working here.
crictl version
# RuntimeName: containerd
# RuntimeApiVersion: v1Затем спросите демон о том, что он давно пытается вам сказать. Начиная с 1.6.27 и 1.7.12 containerd отдаёт предупреждения об устаревании через introspection API — ровно для того, чтобы этот переход можно было спланировать, а не обнаружить. Подкоманда называется ctr deprecations list — во множественном числе, и это стоит проговорить, потому что как минимум один официальный документ пишет её в единственном, а такой формы не существует. Запустите её с --format json по всему парку. Это не справка о здоровье: предупреждения появляются при использовании, так что узел, не тянувший образ schema 1 с момента последнего перезапуска, ничего о нём не сообщит. Это стартовый список того, о чём вы уже знаете, что оно неправильно.[depsrc]
# containerd has been telling you what will break since 1.6.27 / 1.7.12, through
# the introspection API. Almost nobody reads it, because the warnings go into
# the daemon log rather than anywhere you look. Ask directly.
#
# Note the subcommand is `deprecations`, plural. Some documentation writes it
# in the singular; that form does not exist and returns a usage error.
ctr deprecations list
# ID LAST OCCURRENCE MESSAGE
# io.containerd.deprecation/pull-schema-1-image 2026-08-24... Schema 1 image...
# io.containerd.deprecation/cri-registry-mirrors 2026-08-24... `mirrors` is deprecated...
# Machine-readable, which is the form you want across a fleet:
ctr deprecations list --format json | jq -r '.[].id' | sort -u
# Run it on every node and count, rather than sampling. The warnings are
# emitted on use, so a node that has not pulled a schema 1 image since the last
# daemon restart will not report one - which is why this is a starting point
# and not a clean bill of health.
for n in $(kubectl get nodes -o name); do
printf '%-22s ' "${n#node/}"
kubectl debug "$n" -it --image=busybox --profile=general -- \
chroot /host ctr deprecations list --format json 2>/dev/null \
| jq -r '[.[].id] | join(",")' || echo '(unavailable)'
done
# Clean up afterwards. `kubectl debug node/...` names its pods
# node-debugger-<node>-<suffix> and applies no label of its own, so there is
# nothing to select on - match the name instead, or they accumulate silently.
kubectl get pods -n default -o name | grep '^pod/node-debugger-' | xargs -r kubectl deleteФайл конфигурации: с версии 2 на версию 3 — и теперь 4
Файл конфигурации — это суть перехода, и первым делом стоит выяснить, что вообще означает «последняя версия», потому что в этом году она сдвинулась. Версия 3 пришла вместе с containerd 2.0 — именно она разделила CRI-плагин надвое. Версия 4 пришла с 2.3 и делает нечто совсем другое, о чём следующий раздел. Версия 2 по-прежнему читается и преобразуется в памяти при каждом старте — демон пишет об этом строку в журнал, и это самый дешёвый способ выяснить, действительно ли узел переведён или его просто терпят. В демон встроен конвертер containerd config migrate, который читает ваш текущий файл и печатает последнюю версию в стандартный вывод. В man-странице его нет — containerd-config(8) документирует только default, — и это во многом объясняет, почему о нём так мало кто знает.[cricfg][cfgsrc]
# containerd 2.x reads a version 2 file and converts it in memory on every start.
# That is a compatibility shim, not a plan: it costs startup time, it is where
# the registry bug below lives, and the daemon says so on every boot.
journalctl -u containerd | grep -m1 'Configuration migrated from version'
# Configuration migrated from version 2, use `containerd config migrate` to
# avoid migration
# `containerd config migrate` reads your current file and prints the LATEST
# version on stdout. It is not in the man page - only `default` is - but it has
# been in the binary since 2.0.
#
# Note which version "latest" means, because it moved. Version 3 arrived in
# containerd 2.0 and is the one that split the CRI plugin in two. Version 4
# arrived in 2.3 and moves the server sockets into plugins (see below).
containerd config migrate > /tmp/config.new.toml
head -1 /tmp/config.new.toml
# version = 4 <- on containerd 2.3. On 2.0-2.2 this says 3.
# Two things to know before you trust the output. First, `migrate` and `dump`
# share one implementation, so the result is the FULLY POPULATED configuration,
# defaults and all - not a minimal file. Every default you did not choose is now
# pinned in your file and stops following the daemon when upstream changes it.
wc -l /etc/containerd/config.toml /tmp/config.new.toml
# 41 /etc/containerd/config.toml
# 318 /tmp/config.new.toml
# Second, and this is upstream's own warning: migrating the file to the latest
# version limits which containerd versions can read it. A version 4 file needs
# 2.3.0 or newer. If you might want to roll the binary back tonight, write a
# version 3 file instead - 2.0 and later read it, and it still gets you the
# plugin split, which is the part that matters.
# So: use the output to learn the new names, then hand-write the short version.
# What did it actually change? Compare the keys, not the files.
grep -oE '^\s*\[[^]]+\]' /tmp/config.new.toml | tr -d ' []' | sort > /tmp/new.keys
grep -oE '^\s*\[[^]]+\]' /etc/containerd/config.toml | tr -d ' []' | sort > /tmp/old.keys
diff -u /tmp/old.keys /tmp/new.keys
# Validate before you restart anything. `config dump` loads the file the daemon
# would load, including everything pulled in by `imports`, and fails loudly on
# a file it cannot parse. Note that --config is a global flag: it goes BEFORE
# the subcommand. Putting it after `config dump` is a usage error, not a check -
# urfave/cli rejects it with "flag provided but not defined: -config".
containerd --config /tmp/config.new.toml config dump >/dev/null && echo 'parses'
# Keep the old one. It is the fastest rollback you have.
cp -a /etc/containerd/config.toml /etc/containerd/config.toml.v2.bakДве вещи про этот конвертер стоит знать до того, как направлять его вывод поверх боевой конфигурации. Первое: migrate и dump — это один и тот же путь в коде, поэтому возвращается полностью развёрнутая конфигурация: каждое значение по умолчанию выписано явно. Файл на сорок строк превращается в триста, и каждое значение по умолчанию, которого вы не выбирали, теперь зафиксировано у вас и перестанет следовать за апстримом при его изменении. Используйте вывод, чтобы выучить новые имена ключей, а короткую версию напишите руками. Второе: проверьте файл-кандидат до любого перезапуска — но учтите, что --config это глобальный флаг, а не флаг подкоманды, поэтому он ставится перед config dump: containerd --config <файл> config dump. Поставленный после, он даёт ошибку использования, а не проверку. Запущенная правильно, команда загружает файл ровно так, как загрузил бы его демон, идёт по imports и громко падает на том, что не может разобрать, — а это куда лучшее место для обнаружения опечатки, чем узел, который не вернулся.[cfgman]
# Configuration version 4 (containerd 2.3 and later). It changes nothing about
# CRI: the whole of the plugin split above is version 3 work. What it moves is
# the daemon's own sockets, out of top-level tables and into server plugins.
# --- version 3 and earlier -------------------------------------------------
# [grpc]
# address = "/run/containerd/containerd.sock"
# uid = 0
# gid = 0
# [ttrpc]
# address = "/run/containerd/containerd.sock.ttrpc"
# [metrics]
# address = "127.0.0.1:1338"
# [debug]
# address = "/run/containerd/debug.sock"
# level = "info"
# --- version 4 -------------------------------------------------------------
version = 4
[plugins.'io.containerd.server.v1.grpc']
address = '/run/containerd/containerd.sock'
uid = 0
gid = 0
[plugins.'io.containerd.server.v1.ttrpc']
address = '/run/containerd/containerd.sock.ttrpc'
[plugins.'io.containerd.server.v1.metrics']
address = '127.0.0.1:1338'
[plugins.'io.containerd.server.v1.debug']
address = '/run/containerd/debug.sock'
# `[debug]` does not disappear: level, format and log_trace_id stay at the top
# level. Only the socket fields move.
#
# One behaviour change hides in here. Before version 4, an unset ttrpc address
# was derived from the grpc address as "<grpc address>.ttrpc" and inherited its
# uid and gid. In version 4 the ttrpc plugin is independent and falls back to
# its own default. If anything of yours connects to that socket by path -
# a shim debugger, a monitoring agent - set it explicitly rather than assuming.Версия 4 заслуживает отдельного взгляда: почти все разборы этого перехода останавливаются на версии 3, и вдобавок она накладывает ограничение на откат. К CRI она не имеет отношения — она выносит собственные сокеты демона из верхнеуровневых таблиц [grpc], [ttrpc], [metrics] и [debug] в плагины io.containerd.server.v1.*. Отсюда два следствия. Поведенческое: до версии 4 незаданный адрес ttrpc выводился из адреса gRPC как <адрес gRPC>.ttrpc и наследовал его uid и gid, а в версии 4 плагин ttrpc самостоятелен и берёт собственное значение по умолчанию — так что всё ваше, что подключается к этому сокету по пути, теперь стоит настроить явно. Эксплуатационное — это предупреждение самого апстрима: перевод файла на последнюю версию сужает круг версий containerd, способных его прочитать. Файлу версии 4 нужен 2.3.0 или новее; файл версии 3 читают 2.0 и все последующие. Если откат бинарника в ту же ночь входит в ваш план, пишите версию 3 — разделение плагина, то есть самое важное, вы всё равно получаете.[cfgver][srvmig]
Один плагин стал двумя, и настройки уехали вместе с ним
Структурное изменение в том, что единственный CRI-плагин разделили надвое. Раньше io.containerd.grpc.v1.cri содержал всё; в версии 3 в нём остались только опции стримингового сервера, а содержимое живёт под двумя новыми идентификаторами: io.containerd.cri.v1.runtime — всё, что относится к запуску контейнеров: среды выполнения, CNI, песочницы, SELinux, обработка OOM; io.containerd.cri.v1.images — всё, что относится к образам: snapshotter, реестр, закреплённый образ песочницы, параллельность загрузки. Разделение лучше прежнего, и оно означает, что механическая замена идентификатора плагина уложит примерно половину ваших настроек не в ту таблицу.[ctrarch]
# /etc/containerd/config.toml - containerd 1.7, the file most clusters have.
# Everything lives under one plugin ID: io.containerd.grpc.v1.cri
version = 2
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.10"
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "overlayfs"
default_runtime_name = "runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.gvisor]
runtime_type = "io.containerd.runsc.v1"
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
# The block that causes the most trouble in this migration.
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://mirror.internal.example.com"]# /etc/containerd/config.toml - containerd 2.x. The single CRI plugin has been
# split in two, and the settings moved with the split: anything about running
# containers is now under io.containerd.cri.v1.runtime, anything about images
# under io.containerd.cri.v1.images. io.containerd.grpc.v1.cri still exists,
# but only for the streaming server options.
version = 3
[plugins.'io.containerd.cri.v1.images']
snapshotter = 'overlayfs' # moved: was under ...cri.containerd
[plugins.'io.containerd.cri.v1.images'.pinned_images]
sandbox = 'registry.k8s.io/pause:3.10.2' # replaces sandbox_image
[plugins.'io.containerd.cri.v1.images'.registry]
config_path = '/etc/containerd/certs.d' # replaces the mirrors block
[plugins.'io.containerd.cri.v1.runtime']
[plugins.'io.containerd.cri.v1.runtime'.containerd]
default_runtime_name = 'runc'
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
runtime_type = 'io.containerd.runc.v2'
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.gvisor]
runtime_type = 'io.containerd.runsc.v1'
[plugins.'io.containerd.cri.v1.runtime'.cni]
bin_dirs = ['/opt/cni/bin'] # bin_dir is deprecated since 2.1: plural now
conf_dir = '/etc/cni/net.d'
# Note what is NOT here: no registry.mirrors alongside config_path. Setting
# both is an error - "`mirrors` cannot be set when `config_path` is provided" -
# and the CRI plugin refuses to load rather than picking one.
#
# And note the clock on the old keys. registry.mirrors and registry.configs
# were deprecated in containerd 1.5, registry.auths in 1.3, and cni.bin_dir in
# 2.1. All four carry the same removal target: containerd 2.4. Converting them
# is not housekeeping you can defer past the next release.| containerd 1.x (версия 2) | containerd 2.x (версия 3) | Примечание |
|---|---|---|
version = 2 | version = 3 (2.0) / version = 4 (2.3) | Версия 2 по-прежнему читается и преобразуется в памяти. Файлу версии 4 нужен 2.3.0 или новее |
plugins."io.containerd.grpc.v1.cri" | plugins.'io.containerd.cri.v1.runtime' | Всё о запуске контейнеров: среды выполнения, CNI, SELinux, OOM, песочницы |
plugins."io.containerd.grpc.v1.cri" | plugins.'io.containerd.cri.v1.images' | Всё об образах: snapshotter, реестр, закреплённые образы, параметры загрузки |
plugins."io.containerd.grpc.v1.cri" | plugins.'io.containerd.grpc.v1.cri' | Существует по-прежнему, но только для опций стримингового сервера |
sandbox_image = "…" | pinned_images.sandbox = '…' | Переименован и перенесён. Потеряете — изолированный узел полезет на registry.k8s.io |
…cri".containerd.snapshotter | …cri.v1.images'.snapshotter | Меняет сторону — со среды выполнения на образы |
…cri".containerd.runtimes.* | …cri.v1.runtime'.containerd.runtimes.* | Меняется только путь. runtime_type = io.containerd.runc.v2 прежний |
…cri".registry.mirrors | …cri.v1.images'.registry.config_path | Другой механизм. Каталог с файлами hosts.toml, а не таблица. Срок удаления — 2.4 |
…cri".registry.auths | — (imagePullSecrets) | Замены нет намеренно. Учётные данные переезжают в кластер. Срок удаления — 2.4 |
…cri".cni.bin_dir | …cri.v1.runtime'.cni.bin_dirs | Множественное число, и это список. Устарел в 2.1, срок удаления — 2.4 |
plugin_dir (Go-плагин .so) | — (proxy- или бинарные плагины) | Уже удалены в 2.1, а не просто объявлены устаревшими |
Два переименования внутри этого разделения наносят основной ущерб. sandbox_image стал pinned_images.sandbox, поэтому кластер, у которого образ pause указывал на внутреннее зеркало, молча возвращается к registry.k8s.io — и это не проблема до того дня, когда у узла не будет выхода наружу. А snapshotter переехал со стороны среды выполнения на сторону образов, и это достаточно неинтуитивно, чтобы проверить, а не предположить. Всё остальное в таблице ниже — смена пути, а не поведения.[ctrplug]
Реестры: та часть, из-за которой падают кластеры
Конфигурация реестров — место, где этот переход из нудного становится рискованным, и у него есть дата: mirrors и configs объявлены устаревшими в containerd 1.5, auths — в 1.3, и у всех трёх срок удаления назначен на containerd 2.4, релиз, следующий за тем, который рекомендует эта статья. Заменой первым двум служит дерево каталогов: по подкаталогу на пространство имён хоста реестра под единым config_path, в каждом — hosts.toml. Файлов больше, магии заметно меньше, и это честно лучше: хосты перебираются по порядку, capabilities заданы явно, а собственный CA у реестра — строка в файле, а не особый случай. У третьего свойства, auths, файловой замены нет намеренно: учётные данные принадлежат секрету загрузки образов в Kubernetes, а не конфигурации узла, которую наследует каждая работающая на нём нагрузка.[crireg][hosts]
# The mirrors / configs / auths properties are deprecated. The replacement is a
# directory of hosts.toml files, one per registry host namespace, pointed at by
# a single config_path. It is more files and considerably less magic.
# Directory naming, which is where this goes wrong silently. containerd looks
# for the host namespace in three forms, in order:
# <host>_<port>_ e.g. registry.internal.example.com_5000_
# <host>:<port> e.g. registry.internal.example.com:5000
# _default
# The first form is the portable one - a colon is not a legal filename on
# Windows - so prefer it. A directory named anything else looks perfectly
# correct and simply never matches.
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<'TOML'
server = "https://docker.io"
[host."https://mirror.internal.example.com"]
capabilities = ["pull", "resolve"]
# Fall through to the real registry if the mirror does not have the layer.
# Order matters: hosts are tried top to bottom.
[host."https://registry-1.docker.io"]
capabilities = ["pull", "resolve"]
TOML
# A private registry on a port, with its own CA:
mkdir -p /etc/containerd/certs.d/registry.internal.example.com_5000_
cat > /etc/containerd/certs.d/registry.internal.example.com_5000_/hosts.toml <<'TOML'
server = "https://registry.internal.example.com:5000"
[host."https://registry.internal.example.com:5000"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/internal-ca.crt"
TOML
# registry.auths has no file equivalent, on purpose. Credentials belong in a
# Kubernetes imagePullSecret, not in the node's runtime configuration where
# every workload on the node inherits them.
kubectl create secret docker-registry regcred \
--docker-server=registry.internal.example.com:5000 \
--docker-username=ci --docker-password="$REG_PASSWORD"
# Verify resolution without restarting anything. --hosts-dir makes ctr read the
# same tree the CRI plugin will read.
ctr images pull --hosts-dir /etc/containerd/certs.d docker.io/library/alpine:3.22
# And confirm the daemon agrees once it has restarted:
containerd config dump | grep -A3 "cri.v1.images'.registry"
# config_path = '/etc/containerd/certs.d'Баг стоит сформулировать точно, потому что неточная формулировка отправляет искать не там. Ничего необычного от вас для этого не требуется. Запустите containerd 2.2.0 с обычным файлом версии 2, где есть блок registry.mirrors и больше ничего, — и миграция в памяти добавит рядом с ним config_path по умолчанию, а CRI-плагин отвергает такую комбинацию наотрез: `mirrors` cannot be set when `config_path` is provided. Плагин не загружается. Демон стартует всё равно. Каждый под, назначенный на этот узел, после этого не создаётся, а верхнеуровневый статус всех служб на машине остаётся зелёным. Поиск обоих ключей по вашему собственному файлу не находит ничего, потому что один из них вы не писали. Проблему сообщили для 2.2.0 и исправили в pull request 12617, который вошёл до 2.3.0 и был бэкпортирован в ветку 2.2, — так что на актуальной 2.3 или на свежем патче 2.2 вы не уязвимы, а на 2.0 или 2.1 уязвимы. Инструкция, которая остаётся верной в любом случае, — довести конвертацию до конца: построить дерево certs.d, направить туда config_path и удалить блок mirrors, чтобы никакой миграции не пришлось ничего угадывать.[iss12612][pr12617]
Путь загрузки образов поменялся у вас под ногами
Начиная с containerd 2.1 CRI-плагин тянет образы через Transfer Service, а не внутри своего процесса. Это значение по умолчанию, а не включённая вами опция, и само по себе оно ничем не примечательно. Отдельного раздела заслуживает откат. Если в конфигурации образов CRI есть что-то, чего Transfer Service не может выполнить, containerd выставляет use_local_image_pull для всего узла и пишет предупреждение. Он не падает, не сообщает об этом в момент использования и не сообщает кластеру.[ctrxfer]
# Since 2.1 the CRI plugin pulls images through the Transfer Service instead of
# pulling in-process. This is not a flag you set; it is the default. What makes
# it worth knowing is the fallback: if the CRI image configuration contains
# anything the Transfer Service cannot honour, containerd silently switches the
# whole node back to local pull and logs a warning.
#
# The triggers, from the CRI config guide:
# Registry.Mirrors set Registry.Configs set Registry.Auths set
# MaxConcurrentDownloads != 3 DiscardUnpackedLayers = true
# ImagePullWithSyncFs = true DisableSnapshotAnnotations = false
#
# Which means the perfectly reasonable act of raising the download concurrency
# quietly changes the code path your images are pulled through.
journalctl -u containerd --since '10 min ago' \
| grep -iE 'transfer|use_local_image_pull|falling back'
# If you want local pull, ask for it rather than triggering it by accident:
# [plugins.'io.containerd.cri.v1.images']
# use_local_image_pull = true
#
# If you want the Transfer Service, move the settings to where it reads them:
# [plugins.'io.containerd.transfer.v1.local']
# max_concurrent_downloads = 6
# Check which path a real pull took, end to end:
crictl pull registry.k8s.io/pause:3.10.2
crictl images | head
ctr -n k8s.io images ls | wc -l| Настройка | Локальная загрузка | Transfer Service (по умолчанию с 2.1) |
|---|---|---|
snapshotter | Поддерживается | Поддерживается |
ImagePullProgressTimeout | Поддерживается | Поддерживается |
PinnedImages | Поддерживается | Поддерживается |
Registry.Mirrors / Configs / Auths | Поддерживается (всё устарело) | Не поддерживается — включает откат к локальной загрузке |
MaxConcurrentDownloads | Читается из конфигурации образов CRI | Нужно перенести в plugins.'io.containerd.transfer.v1.local'; любое значение, кроме 3, включает откат |
DiscardUnpackedLayers | Поддерживается | Не поддерживается — включает откат |
ImagePullWithSyncFs | Поддерживается | Не поддерживается — включает откат |
DisableSnapshotAnnotations | Поддерживается | Настраивается в плагине snapshotter; значение false включает откат |
Прочитайте список триггеров один раз — и следствие станет очевидным: поднять max_concurrent_downloads с 3 до 6, обычное и разумное действие на узле с широким каналом, переводит все загрузки образов на этом узле на другой путь в коде. То же делает сохранённый устаревший блок mirrors — второй довод в пользу того, чтобы довести конвертацию реестров до конца, а не бросить её. Если вам нужна локальная загрузка, поставьте use_local_image_pull = true осознанно. Если нужен Transfer Service, перенесите настройку параллельности в [plugins.'io.containerd.transfer.v1.local'], где она действительно читается.[cricfg]
Что действительно удалили
Теперь удаления — та часть, которая действительно исчезла, а не просто переименована. Список короткий, и у всего в нём есть задокументированная замена, но читайте номера версий, а не пересказы: документ о переходе на containerd 2.0 и RELEASES.md расходятся в двух местах, и оба раза цитируют именно документ о переходе. Важный случай — загрузка образов Docker schema 1: её отключили в 2.0, где вернуть её обратно ещё могла переменная окружения, и удалили в 2.1, где не возвращает уже ничто. Раз целью здесь является 2.3, считайте, что её нет. Это важно, потому что образы, до сих пор лежащие в schema 1, по определению никто не пересобирал примерно с 2017 года, а значит, и Dockerfile ни у кого нет. Найдите их до обновления, а не после: с 1.7.8 и 1.6.25 сконвертированные образы несут метку, по которой их можно найти.[ctr20][ctrrelmd]
# Docker schema 1 manifests. Get the timeline right, because it decides whether
# you have a workaround or a deadline:
# containerd 2.0 pulling is DISABLED by default, and the environment variable
# CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1 re-enables it
# containerd 2.1 support REMOVED. The variable does nothing. So does anything else.
# Since 2.3 is the target, treat this as removed and find the images NOW.
#
# Since 1.7.8 / 1.6.25 converted images carry a label, so they are findable:
ctr namespaces list --quiet | xargs -I{} -- \
ctr --namespace={} image list \
'labels."io.containerd.image/converted-docker-schema1"'
# On a node still running 1.7, the same list from the CRI side:
crictl images -o json | jq -r '.images[].repoTags[]' | sort -u > /tmp/node-images.txt
# For each one, ask the registry what media type it actually serves. A schema 1
# manifest answers with application/vnd.docker.distribution.manifest.v1+prettyjws.
# Anything that does needs rebuilding in schema 2 or OCI before the node moves.
# The runtime v1 shims were removed in 2.0. Anything still asking for them
# fails to start the container, with an error about an unknown runtime:
grep -rn 'io.containerd.runtime.v1.linux\|io.containerd.runc.v1' \
/etc/containerd/ /etc/crio/ 2>/dev/null
kubectl get runtimeclass -o custom-columns=NAME:.metadata.name,HANDLER:.handler
# The AUFS snapshotter was removed. Almost nobody sets this, and the ones who
# do have a kernel from 2016 underneath it:
containerd config dump | grep -E "snapshotter\s*=" | sort -u
# LimitNOFILE is no longer set in the reference unit. On systemd 240 and newer
# the default is fine; below that the kernel default of 4096 applies, and
# containers inherit it.
systemctl show containerd -p LimitNOFILE -p LimitNOFILESoft
systemctl --version | head -1| Возможность | Устарела с | Удалена в | Что вместо этого |
|---|---|---|---|
Runtime V1, io.containerd.runtime.v1.linux | 1.4 | 2.0 | io.containerd.runc.v2 |
Runc V1, io.containerd.runc.v1 | 1.4 | 2.0 | io.containerd.runc.v2 |
| Встроенный snapshotter AUFS | 1.5 | 2.0 | overlayfs |
Метка containerd.io/restart.logpath | 1.5 | 2.0 | containerd.io/restart.loguri |
Архивы cri-containerd-*.tar.gz | 1.6 | 2.0 | Устанавливать containerd, runc и плагины CNI по отдельности |
CRI API v1alpha2 | 1.7 | 2.0 | Только CRI v1. Проверьте, что crictl version отдаёт RuntimeApiVersion: v1 |
| Старая реализация podsandbox в CRI | 2.0 | 2.0 | Контроллер песочниц, он же значение по умолчанию |
| Загрузка образов Docker schema 1 | 1.7 | 2.1 (отключена в 2.0) | Пересобрать в schema 2 / OCI. Обходной путь через переменную окружения перестал работать в 2.1 |
Плагины среды выполнения как Go-библиотеки (*.so) | 2.0 | 2.1 | Внешние плагины: proxy- или бинарные |
Явный LimitNOFILE в эталонном юните | — | 2.0 | Использовать значение systemd по умолчанию; ниже systemd 240 задать 1024:524288 вручную |
io_uring_* в профиле seccomp по умолчанию | — | 2.0 | Явный профиль seccomp и разговор о том, нужен ли он |
Одно удаление тише прочих и заслуживает отдельного упоминания. Эталонный юнит containerd.service больше не задаёт LimitNOFILE явно. Rlimits демона наследуются контейнерами, которые он запускает, так что это не настройка только для демона: на systemd 240 и новее значение по умолчанию разумно и ничего не происходит, а на более старых применяется значение ядра — 4096, — и его наследует каждый контейнер на машине. Рекомендация апстрима для таких хостов — вернуть LimitNOFILE=1024:524288 вручную.[pr8924][sdexec]
Значения по умолчанию, которые перевернулись без спроса
Отдельно от удалений несколько значений по умолчанию поменяли значение. Именно из-за них узел ведёт себя иначе после обновления, в котором вы не тронули ни одной настройки, и они заслуживают осознанного решения, а не принятия по инерции.[ctr20]
| Значение по умолчанию | containerd 1.x | containerd 2.x | Почему это важно |
|---|---|---|---|
enable_unprivileged_ports | false | true | Контейнеры слушают ниже 1024 без CAP_NET_BIND_SERVICE |
enable_unprivileged_icmp | false | true | ping работает без CAP_NET_RAW |
enable_cdi | выключено | true | Файлы спецификаций в /etc/cdi и /var/run/cdi описывают доступ к устройствам. Сам выключатель устарел в 2.2 и исчезнет в 2.4 |
| NRI | отключён | включён | Сокет NRI становится частью поверхности атаки узла |
| CRI с песочницей | старый CRI-сервер | контроллер песочниц | Незаметно для runc, стоит проверить для Kata и gVisor |
| Путь загрузки образов | внутри процесса | Transfer Service (с 2.1) | Молча откатывается к локальной загрузке при нескольких настройках |
Вызовы io_uring_* | разрешены | заблокированы | Убраны из списка seccomp по умолчанию после череды эксплойтов ядра |
| Образ песочницы | sandbox_image | pinned_images.sandbox | То же значение, другой ключ. Легко потерять при переписывании |
- Непривилегированные порты и ICMP включены. CRI-плагин теперь выставляет
net.ipv4.ip_unprivileged_port_start=0иnet.ipv4.ping_group_range=0 2147483647для контейнеров, которые не используют ни сетевое пространство имён хоста, ни пространства имён пользователей. Привязка ниже порта 1024 больше не требуетCAP_NET_BIND_SERVICE, аping—CAP_NET_RAW. Удобно и одновременно меняет вашу модель безопасности контейнеров: прежнее поведение возвращается установкойenable_unprivileged_portsиenable_unprivileged_icmpвfalse. - NRI включён. Node Resource Interface позволяет плагинам менять контейнеры при создании. Доступ регулируется доступом к общесистемному сокету NRI, а значит, этот сокет уже входит в поверхность атаки узла независимо от того, используете вы хоть один NRI-плагин или нет.
- CDI включён, и сам выключатель доживает последнее. Container Device Interface активен,
cdi_spec_dirsпо умолчанию указывает на/etc/cdiи/var/run/cdi, так что всё, что способно записать файл спецификации в эти каталоги, может описать доступ к устройствам для контейнеров. Учтите, что самenable_cdiобъявлен устаревшим в containerd 2.2 со сроком удаления в 2.4, после чего CDI просто всегда включён, — и если вы планировали его выключить, у этого плана есть дата истечения. - io_uring больше не в списке разрешённых seccomp по умолчанию.
io_uring_enter,io_uring_registerиio_uring_setupубрали после достаточно длинной череды эксплойтов ядра, чтобы проект счёл их небезопасными по умолчанию. Нагрузке, построенной вокруг io_uring, понадобится явный профиль — и разговор о том, должна ли она его получить. - Реализация CRI с песочницей стала стандартной. CRI-плагин использует стабильный контроллер песочниц вместо старого CRI-сервера. В обычной работе это незаметно и очень заметно, если вы запускаете среду с песочницей вроде Kata или gVisor, — ровно тот случай, который стоит проверить до раскатки на парк.
Ни один из этих пунктов не повод не обновляться. Это повод обновить один узел, посмотреть на него и только после этого вписать изменение в сборку образа — в этом разница между переходом и сюрпризом на весь парк.[ctrnri][cdi]
Обновление узла, по порядку
Механическая часть короткая, и изменилось скорее как это делается, чем что делается. Совмещённые архивы cri-containerd-cni-VERSION-OS-ARCH.tar.gz удалили в 2.0, так что containerd, runc и плагины CNI — теперь три отдельные установки с тремя отдельными решениями о версиях. Это явнее и чуть больше работы, зато исчезает давний источник путаницы, когда обновляли containerd и незаметно обновляли заодно runc. Одно успокоительное соображение про сам прыжок: containerd поддерживает обновления между последовательными минорными релизами и, отдельно, прямые обновления между последовательными LTS-релизами — и переход с 1.7 (LTS) на 2.3 (LTS) приведён примером в его же релизном документе. Вы не перепрыгиваете ничего, на что должны были приземлиться.[ctrstart]
#!/usr/bin/env bash
# One node, from containerd 1.7 to 2.3 LTS. Run it on a drained node.
#
# 1.7 -> 2.3 is a supported jump. containerd supports sequential minor upgrades
# and, separately, direct upgrades between sequential LTS releases - and it
# names 1.7 (LTS) to 2.3 (LTS) as an example. That is exactly this path.
set -euo pipefail
VER=2.3.4 # check https://containerd.io/releases/ before pinning
RUNC_VER=1.5.0
CNI_VER=1.9.1
ARCH=amd64
# 0. Get the workloads off, and keep the node out of rotation until verified.
# (From the control plane, not from the node.)
# kubectl drain node-01 --ignore-daemonsets --delete-emptydir-data
# 1. Back up everything the daemon owns. /var/lib/containerd is the image and
# snapshot store; losing it means re-pulling every image on the node.
systemctl stop kubelet containerd
cp -a /etc/containerd/config.toml /etc/containerd/config.toml.bak
tar -C /var/lib -czf "/var/backups/containerd-lib-$(date +%F).tgz" containerd
# 2. Install the three components separately. The combined
# cri-containerd-cni-*.tar.gz bundles were removed in 2.0; this is now the
# supported route rather than a workaround.
curl -fsSLO "https://github.com/containerd/containerd/releases/download/v${VER}/containerd-${VER}-linux-${ARCH}.tar.gz"
curl -fsSLO "https://github.com/containerd/containerd/releases/download/v${VER}/containerd-${VER}-linux-${ARCH}.tar.gz.sha256sum"
sha256sum -c "containerd-${VER}-linux-${ARCH}.tar.gz.sha256sum"
tar -C /usr/local -xzf "containerd-${VER}-linux-${ARCH}.tar.gz"
# runc and the CNI plugins, pinned deliberately rather than left behind.
# These used to ride along inside the removed bundles; now they are yours
# to choose, which also means yours to forget.
curl -fsSL -o /usr/local/sbin/runc \
"https://github.com/opencontainers/runc/releases/download/v${RUNC_VER}/runc.${ARCH}"
chmod 755 /usr/local/sbin/runc
mkdir -p /opt/cni/bin
curl -fsSLO "https://github.com/containernetworking/plugins/releases/download/v${CNI_VER}/cni-plugins-linux-${ARCH}-v${CNI_VER}.tgz"
tar -C /opt/cni/bin -xzf "cni-plugins-linux-${ARCH}-v${CNI_VER}.tgz"
# 3. Refresh the systemd unit from the release, then reapply any drop-in of
# your own. Note the reference unit no longer sets LimitNOFILE, and that the
# directory does not exist on a host that came from a distribution package.
mkdir -p /usr/local/lib/systemd/system
curl -fsSL -o /usr/local/lib/systemd/system/containerd.service \
"https://raw.githubusercontent.com/containerd/containerd/v${VER}/containerd.service"
systemctl daemon-reload
# 4. Put the new configuration in place - the hand-written one, not the
# 318-line dump - and prove it parses before anything restarts.
install -m 0644 /tmp/config.new.toml /etc/containerd/config.toml
containerd --config /etc/containerd/config.toml config dump >/dev/null
# 5. Bring it back, runtime first, kubelet second.
systemctl start containerd
sleep 3
ctr plugins ls | awk '$4!="ok"' # must print only the header
systemctl start kubelet
# 6. Then verify from the cluster's point of view before uncordoning:
# kubectl get node node-01 -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}'
# kubectl uncordon node-01Две заметки о порядке, которые легко нарушить в спешке. Сделайте резервную копию /var/lib/containerd до начала — не потому, что обновление, вероятно, его испортит, а потому, что это хранилище образов и снапшотов: потерять его значит заново вытянуть все образы узла, что на крупном узле измеряется десятками минут и заметным исходящим трафиком. И поднимайте среду выполнения раньше kubelet, а перед запуском kubelet проверьте ctr plugins ls на всё, что не в состоянии ok. CRI-плагин, который не загрузился, — это узел, который принимает поды и не может их создать.[runc][cni]
То же самое изменение со стороны Kubernetes
Со стороны кластера делать нечего, а проверить нужно одно. Матрица поддержки — документ проекта, а не политика допуска: ни один kubelet не откажется стартовать с неподдерживаемым containerd, и никакого события нигде не появится. Значит, аудит должен быть явным: обойти узлы, прочитать containerRuntimeVersion и сверить с матрицей для версии kubelet на том же узле. Разнородные пулы узлов — норма, а не исключение, особенно там, где образы узлов обновляются по собственному графику.[k8sruntime]
# The support matrix is a project document, not a runtime check: nothing stops
# a kubelet from talking to an unsupported containerd. That is precisely the
# problem - you find out from a bug, not from a startup error. So audit it.
kubectl get nodes -o json | jq -r '
.items[] | [.metadata.name,
.status.nodeInfo.kubeletVersion,
.status.nodeInfo.containerRuntimeVersion] | @tsv' \
| while IFS=$'\t' read -r node kubelet runtime; do
ctd=${runtime#containerd://}
case "${kubelet%.*}/${ctd%%.*}" in
v1.36/1|v1.37/1) verdict='NOT LISTED - upgrade the runtime' ;;
*) verdict='check against containerd.io/releases' ;;
esac
printf '%-22s kubelet=%-9s containerd=%-9s %s\n' \
"$node" "$kubelet" "$ctd" "$verdict"
done
# Carry the cgroup driver across explicitly. Kubernetes 1.28 added the ability
# for the kubelet to read it from the CRI runtime instead of its own config file
# - but that arrived as an alpha feature behind the KubeletCgroupDriverFromCRI
# gate, and on the containerd side it needs 2.0 or later. Either way the
# containerd setting has to be right, so do not treat it as automatic:
grep -rn 'SystemdCgroup' /etc/containerd/config.toml
grep -E '^cgroupDriver:' /var/lib/kubelet/config.yaml
# Runtime handlers are the part most often lost, because they are the part
# somebody added by hand. Every handler referenced by a RuntimeClass must still
# exist in the rewritten configuration:
kubectl get runtimeclass -o jsonpath='{range .items[*]}{.handler}{"\n"}{end}' \
| sort -u | while read -r h; do
grep -q "runtimes\.${h}\b" /etc/containerd/config.toml \
&& echo "ok $h" || echo "MISSING $h"
done
# And the sandbox image. It moved from sandbox_image to pinned_images.sandbox,
# and if you had it pointed at an internal mirror, that is a setting to carry
# across rather than a default to accept.
crictl info | jq -r '.config.sandboxImage // .config.containerd.sandboxImage'Настройка, которую в этом переходе теряют чаще всего, — обработчик среды выполнения, потому что это настройка, которую чаще всего добавляли руками. Каждый RuntimeClass в кластере называет обработчик, который должен существовать в переписанной конфигурации; если его нет, падают только нагрузки, которые его запрашивают, то есть отказ ограничен той командой, что использовала gVisor или Kata, и неделю этого никто больше не замечает. Сверьте их поимённо с новым файлом. То же касается драйвера cgroup, и здесь успокоительная версия истории не вполне верна: в Kubernetes 1.28 у kubelet появилась возможность спросить у CRI-среды, какой драйвер она использует, но это альфа-функция за флагом KubeletCgroupDriverFromCRI, а со стороны containerd для неё нужен 2.0 или новее. То есть автоматически ничего не происходит, SystemdCgroup = true всё равно должен пережить переписывание — и теперь он лежит по другому пути.[k8srtc][k8skubeadm]
Откат — и то, что откатить нельзя
Откат заслуживает прямого ответа, а не успокаивающего. Бинарники и файл конфигурации откатываются чисто — и то и другое просто файлы на диске, и если вы сохранили старый архив и старый config.toml, вы в десяти минутах от исходной точки. Это честно больше, чем предлагает большинство миграций.[ctrgh]
# Rolling back is realistic here, which is not true of every migration on this
# site - but only if you kept the two things that matter and only within
# limits. Know which of these applies before you start the window.
# --- what rolls back cleanly ---------------------------------------------
# The binaries and the configuration file. Both are files on disk.
systemctl stop kubelet containerd
tar -C /usr/local -xzf /var/backups/containerd-1.7.28-linux-amd64.tar.gz
cp -a /etc/containerd/config.toml.v2.bak /etc/containerd/config.toml
systemctl daemon-reload && systemctl start containerd kubelet
containerd --version
# --- what does not ---------------------------------------------------------
# 1. The image and snapshot store, in the sense that nobody promises it will.
# containerd's stability document puts file system layout, storage formats
# and snapshot formats explicitly OUTSIDE its guarantees and says the project
# may migrate these formats between minor versions. A downgrade against a
# store that 2.x has already written to is therefore undefined rather than
# documented-as-broken. Restore the tarball instead of finding out:
# systemctl stop containerd
# mv /var/lib/containerd /var/lib/containerd.v2
# tar -C /var/lib -xzf /var/backups/containerd-lib-2026-08-24.tgz
# (Container root filesystems are maintained on upgrade; it is the metadata
# around them that has no promise attached.)
#
# 2. A configuration file you already migrated. A version 4 file needs
# containerd 2.3.0 or newer, and a version 3 file needs 2.0 or newer. This
# is why the config.toml backup matters as much as the binary one, and why
# writing version 3 rather than 4 keeps your options open for a while.
#
# 3. Nothing about the Kubernetes control plane. This is a node-level change:
# do NOT roll the cluster back because one node's runtime misbehaved.
# The honest limit on all of this: rollback buys you a night, not a quarter.
# containerd 1.7 leaves extended support in September 2026, and that extension
# only ever covered Kubernetes 1.30, 1.31 and 1.32 on GKE - all three of which
# are already out of support upstream.Две вещи откатываются не так легко. Первая — каталог состояния, и честная формулировка в том, что никто и не обещал обратного: документ containerd о стабильности прямо выносит структуру файловой системы, форматы хранилища и форматы снапшотов за пределы своих гарантий и говорит, что проект может менять эти форматы между минорными версиями. Поэтому понижение версии поверх /var/lib/containerd, в который 2.x уже писал, — это не «задокументированно сломано», а попросту неопределённое поведение, и это не то различие, которое стоит выяснять в три часа ночи: восстанавливайте архив или соглашайтесь заново тянуть все образы. Вторая — сам файл конфигурации, если вы его уже перевели: файлу версии 4 нужен containerd 2.3.0 или новее, иначе он вообще не будет прочитан. И более общую мысль стоит сказать прямо: откат покупает вам ночь, а не квартал. Ветка 1.7 покидает расширенную поддержку в сентябре 2026 года и до этого не получает исправлений ни для чего за пределами управляемого сервиса одного поставщика. Откат — способ аккуратно закрыть неудавшееся окно обслуживания, а не способ отложить решение.[ctrsec]
Проверять, а не надеяться
Проверка — не вопрос вкуса, и в этом переходе у неё особая форма: почти всё, что идёт не так, оставляет демон работающим. Поэтому проверка «containerd запущен» не доказывает ничего. Скрипт ниже проверяет то, что может быть неправильным молча — версию конфигурации, наличие плагинов в состоянии ошибки, отвечает ли CRI по v1, пережили ли переписывание драйвер cgroup и обработчики среды выполнения, — а затем делает две вещи, которые осмотром установить нельзя вовсе.[ctrcrictl]
#!/usr/bin/env bash
# Post-upgrade verification. Every check prints OK or explains itself; the exit
# code is the number of failures, so this can run straight from your config
# management after the node comes back.
fail=0
chk() { if eval "$2" >/dev/null 2>&1; then printf 'OK %s\n' "$1";
else printf 'FAIL %s\n' "$1"; fail=$((fail+1)); fi; }
chk 'containerd is 2.x' 'containerd --version | grep -qE " v2\."'
chk 'config is version 3+' 'grep -qE "^version = [34]$" /etc/containerd/config.toml'
chk 'no in-memory migration' '! journalctl -u containerd -b | grep -q "Configuration migrated from version"'
chk 'config parses' 'containerd --config /etc/containerd/config.toml config dump'
chk 'no plugin in error' '[ "$(ctr plugins ls | awk "NR>1 && \$4!=\"ok\"" | wc -l)" -eq 0 ]'
chk 'CRI answers on v1' 'crictl version | grep -q "RuntimeApiVersion: v1"'
chk 'runtime handler runc' 'containerd config dump | grep -q "runtimes.runc"'
chk 'systemd cgroup driver' 'containerd config dump | grep -q "SystemdCgroup = true"'
chk 'no legacy shims' "! containerd config dump | grep -qE 'io\.containerd\.runtime\.v1\.linux|io\.containerd\.runc\.v1'"
chk 'mirrors not set' "! containerd config dump | grep -q 'registry.mirrors'"
chk 'sandbox image pinned' 'containerd config dump | grep -q "pinned_images"'
chk 'kubelet is running' 'systemctl is-active --quiet kubelet'
# The two that are worth reading rather than counting. First: registry
# resolution has to be exercised, not inspected - a hosts.toml with the wrong
# directory name looks perfectly fine and simply never matches.
crictl pull registry.k8s.io/pause:3.10.2 >/dev/null 2>&1 \
&& echo 'OK pull through the configured hosts' \
|| { echo 'FAIL pull through the configured hosts'; fail=$((fail+1)); }
# Second: the deprecation list should be shorter than it was before, not
# longer. A new entry here is something the migration introduced.
ctr deprecations list --format json 2>/dev/null | jq -r '.[].id' | sed 's/^/ still deprecated: /'
# And a real workload, because none of the above proves a container starts.
ctr run --rm docker.io/library/alpine:3.22 verify-"$$" /bin/true \
&& echo 'OK container runs' \
|| { echo 'FAIL container runs'; fail=$((fail+1)); }
printf '\n%d failure(s)\n' "$fail"; exit "$fail" Эти две вещи — настоящая загрузка и настоящий контейнер. hosts.toml в каталоге, имя которого не совпадает в точности с пространством имён хоста реестра, выглядит совершенно правильным и просто никогда не срабатывает, и никакое перечитывание файла вам этого не покажет — покажет только загрузка. А конфигурация может быть валидной во всех отношениях и всё равно не запустить контейнер, потому что бинарник среды выполнения не той версии или лежит не по тому пути, которого ждёт shim. Выполните и то и другое на первом узле, прежде чем переходить ко второму.[critools]
В каком порядке всё это делать
В сжатом виде решение меньше самой статьи. Целевая версия одна, и это 2.3 — текущая LTS с поддержкой до апреля 2028 года. Всё остальное в таблице либо уже без поддержки, либо останется без неё через считаные месяцы, либо является веткой расширенной поддержки, скроенной под чужой управляемый сервис. Работа — не замена бинарника, она занимает пятнадцать минут; работа — это переписывание конфигурации, а внутри него — конвертация реестров.[ctrrel]
| Если ваша ситуация… | то цель… | а работа… |
|---|---|---|
| containerd 1.7 на Kubernetes 1.34 или 1.35 | 2.3 LTS, одним шагом | Полное переписывание конфигурации. Переход с LTS на LTS явно поддерживается, и останавливаться на 2.2 незачем |
| containerd 1.6, где угодно | 2.3 LTS, срочно | Без поддержки с августа 2025, и 1.6 → 2.3 — это ни последовательные миноры, ни LTS на LTS: идите через 1.7. Считайте это вопросом безопасности, а не обслуживания |
| containerd 2.1, обновлялись в прошлом году | 2.3 LTS | Конфигурация уже версии 3, так что в основном это замена бинарника, — но 2.1 → 2.3 перепрыгивает 2.2 и лежит вне поддерживаемого пути обновления, так что проверьте, а не предполагайте |
| containerd 2.2, актуальная | 2.3 LTS до ноября 2026 | Минимум, но не тяните — 2.2 заканчивается раньше, чем выйдет следующая LTS |
| Уже Kubernetes 1.36, а containerd 1.x | 2.3 LTS, в это окно | Вы работаете на непроверенной паре. В матрице для 1.36 строки 1.x нет |
| Управляемый сервис (GKE, EKS, AKS) | То, что поставляет провайдер | Читайте его release notes к образам узлов: среда выполнения его, RuntimeClass ваши |
- Инвентаризация до планирования. Версия демона, версия файла конфигурации, версия CRI API и версия kubelet — по каждому узлу. Затем
ctr deprecations list --format jsonпо всему парку, и сохраните вывод: это список того, о чём вы уже знаете, что оно неправильно. - Перепишите конфигурацию вручную, используя конвертер как словарь. Запустите
containerd config migrate, чтобы выучить новые имена ключей, и напишите короткий файл версии 3 сами. Не устанавливайте трёхсотстрочный дамп: он фиксирует все значения по умолчанию, которых вы никогда не выбирали. - Сделайте конвертацию реестров первой и отдельно. Постройте дерево
certs.d, докажите его черезctr images pull --hosts-dirи убедитесь, чтоmirrorsиconfig_pathникогда не встречаются в одном файле. Именно этот шаг вызывает аварии. - Примите решение по изменившимся значениям по умолчанию, а не наследуйте их. Непривилегированные порты, NRI, CDI и изменение seccomp по io_uring относятся к безопасности. Выберите, зафиксируйте письменно и внесите в сборку образа.
- Один узел, потом пул, потом парк. Резервная копия
/var/lib/containerd, обновление, запуск скрипта проверки, снятие cordon. И только затем вносите изменение в образ узла — и проверьте обработчики среды выполнения, от которых зависят ваши RuntimeClass, потому что больше этого не сделает ничто.
Это одно из четырёх изменений, приходящих на одни и те же узлы в один и тот же год, и вместе они дешевле, чем по отдельности: переход с cgroup v1 на cgroup v2, потому что настройка драйвера cgroup должна пережить оба переписывания, а kubelet теперь узнаёт её у среды выполнения; ломающие изменения в Docker Engine 29 — тот же контейнерный фундамент со стороны Docker; и переход с ingress-nginx на Gateway API, если узел всё равно пересобирается ради смены входного слоя. Если вы взвешиваете, сколько из этого вам вообще нужно, когда не стоит использовать Kubernetes — другая сторона того же аргумента.
Частые вопросы
Поддерживается ли ещё containerd 1.7?
Только в очень узком смысле. Таблица релизов containerd показывает 1.7 как LTS до сентября 2026 года, но важна сноска: общая поддержка со стороны коммитеров закончилась в марте 2026 года, а продление обеспечивают два названных по имени мейнтейнера, и оно ориентировано на использование с Kubernetes 1.32, 1.31 и 1.30 через Google Kubernetes Engine, причём изменения могут быть отклонены, если для этого сценария они не нужны. Все три версии Kubernetes уже сняты с поддержки в апстриме. Если вы не держите на GKE неподдерживаемый Kubernetes, считайте 1.7 неподдерживаемой уже сегодня, а не в сентябре.
На какую версию containerd 2.x обновляться?
На 2.3. Это текущая ветка LTS, она началась 30 апреля 2026 года и поддерживается до 30 апреля 2028 года. Альтернативы хуже, каждая по-своему: 2.1 снята с поддержки 3 июля 2026 года, 2.2 поддерживается только до 6 ноября 2026 года, а 2.0 находится на той же вендорской расширенной поддержке, что и 1.7. Если вы сегодня на 2.1 или 2.2, переход на 2.3 короткий, потому что ваша конфигурация уже версии 3.
Нужно ли переписывать config.toml или containerd 2.x прочитает старый файл?
Он прочитает файл версии 2 и будет преобразовывать его в памяти при каждом старте, так что ничто вас не заставляет, — и каждый раз, когда он это делает, он пишет в журнал Configuration migrated from version 2, use `containerd config migrate` to avoid migration, что даёт самый быстрый способ проверить парк. Довод в пользу того, чтобы всё-таки переписать, в том, что путь совместимости — именно то место, где живут известные проблемы, в частности миграция реестров, подставляющая config_path рядом с вашим блоком mirrors и не дающая CRI-плагину загрузиться. Используйте containerd config migrate, чтобы выучить новые имена ключей, и напишите короткий файл руками. Версию выбирайте осознанно: файл версии 3 читают containerd 2.0 и все последующие, файлу версии 4 нужен 2.3.0 или новее, а на 2.3 команда migrate выдаёт именно версию 4.
Что такое версия конфигурации 4 и нужна ли она мне?
Версия 4 появилась в containerd 2.3. К CRI она не имеет отношения: разделение плагина, вокруг которого крутится основная часть этого перехода, — это работа версии 3, появившейся в 2.0. Версия 4 выносит собственные сокеты демона из верхнеуровневых таблиц [grpc], [ttrpc], [metrics] и [debug] в блоки плагинов io.containerd.server.v1.grpc, …v1.ttrpc, …v1.metrics и …v1.debug; у [debug] на верхнем уровне остаются level, format и log_trace_id. Одно изменение поведения легко упустить: до версии 4 незаданный адрес ttrpc выводился из адреса gRPC как <адрес gRPC>.ttrpc и наследовал его uid и gid, а в версии 4 плагин ttrpc берёт вместо этого собственное значение по умолчанию. Версия 4 вам не нужна, и есть причина какое-то время предпочитать версию 3: файл версии 4 не прочитают ни 2.0, ни 2.1, ни 2.2, так что, написав его, вы сужаете себе возможности отката.
Что означает «`mirrors` cannot be set when `config_path` is provided»?
Это значит, что плагин службы образов CRI отказался загружаться, потому что конфигурация реестров в итоге задала одновременно устаревшую таблицу mirrors и более новый config_path. containerd стартует несмотря на это — демон здоров, плагин нет, — и каждый под, назначенный на этот узел, не создаётся. Ловушка в том, что вы могли и не писать оба: проблему сообщили для containerd 2.2.0 с обычным файлом версии 2, где был только блок registry.mirrors, потому что миграция в памяти добавляет рядом config_path по умолчанию. Поэтому поиск по вашему собственному файлу ничего не находит. Исправлено в pull request 12617, который вошёл до 2.3.0 и был бэкпортирован в 2.2, так что актуальная 2.3 и свежие патчи 2.2 не уязвимы, а 2.0 и 2.1 уязвимы. В любом случае долговременное решение — держать ровно одно из двух: построить дерево certs.d, направить туда config_path и удалить блок mirrors.
Что заменило блок зеркал реестра?
Дерево каталогов. Задайте config_path в [plugins.'io.containerd.cri.v1.images'.registry] — по договорённости это /etc/containerd/certs.d — и создайте по подкаталогу на пространство имён хоста реестра, в каждом — hosts.toml. В каждом файле указывается server и одна или несколько записей [host."…"] с явными capabilities, которые перебираются по порядку, так что зеркало без нужного слоя может провалиться на исходный реестр. CA и клиентские сертификаты для конкретного реестра — ключи в том же файле. Имя каталога должно точно совпадать с пространством имён хоста, включая порт: это самая частая причина того, что правильно выглядящая конфигурация никогда не срабатывает.
Куда делся sandbox_image?
Он стал ключом sandbox внутри [plugins.'io.containerd.cri.v1.images'.pinned_images]. Этот пункт стоит проверять руками после любого переписывания, потому что отказ отложенный и зависит от окружения: узел с доступом в интернет спокойно вытянет образ pause с registry.k8s.io, и ничего не покажется странным, тогда как изолированный узел или узел с ограниченным исходящим трафиком не создаст ни одного пода. Если он указывал на внутреннее зеркало, перенесите значение явно.
Почему загрузка образов повела себя иначе, хотя я только поднял параллельность?
Потому что начиная с containerd 2.1 CRI-плагин по умолчанию тянет через Transfer Service, а Transfer Service не читает max_concurrent_downloads из конфигурации образов CRI. Обнаружив настройку, которую Transfer Service не может выполнить, containerd выставляет use_local_image_pull = true для узла, пишет предупреждение и продолжает работу. Полный список триггеров: Registry.Mirrors, Registry.Configs, Registry.Auths, MaxConcurrentDownloads, отличный от 3, DiscardUnpackedLayers, ImagePullWithSyncFs и DisableSnapshotAnnotations = false. Чтобы поднять параллельность, не меняя путь в коде, задайте её в [plugins.'io.containerd.transfer.v1.local'].
Перестанут ли работать мои старые образы?
Только образы Docker schema 1 и только при загрузке — но сверьте номера версий, потому что многое из написанного об этом отстало на релиз. Поддержку отключили по умолчанию в containerd 2.0, где переменная окружения CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1 возвращала её обратно, и удалили в 2.1, где не возвращает уже ничто. Раз целью является 2.3, обходного пути нет: образы придётся пересобрать. Найдите их до обновления: начиная с containerd 1.7.8 и 1.6.25 сконвертированные из schema 1 образы помечаются меткой io.containerd.image/converted-docker-schema1, так что ctr image list с этой меткой находит их во всех пространствах имён. Настоящее решение — пересобрать в schema 2 или OCI, и такие образы обычно достаточно стары, чтобы самой трудной частью оказался поиск Dockerfile.
Можно ли откатиться с containerd 2.x на 1.7?
Бинарники откатываются чисто — сохраните старый архив, и это операция на десять минут. Осложняют дело две вещи. Первая — каталог состояния: документ containerd о стабильности прямо выносит структуру файловой системы, форматы хранилища и форматы снапшотов за пределы своих гарантий и говорит, что проект может менять эти форматы между минорными версиями, поэтому понижение версии поверх /var/lib/containerd, в который 2.x уже писал, не просто рискованно, а не определено вовсе. Восстанавливайте архив, снятый до обновления, либо соглашайтесь заново вытянуть все образы узла. Вторая — файл конфигурации: если вы его перевели, файлу версии 4 нужен 2.3.0 или новее, а файлу версии 3 — 2.0 или новее, так что храните оригинал версии 2. Относитесь к откату как к способу аккуратно закрыть окно обслуживания, а не как к способу отложить переход: 1.7 покидает расширенную поддержку в сентябре 2026 года.
Перестанет ли работать kubelet, если версия containerd не поддерживается?
Нет, и именно поэтому нужен явный аудит. Матрица поддержки Kubernetes и containerd — утверждение о том, какие пары тестируют проекты, а не политика допуска: ничто в kubelet не проверяет версию среды выполнения, событие нигде не появляется, и неподдерживаемая пара запустится и будет выглядеть работоспособной. Теряете вы тестирование: для Kubernetes 1.36 матрица перечисляет только containerd 2.3.0+ и 2.2.0+, без единой записи 1.x, так что узел на 1.7 в кластере 1.36 — комбинация, которую за вас никто не прогонял. Обойдите узлы, прочитайте containerRuntimeVersion и сверьте сами.
Плоскость данных Service на тех же узлах живёт по своим часам: Kubernetes 1.37 объявил режим ipvs в kube-proxy устаревшим и спрятал его за feature gate, 1.40 выключает его по умолчанию, а 1.43 удаляет код. переход kube-proxy с IPVS на nftables — это порог ядра 5.13, поведение NodePort, которое меняется без предупреждения, и оставшийся kube-ipvs0, который поглощает трафик, если его никто не убрал.
Замечание на уровне релиза, потому что баланс 1.37 не такой, каким его описывают: пады действительно могут застрять в ContainerCreating из-за перехода SELinuxMount в GA, тогда как отказ на cgroup v1 появился в 1.35, ограничение статических подов - в 1.34, а обрыв с containerd ещё впереди, в 1.38. что действительно ломается при обновлении до Kubernetes 1.37 разделяет эти три колонки и даёт аудит, который нужно провести до обновления, а не после.
Источники
Сначала первичные источники. Релизная политика containerd и документ о переходе на 2.0 — единственные авторитетные утверждения о том, что удалено и до какого момента ветка получает исправления; руководство по конфигурации CRI — единственный полный список переименованных ключей. Там, где эта статья говорит то, чего не говорит вторичный источник — что 2.1 уже снят с поддержки, что расширенная поддержка 1.7 скроена под версии Kubernetes, которые сами без поддержки, — расхождение с пересказом, а не с проектом.
- containerd - Versioning and release (RELEASES.md): the release-status table quoted throughout this article, including the end-of-life dates for 1.7, 2.0, 2.1, 2.2 and 2.3, the footnotes explaining that 1.7's and 2.0's extended support is scoped to specific Kubernetes versions on GKE, the Kubernetes/containerd support matrix, the daemon-configuration version table (version 3 needs 2.0, version 4 needs 2.3), the deprecation tables with their removal targets, the upgrade-path rules, and the "Not Covered" section that places storage and snapshot formats outside the stability guarantee
- containerd RELEASES.md on GitHub - the same document at its source, which is worth reading directly because the rendered docs site and the repository occasionally disagree with the older containerd-2.0 transition page (config version 1, and the deprecation release of the cri-containerd bundles, are two places where they do)
- containerd source, version/version.go on release/2.3: `const ConfigVersion = 4`. This is what `containerd config migrate` targets, and the reason a migrated file on 2.3 comes back as version 4 rather than version 3
- containerd source, the config migration table and serviceMigrate: the function that moves the top-level [grpc], [ttrpc], [metrics] and debug socket settings into io.containerd.server.v1.* plugins for version 4, including the note that an unset ttrpc address is no longer derived from the grpc address
- containerd 2.0 - what's new, what's breaking, what's changing: the single authoritative list of removals (CRI v1alpha2, the AUFS snapshotter, the runtime v1 shims, LimitNOFILE, the cri-containerd release bundles), the default flips, and the deprecation of the CRI registry properties
- CRI Plugin Config Guide - config versions 1, 2 and 3 side by side, the renamed plugin IDs, the full annotated default configuration, and the table of which image-pull options the Transfer Service does and does not support
- containerd - CRI registry configuration: how the deprecated mirrors, configs and auths properties map onto a certs.d directory tree, which is the conversion this migration actually turns on
- containerd - Registry Configuration (hosts.toml): the host-namespace directory layout under config_path, the capabilities key, and the per-host CA and client-certificate settings
- containerd-config(8): the manual page. Note that it documents only the `default` subcommand - `dump` and `migrate` exist in the binary but not in this page, which is why so few people know about them
- containerd-config.toml(5): the daemon configuration file itself - the version header, the plugins table, imports, and the state and root directories
- containerd source, cmd/containerd/command/config.go: the definition of `containerd config default`, `dump` and `migrate`. `migrate` and `dump` share one implementation, which is why the migrated file comes back fully populated with defaults instead of as a minimal diff
- containerd source, ctr deprecations: the subcommand is `deprecations` (plural), it takes --format json, and it sets CONTAINERD_SUPPRESS_DEPRECATION_WARNINGS while it runs. Some documentation writes it in the singular, which does not exist
- containerd issue 12612 - the CRI plugin fails to load when a version 2 config with registry.mirrors is migrated, because the result carries both config_path and mirrors: "`mirrors` cannot be set when `config_path` is provided". Reported against 2.2.0
- containerd pull request 12617 - the fix for the migration that emitted both config_path and mirrors. Worth checking against the exact patch release you are installing rather than assuming
- containerd - Plugins: the plugin model behind the renamed IDs, and the distinction between built-in, proxy and binary external plugins that matters if you still load Go plugin libraries from plugin_dir
- containerd - Ops: running the daemon, the systemd unit, the socket and state directories, and the configuration import mechanism
- containerd - Getting started: the officially supported installation route now that the cri-containerd bundles are gone, which is containerd, runc and the CNI plugins installed as three separate components
- containerd - Transfer service: the stable API that the CRI plugin uses for image pull by default from 2.1 onwards, and the reason a handful of registry settings now behave differently
- containerd - Snapshotters: overlayfs as the default and the replacement for the removed AUFS snapshotter, plus the blockfile, devmapper and erofs alternatives
- containerd - NRI, the Node Resource Interface: enabled by default from 2.0, which means access to the NRI socket is now part of your node's security surface whether or not you use it
- containerd - user namespaces in CRI: supported from 2.0 and requiring runc v1.2.0 or later, which is one of the reasons the runtime binary needs upgrading alongside the daemon
- containerd - CRI plugin architecture: how the kubelet, the CRI plugin, the snapshotters and the shims fit together, which is the mental model the renamed plugin IDs now reflect
- containerd - crictl: the CRI-level debugging tool, and the right way to confirm that the kubelet's view of the runtime matches yours
- containerd releases on GitHub: the binary tarballs, the checksums and the per-release notes. Also the place to confirm that the cri-containerd-(cni-)VERSION-OS-ARCH.tar.gz bundles really are gone rather than moved
- containerd - Security and audits: the project's security policy and advisory history, which is the argument for not staying on a branch that only accepts patches for someone else's managed service
- containerd pull request 8924 - the discussion behind removing the explicit LimitNOFILE from the reference systemd unit, including why hosts on systemd older than 240 must set it back by hand
- Kubernetes - Releases: the supported branches and their end-of-life dates. This is what turns containerd's extended-support footnotes into a dead end, because the Kubernetes versions they name are already out of support
- Kubernetes - Container runtimes: installing and configuring containerd for a cluster, including the cgroup driver requirement and the sandbox image setting
- Kubernetes - Container Runtime Interface: the API the kubelet speaks, and the reason the removal of CRI v1alpha2 in containerd 2.0 is a compatibility statement rather than an implementation detail
- Kubernetes - Configuring a cgroup driver: the kubelet side of the SystemdCgroup setting that has to be carried across when the containerd configuration is rewritten
- Kubernetes - Runtime Class: the resource that maps a pod onto one of the runtime handlers defined in the containerd configuration, which is the part of the config most likely to be hand-written and therefore most likely to be lost in a migration
- Kubernetes - User namespaces for pods: one of the capabilities that only exists once the node is on containerd 2.x with a recent enough runc
- Kubernetes - Pull an image from a private registry: the ImagePullSecrets mechanism that replaces the deprecated registry.auths block in the containerd configuration
- Kubernetes - Upgrading kubeadm clusters: the drain, upgrade, uncordon sequence this migration slots into, and the reminder that node components are upgraded one node at a time
- runc releases: the OCI runtime that has to be installed separately now that the combined containerd bundles are gone, and whose version gates CRI user namespaces
- CNI plugins releases: the third component of the install, previously bundled in cri-containerd-cni-*.tar.gz and now shipped on its own
- cri-tools: crictl and critest, the CRI-level client used throughout this article to verify that the runtime is answering on v1 and that images and pods survived the upgrade
- Container Device Interface: the specification behind enable_cdi and cdi_spec_dirs, both enabled by default from containerd 2.0
- Google Security Blog - learnings from the kCTF VRP: the exploit history that led to io_uring_enter, io_uring_register and io_uring_setup being dropped from containerd's default seccomp allowlist in 2.0
- OCI/Docker image manifest version 2, schema 2: the format that replaced the Docker schema 1 manifests whose pull support is disabled by default from containerd 2.0
- systemd.exec(5) - LimitNOFILE and the rest of the resource limits a unit inherits, which containers then inherit from containerd. Relevant because the reference unit stopped setting it explicitly
Было полезно?