Kubernetes · Chapitre 1

Les fondamentaux de Kubernetes

Conteneurs, orchestration, architecture d'un cluster et la façon dont Kubernetes communique avec les runtimes via CRI et OCI.

Avant de parler de Pods, de Deployments ou de Services, il faut comprendre deux idées : ce qu'est un conteneur et pourquoi il faut un orchestrateur pour en gérer beaucoup. Cette note parcourt ces fondamentaux et se termine par un point qui prête souvent à confusion : comment Kubernetes communique avec Docker, containerd et les autres runtimes.

C'est la première note de ma préparation à la certification KCNA (Kubernetes and Cloud Native Associate) de la CNCF.

Le problème que résolvent les conteneurs

Une application moderne est rarement un seul programme. Elle peut comprendre un serveur web, une base de données, un système de messagerie et un moteur d'automatisation, chacun avec ses propres bibliothèques et versions. Quand elle doit en plus fonctionner sur le portable du développeur, en test et en production, on obtient ce qu'on appelle la « matrice de l'enfer » : un immense tableau de combinaisons entre composants, bibliothèques et systèmes d'exploitation où la moindre mise à jour peut tout casser.

Les conteneurs règlent ce problème en empaquetant chaque composant avec toutes ses dépendances dans un environnement isolé. Ce qui fonctionne sur la machine du développeur fonctionne de la même façon en production, car c'est exactement le même paquet qui est livré.

Conteneurs et machines virtuelles

Un système d'exploitation a deux couches : le noyau (kernel), qui dialogue avec le matériel, et le logiciel par-dessus (bibliothèques, outils, gestionnaire de paquets) qui distingue Ubuntu de Fedora ou de Red Hat.

Comparaison entre machines virtuelles et conteneurs

  • Une machine virtuelle virtualise le matériel : chaque VM embarque son propre système d'exploitation complet, noyau compris. Elle isole beaucoup, mais pèse des gigaoctets et met des minutes à démarrer.
  • Un conteneur ne virtualise pas le système d'exploitation : il partage le noyau de l'hôte et n'empaquette que l'application et ses bibliothèques. Il pèse des mégaoctets et démarre en quelques secondes.

Comme toutes les distributions Linux utilisent le même noyau, un hôte Ubuntu peut exécuter un conteneur basé sur Fedora ou Red Hat : le conteneur apporte son propre espace utilisateur et utilise le noyau de l'hôte.

Attention avec Windows : un conteneur Windows a besoin d'un noyau Windows. Il ne peut pas tourner directement sur un hôte Linux, car ils ne partagent pas le même noyau.

Docker n'a pas inventé l'isolation : il s'appuie sur des fonctions du noyau Linux (namespaces et cgroups) que des technologies comme LXC utilisaient déjà. Son apport a été de rendre simples l'empaquetage, la distribution et l'exécution des conteneurs.

Images et conteneurs

Deux notions à ne pas confondre :

  • Une image est le modèle : un paquet en lecture seule avec l'application, ses dépendances et les instructions pour la démarrer.
  • Un conteneur est une instance en cours d'exécution de cette image. À partir d'une même image, on peut lancer de nombreux conteneurs.

Une image sert de modèle à plusieurs conteneurs

Les images sont stockées dans un registre. Le plus connu est Docker Hub, qui propose des images officielles de bases de données, de serveurs web et de langages, prêtes à l'emploi.

Premières commandes Docker

Sous Ubuntu ou Debian, Docker s'installe depuis les dépôts et une seule commande suffit pour l'essayer :

# Installer Docker (Ubuntu / Debian)
sudo apt-get update
sudo apt-get install -y docker.io

# Vérifier que tout fonctionne
sudo docker run hello-world

# Exécuter une commande dans un conteneur Alpine et le supprimer à la fin
sudo docker run --rm alpine echo "bonjour Tenci.io"

# Lancer des services à partir d'images officielles
sudo docker run -d --name mi-base mongo
sudo docker run -d --name mi-web -p 8080:80 nginx

docker run télécharge l'image si elle n'est pas présente sur la machine et crée un conteneur à partir de celle-ci.

L'orchestration de conteneurs

Lancer un conteneur, c'est facile. En lancer des centaines, répartis sur plusieurs serveurs, ne l'est pas : que se passe-t-il si un serveur tombe ? Comment monter en charge quand le trafic augmente ? Comment les conteneurs se trouvent-ils entre eux ?

L'orchestration, c'est l'automatisation de tout cela sur un groupe de machines (des nœuds) :

  • Haute disponibilité : si un conteneur ou un nœud tombe, il est recréé ailleurs.
  • Mise à l'échelle : ajouter ou retirer des copies selon la demande.
  • Répartition de charge entre les copies d'une application.
  • Configuration déclarative : on décrit dans des fichiers YAML l'état voulu du système, et l'orchestrateur se charge de l'atteindre et de le maintenir.

Il existe plusieurs options (Docker Swarm était l'une des plus simples), mais Kubernetes est devenu le standard de fait. Son installation est plus exigeante, en échange de bien plus de fonctionnalités et du plus grand écosystème du monde cloud native.

L'architecture de Kubernetes

Un cluster Kubernetes est un ensemble de nœuds qui jouent deux rôles distincts :

  • Le plan de contrôle (autrefois appelé master) : il prend les décisions du cluster. En production, il est répliqué sur plusieurs nœuds pour éviter un point de défaillance unique.
  • Les nœuds de travail (worker nodes, jadis appelés « minions ») : ils exécutent les conteneurs des applications.

Architecture d'un cluster Kubernetes

Composants principaux

Composant Où il se trouve Ce qu'il fait
kube-apiserver Plan de contrôle Le point d'entrée du cluster. Utilisateurs, outils et autres composants passent toujours par lui.
etcd Plan de contrôle Base de données clé-valeur distribuée qui stocke tout l'état du cluster. Elle garantit la cohérence entre plusieurs nœuds du plan de contrôle.
kube-scheduler Plan de contrôle Décide sur quel nœud s'exécute chaque nouveau conteneur (Pod). Il affecte, mais n'exécute pas.
kube-controller-manager Plan de contrôle Les contrôleurs surveillent l'état réel et agissent quand il ne correspond pas à l'état souhaité : si un nœud ou un conteneur tombe, ils réagissent. C'est le « cerveau » de l'orchestration.
kubelet Chaque nœud Agent qui reçoit les instructions du plan de contrôle et veille à ce que les conteneurs de son nœud tournent comme prévu.
Container runtime Chaque nœud Le logiciel qui exécute réellement les conteneurs : containerd, CRI-O, etc.

Un autre composant présent sur chaque nœud est kube-proxy, qui maintient les règles réseau permettant d'accéder aux services.

kubectl : la ligne de commande de Kubernetes

kubectl est l'outil pour gérer le cluster : créer des ressources, les consulter et vérifier leur état. Tout ce qu'il fait passe par le kube-apiserver.

# Informations sur le cluster et ses nœuds
kubectl cluster-info
kubectl get nodes

# Créer un Pod à partir d'une image
kubectl run hola-nginx --image=nginx

# Lister les Pods en cours d'exécution
kubectl get pods

Comment Kubernetes communique avec les runtimes : CRI et OCI

Au départ, Kubernetes ne savait travailler qu'avec Docker, grâce à du code écrit spécialement pour lui. Quand d'autres runtimes sont apparus, Kubernetes a défini une interface commune :

  • CRI (Container Runtime Interface) : le contrat, basé sur gRPC, que le kubelet utilise pour dialoguer avec n'importe quel runtime. Si un runtime implémente CRI, Kubernetes peut l'utiliser : containerd, CRI-O et d'autres.
  • OCI (Open Container Initiative) : le standard qui décrit les conteneurs eux-mêmes. Il comporte deux parties :
  • image-spec : comment une image doit être construite.
  • runtime-spec : comment un runtime doit se comporter quand il l'exécute.

Kubernetes communique avec les runtimes via CRI

La fin de dockershim

Docker est antérieur à CRI et ne l'a jamais implémenté. Pour continuer à le prendre en charge, Kubernetes a maintenu une couche de traduction appelée dockershim. C'était une solution temporaire, et elle a été complètement supprimée dans Kubernetes 1.24.

Cela veut-il dire que les images Docker ne fonctionnent plus ? Non. Les images construites avec Docker respectent le standard OCI, donc n'importe quel runtime compatible (comme containerd) les exécute sans modification. Ce qui a disparu, c'est l'utilisation du moteur Docker comme runtime du cluster.

Docker et containerd

Docker n'est pas qu'un runtime : c'est un ensemble d'outils (CLI, API, construction d'images, volumes, authentification, sécurité). En coulisses, Docker utilise containerd pour exécuter les conteneurs.

containerd est un projet indépendant et diplômé (graduated) de la CNCF. On peut l'installer seul, sans Docker, comme runtime de Kubernetes : c'est la configuration habituelle aujourd'hui quand le reste des outils Docker n'est pas nécessaire.

Outils en ligne de commande pour les runtimes

Sans Docker sur le nœud, il faut distinguer trois outils :

Outil Provient de À quoi il sert Usage quotidien ?
ctr containerd Déboguer containerd. Peu convivial et limité. Non
nerdctl Projet containerd CLI compatible Docker pour containerd. Oui, pour remplacer Docker
crictl Kubernetes Inspecter et déboguer tout runtime compatible CRI. Pour le diagnostic des nœuds

ctr

Il est fourni avec containerd. Il sert aux tests et au débogage, pas à l'administration d'environnements :

ctr images pull docker.io/library/redis:alpine
ctr run docker.io/library/redis:alpine redis

nerdctl

Il offre une expérience presque identique à Docker sur containerd, prend en charge Docker Compose et ajoute des fonctions que Docker n'a pas, comme le téléchargement paresseux des images (lazy pulling), les images chiffrées, la distribution P2P ainsi que la signature et la vérification des images :

nerdctl run --name redis redis:alpine
nerdctl run --name web -p 8080:80 -d nginx:latest

crictl

Il est maintenu par la communauté Kubernetes et fonctionne avec n'importe quel runtime CRI. Il s'installe séparément et sa syntaxe rappelle celle de Docker. Il est conçu pour inspecter et déboguer, pas pour créer des conteneurs : si vous créez quelque chose avec crictl en dehors du contrôle du kubelet, celui-ci peut le supprimer.

crictl pull busybox
crictl images
crictl pods
crictl ps -a
crictl logs <id-du-conteneur>
crictl exec -it <id-du-conteneur> ls

Une différence avec Docker : crictl comprend les Pods (crictl pods), car il parle la même langue que le kubelet.

Si le nœud a plusieurs runtimes, on indique celui à utiliser :

crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps

# ou de façon permanente pour la session
export CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock

À retenir

  • Un conteneur partage le noyau de l'hôte ; une VM embarque son propre système d'exploitation.
  • Une image est le modèle ; un conteneur, une instance en cours d'exécution.
  • L'orchestration automatise la disponibilité, la mise à l'échelle, la répartition de charge et la configuration déclarative. Kubernetes est le standard.
  • Un cluster comprend un plan de contrôle (kube-apiserver, etcd, kube-scheduler, kube-controller-manager) et des nœuds de travail (kubelet, runtime, kube-proxy).
  • Le kubelet dialogue avec les runtimes via CRI ; les images suivent le standard OCI.
  • dockershim a été supprimé dans Kubernetes 1.24 ; les images Docker fonctionnent toujours.
  • ctr pour déboguer containerd, nerdctl pour remplacer Docker, crictl pour déboguer n'importe quel runtime CRI.

Dans la prochaine note : les ressources de base de Kubernetes, à commencer par les Pods.