containerd 1.x se ha quedado sin margen.
containerd 1.7 sale del soporte extendido en septiembre de 2026, y ese soporte solo cubría versiones de Kubernetes que ya están fuera de mantenimiento. Aquí está la reescritura a la versión 3 del fichero, la conversión de registros que tumba clústeres y la única rama a la que conviene ir.
- containerd
- Kubernetes
- Contenedores
- Linux
Hay una versión de esta migración que se lee como una tarea pendiente y otra que se lee como una fecha límite, y en cuál de las dos estás depende de una nota al pie. La tabla de versiones de containerd marca la rama 1.7 como LTS hasta septiembre de 2026, lo que suena a margen de sobra. La nota de debajo aclara que desde marzo de 2026 ese soporte lo prestan dos mantenedores concretos y que está centrado en el uso con Kubernetes 1.32, 1.31 y 1.30 a través de Google Kubernetes Engine, y que los cambios que no hagan falta para ese uso pueden rechazarse. Kubernetes 1.32 llegó a su fin de vida en febrero de 2026. Si no estás ejecutando un Kubernetes sin soporte sobre GKE, ese salvavidas nunca apuntó hacia ti.

Así que esta es una migración con fecha real, y merece algo más que subir un número en un playbook. Lo que viene es todo el asunto: cómo leer la política de versiones de containerd sin dejarse engañar por la palabra LTS, por qué 2.1 es el peor sitio donde aterrizar, la reescritura de la configuración de la versión 2 a la 3 con los identificadores de plugin que se movieron, la conversión de registros que tiene su propio bug y su propia clase de caída, la ruta de descarga de imágenes que cambió sin ruido en 2.1, todo lo que 2.0 eliminó, un runbook por nodo, una explicación honesta de qué recupera y qué no recupera una vuelta atrás, y un script de verificación que devuelve un código distinto de cero cuando algo va mal.
Los síntomas, y por qué ninguno menciona containerd
Ninguno de estos fallos se presenta como un problema de versión del runtime, y por eso se diagnostica tarde. Un nodo vuelve de una actualización y el plugin CRI sencillamente no está: el demonio corre, systemctl status está en verde y todos los pods del nodo se quedan en ContainerCreating. Una imagen que llevaba cuatro años descargándose del mirror interno empieza a descargarse de Docker Hub, y lo primero que lo delata es la factura de salida. Una RuntimeClass que un equipo añadió a mano hace dos años deja de resolver, y solo fallan las cargas que la usan. En todos los casos el runtime está funcionando y el runtime está mal.[ctrrel]
| Lo que ves | Lo que suele significar | Dónde se trata |
|---|---|---|
Todos los pods de un nodo atascados en ContainerCreating, con el demonio sano | El plugin de CRI no cargó. containerd arranca igual y publica el fallo solo en su propio log | Registros |
| Las imágenes empiezan a descargarse del registro público y no del mirror interno | El bloque registry.mirrors no sobrevivió a la reescritura hacia config_path | Registros |
El contenedor pause se descarga de registry.k8s.io en un nodo sin salida a internet | sandbox_image no se trasladó a pinned_images.sandbox | División del plugin |
| Solo fallan las cargas de gVisor o Kata; el resto va bien | Falta en la nueva configuración un manejador de runtime nombrado por una RuntimeClass | Kubernetes |
| Una imagen antigua que se descargaba la semana pasada falla ahora con un error de manifiesto | Descarga de Docker schema 1: desactivada por defecto en containerd 2.0, eliminada del todo en la 2.1 | Eliminaciones |
| Las descargas se comportan distinto tras un cambio que nadie relacionó con descargar | Un ajuste que el Transfer Service no puede respetar devolvió el nodo a la descarga local | Descarga de imágenes |
| El demonio escribe «Configuration migrated from version 2» en el log en cada arranque | El fichero nunca se reescribió. Lo está sosteniendo la capa de compatibilidad, y ahí es donde vive el bug de registros | Configuración |
El hilo común es que containerd 2.x es deliberadamente tolerante con un fichero de configuración de versión 2: lo lee, lo convierte en memoria y arranca. Eso es una amabilidad en el momento de actualizar y un lastre después, porque significa que la migración puede quedarse a medias indefinidamente sin que nada obligue a terminarla. El demonio anota sus quejas y arranca igual; el plugin que no cargó publica un estado de error que nadie te enseña. Todo este artículo es, en cierto sentido, un argumento a favor de terminar la migración en vez de dejar que la cargue la capa de compatibilidad.[ctr20]
Leer bien la tabla de soporte
Empieza por la tabla de versiones, porque es el único documento que decide algo y se lee mal de forma sistemática. containerd mantiene dos tipos de rama. Una versión normal se sostiene ocho meses. Una versión al año se designa LTS y se sostiene al menos dos años. Y por encima de eso, una rama concreta puede recibir soporte extendido de mantenedores con nombre y apellidos una vez cerrada la ventana general, que es una cosa distinta con la misma etiqueta en la misma columna.[ctrrel]
| Rama | Estado | Fin de vida | Qué significa para ti |
|---|---|---|---|
| 1.6 | Fin de vida | 23 de agosto de 2025 | Sin soporte desde hace un año. No va a llegar nada, tampoco parches de seguridad |
| 1.7 | LTS, extendido | septiembre de 2026 | Solo soporte extendido, de dos mantenedores concretos, acotado a Kubernetes 1.30–1.32 en GKE |
| 2.0 | LTS, extendido | marzo de 2027 | Misma forma: soporte extendido acotado a Kubernetes 1.33 en GKE, que a su vez está fuera de soporte |
| 2.1 | Fin de vida | 3 de julio de 2026 | Ya no está. La versión a la que mucha gente actualizó primero, y el peor sitio donde pararse |
| 2.2 | Activa | 6 de noviembre de 2026 | Recibe parches, pero quedan diez semanas. Vale como escala, no como destino |
| 2.3 | LTS | 30 de abril de 2028 | El objetivo. Rama de largo plazo actual, con casi dos años de soporte por delante |
| 2.4 | Futura | tentativamente abril de 2027 | Una versión normal de ocho meses. No sustituye a la LTS |
Ahora superpón la matriz de soporte de Kubernetes, que es donde se encuentran los dos proyectos. containerd publica una lista de versiones recomendadas por cada versión menor de Kubernetes. Para Kubernetes 1.36 esa lista dice 2.3.0+, 2.2.0+, y no hay ninguna entrada 1.x. Nada en el kubelet lo impone: una combinación no soportada arranca, funciona y parece correcta, hasta que deja de serlo y ahí ya estás depurando en solitario. La matriz es una afirmación sobre lo que se ha probado, y esas pruebas son lo único que hay entre tú y un bug del runtime que no ha visto nadie más.[k8srel]
| Kubernetes | Versiones de containerd recomendadas | Fin de vida de Kubernetes | Lectura |
|---|---|---|---|
| 1.33 | 2.1.0+, 2.0.4+, 1.7.24+, 1.6.36+ | 28 de junio de 2026 | Ya sin soporte. Esta es la combinación que nombra la extensión de containerd 2.0 |
| 1.34 | 2.1.3+, 2.0.6+, 1.7.28+, 1.6.39+ | 27 de octubre de 2026 | Quedan dos meses, y dos de las cuatro opciones de containerd —la 2.1 y la 1.6— están a su vez en fin de vida |
| 1.35 | 2.2.0+, 2.1.5+, 1.7.28+ | 28 de febrero de 2027 | La última fila donde aparece siquiera un runtime 1.x |
| 1.36 | 2.3.0+, 2.2.0+ | 28 de junio de 2027 | Sin entrada 1.x. Esta es la línea donde la migración deja de ser opcional |
La trampa dentro de la trampa es containerd 2.1. Era el sitio obvio donde aterrizar para quien actualizó en la segunda mitad de 2025, sigue siendo lo que dice mucha documentación interna, y llegó a su fin de vida el 3 de julio de 2026: antes que 2.2, que llega hasta noviembre de 2026, y mucho antes que 2.3, que es la LTS actual y está soportada hasta abril de 2028. «Pasar a 2.x» no es un plan. Hay exactamente una rama a la que apuntar desde cero, y es la 2.3.
Qué hay instalado realmente en estos nodos
Antes de tocar nada, establece qué hay instalado de verdad, porque en una flota de cualquier tamaño la respuesta no es una sola versión. Importan tres cosas y son independientes: la versión del demonio, la versión del fichero de configuración y la versión de la API de CRI que se le está sirviendo al kubelet. La versión de configuración es la que se olvida, y es la que puede faltar: un fichero sin línea version se trata como un fichero de versión 1. Aquí los propios documentos del proyecto se contradicen entre sí, y conviene saber en qué sentido: la guía de configuración de CRI dice que la versión 1 se eliminó en containerd 2.0, mientras que RELEASES.md dice que la ausencia de versión se interpreta como versión 1 y que todas las versiones anteriores están soportadas mediante migración, y el código fuente sigue trayendo una función de migración de la v1. Trata un fichero de versión 1 como algo que hay que arreglar en cuanto se ve, más que como algo sobre lo que puedas razonar con confianza.[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: v1Después, pregúntale al demonio qué lleva tiempo intentando decirte. Desde 1.6.27 y 1.7.12 containerd expone avisos de obsolescencia por la API de introspección, precisamente para que esta migración se pudiera planificar en vez de descubrir. El subcomando es ctr deprecations list —en plural, y conviene decirlo porque al menos un documento oficial lo escribe en singular y esa forma no existe—. Ejecútalo con --format json por toda la flota. No es un certificado de buena salud, porque los avisos se emiten al usar: un nodo que no ha descargado una imagen schema 1 desde el último reinicio no va a reportar ninguna. Es la lista inicial de lo que ya sabes que está mal.[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 deleteEl fichero de configuración, de la versión 2 a la 3 — y ahora a la 4
El fichero de configuración es la sustancia de la migración, y lo primero que hay que fijar es qué significa «la última versión», porque este año se ha movido. La versión 3 llegó con containerd 2.0 y es la que partió en dos el plugin de CRI. La versión 4 llegó con la 2.3 y hace algo distinto, que es lo que cuenta la sección siguiente. La versión 2 se sigue leyendo y convirtiendo en memoria en cada arranque, y el demonio escribe una línea en el log diciéndolo, que es la forma más barata de averiguar si un nodo está migrado de verdad o simplemente tolerado. El demonio trae un conversor, containerd config migrate, que lee tu fichero actual e imprime la última versión por la salida estándar. No aparece en la página de manual —containerd-config(8) solo documenta default—, y eso explica en buena medida por qué tan poca gente sabe que existe.[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.bakHay dos cosas que conviene saber de ese conversor antes de volcar su salida sobre la configuración en producción. La primera: migrate y dump son el mismo camino de código, así que lo que devuelve es la configuración completamente poblada, con todos los valores por defecto del demonio escritos explícitamente. Un fichero de cuarenta líneas se convierte en trescientas, y cada valor por defecto que no elegiste queda fijado en tu fichero y dejará de seguir a upstream cuando cambie. Usa la salida para aprender los nombres nuevos de las claves y escribe a mano la versión corta. La segunda: valida el fichero candidato antes de reiniciar nada, pero ten en cuenta que --config es un flag global y no del subcomando, así que va antes de config dump, y ponerlo detrás no es una comprobación sino un error de uso. Ejecutado bien, carga el fichero que cargaría el demonio, sigue los imports y falla ruidosamente con algo que no puede parsear, que es mucho mejor sitio para descubrir una errata que un nodo que no vuelve.[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.La versión 4 merece mirada propia, porque casi todo lo que se ha escrito sobre esta migración se queda en la versión 3 y porque trae consigo una restricción para la vuelta atrás. No cambia nada de CRI: saca los sockets propios del demonio de las tablas de primer nivel [grpc], [ttrpc], [metrics] y [debug] y los lleva a plugins io.containerd.server.v1.*. De ahí salen dos consecuencias. La de comportamiento: antes de la versión 4, una dirección ttrpc sin definir se derivaba de la de gRPC como <dirección grpc>.ttrpc y heredaba su uid y su gid, mientras que en la versión 4 el plugin de ttrpc es independiente y recurre a su propio valor por defecto, así que cualquier cosa tuya que se conecte a ese socket por ruta debería fijarlo ahora explícitamente. La operativa es el propio aviso de upstream: migrar un fichero a la última versión limita qué versiones de containerd pueden leerlo. Un fichero de versión 4 necesita la 2.3.0 o posterior; un fichero de versión 3 lo leen la 2.0 y siguientes. Si tu plan contempla volver atrás de binarios esa misma noche, escribe versión 3: sigues quedándote con la división del plugin, que es la parte que importa.[cfgver][srvmig]
Un plugin se partió en dos, y los ajustes se fueron con él
El cambio estructural es que el único plugin de CRI se partió en dos. io.containerd.grpc.v1.cri lo contenía todo; en la versión 3 solo contiene las opciones del servidor de streaming, y la sustancia vive bajo dos identificadores nuevos: io.containerd.cri.v1.runtime para todo lo relativo a ejecutar contenedores —runtimes, CNI, sandboxes, SELinux, gestión de OOM— e io.containerd.cri.v1.images para todo lo relativo a imágenes: el snapshotter, el registro, la imagen de sandbox fijada, la concurrencia de descarga. Es una división mejor que la anterior, y significa que un buscar-y-reemplazar mecánico del identificador te va a dejar aproximadamente la mitad de los ajustes en la tabla equivocada.[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 (versión 2) | containerd 2.x (versión 3) | Nota |
|---|---|---|
version = 2 | version = 3 (2.0) / version = 4 (2.3) | La versión 2 se sigue leyendo y convirtiendo en memoria. Un fichero de versión 4 necesita la 2.3.0 o posterior |
plugins."io.containerd.grpc.v1.cri" | plugins.'io.containerd.cri.v1.runtime' | Todo lo relativo a ejecutar contenedores: runtimes, CNI, SELinux, OOM, sandboxes |
plugins."io.containerd.grpc.v1.cri" | plugins.'io.containerd.cri.v1.images' | Todo lo relativo a imágenes: snapshotter, registro, imágenes fijadas, ajustes de descarga |
plugins."io.containerd.grpc.v1.cri" | plugins.'io.containerd.grpc.v1.cri' | Sigue existiendo, pero solo para las opciones del servidor de streaming |
sandbox_image = "…" | pinned_images.sandbox = '…' | Renombrado y movido. Piérdelo y un nodo sin salida busca registry.k8s.io |
…cri".containerd.snapshotter | …cri.v1.images'.snapshotter | Cambia de lado, del runtime a las imágenes |
…cri".containerd.runtimes.* | …cri.v1.runtime'.containerd.runtimes.* | Solo cambia la ruta. runtime_type = io.containerd.runc.v2 no cambia |
…cri".registry.mirrors | …cri.v1.images'.registry.config_path | Mecanismo distinto. Un directorio de ficheros hosts.toml, no una tabla. Objetivo de eliminación: 2.4 |
…cri".registry.auths | — (imagePullSecrets) | Sin sustituto, a propósito. Las credenciales pasan al clúster. Objetivo de eliminación: 2.4 |
…cri".cni.bin_dir | …cri.v1.runtime'.cni.bin_dirs | En plural, y es una lista. Obsoleto desde 2.1, objetivo de eliminación 2.4 |
plugin_dir (plugin Go .so) | — (plugins proxy o binarios) | Ya eliminado en la 2.1, no solo marcado como obsoleto |
Dos renombrados dentro de esa división causan casi todo el daño. sandbox_image pasó a ser pinned_images.sandbox, de modo que un clúster que apuntaba su imagen de pause a un mirror interno vuelve en silencio a registry.k8s.io, lo cual no da problemas hasta el día en que el nodo no tiene salida a internet. Y snapshotter se movió del lado del runtime al lado de las imágenes, que es lo bastante contraintuitivo como para comprobarlo en vez de suponerlo. Todo lo demás de la tabla siguiente es un cambio de ruta, no de comportamiento.[ctrplug]
Registros: la parte que tumba clústeres
La configuración de registros es donde esta migración pasa de tediosa a arriesgada, y además tiene fecha: mirrors y configs quedaron obsoletos en containerd 1.5 y auths en la 1.3, y los tres tienen como objetivo de eliminación containerd 2.4, la versión siguiente a la que recomienda este artículo. El sustituto de las dos primeras es un árbol de directorios: un subdirectorio por espacio de nombres de registro bajo un único config_path, cada uno con su hosts.toml. Son más ficheros y mucha menos magia, y es genuinamente mejor: los hosts se prueban en orden, las capacidades son explícitas y una CA por registro es una línea en un fichero en lugar de un caso especial. La tercera propiedad, auths, no tiene sustituto en fichero a propósito: las credenciales van en un secret de descarga de imágenes de Kubernetes, no en una configuración de nodo que heredan todas las cargas que corren ahí.[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'El bug conviene enunciarlo con precisión, porque la versión imprecisa te manda a buscar donde no es. No hace falta que hayas escrito nada raro. Arranca containerd 2.2.0 con un fichero de versión 2 corriente que contenga un bloque registry.mirrors y nada más, y la migración en memoria le añade al lado el config_path por defecto: el plugin de CRI rechaza esa combinación de plano con `mirrors` cannot be set when `config_path` is provided. El plugin no carga. El demonio arranca igual. Todos los pods programados en ese nodo fallan al crearse mientras el estado general de todos los servicios de la máquina sigue en verde. Buscar las dos claves en tu propio fichero no encuentra nada, porque una de ellas no la escribiste tú. Se reportó contra la 2.2.0 y se corrigió en la pull request 12617, que entró antes de la 2.3.0 y se retroportó a la rama 2.2, así que en una 2.3 actual o en un parche reciente de la 2.2 no estás expuesto, y en la 2.0 o la 2.1 sí. La instrucción que vale en cualquier caso es terminar la conversión: construye el árbol certs.d, apunta config_path ahí y borra el bloque de mirrors, para que ninguna migración tenga que adivinar.[iss12612][pr12617]
La descarga de imágenes cambió sin avisar
Desde containerd 2.1 el plugin de CRI descarga las imágenes a través del Transfer Service en lugar de hacerlo en proceso. Es un valor por defecto, no una opción que activaras, y por sí solo no tiene nada de particular. Lo que le da derecho a una sección es el mecanismo de repliegue. Si la configuración de imágenes de CRI contiene algo que el Transfer Service no puede respetar, containerd activa use_local_image_pull para todo el nodo y registra un aviso. No falla, no te lo dice en el momento de usarlo y no se lo dice al clúster.[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| Ajuste | Descarga local | Transfer Service (por defecto desde 2.1) |
|---|---|---|
snapshotter | Soportado | Soportado |
ImagePullProgressTimeout | Soportado | Soportado |
PinnedImages | Soportado | Soportado |
Registry.Mirrors / Configs / Auths | Soportado (todos obsoletos) | No soportado: dispara el repliegue a descarga local |
MaxConcurrentDownloads | Se lee de la configuración de imágenes de CRI | Hay que moverlo a plugins.'io.containerd.transfer.v1.local'; cualquier valor distinto de 3 dispara el repliegue |
DiscardUnpackedLayers | Soportado | No soportado: dispara el repliegue |
ImagePullWithSyncFs | Soportado | No soportado: dispara el repliegue |
DisableSnapshotAnnotations | Soportado | Configúralo en el plugin de snapshotter; con false dispara el repliegue |
Lee la lista de disparadores una vez y la implicación se vuelve evidente: subir max_concurrent_downloads de 3 a 6 —algo normal y sensato en un nodo con buen ancho de banda— mueve todas las descargas de ese nodo a otro camino de código. Lo mismo hace conservar el bloque obsoleto mirrors, que es una segunda razón para terminar la conversión de registros en vez de dejarla a medias. Si quieres descarga local, pon use_local_image_pull = true y asúmelo. Si quieres el Transfer Service, mueve el ajuste de concurrencia a [plugins.'io.containerd.transfer.v1.local'], que es donde se lee de verdad.[cricfg]
Lo que se eliminó de verdad
Vamos con las eliminaciones, que es la parte que se ha ido de verdad y no solo se ha renombrado. La lista es corta y todo lo que aparece tiene sustituto documentado, pero lee los números de versión en lugar de los resúmenes: el documento de transición a containerd 2.0 y RELEASES.md se contradicen en dos puntos, y las dos veces el documento de transición es el que cita todo el mundo. El caso importante es la descarga de imágenes Docker schema 1: se desactivó en la 2.0, donde una variable de entorno la devolvía, y se eliminó en la 2.1, donde no la devuelve nada. Como aquí el objetivo es la 2.3, dala por perdida. Y eso importa porque las imágenes que siguen en schema 1 son por definición imágenes que nadie ha reconstruido desde 2017 más o menos, lo que significa que tampoco hay Dockerfile. Búscalas antes de la actualización, no después: desde 1.7.8 y 1.6.25 las imágenes convertidas llevan una etiqueta que permite encontrarlas.[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| Funcionalidad | Obsoleta desde | Eliminada en | Qué usar en su lugar |
|---|---|---|---|
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 integrado | 1.5 | 2.0 | overlayfs |
Etiqueta containerd.io/restart.logpath | 1.5 | 2.0 | containerd.io/restart.loguri |
Paquetes cri-containerd-*.tar.gz | 1.6 | 2.0 | Instalar containerd, runc y los plugins de CNI por separado |
API de CRI v1alpha2 | 1.7 | 2.0 | Solo CRI v1. Comprueba que crictl version reporta RuntimeApiVersion: v1 |
| Implementación heredada de podsandbox en CRI | 2.0 | 2.0 | El controlador de sandbox, que es el predeterminado |
| Descarga de imágenes Docker schema 1 | 1.7 | 2.1 (desactivada en la 2.0) | Reconstruir en schema 2 / OCI. La variable de entorno de escape dejó de funcionar en la 2.1 |
Plugins de runtime como biblioteca Go (*.so) | 2.0 | 2.1 | Plugins externos: proxy o binarios |
LimitNOFILE explícito en la unidad de referencia | — | 2.0 | Usar el valor por defecto de systemd; por debajo de systemd 240, poner 1024:524288 a mano |
io_uring_* en el perfil seccomp por defecto | — | 2.0 | Un perfil seccomp explícito, y una conversación sobre si lo quieres |
Una eliminación es más silenciosa que el resto y merece mención. La unidad de referencia containerd.service ya no fija LimitNOFILE explícitamente. Los rlimits de containerd los heredan los contenedores que arranca, así que esto no es un ajuste solo del demonio: en systemd 240 y posteriores el valor por defecto es razonable y no pasa nada, pero por debajo se aplica el valor por defecto del kernel, 4096, y lo hereda cada contenedor de la máquina. La recomendación de upstream para esos hosts es volver a poner LimitNOFILE=1024:524288 a mano.[pr8924][sdexec]
Valores por defecto que cambiaron sin preguntar
Aparte de las eliminaciones, varios valores por defecto cambiaron. Son los que hacen que un nodo se comporte distinto tras una actualización en la que no tocaste ningún ajuste, y merecen una decisión deliberada en lugar de aceptarse por inercia.[ctr20]
| Valor por defecto | containerd 1.x | containerd 2.x | Por qué importa |
|---|---|---|---|
enable_unprivileged_ports | false | true | Los contenedores escuchan por debajo de 1024 sin CAP_NET_BIND_SERVICE |
enable_unprivileged_icmp | false | true | ping funciona sin CAP_NET_RAW |
enable_cdi | apagado | true | Los ficheros de spec en /etc/cdi y /var/run/cdi describen acceso a dispositivos. El propio interruptor está obsoleto desde la 2.2 y desaparece en la 2.4 |
| NRI | deshabilitado | habilitado | El socket de NRI pasa a formar parte de la superficie de ataque del nodo |
| CRI con sandbox | servidor CRI heredado | controlador de sandbox | Invisible con runc, conviene probarlo con Kata y gVisor |
| Ruta de descarga de imágenes | en proceso | Transfer Service (desde 2.1) | Se repliega a descarga local en silencio con varios ajustes |
Llamadas io_uring_* | permitidas | bloqueadas | Retiradas de la lista blanca de seccomp por defecto tras exploits repetidos de kernel |
| Imagen de sandbox | sandbox_image | pinned_images.sandbox | Mismo valor, clave distinta. Fácil de perder en la reescritura |
- Puertos e ICMP sin privilegios están activados. El plugin de CRI fija ahora
net.ipv4.ip_unprivileged_port_start=0ynet.ipv4.ping_group_range=0 2147483647para los contenedores que no usan ni el espacio de nombres de red del host ni espacios de nombres de usuario. Escuchar por debajo del puerto 1024 ya no requiereCAP_NET_BIND_SERVICE, ypingya no requiereCAP_NET_RAW. Cómodo, y también un cambio en la postura de seguridad de tus contenedores: ponerenable_unprivileged_portsyenable_unprivileged_icmpafalserestaura el comportamiento anterior. - NRI está activado. La Node Resource Interface permite que plugins modifiquen los contenedores según se crean. El acceso se controla mediante el acceso al socket NRI del sistema, lo que significa que ese socket forma ya parte de la superficie de ataque del nodo, uses o no un solo plugin NRI.
- CDI está activado, y el interruptor tiene los días contados. La Container Device Interface viene encendida con
cdi_spec_dirsapuntando por defecto a/etc/cdiy/var/run/cdi, de modo que cualquier cosa capaz de escribir un fichero de spec en esos directorios puede describir acceso a dispositivos para los contenedores. Ojo, porqueenable_cdiquedó obsoleto en containerd 2.2 con objetivo de eliminación en la 2.4, y a partir de ahí CDI estará siempre encendido sin más: si tu plan era apagarlo, ese plan tiene fecha de caducidad. - io_uring ya no está en la lista blanca de seccomp por defecto.
io_uring_enter,io_uring_registereio_uring_setupse retiraron tras una racha suficientemente larga de exploits de kernel como para que el proyecto los considerara inseguros por defecto. Una carga construida sobre io_uring necesitará un perfil explícito, y una conversación sobre si debería tenerlo. - La implementación de CRI con sandbox es la predeterminada. El plugin de CRI usa el controlador de sandbox estable en lugar del servidor CRI heredado. Es invisible en operación normal y muy visible si ejecutas un runtime con sandbox como Kata o gVisor, que es exactamente el caso que conviene probar antes del despliegue en flota.
Ninguno de estos es motivo para no actualizar. Son motivo para actualizar un nodo, mirarlo, y solo entonces escribir el cambio en la construcción de la imagen, que es la diferencia entre una migración y una sorpresa a escala de flota.[ctrnri][cdi]
La actualización del nodo, en orden
La parte mecánica es corta, y hay un cambio en cómo se hace más que en lo que hace. Los paquetes combinados cri-containerd-cni-VERSION-OS-ARCH.tar.gz se eliminaron en 2.0, así que containerd, runc y los plugins de CNI son ahora tres instalaciones separadas con tres decisiones de versión separadas. Es más explícito y algo más de trabajo, y elimina una fuente de confusión de años en la que la gente actualizaba containerd y actualizaba runc a la vez sin darse cuenta. Una tranquilidad sobre el salto en sí: containerd soporta actualizaciones entre versiones menores consecutivas y, por separado, actualizaciones directas entre versiones LTS consecutivas, y de 1.7 (LTS) a 2.3 (LTS) es justo el ejemplo que pone su propio documento de versiones. No te estás saltando ninguna escala en la que se suponía que tenías que parar.[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-01Dos notas sobre el orden que es fácil equivocar con prisa. Haz copia de /var/lib/containerd antes de empezar, no porque la actualización vaya a corromperlo sino porque es el almacén de imágenes y snapshots: perderlo significa volver a descargar todas las imágenes del nodo, lo que en un nodo grande son decenas de minutos y bastante tráfico de salida. Y levanta el runtime antes que el kubelet, y comprueba ctr plugins ls buscando cualquier cosa que no esté en estado ok antes siquiera de arrancar el kubelet. Un plugin de CRI que no cargó es un nodo que acepta pods y no puede crearlos.[runc][cni]
El mismo cambio visto desde Kubernetes
Desde el lado del clúster no hay nada que hacer y sí una cosa que comprobar. La matriz de soporte es un documento del proyecto, no una política de admisión: ningún kubelet se niega a arrancar contra un containerd no soportado y no aparece ningún evento en ninguna parte. Así que la auditoría tiene que ser explícita: recorre los nodos, lee containerRuntimeVersion y compáralo con la matriz para la versión de kubelet de ese mismo nodo. Los pools de nodos mezclados son el caso normal, no la excepción, sobre todo donde las imágenes de nodo avanzan a su propio ritmo.[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'El ajuste que más se pierde en esta migración es el manejador de runtime, porque es el ajuste que más a menudo se añadió a mano. Cada RuntimeClass del clúster nombra un manejador que tiene que existir en la configuración reescrita; si no existe, solo fallan las cargas que lo piden, lo que quiere decir que el fallo queda acotado al equipo que estuviera usando gVisor o Kata y nadie más se entera en una semana. Compruébalos por nombre contra el fichero nuevo. Lo mismo vale para el driver de cgroups, y aquí la versión tranquilizadora de la historia no es del todo cierta: Kubernetes 1.28 añadió la posibilidad de que el kubelet le pregunte al runtime de CRI qué driver usa, pero como función alfa detrás de la puerta KubeletCgroupDriverFromCRI, y el lado de containerd necesita la 2.0 o posterior. Así que no es automático, SystemdCgroup = true sigue teniendo que sobrevivir a la reescritura, y ahora vive en otra ruta.[k8srtc][k8skubeadm]
Volver atrás, y lo que no se puede recuperar
La vuelta atrás merece una respuesta directa y no una tranquilizadora. Los binarios y el fichero de configuración vuelven atrás limpiamente: los dos son ficheros en disco, y si guardaste el tarball antiguo y el config.toml antiguo estás a diez minutos de donde empezaste. Eso es genuinamente más de lo que ofrecen la mayoría de las migraciones.[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.Hay dos cosas que no vuelven atrás con tanta facilidad. La primera es el directorio de estado, y lo honesto es decir que nadie promete que vaya a hacerlo: el documento de estabilidad de containerd deja explícitamente fuera de sus garantías la disposición del sistema de ficheros, los formatos de almacenamiento y los formatos de snapshot, y dice que el proyecto puede migrar esos formatos entre versiones menores. Así que bajar de versión contra un /var/lib/containerd sobre el que 2.x ya ha escrito es terreno indefinido más que documentado como roto, y no es una distinción que merezca la pena poner a prueba a las tres de la mañana: restaura el tarball o asume volver a descargar todas las imágenes. La segunda es el propio fichero de configuración, si ya lo migraste: un fichero de versión 4 necesita containerd 2.3.0 o posterior para siquiera poder leerse. Y el punto de fondo conviene decirlo sin rodeos: la vuelta atrás te compra una noche, no un trimestre. La rama 1.7 sale del soporte extendido en septiembre de 2026 y mientras tanto no recibe parches para nada que quede fuera del servicio gestionado de un proveedor concreto. Una vuelta atrás es una forma de terminar bien una ventana de mantenimiento que ha ido mal, no una forma de aplazar la decisión.[ctrsec]
Verificar, en lugar de confiar
La verificación no es cuestión de gusto, y en esta migración tiene una forma concreta: casi todo lo que sale mal deja el demonio en marcha. Así que comprobar que containerd está arriba no demuestra nada. El script de abajo comprueba las cosas que pueden estar mal en silencio —la versión de configuración, si algún plugin está en estado de error, si CRI responde en v1, si el driver de cgroups y los manejadores de runtime sobrevivieron— y luego hace las dos cosas que no se pueden establecer inspeccionando.[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" Esas dos son una descarga real y un contenedor real. Un hosts.toml en un directorio cuyo nombre no coincide exactamente con el espacio de nombres del registro parece completamente correcto y sencillamente no hace match nunca, y por mucho que leas el fichero no vas a verlo: solo lo ve una descarga. Y una configuración puede ser válida en todos sus aspectos y aun así no arrancar un contenedor, porque el binario del runtime es de la versión equivocada o no está en la ruta que espera el shim. Ejecuta las dos, en el primer nodo, antes de pasar al segundo.[critools]
El orden en el que hacer esto
Comprimida, la decisión es más pequeña que el artículo. Hay una versión objetivo y es la 2.3: la LTS actual, soportada hasta abril de 2028. Todo lo demás en la tabla está ya sin soporte, se queda sin soporte en meses, o es una rama de soporte extendido acotada al servicio gestionado de otro. El trabajo no es el cambio de binario, que son quince minutos; el trabajo es la reescritura de la configuración y, dentro de ella, la conversión de registros.[ctrrel]
| Si tu situación es… | El objetivo es… | Y el trabajo es… |
|---|---|---|
| containerd 1.7 sobre Kubernetes 1.34 o 1.35 | 2.3 LTS, de un salto | La reescritura completa de la configuración. De LTS a LTS es un salto soportado explícitamente, y no hay razón para pararse en la 2.2 |
| containerd 1.6, en cualquier sitio | 2.3 LTS, con urgencia | Sin soporte desde agosto de 2025, y de 1.6 a 2.3 no es ni consecutivo ni de LTS a LTS: pasa por la 1.7. Trátalo como asunto de seguridad, no de mantenimiento |
| containerd 2.1, actualizado el año pasado | 2.3 LTS | La configuración ya es versión 3, así que es sobre todo un cambio de binario, pero de 2.1 a 2.3 se salta la 2.2 y queda fuera de la ruta de actualización soportada: pruébalo en vez de darlo por hecho |
| containerd 2.2, al día | 2.3 LTS antes de noviembre de 2026 | Mínimo, pero no lo dejes correr: la 2.2 termina antes de que llegue la siguiente LTS |
| Kubernetes 1.36 ya, con containerd 1.x | 2.3 LTS, en esta ventana | Estás en una combinación no probada. La matriz no tiene fila 1.x para la 1.36 |
| Un servicio gestionado (GKE, EKS, AKS) | Lo que traiga el proveedor | Lee las notas de sus imágenes de nodo: el runtime es suyo, las RuntimeClasses son tuyas |
- Inventario antes de planificar. Versión del demonio, versión del fichero de configuración, versión de la API de CRI y versión del kubelet, por nodo. Después
ctr deprecations list --format jsonpor toda la flota, y guarda la salida: es la lista de lo que ya sabes que está mal. - Reescribe la configuración a mano, usando el conversor como diccionario. Ejecuta
containerd config migratepara aprender los nombres nuevos y escribe tú un fichero de versión 3 corto. No instales el volcado de trescientas líneas: fija todos los valores por defecto que nunca elegiste. - Haz la conversión de registros primero y por separado. Construye el árbol
certs.d, demuéstralo conctr images pull --hosts-diry asegúrate de quemirrorsyconfig_pathno aparecen nunca en el mismo fichero. Este es el paso que provoca caídas. - Decide sobre los valores por defecto que cambiaron en lugar de heredarlos. Puertos sin privilegios, NRI, CDI y el cambio de seccomp con io_uring son relevantes para la seguridad. Elige, déjalo escrito y mételo en la construcción de la imagen.
- Un nodo, luego un pool, luego la flota. Copia de
/var/lib/containerd, actualiza, ejecuta el script de verificación y haz uncordon del nodo para volver a marcarlo como programable. Solo entonces escribe el cambio en la imagen de nodo, y comprueba los manejadores de runtime de los que dependen tus RuntimeClasses, porque no lo va a hacer nada más.
Este es uno de cuatro cambios que aterrizan en los mismos nodos el mismo año, y juntos salen más baratos que por separado: la migración de cgroup v1 a cgroup v2, porque el ajuste del driver de cgroups tiene que sobrevivir a las dos reescrituras y el kubelet ahora lo lee del runtime; los cambios que rompen en Docker Engine 29, que es la misma pila de contenedores vista desde el lado de Docker; y pasar de ingress-nginx a la Gateway API, si el nodo se está reconstruyendo también por el cambio de ingress. Si estás sopesando cuánto de todo esto necesitas, cuándo no usar Kubernetes es la otra cara del argumento.
Preguntas frecuentes
¿Sigue teniendo soporte containerd 1.7?
Solo en un sentido muy estrecho. La tabla de versiones de containerd lista la 1.7 como LTS hasta septiembre de 2026, pero lo importante es la nota al pie: el soporte general de los committers terminó en marzo de 2026 y la extensión la prestan dos mantenedores concretos, centrada en el uso con Kubernetes 1.32, 1.31 y 1.30 a través de Google Kubernetes Engine, con cambios que pueden rechazarse si no hacen falta para ese uso. Las tres versiones de Kubernetes están ya en fin de vida upstream. Si no estás en GKE con un Kubernetes sin soporte, trata la 1.7 como no soportada hoy, no en septiembre.
¿A qué versión de containerd 2.x debería actualizar?
A la 2.3. Es la rama LTS actual, empezó el 30 de abril de 2026 y está soportada hasta el 30 de abril de 2028. Las alternativas son peores de formas concretas: la 2.1 llegó a fin de vida el 3 de julio de 2026, la 2.2 solo está soportada hasta el 6 de noviembre de 2026, y la 2.0 está en el mismo tipo de soporte extendido acotado a un proveedor que la 1.7. Si hoy estás en 2.1 o 2.2, el salto a 2.3 es pequeño porque tu configuración ya es versión 3.
¿Tengo que reescribir config.toml o containerd 2.x leerá mi fichero antiguo?
Leerá un fichero de versión 2 y lo convertirá en memoria en cada arranque, así que nada te obliga, y cada vez que lo hace escribe en el log Configuration migrated from version 2, use `containerd config migrate` to avoid migration, que es la forma más rápida de auditar una flota. El argumento para reescribir de todos modos es que la ruta de compatibilidad es donde viven los problemas conocidos, en particular la migración de registros que inyecta config_path junto a tu bloque mirrors e impide que cargue el plugin de CRI. Usa containerd config migrate para aprender los nombres nuevos de las claves y escribe a mano un fichero corto. Elige la versión deliberadamente: la versión 3 la leen containerd 2.0 y posteriores, la versión 4 necesita la 2.3.0 o posterior, y la versión 4 es lo que emite migrate en la 2.3.
¿Qué es la versión 4 de la configuración y la necesito?
La versión 4 llegó con containerd 2.3. No cambia nada de CRI: la división del plugin de la que trata casi toda esta migración es cosa de la versión 3, introducida en la 2.0. Lo que hace la versión 4 es sacar los sockets propios del demonio de las tablas de primer nivel [grpc], [ttrpc], [metrics] y [debug] y llevarlos a bloques de plugin io.containerd.server.v1.grpc, …v1.ttrpc, …v1.metrics y …v1.debug; [debug] conserva level, format y log_trace_id en el primer nivel. Hay un cambio de comportamiento fácil de pasar por alto: antes de la versión 4, una dirección ttrpc sin definir se derivaba de la de gRPC como <dirección grpc>.ttrpc y heredaba su uid y su gid, y en la versión 4 el plugin de ttrpc usa su propio valor por defecto. No necesitas la versión 4, y hay una razón para preferir la 3 durante un tiempo: un fichero de versión 4 no lo pueden leer ni la 2.0, ni la 2.1, ni la 2.2, así que escribir uno estrecha tus opciones de vuelta atrás.
¿Qué significa «`mirrors` cannot be set when `config_path` is provided»?
Significa que el plugin del servicio de imágenes de CRI se negó a cargar porque la configuración de registros acabó especificando a la vez la tabla obsoleta mirrors y el más moderno config_path. containerd arranca igualmente —el demonio está sano, el plugin no— y todos los pods programados en ese nodo fallan al crearse. La trampa es que puede que tú no hayas escrito las dos: se reportó contra containerd 2.2.0 con un fichero de versión 2 corriente que solo contenía un bloque registry.mirrors, porque la migración en memoria le añade al lado el config_path por defecto. Por eso buscar en tu propio fichero no encuentra nada. Se corrigió en la pull request 12617, que entró antes de la 2.3.0 y se retroportó a la 2.2, así que una 2.3 actual y los parches recientes de la 2.2 no están expuestos, y la 2.0 y la 2.1 sí. En cualquier caso, el arreglo duradero es quedarse exactamente con una de las dos: construir un árbol certs.d, apuntar config_path ahí y borrar el bloque de mirrors.
¿Qué sustituye al bloque de mirrors del registro?
Un árbol de directorios. Define config_path bajo [plugins.'io.containerd.cri.v1.images'.registry] —por convención /etc/containerd/certs.d— y crea un subdirectorio por espacio de nombres de registro, cada uno con su hosts.toml. Cada fichero nombra un server y una o más entradas [host."…"] con capabilities explícitas, que se prueban en orden, de modo que un mirror que no tenga una capa puede caer al registro original. Las CA y los certificados de cliente por registro son claves del mismo fichero. El nombre del directorio tiene que coincidir exactamente con el espacio de nombres, incluido el puerto: esa es la causa más común de que una configuración que parece correcta no haga match nunca.
¿Dónde se fue sandbox_image?
Pasó a ser sandbox dentro de [plugins.'io.containerd.cri.v1.images'.pinned_images]. Este conviene comprobarlo a mano después de cualquier reescritura, porque el fallo es diferido y depende del entorno: un nodo con acceso a internet descargará tan campante la imagen de pause de registry.k8s.io y nada parecerá raro, mientras que un nodo aislado o con salida restringida no logrará crear ningún pod. Si lo tenías apuntando a un mirror interno, traslada el valor explícitamente.
¿Por qué cambió el comportamiento de las descargas si solo subí la concurrencia?
Porque desde containerd 2.1 el plugin de CRI descarga a través del Transfer Service por defecto, y el Transfer Service no lee max_concurrent_downloads de la configuración de imágenes de CRI. Cuando containerd encuentra un ajuste que el Transfer Service no puede respetar, activa use_local_image_pull = true para el nodo, registra un aviso y sigue. La lista completa de disparadores es Registry.Mirrors, Registry.Configs, Registry.Auths, un MaxConcurrentDownloads distinto de 3, DiscardUnpackedLayers, ImagePullWithSyncFs y DisableSnapshotAnnotations = false. Para subir la concurrencia sin cambiar el camino de código, ponla bajo [plugins.'io.containerd.transfer.v1.local'].
¿Van a dejar de funcionar mis imágenes antiguas?
Solo las de Docker schema 1, y solo para descargarlas, pero comprueba los números de versión, porque mucho de lo que se ha escrito sobre esto va una versión por detrás. El soporte se desactivó por defecto en containerd 2.0, donde la variable de entorno CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1 lo devolvía, y se eliminó en la 2.1, donde no lo devuelve nada. Como el objetivo es la 2.3, no hay escapatoria: hay que reconstruir las imágenes. Búscalas antes de actualizar: desde containerd 1.7.8 y 1.6.25 las imágenes convertidas desde schema 1 llevan la etiqueta io.containerd.image/converted-docker-schema1, así que ctr image list con esa etiqueta las encuentra en todos los namespaces. La solución de verdad es reconstruirlas en schema 2 u OCI, y esas imágenes suelen ser lo bastante antiguas como para que lo difícil sea encontrar el Dockerfile.
¿Puedo volver de containerd 2.x a 1.7?
Los binarios vuelven atrás limpiamente: guarda el tarball antiguo y es una operación de diez minutos. Hay dos cosas que lo complican. Primero, el directorio de estado: el documento de estabilidad de containerd deja explícitamente fuera de sus garantías la disposición del sistema de ficheros, los formatos de almacenamiento y los formatos de snapshot, y dice que el proyecto puede migrar esos formatos entre versiones menores, así que bajar de versión contra un /var/lib/containerd sobre el que 2.x ya ha escrito es terreno indefinido más que simplemente arriesgado. O restauras el tarball que hiciste antes de actualizar, o asumes volver a descargar todas las imágenes del nodo. Segundo, el fichero de configuración: si lo migraste, un fichero de versión 4 necesita la 2.3.0 o posterior y uno de versión 3 necesita la 2.0 o posterior, así que guarda el original de versión 2. Trata la vuelta atrás como una forma de terminar bien una ventana de mantenimiento, no como una forma de aplazar la migración: la 1.7 sale del soporte extendido en septiembre de 2026.
¿Deja de funcionar el kubelet si containerd está en una versión no soportada?
No, y precisamente por eso hace falta una auditoría explícita. La matriz de soporte de Kubernetes y containerd es una afirmación sobre qué combinaciones prueban los proyectos, no una política de admisión: nada en el kubelet comprueba la versión del runtime, no se emite ningún evento, y una combinación no soportada arranca y parece funcionar. Lo que pierdes son las pruebas: para Kubernetes 1.36 la matriz lista solo containerd 2.3.0+ y 2.2.0+, sin ninguna entrada 1.x, así que un nodo con 1.7 en un clúster 1.36 es una combinación que nadie ha ejercitado por ti. Recorre los nodos, lee containerRuntimeVersion y compáralo tú.
El plano de datos de los Services en esos mismos nodos va a su propio ritmo: Kubernetes 1.37 marcó como obsoleto el modo ipvs de kube-proxy detrás de un feature gate, 1.40 lo desactiva por defecto y 1.43 borra el código. la migración de kube-proxy de IPVS a nftables explica el mínimo de kernel 5.13, el comportamiento de los NodePort que cambia sin avisar y el kube-ipvs0 que queda atrás y se traga el tráfico si nadie lo limpia.
Una nota a nivel de versión, porque el balance de la 1.37 no es el que cuenta casi todo el mundo: el cambio que de verdad puede dejar pods en ContainerCreating es SELinuxMount al llegar a GA, mientras que el fallo de cgroup v1 aterrizó en la 1.35, la restricción de los pods estáticos en la 1.34 y el precipicio de containerd sigue por delante, en la 1.38. qué se rompe de verdad al actualizar a Kubernetes 1.37 separa las tres columnas y da la auditoría que hay que hacer antes de actualizar, no después.
Fuentes
Fuentes primarias primero. La política de versiones de containerd y su documento de transición a 2.0 son las únicas declaraciones autorizadas sobre qué se eliminó y hasta cuándo se parchea cada rama; la guía de configuración de CRI es la única lista completa de las claves renombradas. Donde este artículo dice algo que una fuente secundaria no dice —que 2.1 ya está fuera de mantenimiento, que el soporte extendido de 1.7 está acotado a versiones de Kubernetes que a su vez no tienen soporte— el desacuerdo es con el resumen, no con el proyecto.
- 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
¿Te ha resultado útil?