## 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 :
```bash
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 :
```bash
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 :

```bash
# 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 :
```bash
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 :
```bash
# 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 :
```bash
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 :
```bash
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` :
```bash
# 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` :
```bash
# 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 :
1. **Sauvegardez le fichier de configuration** avant de le modifier.
2. **Changez un paramètre à la fois**.
3. **Testez sur un seul nœud** ou un petit sous-ensemble en utilisant des taints et tolérances.
4. **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 :

1. Installez crun :
```bash
sudo apt-get install crun   # ou équivalent pour votre OS
```
2. Ajoutez un gestionnaire de runtime à containerd. Modifiez `/etc/containerd/config.toml` :
```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"
```
3. Redémarrez containerd :
```bash
sudo systemctl restart containerd
```
4. Vérifiez que crun est disponible :
```bash
sudo crictl info | jq .config.containerd.runtimes
```
Vous devriez voir `crun` listé.
5. Créez un pod qui utilise le nouveau runtime en définissant `runtimeClassName` dans la spécification du pod. D'abord, définissez une RuntimeClass :
```yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: crun
handler: crun
```
Appliquez-la, puis dans une spécification de pod :
```yaml
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 :

1. Téléchargez et installez runsc :
```bash
(
  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
)
```
2. Configurez containerd pour l'utiliser :
```bash
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"
```
3. Redémarrez containerd.
4. Créez une RuntimeClass :
```yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc
```
5. 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 :
```bash
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` :
```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 :
```bash
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` :
```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` :
```toml
[metrics]
  address = "127.0.0.1:1338"
  grpc_histogram = true
```
Redémarrez containerd, puis vérifiez :
```bash
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 :
```bash
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 :
```bash
sudo systemctl restart containerd
```
Après le redémarrage, vérifiez que les pods récupèrent :
```bash
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 :
```bash
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 :
```bash
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 :
```yaml
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 :
```bash
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 :
```bash
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: false` dans 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 :
```toml
[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 :
```yaml
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 :
```bash
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

1. **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 `systemd` dans 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.

2. **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.

3. **Utiliser des tags d'image `latest` avec des fonctionnalités dépendantes du runtime**
   - **Pourquoi** : Certaines fonctionnalités nécessitent des versions spécifiques du runtime ; utiliser `latest` peut 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.

4. **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.

5. **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 `runtimeClassName` pour 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 `runtimeClassName` si 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.