Aller au contenu
← Blog

containerd 1.x n’a plus de marge.

Le support étendu de containerd 1.7 s’arrête en septembre 2026, et il ne couvrait que des versions de Kubernetes déjà hors support. Voici la réécriture vers la version 3 du fichier de configuration, la conversion des registres qui met des clusters à terre, et la seule branche vers laquelle il vaut la peine d’aller.

·26 min de lecture
  • containerd
  • Kubernetes
  • Conteneurs
  • Linux

Il y a une version de cette migration qui se lit comme une corvée et une autre qui se lit comme une échéance, et celle dans laquelle vous êtes tient à une note de bas de page. Le tableau des versions de containerd affiche la branche 1.7 comme LTS jusqu’en septembre 2026, ce qui a l’air confortable. La note en dessous précise que depuis mars 2026 ce support est assuré par deux mainteneurs nommément désignés et qu’il est orienté vers l’usage avec Kubernetes 1.32, 1.31 et 1.30 via Google Kubernetes Engine, les changements inutiles à cet usage pouvant être refusés. Kubernetes 1.32 est arrivé en fin de vie en février 2026. Si vous ne faites pas tourner un Kubernetes hors support sur GKE, cette bouée n’a jamais été lancée dans votre direction.

Image de couverture en trois panneaux. Le panneau de gauche, intitulé « containerd 1.x », montre un unique bloc de configuration nommé io.containerd.grpc.v1.cri contenant les runtimes, le snapshotter, sandbox_image et une table de miroirs de registre, avec un tampon rouge de fin de vie indiquant « 1.7 — le support étendu s’arrête en septembre 2026 ». La colonne centrale, intitulée « ce qui bouge », énumère cinq conversions : de la version 2 à la version 3, io.containerd.grpc.v1.cri scindé en cri.v1.runtime et cri.v1.images, sandbox_image devenu pinned_images.sandbox, registry.mirrors devenu un répertoire certs.d avec config_path, et les archives cri-containerd remplacées par trois installations séparées. Le panneau de droite, intitulé « containerd 2.3 LTS », montre deux blocs de configuration côte à côte, un pour le runtime et un pour les images, au-dessus d’une barre de support allant jusqu’en avril 2028.
Ce qui bouge vraiment : une version de configuration, un plugin scindé en deux, un réglage renommé, un bloc de registres remplacé par une arborescence de répertoires, et un téléchargement groupé remplacé par trois séparés.

C’est donc une migration avec une vraie date, et elle mérite mieux qu’un numéro de version incrémenté dans un playbook. Ce qui suit couvre l’ensemble : comment lire la politique de versions de containerd sans se laisser abuser par le mot LTS, pourquoi 2.1 est le pire endroit où atterrir, la réécriture de la configuration de la version 2 vers la version 3 avec les identifiants de plugin qui ont bougé, la conversion des registres avec son bug propre et sa classe de panne propre, le chemin de téléchargement des images qui a changé discrètement en 2.1, tout ce que 2.0 a supprimé, un runbook par nœud, un exposé honnête de ce qu’un retour arrière récupère et de ce qu’il ne récupère pas, et un script de vérification dont le code de sortie est non nul quand quelque chose ne va pas.

Les symptômes, et pourquoi aucun ne dit containerd

Aucun de ces symptômes ne s’annonce comme un problème de version du runtime, et c’est pour cela qu’il est diagnostiqué tard. Un nœud revient après une mise à jour et le plugin CRI n’est tout simplement pas là : le démon tourne, systemctl status est au vert, et tous les pods du nœud restent en ContainerCreating. Une image que le miroir interne servait depuis quatre ans se télécharge soudain depuis Docker Hub, et le premier signe est la facture de trafic sortant. Une RuntimeClass ajoutée à la main par une équipe il y a deux ans ne résout plus, et seules les charges qui l’utilisent échouent. Dans tous les cas le runtime tourne, et le runtime est faux.[ctrrel]

Ce que vous voyezCe que cela signifie en généralOù c’est traité
Tous les pods d’un nœud bloqués en ContainerCreating, démon en bonne santéLe plugin CRI n’a pas chargé. containerd démarre quand même et ne signale l’échec que dans son propre journalRegistres
Les images se téléchargent soudain depuis le registre public et non depuis le miroir interneLe bloc registry.mirrors n’a pas survécu à la réécriture vers config_pathRegistres
Le conteneur pause se télécharge depuis registry.k8s.io sur un nœud sans accès Internetsandbox_image n’a pas été reporté vers pinned_images.sandboxScission du plugin
Seules les charges gVisor ou Kata échouent, tout le reste va bienUn gestionnaire de runtime nommé par une RuntimeClass manque dans la nouvelle configurationKubernetes
Une vieille image qui se téléchargeait encore la semaine dernière échoue maintenant sur une erreur de manifesteTéléchargement des images Docker schema 1 : désactivé par défaut en containerd 2.0, purement supprimé en 2.1Suppressions
Les téléchargements se comportent autrement après un changement que personne n’a relié aux imagesUn réglage que le Transfer Service ne peut honorer a fait repasser le nœud en téléchargement localTéléchargement
Le démon consigne « Configuration migrated from version 2 » à chaque démarrageLe fichier n’a jamais été réécrit. C’est la couche de compatibilité qui le porte, et c’est là que vit le bug des registresConfiguration

Le fil commun est que containerd 2.x est délibérément tolérant avec un fichier de configuration en version 2 : il le lit, le convertit en mémoire et démarre. C’est une amabilité au moment de la mise à jour et un fardeau ensuite, parce que cela veut dire que la migration peut rester à moitié faite indéfiniment sans que rien ne force la conclusion. Le démon consigne ses objections et démarre quand même ; le plugin qui n’a pas chargé publie un état d’erreur que personne ne vous montre. Tout cet article est, en un sens, un plaidoyer pour terminer la migration au lieu de laisser la couche de compatibilité la porter.[ctr20]

Lire correctement le tableau de support

Commencez par le tableau des versions, parce que c’est le seul document qui tranche quoi que ce soit et qu’il est systématiquement mal lu. containerd maintient deux types de branches. Une version ordinaire est supportée huit mois. Une version par an est désignée LTS et supportée au moins deux ans. Par-dessus cela, une branche donnée peut recevoir un support étendu assuré par des mainteneurs nommés une fois la fenêtre générale close — et c’est autre chose portant la même étiquette dans la même colonne.[ctrrel]

BrancheÉtatFin de vieCe que cela signifie pour vous
1.6Fin de vie23 août 2025Sans support depuis un an. Rien ne viendra plus, correctifs de sécurité compris
1.7LTS, étenduseptembre 2026Support étendu uniquement, par deux mainteneurs nommés, ciblé sur Kubernetes 1.30–1.32 sur GKE
2.0LTS, étendumars 2027Même forme : support étendu ciblé sur Kubernetes 1.33 sur GKE, qui est lui-même en fin de vie
2.1Fin de vie3 juillet 2026Déjà terminé. La version vers laquelle beaucoup ont migré en premier, et le pire endroit où s’arrêter
2.2Active6 novembre 2026Corrigée, mais dix semaines de marge. Correcte comme étape, fausse comme destination
2.3LTS30 avril 2028La cible. Branche long terme actuelle, avec près de deux ans de support devant elle
2.4À veniravril 2027, provisoirementUne version ordinaire de huit mois. Pas un substitut à la LTS

Superposez maintenant la matrice de support Kubernetes, là où les deux projets se rencontrent. containerd publie une liste de versions recommandées par version mineure de Kubernetes. Pour Kubernetes 1.36 cette liste dit 2.3.0+, 2.2.0+ — et il n’y a aucune entrée 1.x. Rien dans le kubelet ne l’impose : une combinaison non supportée démarre, tourne et paraît correcte, jusqu’au moment où elle ne l’est plus, et là vous déboguez seul. La matrice est une affirmation sur ce qui a été testé, et ces tests sont la seule chose entre vous et un bug du runtime que personne d’autre n’a rencontré.[k8srel]

KubernetesVersions de containerd listées comme recommandéesFin de vie de KubernetesLecture
1.332.1.0+, 2.0.4+, 1.7.24+, 1.6.36+28 juin 2026Déjà hors support. C’est exactement l’appariement que nomme l’extension de containerd 2.0
1.342.1.3+, 2.0.6+, 1.7.28+, 1.6.39+27 octobre 2026Deux mois restants, et deux des quatre options containerd — 2.1 et 1.6 — sont elles-mêmes en fin de vie
1.352.2.0+, 2.1.5+, 1.7.28+28 février 2027La dernière ligne où un runtime 1.x apparaît encore
1.362.3.0+, 2.2.0+28 juin 2027Aucune entrée 1.x. C’est la ligne où la migration cesse d’être facultative

Le piège dans le piège s’appelle containerd 2.1. C’était l’endroit évident où atterrir pour qui a mis à jour au second semestre 2025, c’est encore ce que dit beaucoup de documentation interne, et cette branche est arrivée en fin de vie le 3 juillet 2026 : avant 2.2, qui court jusqu’en novembre 2026, et bien avant 2.3, la LTS actuelle, supportée jusqu’en avril 2028. « Passer à 2.x » n’est pas un plan. Depuis un point de départ neuf, il existe exactement une branche à viser, et c’est 2.3.

Ce qui est réellement installé sur ces nœuds

Avant de toucher à quoi que ce soit, établissez ce qui est réellement installé, car sur un parc de taille quelconque la réponse n’est pas une version unique. Trois choses comptent et elles sont indépendantes : la version du démon, la version du fichier de configuration et la version de l’API CRI réellement servie au kubelet. La version de configuration est celle qu’on oublie, et c’est celle qui peut manquer : un fichier sans ligne version est traité comme un fichier en version 1. Sur ce point les documents du projet se contredisent, et il vaut la peine de savoir dans quel sens : le guide de configuration de CRI affirme que la version 1 a été supprimée dans containerd 2.0, tandis que RELEASES.md indique qu’une version absente est interprétée comme la version 1 et que toutes les versions antérieures restent prises en charge par migration — et le code source embarque toujours une fonction de migration depuis la v1. Traitez donc un fichier en version 1 comme quelque chose à corriger dès que vous le voyez, plutôt que comme quelque chose sur quoi raisonner avec assurance.[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

Demandez ensuite au démon ce qu’il essaie de vous dire depuis longtemps. Depuis 1.6.27 et 1.7.12, containerd expose des avertissements d’obsolescence via l’API d’introspection, précisément pour que cette migration puisse être planifiée plutôt que découverte. La sous-commande est ctr deprecations list — au pluriel, ce qui mérite d’être dit parce qu’au moins un document officiel l’écrit au singulier et que cette forme n’existe pas. Lancez-la avec --format json sur tout le parc. Ce n’est pas un certificat de bonne santé, car les avertissements sont émis à l’usage : un nœud qui n’a pas téléchargé d’image schema 1 depuis son dernier redémarrage n’en signalera aucune. C’est la liste de départ de ce que vous savez déjà être faux.[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

Le fichier de configuration, de la version 2 à la version 3 — et maintenant 4

Le fichier de configuration est le cœur de la migration, et la première chose à établir est ce que veut dire « la dernière version », parce qu’elle a bougé cette année. La version 3 est apparue avec containerd 2.0 : c’est elle qui a scindé le plugin CRI en deux. La version 4 est arrivée avec 2.3 et fait tout autre chose, sur quoi revient la section suivante. La version 2, elle, est toujours lue et convertie en mémoire à chaque démarrage — et le démon consigne une ligne qui le dit, ce qui reste la façon la moins coûteuse de savoir si un nœud a réellement été migré ou s’il est seulement toléré. Le démon embarque un convertisseur, containerd config migrate, qui lit votre fichier actuel et écrit la dernière version sur la sortie standard. Il ne figure pas dans la page de manuel — containerd-config(8) ne documente que default —, ce qui explique en grande partie pourquoi si peu de gens savent qu’il 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.bak

Deux choses sur ce convertisseur méritent d’être connues avant de rediriger sa sortie sur votre configuration en production. D’abord, migrate et dump sont le même chemin de code, donc ce qui revient est la configuration entièrement renseignée : chaque valeur par défaut du démon écrite explicitement. Un fichier de quarante lignes en fait trois cents, et chaque valeur par défaut que vous n’avez pas choisie est désormais figée dans votre fichier et cessera de suivre l’amont quand elle changera. Servez-vous de la sortie pour apprendre les nouveaux noms de clés, puis écrivez la version courte à la main. Ensuite, validez le fichier candidat avant tout redémarrage — mais notez que --config est une option globale et non une option de sous-commande : elle se place donc avant config dump, et la mettre après n’est pas une vérification mais une erreur d’usage. Lancée correctement, la commande charge le fichier que le démon chargerait, suit les imports et échoue bruyamment sur ce qu’elle ne sait pas analyser, ce qui est un bien meilleur endroit pour découvrir une faute de frappe qu’un nœud qui ne revient pas.[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 version 4 mérite qu’on s’y arrête, parce que presque tout ce qui s’écrit sur cette migration s’arrête à la version 3 et parce qu’elle emporte une contrainte de retour arrière. Elle ne change rien à CRI : elle sort les sockets propres au démon des tables de premier niveau [grpc], [ttrpc], [metrics] et [debug] pour les confier à des plugins io.containerd.server.v1.*. Deux conséquences en découlent. La première tient au comportement : avant la version 4, une adresse ttrpc non définie était dérivée de l’adresse gRPC sous la forme <adresse grpc>.ttrpc et en héritait les uid et gid, alors qu’en version 4 le plugin ttrpc est indépendant et retombe sur sa propre valeur par défaut — tout ce qui, chez vous, se connecte à ce socket par son chemin doit donc désormais le définir explicitement. La seconde est opérationnelle, et c’est l’avertissement de l’amont lui-même : migrer un fichier vers la dernière version restreint l’ensemble des versions de containerd capables de le lire. Un fichier en version 4 exige 2.3.0 ou plus récent ; un fichier en version 3 est lu par 2.0 et au-delà. Si un retour arrière des binaires dans la même nuit fait partie de votre plan, écrivez la version 3 — vous gardez la scission des plugins, qui est la partie qui compte.[cfgver][srvmig]

Un plugin est devenu deux, et les réglages ont suivi

Le changement structurel, c’est que l’unique plugin CRI a été scindé en deux. io.containerd.grpc.v1.cri contenait tout ; en version 3 il ne contient plus que les options du serveur de streaming, et la substance vit sous deux nouveaux identifiants : io.containerd.cri.v1.runtime pour tout ce qui concerne l’exécution des conteneurs — runtimes, CNI, sandboxes, SELinux, gestion de l’OOM — et io.containerd.cri.v1.images pour tout ce qui concerne les images : le snapshotter, le registre, l’image de sandbox épinglée, la concurrence de téléchargement. C’est une meilleure division que la précédente, et cela signifie qu’un rechercher-remplacer mécanique de l’identifiant mettra environ la moitié de vos réglages dans la mauvaise table.[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 (version 2)containerd 2.x (version 3)Remarque
version = 2version = 3 (2.0) / version = 4 (2.3)La version 2 est encore lue et convertie en mémoire. Un fichier en version 4 exige 2.3.0 ou plus récent
plugins."io.containerd.grpc.v1.cri"plugins.'io.containerd.cri.v1.runtime'Tout ce qui concerne l’exécution des conteneurs : runtimes, CNI, SELinux, OOM, sandboxes
plugins."io.containerd.grpc.v1.cri"plugins.'io.containerd.cri.v1.images'Tout ce qui concerne les images : snapshotter, registre, images épinglées, réglages de téléchargement
plugins."io.containerd.grpc.v1.cri"plugins.'io.containerd.grpc.v1.cri'Existe encore, mais seulement pour les options du serveur de streaming
sandbox_image = "…"pinned_images.sandbox = '…'Renommé et déplacé. Perdez-le et un nœud isolé ira chercher registry.k8s.io
…cri".containerd.snapshotter…cri.v1.images'.snapshotterChange de côté, du runtime vers les images
…cri".containerd.runtimes.*…cri.v1.runtime'.containerd.runtimes.*Seul le chemin change. runtime_type = io.containerd.runc.v2 reste
…cri".registry.mirrors…cri.v1.images'.registry.config_pathMécanisme différent. Un répertoire de fichiers hosts.toml, pas une table. Cible de suppression en 2.4
…cri".registry.auths— (imagePullSecrets)Pas de remplacement, volontairement. Les identifiants passent au cluster. Cible de suppression en 2.4
…cri".cni.bin_dir…cri.v1.runtime'.cni.bin_dirsAu pluriel, et c’est une liste. Obsolète depuis 2.1, cible de suppression en 2.4
plugin_dir (plugin Go .so)— (plugins proxy ou binaires)Déjà supprimé en 2.1, et pas seulement obsolète

Deux renommages à l’intérieur de cette division causent l’essentiel des dégâts. sandbox_image est devenu pinned_images.sandbox, si bien qu’un cluster qui pointait son image pause vers un miroir interne repasse silencieusement sur registry.k8s.io — sans conséquence jusqu’au jour où le nœud n’a plus de sortie. Et snapshotter est passé du côté runtime au côté images, ce qui est assez contre-intuitif pour mériter une vérification plutôt qu’une hypothèse. Tout le reste du tableau ci-dessous est un changement de chemin, pas de comportement.[ctrplug]

Registres : la partie qui met les clusters à terre

La configuration des registres est l’endroit où cette migration passe de fastidieuse à risquée, et elle porte une date : mirrors et configs sont obsolètes depuis containerd 1.5, auths depuis 1.3, et les trois ont pour cible de suppression containerd 2.4 — la version qui suit celle que recommande cet article. Le remplacement des deux premières est une arborescence : un sous-répertoire par espace de noms de registre sous un unique config_path, contenant chacun un hosts.toml. Cela fait plus de fichiers et beaucoup moins de magie, et c’est sincèrement mieux : les hôtes sont essayés dans l’ordre, les capacités sont explicites, et une autorité de certification par registre est une ligne dans un fichier plutôt qu’un cas particulier. La troisième propriété, auths, n’a délibérément pas d’équivalent en fichier : les identifiants ont leur place dans un secret de téléchargement d’images Kubernetes, pas dans une configuration de nœud dont hérite toute charge tournant dessus.[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'

Le bug mérite d’être énoncé avec précision, parce que la version approximative vous envoie chercher au mauvais endroit. Il ne suppose rien d’exotique de votre part. Démarrez containerd 2.2.0 avec un fichier ordinaire en version 2 contenant un bloc registry.mirrors et rien d’autre : la migration en mémoire y ajoute le config_path par défaut, et le plugin CRI rejette catégoriquement cette combinaison — `mirrors` cannot be set when `config_path` is provided. Le plugin ne charge pas. Le démon démarre quand même. Tous les pods planifiés sur ce nœud échouent alors à la création pendant que l’état de premier niveau de tous les services de la machine reste au vert. Et chercher les deux clés dans votre propre fichier ne donne rien, puisque vous n’en avez jamais écrit une. Le problème a été signalé sur 2.2.0 et corrigé dans la pull request 12617, arrivée avant 2.3.0 et rétroportée sur la branche 2.2 : sur une 2.3 à jour ou un correctif 2.2 récent vous n’êtes pas exposé, sur 2.0 ou 2.1 vous l’êtes. L’instruction qui vaut dans les deux cas est de terminer la conversion : construisez l’arborescence certs.d, faites-y pointer config_path et supprimez le bloc mirrors, pour qu’aucune migration n’ait à deviner.[iss12612][pr12617]

Le chemin de téléchargement des images a changé sous vos pieds

Depuis containerd 2.1, le plugin CRI télécharge les images via le Transfer Service au lieu de le faire dans son propre processus. C’est un comportement par défaut, pas une option que vous auriez activée, et en soi cela n’a rien de remarquable. Ce qui lui vaut une section, c’est le repli. Si la configuration d’images de CRI contient quoi que ce soit que le Transfer Service ne peut honorer, containerd active use_local_image_pull pour le nœud entier et consigne un avertissement. Il n’échoue pas, il ne vous le dit pas au moment de l’usage, et il ne le dit pas au cluster.[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
RéglageTéléchargement localTransfer Service (par défaut depuis 2.1)
snapshotterPris en chargePris en charge
ImagePullProgressTimeoutPris en chargePris en charge
PinnedImagesPris en chargePris en charge
Registry.Mirrors / Configs / AuthsPris en charge (tous obsolètes)Non pris en charge — déclenche le repli en téléchargement local
MaxConcurrentDownloadsLu dans la configuration d’images CRIDoit passer sous plugins.'io.containerd.transfer.v1.local' ; toute valeur autre que 3 déclenche le repli
DiscardUnpackedLayersPris en chargeNon pris en charge — déclenche le repli
ImagePullWithSyncFsPris en chargeNon pris en charge — déclenche le repli
DisableSnapshotAnnotationsPris en chargeÀ configurer dans le plugin snapshotter ; false déclenche le repli

Lisez une fois la liste des déclencheurs et l’implication saute aux yeux : porter max_concurrent_downloads de 3 à 6 — geste ordinaire et raisonnable sur un nœud bien connecté — fait basculer tous les téléchargements d’images de ce nœud sur un autre chemin de code. Conserver le bloc obsolète mirrors fait la même chose, ce qui est une seconde raison de terminer la conversion des registres plutôt que de la laisser en plan. Si vous voulez le téléchargement local, mettez use_local_image_pull = true et assumez-le. Si vous voulez le Transfer Service, déplacez le réglage de concurrence dans [plugins.'io.containerd.transfer.v1.local'], là où il est réellement lu.[cricfg]

Ce qui a réellement été supprimé

Passons aux suppressions, la partie réellement partie et pas seulement renommée. La liste est courte et tout ce qui y figure a un remplacement documenté, mais lisez les numéros de version plutôt que les résumés : le document de transition vers containerd 2.0 et RELEASES.md se contredisent sur deux points, et les deux fois c’est le document de transition que l’on cite. Le cas important est le téléchargement des images Docker schema 1 : il a été désactivé en 2.0, où une variable d’environnement le rétablissait, et supprimé en 2.1, où plus rien ne le rétablit. Puisque la cible est ici 2.3, considérez-le comme disparu. Cela compte, parce que les images encore en schema 1 sont par définition des images que personne n’a reconstruites depuis 2017 environ, ce qui veut dire que personne n’a le Dockerfile non plus. Trouvez-les avant la mise à jour, pas après : depuis 1.7.8 et 1.6.25 les images converties portent une étiquette qui les rend repérables.[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
FonctionnalitéObsolète depuisSupprimée enQuoi à la place
Runtime V1, io.containerd.runtime.v1.linux1.42.0io.containerd.runc.v2
Runc V1, io.containerd.runc.v11.42.0io.containerd.runc.v2
Snapshotter AUFS intégré1.52.0overlayfs
Étiquette containerd.io/restart.logpath1.52.0containerd.io/restart.loguri
Archives cri-containerd-*.tar.gz1.62.0Installer containerd, runc et les plugins CNI séparément
API CRI v1alpha21.72.0CRI v1 uniquement. Vérifier que crictl version renvoie RuntimeApiVersion: v1
Ancienne implémentation CRI podsandbox2.02.0Le contrôleur de sandbox, qui est la valeur par défaut
Téléchargement des images Docker schema 11.72.1 (désactivé en 2.0)Reconstruire en schema 2 / OCI. La variable d’environnement de secours ne fonctionne plus depuis 2.1
Plugins runtime en bibliothèque Go (*.so)2.02.1Des plugins externes, proxy ou binaires
LimitNOFILE explicite dans l’unité de référence2.0Utiliser la valeur par défaut de systemd ; en dessous de systemd 240, remettre 1024:524288 à la main
io_uring_* dans le profil seccomp par défaut2.0Un profil seccomp explicite, et une conversation sur son opportunité

Une suppression est plus discrète que les autres et mérite d’être signalée. L’unité de référence containerd.service ne définit plus explicitement LimitNOFILE. Les rlimits de containerd sont héritées par les conteneurs qu’il démarre, ce n’est donc pas un réglage limité au démon : à partir de systemd 240 la valeur par défaut est raisonnable et l’affaire est sans effet, en dessous c’est la valeur par défaut du noyau, 4096, qui s’applique et que chaque conteneur de la machine hérite. La recommandation amont pour ces hôtes est de remettre LimitNOFILE=1024:524288 à la main.[pr8924][sdexec]

Des valeurs par défaut qui ont basculé sans prévenir

Indépendamment des suppressions, plusieurs valeurs par défaut ont changé. Ce sont elles qui font qu’un nœud se comporte différemment après une mise à jour où vous n’avez touché aucun réglage, et elles méritent une décision délibérée plutôt qu’une acceptation par inertie.[ctr20]

Valeur par défautcontainerd 1.xcontainerd 2.xPourquoi ça compte
enable_unprivileged_portsfalsetrueLes conteneurs écoutent sous 1024 sans CAP_NET_BIND_SERVICE
enable_unprivileged_icmpfalsetrueping fonctionne sans CAP_NET_RAW
enable_cdidésactivétrueLes fichiers de spécification dans /etc/cdi et /var/run/cdi décrivent l’accès matériel. L’interrupteur lui-même est obsolète depuis 2.2 et disparaît en 2.4
NRIdésactivéactivéLe socket NRI entre dans la surface d’attaque du nœud
CRI avec sandboxancien serveur CRIcontrôleur de sandboxInvisible avec runc, à tester avec Kata et gVisor
Chemin de téléchargementdans le processusTransfer Service (depuis 2.1)Repli silencieux en téléchargement local sur plusieurs réglages
Appels io_uring_*autorisésbloquésRetirés de la liste blanche seccomp par défaut après des exploits noyau répétés
Image de sandboxsandbox_imagepinned_images.sandboxMême valeur, clé différente. Facile à perdre dans la réécriture
  • Ports non privilégiés et ICMP sont activés. Le plugin CRI définit désormais net.ipv4.ip_unprivileged_port_start=0 et net.ipv4.ping_group_range=0 2147483647 pour les conteneurs qui n’utilisent ni l’espace de noms réseau de l’hôte ni les espaces de noms utilisateur. Écouter en dessous du port 1024 ne demande plus CAP_NET_BIND_SERVICE, et ping ne demande plus CAP_NET_RAW. Pratique, et aussi un changement de votre posture de sécurité : passer enable_unprivileged_ports et enable_unprivileged_icmp à false rétablit l’ancien comportement.
  • NRI est activé. La Node Resource Interface permet à des plugins de modifier les conteneurs à leur création. L’accès est contrôlé par l’accès au socket NRI du système, ce qui signifie que ce socket fait désormais partie de la surface d’attaque du nœud, que vous utilisiez ou non un seul plugin NRI.
  • CDI est activé, et l’interrupteur est en sursis. La Container Device Interface est en marche avec cdi_spec_dirs pointant par défaut sur /etc/cdi et /var/run/cdi, si bien que tout ce qui peut écrire un fichier de spécification dans ces répertoires peut décrire un accès matériel pour les conteneurs. Notez que enable_cdi lui-même est obsolète depuis containerd 2.2, avec une cible de suppression en 2.4, après quoi CDI sera simplement toujours actif — si votre plan était de le désactiver, ce plan a une date de péremption.
  • io_uring n’est plus dans la liste blanche seccomp par défaut. io_uring_enter, io_uring_register et io_uring_setup ont été retirés après une série d’exploits noyau suffisamment longue pour que le projet les juge dangereux par défaut. Une charge bâtie sur io_uring aura besoin d’un profil explicite — et d’une conversation sur l’opportunité de le lui donner.
  • L’implémentation CRI avec sandbox est celle par défaut. Le plugin CRI utilise le contrôleur de sandbox stable plutôt que l’ancien serveur CRI. C’est invisible en fonctionnement normal et très visible si vous exécutez un runtime avec sandbox comme Kata ou gVisor, ce qui est exactement le cas à tester avant un déploiement sur tout le parc.

Aucun de ces points n’est une raison de ne pas mettre à jour. Ce sont des raisons de mettre à jour un nœud, de le regarder, et seulement ensuite d’inscrire le changement dans la construction de l’image — c’est la différence entre une migration et une surprise à l’échelle du parc.[ctrnri][cdi]

La mise à jour d’un nœud, dans l’ordre

La partie mécanique est courte, et ce qui change tient plus à la manière qu’au fond. Les archives combinées cri-containerd-cni-VERSION-OS-ARCH.tar.gz ont été supprimées en 2.0 : containerd, runc et les plugins CNI sont donc désormais trois installations séparées avec trois décisions de version séparées. C’est plus explicite et un peu plus de travail, et cela supprime une source de confusion ancienne où l’on mettait à jour containerd en mettant à jour runc au passage sans s’en apercevoir. Une remarque rassurante sur le saut lui-même : containerd prend en charge les mises à jour entre versions mineures successives et, séparément, les mises à jour directes entre versions LTS successives — et 1.7 (LTS) vers 2.3 (LTS) est précisément l’exemple que donne son propre document de versions. Vous ne sautez rien sur quoi vous étiez censé vous poser.[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

Deux remarques sur l’ordre, faciles à rater dans l’urgence. Sauvegardez /var/lib/containerd avant de commencer, non parce que la mise à jour risque de le corrompre mais parce que c’est le magasin d’images et de snapshots : le perdre signifie retélécharger toutes les images du nœud, ce qui sur un gros nœud se mesure en dizaines de minutes et en beaucoup de trafic sortant. Et remontez le runtime avant le kubelet, puis vérifiez avec ctr plugins ls qu’aucun plugin n’est ailleurs qu’en état ok avant même de démarrer le kubelet. Un plugin CRI qui n’a pas chargé, c’est un nœud qui accepte des pods et ne peut pas les créer.[runc][cni]

Le même changement vu du côté Kubernetes

Du côté du cluster il n’y a rien à faire et une chose à vérifier. La matrice de support est un document de projet, pas une politique d’admission : aucun kubelet ne refuse de démarrer contre un containerd non supporté, et aucun événement n’apparaît nulle part. L’audit doit donc être explicite : parcourir les nœuds, lire containerRuntimeVersion et le comparer à la matrice pour la version de kubelet du même nœud. Les pools de nœuds hétérogènes sont le cas normal et non l’exception, en particulier là où les images de nœuds avancent à leur propre rythme.[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'

Le réglage le plus souvent perdu dans cette migration est le gestionnaire de runtime, parce que c’est le réglage le plus souvent ajouté à la main. Chaque RuntimeClass du cluster nomme un gestionnaire qui doit exister dans la configuration réécrite ; s’il n’existe pas, seules les charges qui le demandent échouent, ce qui veut dire que la panne reste cantonnée à l’équipe qui utilisait gVisor ou Kata et que personne d’autre ne s’en aperçoit pendant une semaine. Vérifiez-les nommément contre le nouveau fichier. Il en va de même pour le pilote de cgroups, et ici la version rassurante de l’histoire n’est pas tout à fait exacte : Kubernetes 1.28 a bien ajouté la possibilité pour le kubelet de demander au runtime CRI quel pilote il utilise, mais en tant que fonctionnalité alpha derrière la feature gate KubeletCgroupDriverFromCRI, et le côté containerd de cette mécanique exige 2.0 ou plus récent. Ce n’est donc pas automatique : SystemdCgroup = true doit toujours survivre à la réécriture, et il vit maintenant à un autre chemin.[k8srtc][k8skubeadm]

Revenir en arrière, et ce qui ne revient pas

Le retour arrière mérite une réponse franche plutôt que rassurante. Les binaires et le fichier de configuration reviennent proprement — ce sont deux fichiers sur disque, et si vous avez gardé l’ancienne archive et l’ancien config.toml vous êtes à dix minutes de votre point de départ. C’est sincèrement plus que ce qu’offrent la plupart des migrations.[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.

Deux choses ne reviennent pas si facilement. Le répertoire d’état d’abord, et la formulation honnête est que personne ne promet le contraire : le document de stabilité de containerd place explicitement l’organisation du système de fichiers, les formats de stockage et les formats de snapshot hors de ses garanties, et précise que le projet peut migrer ces formats d’une version mineure à l’autre. Rétrograder contre un /var/lib/containerd dans lequel 2.x a déjà écrit relève donc de l’indéfini plutôt que du cassé documenté, ce qui n’est pas une nuance qu’on a envie d’éprouver à trois heures du matin : restaurez l’archive ou acceptez de retélécharger toutes les images. La seconde est le fichier de configuration lui-même, si vous l’avez déjà migré : un fichier en version 4 exige containerd 2.3.0 ou plus récent rien que pour être lu. Et le point de fond mérite d’être dit sans détour : un retour arrière vous achète une nuit, pas un trimestre. La branche 1.7 quitte le support étendu en septembre 2026 et ne reçoit d’ici là aucun correctif pour quoi que ce soit hors du service managé d’un fournisseur. Un retour arrière est une façon de terminer proprement une fenêtre de maintenance qui a mal tourné, pas une façon de repousser la décision.[ctrsec]

Vérifier plutôt qu’espérer

La vérification n’est pas une affaire de goût, et sur cette migration elle a une forme précise : presque tout ce qui rate laisse le démon en marche. Vérifier que containerd est debout ne prouve donc rien. Le script ci-dessous contrôle ce qui peut être faux en silence — la version de configuration, l’éventuel plugin en état d’erreur, si CRI répond en v1, si le pilote de cgroups et les gestionnaires de runtime ont survécu — puis fait les deux choses qu’aucune inspection ne peut établir.[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" 

Ces deux choses sont un vrai téléchargement et un vrai conteneur. Un hosts.toml dans un répertoire dont le nom ne correspond pas exactement à l’espace de noms du registre a l’air parfaitement correct et ne correspond tout simplement jamais, et aucune relecture du fichier ne vous le montrera ; seul un téléchargement le fera. Et une configuration peut être valide à tous égards et ne toujours pas démarrer un conteneur, parce que le binaire du runtime est en mauvaise version ou n’est pas au chemin qu’attend le shim. Lancez les deux, sur le premier nœud, avant de passer au second.[critools]

L’ordre dans lequel faire tout ça

Résumée, la décision est plus petite que l’article. Il y a une version cible, et c’est 2.3 — la LTS actuelle, supportée jusqu’en avril 2028. Tout le reste du tableau est soit déjà hors support, soit hors support dans quelques mois, soit une branche de support étendu taillée pour le service managé de quelqu’un d’autre. Le travail n’est pas l’échange de binaires, qui prend quinze minutes ; le travail est la réécriture de la configuration et, à l’intérieur, la conversion des registres.[ctrrel]

Si votre situation est…alors la cible est…et le travail est…
containerd 1.7 sur Kubernetes 1.34 ou 1.352.3 LTS, en un sautLa réécriture complète de la configuration. Le saut de LTS à LTS est explicitement pris en charge, et il n’y a aucune raison de s’arrêter à 2.2
containerd 1.6, n’importe où2.3 LTS, en urgenceSans support depuis août 2025, et 1.6 vers 2.3 n’est ni un saut successif ni un saut de LTS à LTS — passez par 1.7. À traiter comme un sujet de sécurité, pas de maintenance
containerd 2.1, mis à jour l’an dernier2.3 LTSLa configuration est déjà en version 3, c’est donc surtout un échange de binaires — mais 2.1 vers 2.3 saute 2.2 et sort du chemin de mise à jour pris en charge, donc testez plutôt que de supposer
containerd 2.2, à jour2.3 LTS avant novembre 2026Minime, mais ne laissez pas filer : 2.2 s’arrête avant l’arrivée de la LTS suivante
Déjà Kubernetes 1.36, avec containerd 1.x2.3 LTS, dans cette fenêtreVous êtes sur un appariement non testé. La matrice n’a pas de ligne 1.x pour 1.36
Un service managé (GKE, EKS, AKS)Ce que livre le fournisseurLisez ses notes de version d’images de nœuds : le runtime est à lui, les RuntimeClasses sont à vous
  1. Inventaire avant de planifier. Version du démon, version du fichier de configuration, version de l’API CRI et version du kubelet, nœud par nœud. Puis ctr deprecations list --format json sur tout le parc, et gardez la sortie : c’est la liste de ce que vous savez déjà être faux.
  2. Réécrivez la configuration à la main, en utilisant le convertisseur comme dictionnaire. Lancez containerd config migrate pour apprendre les nouveaux noms de clés, puis écrivez vous-même un court fichier en version 3. N’installez pas le vidage de trois cents lignes : il fige toutes les valeurs par défaut que vous n’avez jamais choisies.
  3. Faites la conversion des registres en premier et séparément. Construisez l’arborescence certs.d, prouvez-la avec ctr images pull --hosts-dir, et assurez-vous que mirrors et config_path n’apparaissent jamais dans le même fichier. C’est l’étape qui provoque les pannes.
  4. Décidez au sujet des valeurs par défaut qui ont changé au lieu d’en hériter. Ports non privilégiés, NRI, CDI et le changement seccomp sur io_uring touchent à la sécurité. Choisissez, écrivez-le, mettez-le dans la construction de l’image.
  5. Un nœud, puis un pool, puis le parc. Sauvegardez /var/lib/containerd, mettez à jour, lancez le script de vérification, puis remettez le nœud en ordonnancement. Seulement ensuite, inscrivez le changement dans l’image de nœud — et vérifiez les gestionnaires de runtime dont dépendent vos RuntimeClasses, parce que rien d’autre ne le fera.

C’est l’un de quatre changements qui atterrissent sur les mêmes nœuds la même année, et ils coûtent moins cher ensemble que séparément : la migration de cgroup v1 vers cgroup v2, parce que le réglage du pilote de cgroups doit survivre aux deux réécritures et que le kubelet le lit désormais depuis le runtime ; les ruptures de compatibilité de Docker Engine 29, la même pile de conteneurs vue du côté Docker ; et le passage d’ingress-nginx à la Gateway API, si le nœud est de toute façon reconstruit pour le changement d’ingress. Si vous pesez la quantité de tout cela dont vous avez réellement besoin, quand ne pas utiliser Kubernetes est l’autre versant de l’argument.

Questions fréquentes

containerd 1.7 est-il encore supporté ?

Seulement dans un sens très étroit. Le tableau des versions de containerd présente 1.7 comme LTS jusqu’en septembre 2026, mais la note de bas de page est décisive : le support général assuré par les committers s’est arrêté en mars 2026, et la prolongation est assurée par deux mainteneurs nommés, orientée vers l’usage avec Kubernetes 1.32, 1.31 et 1.30 via Google Kubernetes Engine, les changements pouvant être refusés s’ils ne servent pas cet usage. Ces trois versions de Kubernetes sont déjà en fin de vie en amont. Si vous n’êtes pas sur GKE avec un Kubernetes hors support, traitez 1.7 comme non supportée aujourd’hui, pas en septembre.

Vers quelle version de containerd 2.x faut-il migrer ?

Vers 2.3. C’est la branche LTS actuelle, ouverte le 30 avril 2026 et supportée jusqu’au 30 avril 2028. Les autres options sont moins bonnes, chacune à sa façon : 2.1 est arrivée en fin de vie le 3 juillet 2026, 2.2 n’est supportée que jusqu’au 6 novembre 2026, et 2.0 relève du même type de support étendu ciblé sur un fournisseur que 1.7. Si vous êtes aujourd’hui en 2.1 ou 2.2, le passage à 2.3 est court, puisque votre configuration est déjà en version 3.

Dois-je réécrire config.toml, ou containerd 2.x lira-t-il mon ancien fichier ?

Il lira un fichier en version 2 et le convertira en mémoire à chaque démarrage, donc rien ne vous y oblige — et il consigne Configuration migrated from version 2, use `containerd config migrate` to avoid migration chaque fois qu’il le fait, ce qui est la façon la plus rapide d’auditer un parc. L’argument pour réécrire quand même est que le chemin de compatibilité est précisément là où vivent les problèmes connus, en particulier la migration des registres qui injecte config_path à côté de votre bloc mirrors et empêche le plugin CRI de charger. Utilisez containerd config migrate pour apprendre les nouveaux noms de clés et écrivez à la main un fichier court. Choisissez la version délibérément : la version 3 est lue par containerd 2.0 et au-delà, la version 4 exige 2.3.0 ou plus récent, et c’est la version 4 que migrate produit sur 2.3.

Qu’est-ce que la version 4 de la configuration, et en ai-je besoin ?

La version 4 est arrivée avec containerd 2.3. Elle ne change rien à CRI : la scission du plugin dont traite l’essentiel de cette migration relève de la version 3, introduite en 2.0. Ce que fait la version 4, c’est sortir les sockets propres au démon des tables de premier niveau [grpc], [ttrpc], [metrics] et [debug] pour les confier à des blocs de plugin io.containerd.server.v1.grpc, …v1.ttrpc, …v1.metrics et …v1.debug ; [debug] conserve level, format et log_trace_id au premier niveau. Un changement de comportement est facile à manquer : avant la version 4, une adresse ttrpc non définie était dérivée de l’adresse gRPC sous la forme <adresse grpc>.ttrpc et en héritait les uid et gid, alors qu’en version 4 le plugin ttrpc utilise sa propre valeur par défaut. Vous n’avez pas besoin de la version 4, et il y a une raison de préférer la version 3 pendant quelque temps : un fichier en version 4 ne peut être lu ni par 2.0, ni par 2.1, ni par 2.2, si bien qu’en écrire un réduit vos options de retour arrière.

Que signifie « `mirrors` cannot be set when `config_path` is provided » ?

Cela signifie que le plugin du service d’images CRI a refusé de charger parce que la configuration des registres a fini par indiquer à la fois la table obsolète mirrors et le plus récent config_path. containerd démarre malgré tout — le démon est sain, le plugin non — et tous les pods planifiés sur ce nœud échouent à la création. Le piège, c’est que vous n’avez peut-être pas écrit les deux : le problème a été signalé sur containerd 2.2.0 avec un fichier ordinaire en version 2 ne contenant qu’un bloc registry.mirrors, parce que la migration en mémoire y ajoute le config_path par défaut. Chercher dans votre propre fichier ne donne donc rien. Le correctif est arrivé dans la pull request 12617, avant 2.3.0, et a été rétroporté sur 2.2 : une 2.3 à jour et les correctifs 2.2 récents ne sont pas exposés, 2.0 et 2.1 le sont. Dans tous les cas, la solution durable est de n’en garder qu’un seul : construire une arborescence certs.d, y pointer config_path, et supprimer le bloc mirrors.

Qu’est-ce qui remplace le bloc de miroirs de registre ?

Une arborescence. Définissez config_path sous [plugins.'io.containerd.cri.v1.images'.registry] — par convention /etc/containerd/certs.d — et créez un sous-répertoire par espace de noms de registre, chacun contenant un hosts.toml. Chaque fichier nomme un server et une ou plusieurs entrées [host."…"] avec des capabilities explicites, essayées dans l’ordre, si bien qu’un miroir dépourvu d’une couche peut basculer vers le registre d’origine. Les autorités de certification et les certificats client par registre sont des clés du même fichier. Le nom du répertoire doit correspondre exactement à l’espace de noms, port compris : c’est la cause la plus fréquente d’une configuration qui a l’air juste et ne correspond jamais.

Où est passé sandbox_image ?

Il est devenu sandbox sous [plugins.'io.containerd.cri.v1.images'.pinned_images]. Celui-ci mérite une vérification manuelle après toute réécriture, car la panne est différée et dépend de l’environnement : un nœud avec accès Internet téléchargera tranquillement l’image pause depuis registry.k8s.io et rien ne paraîtra anormal, tandis qu’un nœud isolé ou à sortie restreinte ne parviendra à créer aucun pod. Si vous le pointiez vers un miroir interne, reportez la valeur explicitement.

Pourquoi le comportement des téléchargements a-t-il changé alors que je n’ai touché qu’à la concurrence ?

Parce que depuis containerd 2.1 le plugin CRI télécharge via le Transfer Service par défaut, et que le Transfer Service ne lit pas max_concurrent_downloads dans la configuration d’images CRI. Lorsque containerd trouve un réglage que le Transfer Service ne peut honorer, il active use_local_image_pull = true pour le nœud, consigne un avertissement et continue. La liste complète des déclencheurs est Registry.Mirrors, Registry.Configs, Registry.Auths, un MaxConcurrentDownloads différent de 3, DiscardUnpackedLayers, ImagePullWithSyncFs et DisableSnapshotAnnotations = false. Pour augmenter la concurrence sans changer de chemin de code, définissez-la plutôt sous [plugins.'io.containerd.transfer.v1.local'].

Mes anciennes images vont-elles cesser de fonctionner ?

Seules les images Docker schema 1, et seulement au téléchargement — mais vérifiez les numéros de version, car beaucoup de ce qui s’écrit là-dessus a une version de retard. La prise en charge a été désactivée par défaut en containerd 2.0, où la variable d’environnement CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1 la rétablissait, puis supprimée en 2.1, où plus rien ne la rétablit. Comme la cible est 2.3, il n’y a pas d’échappatoire : ces images doivent être reconstruites. Trouvez-les avant de mettre à jour : depuis containerd 1.7.8 et 1.6.25 les images converties depuis schema 1 portent l’étiquette io.containerd.image/converted-docker-schema1, si bien que ctr image list avec cette étiquette les retrouve dans tous les espaces de noms. Les reconstruire en schema 2 ou OCI est la solution, et ces images sont généralement assez anciennes pour que retrouver le Dockerfile soit la partie difficile.

Puis-je revenir de containerd 2.x à 1.7 ?

Les binaires reviennent proprement — gardez l’ancienne archive et l’opération prend dix minutes. Deux choses compliquent le reste. Le répertoire d’état d’abord : le document de stabilité de containerd place explicitement l’organisation du système de fichiers, les formats de stockage et les formats de snapshot hors de ses garanties, et précise que le projet peut migrer ces formats d’une version mineure à l’autre ; rétrograder contre un /var/lib/containerd dans lequel 2.x a déjà écrit relève donc de l’indéfini, et pas seulement du risqué. Restaurez l’archive prise avant la mise à jour, ou acceptez de retélécharger toutes les images du nœud. Le fichier de configuration ensuite : si vous l’avez migré, un fichier en version 4 exige 2.3.0 ou plus récent et un fichier en version 3 exige 2.0 ou plus récent, donc gardez l’original en version 2. Considérez le retour arrière comme un moyen de terminer proprement une fenêtre de maintenance, pas comme un moyen de repousser la migration : 1.7 quitte le support étendu en septembre 2026.

Le kubelet cesse-t-il de fonctionner si containerd est dans une version non supportée ?

Non, et c’est précisément pour cela qu’un audit explicite est nécessaire. La matrice de support Kubernetes / containerd est une affirmation sur les appariements que les projets testent, pas une politique d’admission : rien dans le kubelet ne vérifie la version du runtime, aucun événement n’est émis, et un appariement non supporté démarre et semble fonctionner. Ce que vous perdez, ce sont les tests : pour Kubernetes 1.36, la matrice ne liste que containerd 2.3.0+ et 2.2.0+, sans aucune entrée 1.x, donc un nœud en 1.7 dans un cluster 1.36 est une combinaison que personne n’a éprouvée pour vous. Parcourez les nœuds, lisez containerRuntimeVersion et comparez vous-même.

Le plan de données des Services sur ces mêmes nœuds suit sa propre horloge : Kubernetes 1.37 a déprécié le mode ipvs de kube-proxy derrière un feature gate, 1.40 le désactive par défaut et 1.43 supprime le code. le passage de kube-proxy d’IPVS à nftables détaille le plancher noyau 5.13, le comportement des NodePort qui change sans prévenir, et le kube-ipvs0 résiduel qui avale le trafic si personne ne le nettoie.

Une note au niveau de la version, car le bilan de la 1.37 n’est pas celui que l’on lit partout : ce qui peut réellement laisser des pods en ContainerCreating, c’est le passage de SELinuxMount en GA - l’échec cgroup v1 date de la 1.35, la restriction des pods statiques de la 1.34, et la falaise containerd est encore devant vous, en 1.38. ce qui casse réellement lors du passage à Kubernetes 1.37 sépare les trois colonnes et donne l’audit à mener avant la mise à niveau, pas après.

Sources

Sources primaires d’abord. La politique de versions de containerd et son document de transition vers 2.0 sont les seules déclarations faisant autorité sur ce qui a été supprimé et jusqu’à quand une branche est corrigée ; le guide de configuration CRI est la seule liste complète des clés renommées. Là où cet article dit quelque chose qu’une source secondaire ne dit pas — que 2.1 est déjà en fin de vie, que le support étendu de 1.7 vise des versions de Kubernetes elles-mêmes hors support —, le désaccord porte sur le résumé, pas sur le projet.

  1. 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
  2. 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)
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. containerd-config.toml(5): the daemon configuration file itself - the version header, the plugins table, imports, and the state and root directories
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. containerd - Ops: running the daemon, the systemd unit, the socket and state directories, and the configuration import mechanism
  17. 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
  18. 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
  19. containerd - Snapshotters: overlayfs as the default and the replacement for the removed AUFS snapshotter, plus the blockfile, devmapper and erofs alternatives
  20. 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
  21. 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
  22. 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
  23. containerd - crictl: the CRI-level debugging tool, and the right way to confirm that the kubelet's view of the runtime matches yours
  24. 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
  25. 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
  26. 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
  27. 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
  28. Kubernetes - Container runtimes: installing and configuring containerd for a cluster, including the cgroup driver requirement and the sandbox image setting
  29. 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
  30. Kubernetes - Configuring a cgroup driver: the kubelet side of the SystemdCgroup setting that has to be carried across when the containerd configuration is rewritten
  31. 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
  32. 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
  33. Kubernetes - Pull an image from a private registry: the ImagePullSecrets mechanism that replaces the deprecated registry.auths block in the containerd configuration
  34. 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
  35. 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
  36. CNI plugins releases: the third component of the install, previously bundled in cri-containerd-cni-*.tar.gz and now shipped on its own
  37. 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
  38. Container Device Interface: the specification behind enable_cdi and cdi_spec_dirs, both enabled by default from containerd 2.0
  39. 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
  40. 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
  41. 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

Cet article vous a été utile ?