Introduction
Les runtimes de conteneurs sont les logiciels qui décompactent les images de conteneurs, créent les namespaces et les cgroups, et démarrent les processus à l'intérieur d'un pod. Dans Kubernetes, le kubelet appelle un runtime via la Container Runtime Interface (CRI). Le choix du runtime affecte l'isolation de sécurité, la latence de démarrage, la compatibilité des images et les modes de défaillance opérationnels.
Ce guide va au-delà des bases. Nous couvrons comment vérifier la configuration de votre runtime, optimiser les performances en toute sécurité, diagnostiquer les pannes courantes et choisir entre containerd, CRI-O et gVisor pour des charges de travail spécifiques. Chaque section inclut des commandes prêtes à copier-coller, les sorties attendues et les étapes de récupération.
Avant de modifier quoi que ce soit, exécutez les vérifications en lecture seule ci-dessous et enregistrez l'état actuel. Cela vous donne un point de restauration et rend les défaillances visibles.
Architecture des runtimes et la CRI
Le kubelet communique en gRPC avec un plugin CRI. containerd prend en charge la CRI via un plugin intégré ; CRI-O implémente la CRI nativement. Tous deux gèrent des runtimes conformes à l'OCI comme runc (par défaut) ou Kata Containers.
La CRI définit deux services :
- RuntimeService : gère les sandboxes de pods et les conteneurs (création, démarrage, arrêt, exécution, etc.)
- ImageService : tire, liste et supprime les images
Lorsque vous créez un pod, le kubelet appelle d'abord RunPodSandbox. Cela crée le namespace réseau, met en place le conteneur pause et applique les cgroups au niveau du pod. Ensuite, il crée chaque conteneur à l'intérieur de ce sandbox.
Pour voir quel runtime utilise votre nœud :
kubectl get nodes -o wide
# La colonne CONTAINER-RUNTIME indique le runtime de conteneurs et sa version.
Sortie attendue (exemple) :
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node-1 Ready control-plane 10d v1.31.0 192.168.1.10 <none> Ubuntu 22.04.3 LTS 5.15.0-91-generic containerd://1.7.2
Sur le nœud lui-même, vérifiez le socket CRI :
sudo crictl info
crictl est une CLI pour les runtimes CRI. Elle affiche le nom du runtime, sa version et sa configuration. Si crictl n'est pas installé, installez-le à partir des assets de release Kubernetes ou de votre gestionnaire de paquets.
Inventaire des versions et de l'environnement
Avant de diagnostiquer tout problème de runtime, collectez des versions et configurations précises. Exécutez ces commandes sur un nœud (via SSH ou un pod de débogage) et enregistrez la sortie :
# Version du runtime
sudo crictl version
# Exemple de sortie :
# Version: 0.1.0
# RuntimeName: containerd
# RuntimeVersion: 1.7.2
# RuntimeApiVersion: v1
# Noyau et système d'exploitation
uname -a
cat /etc/os-release
# Vérifier si cgroups v2 est activé
stat -fc %T /sys/fs/cgroup
# Si la sortie est "cgroup2fs", vous êtes sur cgroups v2. Si "tmpfs", cgroups v1.
# Lister les conteneurs en cours d'exécution avec leurs runtimes
sudo crictl ps -a
Pour containerd, inspectez le runtime par défaut :
sudo containerd config default | grep -A10 'plugins."io.containerd.grpc.v1.cri".containerd.runtimes'
Cela montre les gestionnaires de runtime. Dans un cluster typique, vous verrez runc comme défaut. Si vous avez installé gVisor, il apparaît comme gestionnaire supplémentaire.
Pourquoi c'est important : De nombreuses défaillances mystérieuses proviennent d'une inadéquation entre le pilote cgroup attendu par le runtime et celui du kubelet. Par exemple, si le kubelet utilise systemd mais que containerd utilise cgroupfs, les pods peuvent être tués de manière incorrecte ou ne pas démarrer. Confirmez les deux paramètres :
# Sur le nœud
sudo cat /var/lib/kubelet/config.yaml | grep cgroupDriver
# Souvent attendu : cgroupDriver: systemd
Réconciliez avec la configuration du runtime. S'ils diffèrent, décidez lequel utiliser (généralement systemd sur les distributions modernes) et mettez à jour les deux, puis redémarrez les services avec précaution.
Diagnostiquer les problèmes de runtime
Commencez par une observation en lecture seule. Utilisez kubectl pour obtenir le statut des pods et les événements :
kubectl get pods -n <namespace>
kubectl describe pod <nom-du-pod> -n <namespace>
Recherchez des événements comme FailedCreatePodSandBox, Failed to start container ou CreateContainerConfigError. Ceux-ci indiquent souvent des problèmes de runtime.
Si un conteneur ne cesse de planter, vérifiez ses journaux :
kubectl logs <nom-du-pod> -n <namespace> --previous
Si le conteneur ne démarre jamais parce que le runtime ne peut pas tirer l'image, vous verrez ImagePullBackOff ou ErrImagePull. Vérifiez le nom de l'image et l'accès au registre.
Pour les problèmes au niveau du nœud, inspectez les états des conteneurs avec crictl :
# Lister les pods (sandboxes) sur ce nœud
sudo crictl pods
# Lister les conteneurs
sudo crictl ps -a
# Inspecter un conteneur arrêté
sudo crictl inspect <id-du-conteneur>
La sortie d'inspection inclut la spécification du runtime OCI, qui révèle les limites de ressources, les montages et les variables d'environnement. Elle inclut également la dernière erreur si le conteneur n'a pas démarré.
Pour tester directement le runtime, essayez d'exécuter un conteneur simple avec crictl :
# Tirer une petite image
sudo crictl pull busybox:latest
# Créer un sandbox
POD_ID=$(sudo crictl runp --runtime runc <(echo '{"metadata":{"name":"test-pod","namespace":"default"}}'))
# Créer et démarrer un conteneur dans ce sandbox
CONTAINER_ID=$(sudo crictl create $POD_ID <(echo '{"metadata":{"name":"test-container"},"image":{"image":"busybox"},"command":["sleep","300"]}') <(echo '{}'))
sudo crictl start $CONTAINER_ID
# Vérifier qu'il est en cours d'exécution
sudo crictl ps
# Nettoyer
sudo crictl stop $CONTAINER_ID
sudo crictl rm $CONTAINER_ID
sudo crictl stopp $POD_ID
sudo crictl rmp $POD_ID
Si cela échoue, le runtime a un problème de configuration ou d'installation.
Modifications de configuration en toute sécurité
Lorsque vous devez modifier les paramètres du runtime, suivez ces principes :
- Sauvegardez le fichier de configuration avant de le modifier.
- Changez un paramètre à la fois.
- Testez sur un seul nœud ou un petit sous-ensemble en utilisant des taints et tolérances.
- Documentez le résultat attendu et comment revenir en arrière.
Exemple : Passer du runtime par défaut runc à crun (un runtime OCI plus rapide)
crun est un runtime OCI léger écrit en C, souvent plus rapide que runc pour les charges de travail à haute densité. Pour le tester sur un nœud :
- Installez crun :
sudo apt-get install crun # ou équivalent pour votre OS
- Ajoutez un gestionnaire de runtime à containerd. Modifiez
/etc/containerd/config.toml:
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.crun.options]
BinaryName = "/usr/bin/crun"
- Redémarrez containerd :
sudo systemctl restart containerd
- Vérifiez que crun est disponible :
sudo crictl info | jq .config.containerd.runtimes
Vous devriez voir crun listé.
- Créez un pod qui utilise le nouveau runtime en définissant
runtimeClassNamedans la spécification du pod. D'abord, définissez une RuntimeClass :
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: crun
handler: crun
Appliquez-la, puis dans une spécification de pod :
spec:
runtimeClassName: crun
containers:
- name: demo
image: busybox
command: ["sleep", "3600"]
Déployez et vérifiez que le pod fonctionne. Sur le nœud, sudo crictl inspect <id-du-conteneur> devrait indiquer le runtime comme crun.
Utiliser gVisor pour une isolation renforcée
gVisor (runsc) est un noyau en espace utilisateur qui intercepte les appels système, fournissant un sandbox. Pour ajouter gVisor comme runtime alternatif :
- Téléchargez et installez runsc :
(
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
wget ${URL}/runsc ${URL}/runsc.sha512
sha512sum -c runsc.sha512
rm -f runsc.sha512
sudo mv runsc /usr/local/bin
sudo chown root:root /usr/local/bin/runsc
sudo chmod 0755 /usr/local/bin/runsc
)
- Configurez containerd pour l'utiliser :
sudo containerd config default > /etc/containerd/config.toml
# Modifiez le fichier pour ajouter sous [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
- Redémarrez containerd.
- Créez une RuntimeClass :
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
- Utilisez-la pour les pods nécessitant une isolation.
Note de performance : gVisor ajoute une surcharge car il intercepte chaque appel système. Il n'est pas destiné aux charges de travail à haut débit. Testez avant de généraliser.
Optimisation des performances et observabilité
Les performances du runtime dépendent de la vitesse de tirage des images, du pilote de stockage et de l'allocation CPU/mémoire. Voici des vérifications et optimisations concrètes.
Accélération du tirage d'images
Utilisez crictl pour tirer une image et chronométrez :
time sudo crictl pull nginx:latest
Si les tirages sont lents, envisagez :
- Mise en miroir de registres : Pour containerd, ajoutez un miroir de registre dans
/etc/containerd/certs.d/docker.io/hosts.toml:
server = "https://docker.io"
[host."https://registry-mirror.example.com"]
capabilities = ["pull", "resolve"]
- Pré-tirage d'images sur les nouveaux nœuds à l'aide d'un DaemonSet ou d'un script d'initialisation du nœud.
- Utilisation d'un cache local comme Dragonfly ou Kraken.
Pilote de stockage
Vérifiez le pilote de stockage pour containerd :
sudo ctr version
sudo ctr plugins ls | grep snapshot
Le défaut est overlayfs. C'est généralement le meilleur choix. Évitez devicemapper en production en raison de problèmes de stabilité.
Limites de ressources et surcharge du runtime
Le runtime lui-même consomme du CPU et de la mémoire pour chaque conteneur. Pour les nœuds à haute densité, réduisez la surcharge en utilisant un runtime plus léger comme crun (comme montré précédemment). Assurez-vous également que les drapeaux --system-reserved et --kube-reserved du kubelet réservent des ressources pour les démons système, y compris le runtime.
Exemple de drapeaux kubelet dans /var/lib/kubelet/config.yaml :
systemReserved:
cpu: 500m
memory: 1Gi
kubeReserved:
cpu: 250m
memory: 512Mi
Ajustez en fonction de la taille de votre nœud et du runtime.
Surveillance des métriques du runtime
containerd expose des métriques Prometheus sur /v1/metrics si activé. Pour activer, modifiez config.toml :
[metrics]
address = "127.0.0.1:1338"
grpc_histogram = true
Redémarrez containerd, puis vérifiez :
curl http://127.0.0.1:1338/v1/metrics | grep containerd_runtime
Importez ces métriques dans votre pile de surveillance. Métriques clés : containerd_runtime_metrics_containers (nombre de conteneurs), containerd_runtime_metrics_cpu_usage_nanoseconds, containerd_runtime_metrics_memory_usage_bytes.
Modes de défaillance et récupération
Plantages ou non-réponse du runtime
Si crictl ps se bloque ou renvoie une erreur, le démon du runtime peut être arrêté. Vérifiez :
sudo systemctl status containerd # ou crio
sudo journalctl -u containerd -n 100
Recherchez des OOM kills, des segfaults ou des problèmes de disque. Redémarrez le démon si nécessaire :
sudo systemctl restart containerd
Après le redémarrage, vérifiez que les pods récupèrent :
kubectl get pods --all-namespaces --field-selector=status.phase!=Running
Certains pods peuvent être en Error ou CrashLoopBackOff. Les supprimer forcera leur recréation.
Nœud non prêt
Si un nœud passe à NotReady, vérifiez le kubelet et le runtime :
kubectl describe node <nœud> | grep -A10 Conditions
La condition ContainerRuntimeIsHealthy doit être True. Si False, investiguez le runtime.
Échecs de tirage d'images
Si les pods ne peuvent pas tirer les images, testez manuellement :
sudo crictl pull <image>
Si cela échoue, vérifiez la connectivité au registre et l'authentification. Pour les registres privés, assurez-vous que le nœud dispose des secrets de tirage d'images corrects. Exemple de spécification de pod :
spec:
imagePullSecrets:
- name: my-registry-secret
containers:
- name: app
image: private-registry.example.com/app:v1
Échecs de création de sandbox
Si la création du sandbox de pod échoue, vérifiez les plugins CNI :
ls /opt/cni/bin
Des plugins manquants provoquent FailedCreatePodSandBox. Installez les plugins CNI requis ou réparez la configuration réseau.
Stratégie de retour en arrière
Gardez toujours une sauvegarde des fichiers de configuration du runtime. Pour containerd :
sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak-$(date +%Y%m%d)
Si un changement casse les nœuds, restaurez la sauvegarde et redémarrez le démon. Pour la configuration du kubelet, sauvegardez /var/lib/kubelet/config.yaml.
Documentez les étapes de retour en arrière dans vos runbooks. Testez un retour en arrière sur un seul nœud avant d'appliquer les changements à tout le cluster.
Durcissement de la sécurité
Sandboxing du runtime
Pour les charges de travail non fiables, utilisez gVisor ou Kata Containers. gVisor est plus facile à intégrer ; Kata fournit une isolation par virtualisation matérielle mais nécessite plus de configuration. Choisissez en fonction de votre modèle de menace et de vos besoins en performances.
Restriction des privilèges du runtime
Même avec runc, vous pouvez durcir les conteneurs :
- Définissez
allowPrivilegeEscalation: falsedans les contextes de sécurité des pods. - Utilisez des profils
seccomp. Le profil par défaut bloque de nombreux appels système dangereux. Pour un seccomp au niveau du runtime, configurez dans le gestionnaire de runtime :
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
NoNewPrivileges = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SeccompProfile = "/chemin/vers/seccomp.json"
- Réduisez les capacités :
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
Maintenir les runtimes à jour
Les vulnérabilités des runtimes sont fréquentes. Suivez les CVE pour containerd, CRI-O et runc. Automatisez les mises à jour via votre gestionnaire de paquets et testez en staging. Exemple pour Ubuntu :
sudo apt-get update && sudo apt-get upgrade containerd.io
Redémarrez le démon après la mise à niveau et vérifiez la santé du cluster.
Pièges courants
- Configurer incorrectement le pilote cgroup
- Pourquoi cela arrive : Les distributions Linux utilisent par défaut les cgroups systemd, mais certains guides utilisent encore cgroupfs. Cette inadéquation fait que le kubelet et le runtime se disputent la gestion des cgroups.
- Comment éviter : Définissez toujours le pilote cgroup sur
systemddans les configurations du kubelet et du runtime, sauf raison spécifique d'utiliser cgroupfs. - Récupération : Mettez à jour les deux configurations, redémarrez les services et testez avec un pod simple.
- Ignorer les journaux du runtime au démarrage
- Pourquoi : Les développeurs se concentrent sur les événements des pods, mais la cause première peut être dans les journaux du runtime.
- Éviter : Prenez l'habitude de vérifier
journalctl -u containerd(ou CRI-O) lors de l'investigation des échecs de démarrage. - Récupération : Corrélez les horodatages entre les événements des pods et les journaux du runtime.
- Utiliser des tags d'image
latestavec des fonctionnalités dépendantes du runtime
- Pourquoi : Certaines fonctionnalités nécessitent des versions spécifiques du runtime ; utiliser
latestpeut casser si le runtime ne les prend pas en charge. - Éviter : Épinglez les versions d'image et testez la compatibilité du runtime en CI.
- Récupération : Revenez au tag d'image précédent ou mettez à niveau le runtime.
- Surcharger les nœuds sans tenir compte de la surcharge du runtime
- Pourquoi : runc ajoute une certaine surcharge par conteneur. Trop de conteneurs peuvent épuiser les ressources du nœud.
- Éviter : Surveillez les métriques du runtime et définissez des limites de densité de pods appropriées par type de nœud.
- Récupération : Évacuez les pods vers d'autres nœuds ou réduisez le nombre de conteneurs.
- Ne pas utiliser correctement RuntimeClass
- Pourquoi : Des gestionnaires mal configurés font échouer les pods avec
FailedCreatePodSandBox. - Éviter : Testez RuntimeClass sur un pod canari avant de l'assigner aux charges de travail.
- Récupération : Supprimez le champ
runtimeClassNamepour revenir au runtime par défaut.
Checklist des opérations
Voici une checklist concise pour les opérations de runtime. Pour chaque élément, assignez un responsable unique dans votre équipe. Révisez cette checklist mensuellement ou après tout incident.
Vérifications quotidiennes/hebdomadaires (Responsable : Ingénieur Plateforme)
- [ ] Vérifier les conditions des nœuds :
kubectl get nodes -o custom-columns='NAME:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,RUNTIME:.status.nodeInfo.containerRuntimeVersion' - [ ] Examiner les métriques du runtime : CPU, mémoire, nombre de conteneurs, latence de tirage d'images.
- [ ] Examiner les journaux pour les erreurs :
sudo journalctl -u containerd --since "24 hours ago" | grep -i error(exécuter sur les nœuds via l'automatisation). - [ ] Vérifier que la sauvegarde des configurations du runtime est à jour.
Avant tout changement (Responsable : Lead SRE)
- [ ] Capturer la référence : version du runtime, nombre de nœuds, taux de succès des pods.
- [ ] Tester le changement sur un nœud (en utilisant taint/tolérance pour isoler).
- [ ] Documenter la procédure de retour en arrière et les commandes exactes.
- [ ] Annoncer le changement et l'impact attendu dans le canal de communication.
- [ ] Configurer des alertes de surveillance pour les métriques affectées.
Après tout changement (Responsable : Lead SRE)
- [ ] Vérifier que le nœud est Ready et que tous les pods sont Running.
- [ ] Exécuter des tests de fumée : créer un pod utilisant
runtimeClassNamesi changé. - [ ] Attendre la période d'observation (par exemple, 30 minutes) et comparer les taux d'erreur.
- [ ] Si des problèmes surviennent, exécuter le retour en arrière immédiatement et capturer les journaux pour le post-mortem.
Trimestriel (Responsable : Ingénieur Sécurité)
- [ ] Examiner les CVE des runtimes et appliquer les correctifs.
- [ ] Réévaluer les besoins de sandboxing et ajuster les RuntimeClasses.
- [ ] Auditer les configurations des runtimes par rapport aux benchmarks CIS ou aux normes internes.
- [ ] Tester la reprise après sinistre : simuler une panne du démon du runtime et restaurer.
Conclusion
La gestion avancée des runtimes de conteneurs dans Kubernetes exige une approche disciplinée. Vous devez comprendre la CRI, vérifier systématiquement les configurations, tester les changements en isolation et disposer de plans de retour en arrière robustes. Les commandes et checklists de ce guide fournissent une base.
Commencez par auditer votre configuration actuelle du runtime avec les commandes d'inventaire. Ensuite, choisissez une amélioration : passer à un runtime plus rapide comme crun, ajouter gVisor pour les charges de travail sensibles, ou renforcer la surveillance. Documentez le changement, assignez un responsable et suivez les résultats sur un mois.
Rappelez-vous, le runtime est la fondation de chaque pod. Quand il fonctionne, personne ne le remarque. Quand il échoue, tout s'arrête. Traitez-le comme une infrastructure critique : observez, planifiez, changez et vérifiez.