Comment Kubernetes applique le principe du desired state

5 min de lecture
Comment Kubernetes applique le principe du desired state

Cet article a été initialement publié sur Medium

Le desired state (état désiré) est l’un des concepts fondamentaux de Kubernetes. Via une API déclarative ou impérative (je reviendrai sur cette notion plus loin), vous décrivez l’état des objets (Pod, ReplicaSet, Deployment, …) qui feront tourner les conteneurs.

Kubernetes, via les « Kube controller managers » (qui font partie des composants du master), est chargé de réguler l’état du système. Le composant kubelet (présent sur chaque worker node) reçoit les requêtes et applique la nouvelle configuration pour faire tourner les conteneurs. L’état, la configuration, les API Objects, … sont stockés dans un stockage clé/valeur distribué simple : le cluster etcd.

Si, suite à une panne, un conteneur s’arrête, le kubelet recrée le Pod à partir des informations du desired state stockées dans etcd.

Kubernetes garantit ainsi strictement que tous les conteneurs qui tournent sur le cluster sont toujours dans l’état désiré.

Voici par exemple un objet Deployment Kubernetes simple :

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: nginx
  template:
    metadata:
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx:1.15.8
        name: nginx
        ports:
        - containerPort: 80

Qui est déployé via la commande suivante :

kubectl apply -f deployment.yaml

Cet exemple très simple met en évidence plusieurs points importants.

Les Deployments (comme les autres objets) de Kubernetes sont déclaratifs : l’utilisateur spécifie le nombre d’instances d’une version donnée d’un Pod à déployer, et Kubernetes calcule les actions nécessaires pour passer de l’état courant à l’état désiré, en déployant ou en supprimant des Pods sur les Nodes.

Si vous connaissez les fichiers de configuration Kubernetes, vous avez déjà remarqué les champs apiVersion et kind au début de chaque fichier, qui décrivent le type d’objet, sa version et son group.

Cela peut prêter à confusion, car vous verrez de nombreuses versions différentes comme apps/v1beta2, apps/v1, etc. et même sans group … Les choses évoluent très vite dans l’univers Kubernetes, la notion de group a été ajoutée plus tard pour faciliter l’extension de l’API Kubernetes et découper le serveur API monolithique en composants plus petits, permettant d’activer ou de désactiver des groupes …

Alors, comment savoir quelles versions utiliser ? Il suffit d’interroger le serveur API via la CLI

kubectl api-versions:

Kubectl api-version

La sémantique de la version et du group est bien expliquée dans la documentation Kubernetes : https://kubernetes.io/docs/reference/using-api/#api-versioning

et si vous voulez lister les ressources « kind » avec leur group API associé, utilisez simplement

kubectl api-resources -o wide

kubectl api-resource -o wide

Pour connaître la dernière apiVersion supportée d’une ressource, utilisez la commande kubectl explain StatefulSet ou consultez la documentation de référence de l’API : https://kubernetes.io/docs/reference/#api-reference

Dans la commande précédente, j’ai utilisé « -o wide » pour afficher les verbes associés qui peuvent être utilisés pour interagir avec la ressource. Le style général de l’API Kubernetes est RESTful : les clients créent, mettent à jour, suppriment ou récupèrent la description d’un objet via les verbes HTTP standards (POST, PUT, DELETE et GET), et ces API acceptent et retournent du JSON (Kubernetes expose également des endpoints supplémentaires pour des verbes non standards et permet d’autres types de contenu).

Si l’on regarde sous le capot comment fonctionnent les différents composants Kubernetes, on constate qu’ils utilisent tous la même API. Le control plane (API Server, Controllers, Schedulers, etcd, …) est transparent, il n’existe aucune API interne cachée.

Vous pouvez facilement explorer l’API utilisée par la CLI en augmentant la verbosité :

kubectl get deployments -v=7

et vous pouvez ensuite utiliser la même URI REST que celle affichée dans les logs pour lister les deployments via l’url :

curl http://localhost:8080/apis/extensions/v1beta1/namespaces/default/deployments?limit=500

Astuce : pour accéder à une API Kubernetes distante, vous pouvez utiliser la commande kubectl proxy qui agit comme un reverse proxy (elle gère la localisation du serveur API ainsi que l’authentification), voir https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/

Le fait que les composants Kubernetes utilisent tous la même API externe rend Kubernetes composable et extensible.

Kubernetes peut donc gérer une API déclarative et une API impérative.

Dans mon premier exemple, j’ai utilisé l’approche déclarative en créant un fichier yaml et en utilisant la commande kubectl apply.

J’aurais pu, à la place, utiliser l’approche impérative :

kubectl run nginx --image=nginx:1.15.8 --replicas=2 --port=80

Je recommande vivement d’utiliser la première approche. Elle permet aux utilisateurs de commit (dans un système de SCM) l’état désiré du cluster, et de garder une trace des différentes versions, ce qui améliore l’auditabilité et l’automatisation via les pipelines CI/CD.

Cela signifie-t-il que l’API impérative est inutile ?

Bien sûr que non ! Il existe plusieurs cas d’usage où l’API impérative a du sens. Par exemple pour interroger l’état des objets, des clusters, … Elle peut être utilisée par des outils de monitoring ou pour du troubleshooting sur certains problèmes… Un autre cas d’usage très pratique consiste à utiliser l’API impérative pour l’approche déclarative, grâce aux options –dry-run et -o yaml :

kubectl run nginx --image=nginx:1.15.8 --replicas=2 --port=80 --dry-run -o yaml > deployment.yaml

L’utilisation de ces options permet de ne faire qu’afficher l’objet sans l’envoyer.

Vous pouvez choisir le type de ressource à générer via l’option –generator

par exemple pour générer une ressource pod :

kubectl run --generator=run-pod/v1 nginx-pod --image=nginx:1.15.8 --port=80 --dry-run -o yaml > pod.yaml

au lieu d’utiliser l’option –generator, vous pouvez aussi jouer avec l’option –restart (–restart=Never générera un objet pod)

Pour plus d’informations, consultez cette page : https://kubernetes.io/docs/reference/kubectl/conventions/

C’est un moyen très rapide de « bootstrapper » un nouvel objet Kubernetes plutôt que d’écrire un fichier yaml depuis zéro !

Cela m’a énormément aidé lorsque j’ai passé l’examen de certification CKAD.

Conclusion

La gestion de configuration déclarative est l’une des forces majeures de Kubernetes, dans laquelle on définit tout sous forme de desired state.

Cela correspond parfaitement aux exigences du GitOps, qui permet d’utiliser les pull requests Git pour gérer le provisionnement de l’infrastructure et le déploiement des logiciels.

Cela signifie que l’application et l’infrastructure sont désormais toutes deux des artefacts versionnés, faciles à auditer, ce qui est indispensable dans le développement et la livraison logicielle.

Pour aller plus loin, découvrez notre formation Kubernetes et l’accompagnement de nos experts Kubernetes.