La jungle des container runtimes de Kubernetes

Cet article a été initialement publié sur Medium
Docker, Containerd, CRI-O, tous parlent de conteneurs, mais quelles sont les différences, les avantages et les inconvénients, et quel est l’intérêt d’avoir plusieurs « implémentations Docker » ? Sont-elles toutes compatibles avec Kubernetes ?
La multiplicité des moteurs de conteneurs et des outils comme crictl, podman, buildah, skopeo, kaniko, jib, … autour des conteneurs montre le besoin d’évolution et d’adaptabilité dans le monde Kubernetes.
La Cloud Native Computing Foundation (CNCF), qui inclut le projet Kubernetes, a créé une cartographie de l’écosystème avec une section dédiée aux container runtimes : https://landscape.cncf.io/images/landscape.png :

Ce n’est pas une liste exhaustive, mais elle montre la croissance des container runtimes pour Kubernetes.
Docker a longtemps été le standard de facto. Initialement conçu pour étendre les capacités des Linux Containers (LXC), Docker a été créé comme projet open source en 2013. La solution de l’entreprise est aujourd’hui la plateforme de conteneurisation logicielle leader du marché. En s’appuyant sur LXC, Docker agit comme un moteur de conteneurs portable pour empaqueter applications et dépendances dans des conteneurs facilement déployables sur n’importe quel système.
Avant Docker 1.11 (mai 2016), le démon Docker Engine était en charge du téléchargement des images de conteneurs, du lancement des processus de conteneurs, de l’exposition d’une API pour interagir avec le démon, etc., et tout cela au sein d’un unique processus central disposant des privilèges root.
Cette approche est pratique pour le déploiement, mais un container runtime monolithique qui gère à la fois les images et les conteneurs ne respecte pas les bonnes pratiques en matière de séparation des processus et de privilèges Unix.
OCI : Open Container Initiative
Créée en juin 2015 par Docker et d’autres acteurs majeurs de l’industrie des conteneurs (et transférée à la Linux Foundation), l’OCI comprend actuellement deux spécifications :
- Runtime Specification, avec runc comme implémentation de référence (également offerte par Docker). C’est un binaire autonome qui prend un conteneur OCI et l’exécute. Pour simplifier, runC est essentiellement un petit outil en ligne de commande qui exploite directement libcontainer (une librairie s’interfaçant avec les fonctionnalités du noyau Linux comme les cgroups et les namespaces), sans passer par Docker Engine.
- Image Specification : l’objectif de cette spécification est de permettre la création d’outils interopérables pour construire, transporter et préparer une image de conteneur à l’exécution.
Mais runc est un container runtime de bas niveau, sans API (comme gVisor, Kata Containers ou Nabla Containers).
Pour les développeurs et les administrateurs système, il faut un container runtime de haut niveau fournissant une API et une CLI permettant d’interagir facilement avec les images : c’est Docker.
Containerd
Mais pour apporter plus de modularité à l’architecture de Docker et plus de neutralité vis-à-vis des autres acteurs de l’industrie, Docker a introduit Containerd, qui agit comme une façade d’API vers le container runtime (runc dans ce cas).
Containerd a un périmètre plus restreint que Docker (par exemple, il ne peut pas construire d’images), fournit une API client, et est davantage pensé pour être embarquable.
Pour exécuter un conteneur, Docker Engine crée l’image et la transmet à Containerd. Containerd appelle « containerd-shim », qui utilise runC pour exécuter le conteneur.
Le shim permet des conteneurs sans démon. Il se positionne essentiellement comme le parent du processus du conteneur pour faciliter certaines opérations (comme éviter les processus runtime longs pour les conteneurs).
Ainsi, depuis Docker 1.11, Docker est construit sur runC et Containerd, et c’est donc la première version de runtime conforme OCI.
Kubernetes
Mais cela n’était pas suffisant pour Kubernetes.
Chaque container runtime a ses propres forces, et il était indispensable que Kubernetes supporte davantage de runtimes.
C’est pourquoi Kubernetes 1.5 a introduit la Container Runtime Interface (CRI), qui permet au kubelet (le composant Kubernetes installé sur chaque nœud worker et en charge du cycle de vie des conteneurs) d’utiliser une grande variété de container runtimes, sans avoir besoin de recompiler.
Le support de container runtimes interchangeables n’était pas un concept nouveau dans Kubernetes, mais au départ, Docker et rkt étaient tous deux intégrés directement et en profondeur dans le code source du kubelet, via une interface interne et instable.
Le kubelet communique avec le container runtime (ou un shim CRI pour ce runtime) via des sockets Unix en utilisant le framework gRPC, où le kubelet agit comme client et le shim CRI comme serveur.
L’API en protocol buffers comprend deux services gRPC : ImageService et RuntimeService. ImageService fournit des RPC pour récupérer une image depuis un dépôt, l’inspecter et la supprimer. RuntimeService contient des RPC pour gérer le cycle de vie des pods et des conteneurs, ainsi que des appels pour interagir avec les conteneurs (exec/attach/port-forward). Un container runtime monolithique qui gère à la fois les images et les conteneurs (comme Docker et rkt) peut fournir les deux services simultanément avec un seul socket.

image from https://kubernetes.io
Pour implémenter une intégration CRI avec Kubernetes permettant d’exécuter des conteneurs, un environnement de container runtime doit être conforme à la spécification runtime de l’Open Container Initiative (décrite ci-dessus).
Depuis Kubernetes 1.10, Containerd 1.1 et supérieur est prêt pour la production.
Il existe plusieurs implémentations CRI :
- Docker CRI shim
- Containerd CRI plugin
- Cri-o : runtimes conformes OCI.
- …
Une conséquence directe de ces éléments est que Docker n’est pas un composant obligatoire de Kubernetes.
Comme mentionné précédemment, Docker s’appuie désormais sur Containerd, ce qui signifie que les utilisateurs auront la possibilité de continuer à utiliser Docker Engine pour d’autres usages typiques, tout en pouvant configurer Kubernetes pour utiliser le containerd sous-jacent, livré avec Docker Engine et utilisé simultanément par celui-ci sur le même nœud :

image from https://kubernetes.io
CRI-O
Cri-o est un autre container runtime CRI de haut niveau, conçu uniquement pour Kubernetes, qui supporte des runtimes de bas niveau comme runc et Clear Containers (qui fait partie de Kata container).
Les principaux contributeurs de CRI-O sont RedHat, IBM, Intel, Suse et Hyper.
L’architecture globale de CRI-O ressemble à ceci :

image from https://cri-o.io
Alors, sans Docker, comment interagir en ligne de commande avec les container runtimes et les images Kubernetes de haut niveau ?
Outillage
crictl est un outil offrant une expérience similaire à la CLI Docker pour le dépannage des nœuds Kubernetes, et crictl fonctionne de manière cohérente sur tous les container runtimes compatibles CRI. Son objectif n’est pas de remplacer Docker ou kubectl, mais de fournir juste assez de commandes pour le dépannage des nœuds, ce qui est plus sûr à utiliser en production. Il offre également une vue plus adaptée à Kubernetes des conteneurs, en s’appuyant sur les notions de pod et de namespace. Il est utilisé en interne par le projet Kubernetes pour tester et valider la CRI du Kubelet.
Ainsi, par exemple, vous pouvez faire :
|
D’autres outils utiles qui ne dépendent pas de Docker :
- podman : pour gérer les pods et les images de conteneurs (run, stop, start, ps, attach, exec, etc.) en dehors du moteur de conteneurs (donc sans avoir besoin d’un moteur de conteneurs pour exécuter des pods)
- buildah : pour construire, pousser et signer des images de conteneurs
- skopeo : pour copier, inspecter, supprimer et signer des images
- kaniko : pour construire des images de conteneurs à partir d’un Dockerfile, à l’intérieur d’un conteneur ou d’un cluster Kubernetes
- jib : pour construire des images de conteneurs pour des applications Java (intégré avec maven et gradle), avec une gestion intelligente de plusieurs couches pour des builds incrémentaux plus granulaires
Autres container runtimes de bas niveau
Nous avons déjà parlé de runc, probablement le container runtime de bas niveau le plus populaire, mais il existe aussi des projets plutôt intéressants (faisant partie de la cartographie CNCF dans la section container runtime) :
kata containers fournit une isolation plus poussée (comme des VMs), mais qui peut facilement s’intégrer à tout l’outillage existant autour des conteneurs. Cela signifie que vous pouvez démarrer ces VMs (ou pourrait-on dire, ces kata containers) via Kubernetes.
« Kata Containers est une communauté open source qui travaille à construire un container runtime sécurisé avec des machines virtuelles légères qui se comportent et performent comme des conteneurs, mais offrent une isolation plus forte des charges de travail en utilisant la virtualisation matérielle comme seconde couche de défense. »
Dans le même domaine, il y a gVisor de Google, qui crée un noyau invité spécialisé pour exécuter les conteneurs. Il limite la surface du noyau hôte accessible à l’application.
« gVisor est un noyau en espace utilisateur, écrit en Go, qui implémente une part substantielle de la surface système Linux. Il inclut un runtime Open Container Initiative (OCI) appelé runsc qui fournit une frontière d’isolation entre l’application et le noyau hôte. Le runtime runsc s’intègre avec Docker et Kubernetes, ce qui simplifie l’exécution de conteneurs en bac à sable (sandbox). »
Conclusion
Cet article était un peu théorique et représente ma compréhension de la jungle des container runtimes de Kubernetes. Je pense qu’il explique la situation actuelle, les progrès réalisés et donne un aperçu de l’avenir.
Beaucoup d’évolutions sont apparues depuis la naissance de Docker. La standardisation est une évolution naturelle lorsque les fonctionnalités et les acteurs se multiplient.
Une meilleure architecture s’est mise en place dans le monde des container runtimes de Kubernetes grâce à la CRI, avec une séparation des responsabilités et des outils dédiés à des tâches spécifiques.
Kata container, gVisor et les autres systèmes de virtualisation au sein de Kubernetes sont des technologies jeunes, mais très prometteuses.
L’usage de ces nouvelles technologies de virtualisation au sein de Kubernetes, combiné à l’annonce de VMware sur le projet Pacific (plaçant K8s au cœur de la plateforme vSphere), montre (de mon point de vue) la future convergence entre conteneurs et machines virtuelles.
Pour aller plus loin, découvrez notre formation Docker et l’accompagnement de nos experts Kubernetes.