Aller au contenu
Conteneurs & Orchestration medium
🔐 Alerte sécurité — Incident supply chain Trivy : lire mon analyse de l'attaque

Runtime Sandboxes : gVisor, Kata Containers et RuntimeClass

16 min de lecture

Les conteneurs partagent le noyau Linux de l’hôte. Une faille dans le noyau ou un appel système mal filtré peut permettre à un conteneur malveillant de s’échapper vers l’hôte. Les runtimes sandboxés (gVisor, Kata Containers) ajoutent une couche d’isolation supplémentaire entre le conteneur et le noyau hôte, réduisant drastiquement la surface d’attaque. La ressource Kubernetes RuntimeClass permet de diriger certains Pods vers ces runtimes alternatifs. Ce guide couvre la configuration, l’utilisation et les critères de choix pour l’examen CKS.

  • Pourquoi les conteneurs classiques ne suffisent pas pour certains workloads
  • Comment gVisor et Kata Containers isolent les conteneurs
  • Configurer containerd pour utiliser un runtime alternatif
  • Créer une ressource RuntimeClass et l’affecter à un Pod
  • Choisir le bon runtime selon vos contraintes

Un conteneur Linux standard (runc) s’exécute comme un processus isolé grâce aux namespaces et cgroups, mais il partage le noyau de l’hôte. Chaque appel système du conteneur est traité directement par le noyau hôte.

Analogie : un conteneur classique, c’est comme un appartement dans un immeuble. Les murs (namespaces) séparent les locataires, mais ils partagent les fondations (le noyau). Si les fondations sont compromises, tous les appartements sont exposés.

Ce partage du noyau pose deux problèmes de sécurité :

  • Surface d’attaque large : le noyau Linux expose plus de 300 appels système. Une vulnérabilité dans l’un d’eux peut permettre une évasion de conteneur (container escape).
  • Isolation incomplète : certaines ressources du noyau (comme /proc, /sys) ne sont pas entièrement cloisonnées par les namespaces.

Les runtimes sandboxés résolvent ce problème en interposant une couche supplémentaire entre le conteneur et le noyau hôte.

ApprocheIsolationPerformanceCompatibilité
runc (standard)Namespaces + cgroupsNativeTotale
gVisor (runsc)Noyau applicatif en userspaceOverhead syscallsÉlevée pour les workloads courants
Kata ContainersVM légère par PodOverhead démarrageQuasi-totale

gVisor est un projet open source de Google qui intercepte les appels système des conteneurs et les traite dans un noyau applicatif écrit en Go, appelé le Sentry. Les processus du conteneur n’appellent pas directement le noyau hôte : leurs appels système sont interceptés et servis par le Sentry, qui ne s’appuie sur le noyau hôte que de manière contrôlée.

gVisor se compose de deux processus principaux :

  • Sentry : le noyau applicatif. Il implémente les appels système Linux (gestion mémoire, threading, signaux, réseau) et ne s’appuie sur le noyau hôte que pour un ensemble restreint d’opérations. Il s’exécute en userspace avec un profil seccomp très restrictif.
  • Gofer : un processus médiateur qui gère les accès au système de fichiers via le protocole 9P. Le Sentry n’a pas d’accès direct aux fichiers de l’hôte.

L’exécutable OCI de gVisor s’appelle runsc (run sandboxed container). Il s’intègre avec containerd et CRI-O comme n’importe quel runtime OCI.

Avantages :

  • Faible empreinte : pas de VM, pas de noyau invité complet. Un sandbox gVisor consomme peu de mémoire supplémentaire.
  • Démarrage rapide : comparable à runc, car il n’y a pas de VM à lancer.
  • Écrit en Go : le fait que gVisor soit majoritairement écrit en Go réduit certaines classes classiques de vulnérabilités mémoire (use-after-free, buffer overflow) par rapport à du code noyau en C/C++.

Limites :

  • Overhead par syscall : chaque appel système passe par le Sentry, ce qui ajoute de la latence. Les workloads intensifs en I/O ou en syscalls sont les plus impactés.
  • Compatibilité : gVisor n’implémente pas tous les appels système Linux. Certaines applications très spécifiques peuvent ne pas fonctionner.
  • Pas de modules noyau : les applications qui nécessitent des modules noyau spécifiques (iptables avancé, eBPF) ne sont pas compatibles.

Kata Containers prend l’approche inverse de gVisor : chaque Pod s’exécute dans une machine virtuelle légère dédiée. Le conteneur dispose de son propre noyau Linux invité, isolé du noyau hôte par un hyperviseur (QEMU/KVM, Cloud Hypervisor ou Firecracker).

Avantages :

  • Isolation matérielle : la séparation est assurée par l’hyperviseur et les mécanismes de virtualisation du CPU (VT-x/AMD-V). L’isolation est comparable à celle d’une VM classique.
  • Compatibilité quasi-totale : le conteneur s’exécute sur un vrai noyau Linux, sans restriction d’appels système.

Limites :

  • Overhead de démarrage : lancer une micro-VM prend quelques secondes, contre quelques millisecondes pour runc.
  • Consommation mémoire : chaque Pod nécessite son propre noyau invité en mémoire.
  • Virtualisation imbriquée : si vos nœuds Kubernetes tournent eux-mêmes dans des VM, Kata peut nécessiter la virtualisation imbriquée (nested virtualization), ce qui dépend du fournisseur cloud et du type d’instance.
CritèregVisorKata Containers
MécanismeNoyau userspace (Go)VM légère (hyperviseur)
IsolationForte (interception syscalls)Très forte (noyau séparé)
PerformanceOverhead par syscallOverhead démarrage + mémoire
CompatibilitéÉlevée pour les workloads courantsQuasi-totale
Nested virt.Non requiseRequise si nœuds virtualisés
Cas d’usageMulti-tenant, CI/CD, serverlessWorkloads réglementés, défense en profondeur

La ressource Kubernetes RuntimeClass permet d’affecter un Pod à un runtime CRI spécifique. C’est une ressource non-namespacée (cluster-scope), stable depuis Kubernetes v1.20, qui mappe un nom lisible vers un handler CRI configuré sur les nœuds.

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
# Le handler doit correspondre au nom de configuration exposé par le CRI
handler: runsc

Le champ handler doit correspondre exactement au nom de configuration exposé par l’implémentation CRI du nœud, par exemple containerd ou CRI-O.

Pour exécuter un Pod avec un runtime sandboxé, ajoutez le champ runtimeClassName dans la spec du Pod :

apiVersion: v1
kind: Pod
metadata:
name: sandbox-test
spec:
runtimeClassName: gvisor
containers:
- name: app
image: nginx:1.27

Si le RuntimeClass référencé n’existe pas, la création du Pod est rejetée à l’admission (Forbidden: RuntimeClass not found). Si le RuntimeClass existe mais que le handler n’est pas configuré sur le nœud, le Pod reste bloqué en ContainerCreating avec un événement FailedCreatePodSandBox.

Par défaut, RuntimeClass suppose un cluster homogène où tous les nœuds supportent le runtime. En pratique, seuls certains nœuds ont gVisor ou Kata installés.

RuntimeClass supporte un champ scheduling qui injecte un nodeSelector et des tolerations dans les Pods à l’admission. Ces contraintes se combinent avec celles déjà présentes dans le Pod ; elles ne les remplacent pas :

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
scheduling:
nodeSelector:
runtime: gvisor
tolerations:
- key: runtime
value: gvisor
effect: NoSchedule

Avec cette configuration, les Pods utilisant runtimeClassName: gvisor seront automatiquement dirigés vers les nœuds labellisés runtime=gvisor.

Les runtimes sandboxés consomment des ressources supplémentaires (mémoire pour le Sentry gVisor, ou pour le noyau invité Kata). Le champ overhead permet de déclarer cette consommation pour que le scheduler en tienne compte :

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
overhead:
podFixed:
memory: "120Mi"
cpu: "250m"

L’overhead est injecté dans le Pod à l’admission à partir du RuntimeClass, puis pris en compte pour le scheduling, les resource quotas, le cgroup du Pod et les décisions d’éviction.

L’installation du runtime sur les nœuds se fait hors de Kubernetes. Voici la procédure pour containerd, le runtime CRI le plus courant.

  1. Installer runsc sur chaque nœud cible

    Exemple d’installation depuis les releases officielles (adaptez à votre OS et architecture) :

    Fenêtre de terminal
    ARCH=$(uname -m)
    URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
    wget ${URL}/runsc ${URL}/runsc.sha512 \
    ${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
    sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
    rm -f *.sha512
    chmod a+rx runsc containerd-shim-runsc-v1
    sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
  2. Configurer containerd pour déclarer le handler runsc

    Ajouter dans /etc/containerd/config.toml (syntaxe containerd 2.x / config v3) :

    [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runsc]
    runtime_type = "io.containerd.runsc.v1"
  3. Redémarrer containerd

    Fenêtre de terminal
    sudo systemctl restart containerd
  4. Vérifier que le runtime est disponible

    Fenêtre de terminal
    sudo crictl info | grep -A 5 runsc

Voici la procédure complète pour déployer un Pod sandboxé :

  1. Installer le runtime sur les nœuds ciblés (gVisor ou Kata)

  2. Configurer containerd avec le handler correspondant

  3. Labelliser les nœuds qui supportent le runtime

    Fenêtre de terminal
    kubectl label nodes <node-name> runtime=gvisor
  4. Créer le RuntimeClass dans le cluster

    Fenêtre de terminal
    kubectl apply -f - <<EOF
    apiVersion: node.k8s.io/v1
    kind: RuntimeClass
    metadata:
    name: gvisor
    handler: runsc
    scheduling:
    nodeSelector:
    runtime: gvisor
    EOF
  5. Déployer un Pod avec le RuntimeClass

    Fenêtre de terminal
    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Pod
    metadata:
    name: sandbox-test
    spec:
    runtimeClassName: gvisor
    containers:
    - name: test
    image: nginx:1.27
    command: ["sh", "-c", "uname -a && sleep 3600"]
    EOF
  6. Vérifier que le Pod utilise le bon runtime

    Fenêtre de terminal
    # Confirmer le runtimeClassName dans la spec
    kubectl get pod sandbox-test -o jsonpath='{.spec.runtimeClassName}'
    # gvisor
    # Comparer le noyau vu par le conteneur (gVisor rapporte 4.4.0)
    kubectl exec sandbox-test -- uname -r
    # 4.4.0 (au lieu du noyau hôte, ex. 6.8.x)
    # dmesg affiche les messages du Sentry gVisor
    kubectl exec sandbox-test -- dmesg | head -3
    # [0.000000] Starting gVisor...
    # Pour un Pod Kata, uname -r affiche le noyau invité (ex. 6.18.x)
    # et dmesg montre un boot Linux complet (BIOS, kata-containers.target)
  • Restreindre la création de RuntimeClass aux administrateurs cluster (RBAC). Un utilisateur ne devrait pas pouvoir créer ou modifier des RuntimeClass.
  • Forcer le RuntimeClass sur les namespaces sensibles avec un admission controller (Kyverno, Gatekeeper) pour garantir que les workloads multi-tenant utilisent toujours un runtime sandboxé.
  • Combiner avec d’autres mécanismes : RuntimeClass ne remplace pas les Pod Security Standards, AppArmor/Seccomp ou les Network Policies. C’est une couche supplémentaire de défense en profondeur.
  • Tester avant de déployer : mesurez l’impact sur vos workloads réels. gVisor ajoute un overhead variable selon le nombre d’appels système.
  • Séparer les nœuds : dédiez certains nœuds aux workloads sandboxés pour éviter que l’overhead n’impacte les applications standards.
  • Déclarer le Pod Overhead : configurez le champ overhead dans le RuntimeClass pour que le scheduler planifie correctement les Pods.
SymptômeCause probableSolution
Rejet à la création : Forbidden: RuntimeClass "xxx" not foundRuntimeClass absent du clusterVérifier kubectl get runtimeclass
Pod bloqué en ContainerCreating avec événement FailedCreatePodSandBoxHandler non configuré dans containerd / CRI-OVérifier /etc/containerd/config.toml (chemin selon la version), redémarrer containerd
Pod Pending indéfinimentPas de nœud avec le label requisVérifier les labels (kubectl get nodes --show-labels) et le scheduling.nodeSelector du RuntimeClass
Erreur not supported avec KataVirtualisation matérielle non disponibleVérifier avec kata-runtime check ; activer le nested virt. si nœuds virtualisés
Application qui plante sous gVisorAppel système non implémentéConsulter les logs gVisor (runsc debug) ; vérifier la compatibilité sur gvisor.dev
  1. Les conteneurs standard partagent le noyau hôte, ce qui expose une large surface d’attaque via les appels système
  2. gVisor intercepte les appels système dans un noyau applicatif en Go — adapté aux workloads multi-tenant et CI/CD
  3. Kata Containers exécute chaque Pod dans une VM légère — isolation matérielle maximale, mais overhead plus important
  4. RuntimeClass est la ressource Kubernetes qui mappe un nom vers un handler CRI configuré sur les nœuds
  5. Le champ runtimeClassName dans la spec du Pod active le runtime alternatif
  6. Le champ scheduling du RuntimeClass permet de cibler automatiquement les nœuds compatibles
  7. Le champ overhead déclare les ressources supplémentaires du runtime pour le scheduler
  8. Les runtimes sandboxés complètent (et ne remplacent pas) les autres mécanismes de sécurité Kubernetes

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn