Drôle d’ambiance autour de Kubernetes, en ce moment. Le Docker bashing à toujours la cote : des gens avec des avis tranchés, qui ont réussi avec arrogance, là où LXC pataugeait depuis toujours, pour finalement aboutir à une norme et des spécifications. Mais, en même temps, Kubernetes, qui est prêt pour la prod, lui, est encensé, attendu comme le messie. Ça donne des tweets de ce genre.
Services
Commençons par le commencement, votre application est orientée service : c’est l’assemblage de différents services. Un truc comme django/celery/postgresql/redis ou même php-fpm/mariadb/memcache/varnish/nginx ou que sais-je dans ce gout-là.
Si votre application est un gros binaire qui contient tout, un peu comme la boite contenant le mouton du Petit Prince, félicitation, vous faites du Zope, vous n’avez pas à vous enquiquiner avec ces histoires de Kubernetes.
Microservices
La question n’est pas là, utiliser des services est déjà suffisant, et ce sera encore plus fun avec des microservices.
Conteneur
Docker quoi, on peut tourner autour du pot, mais même avec la normalisation de l’OCI, Docker est pour l’instant le standard de fait. Le travail d’intégration sur les postes de travail, sur Windows/Mac/Linux, est trop énorme (et trop pénible) pour être paraphrasé par un concurrent.
Un conteneur, c’est juste un format de livraison d’un service standardisé, immuable, et la possibilité de le lancer avec des paramètres, de l’isolation et des contraintes matérielles (RAM, CPU, IO).
Son immuabilité renforce et facilite la mise en place de tests fonctionnels et autres étapes de qualification.
La construction déterministe, et le résultat déterminé facilitent le déploiement, sur un ou plusieurs serveurs, et le retour en arrière en cas de drame.
Les conteneurs donnent aux développeurs la responsabilité de la création du conteneur, il choisit les paquets et applications nécessaires. Ces choix sont décrits de manière simple et lisible, qui seront versionnés, deux points qui facilitent l’analyse statique et les audits.
L’étape de créations des images et les tests/analyses qui suivent doivent être construits depuis un service d’intégration continue.
La recette de build automatique, les versions d’outils explicites, et les différentes astuces pour faciliter le développement diminuent drastiquement le ticket d’entrée pour un nouveau développeur sur un projet. Avec une VM, on comptait un jour, avec des conteneurs, une heure.
Composition
Docker-compose est le standard de fait pour décrire la composition de services, hey wouais, l’OCI n’a encore rien dit là-dessus.
Tout comme le Dockerfile
décrit de manière non ambigüe la création d’une image, docker-compose.yml
va décrire l’assemblage de différents services, avec les paramètres et les liens entre eux.
L’outil docker-compose
va gérer tranquillement tout plein de détails pénibles, comme la création d’un réseau isolé, les dépendances entres services, les logs, et même un peu de scale. Il est bon pour mettre à jour le service que l’on vient de mettre à jour et de relancer les services qui l’utilisent, sans forcément tout relancer.
Techniquement, le fichier docker-compose.yml
expose à peu près tous les choix que peut faire un développeur pour décrire son application. Bon, Swarm a un peu cochonné le format, mais ça reste anecdotique. Pour ce qui manque, les labels permettent de décrire les intentions sans trop de difficultés ou de circonvolutions.
Le format du fichier docker-compose.yml
permet de générer des configurations vers d’autres outils : - Kompose pour Kubernetes - dce-go pour Mesos - Bon, Nomad est tellement peu opiniated qu’il faudra faire vos choix et votre moulinette.
Supervision
Le service dockerd
(containerd
dans les faits) est aussi un superviseur, il va suivre et commander le cycle de vie des différents conteneurs.
dockerd
empiète un peu sur systemd
, et les deux projets aiment ce jeter des cailloux dessus.
Le monde se divise en deux catégories, enfin, le monde, les services : systèmes et applicatif. Les applications s’appuyant sur les services systèmes (en dessous), pour être exposées à des utilisateurs (au-dessus).
Systemd, ça gère la couche système, et seul l’utilisateur root peut le manipuler.
La première cible de Docker est la couche applicative, et n’importe qui avec le bon certificat SSL ou le bon groupe peut l’utiliser. Ça ne veut pas pour autant dire que c’est une super idée d’avoir des utilisateurs qui tripote des dockers en prod depuis la console.
Il est tout à fait légitime d’avoir un service lancé par systemd
qui gère des conteneurs systèmes .C’est comme ça qu’est implémenté Kubernetes, d’ailleurs, avec son kubelet
, et les namespaces de containerd
vont pouvoir encore mieux isoler ces groupes distincts de conteneurs.
Par contre, vouloir orchestrer un ensemble de services formant une application avec un ensemble de fichiers `.service
systemd est une drôle d’idée. Les conteneurs sont des fils des docker-containerd-shim
qui vont cafter au démon containerd
l’état du conteneur, excluant complètement systemd
de la boucle, qui serait bien incapable de superviser quoi que ce soit.
Instrumentation
Docker va gérer des services, bien rangés dans des cgroups qui maintiennent des métriques, et branche STDOUT vers une sortie quelconque, un fichier ou un service de logging. 12 factors prétends que ça suffit, mais c’est juste le strict minimum au niveau observabilité.
Il faut passer rapidement à des trucs plus sérieux, comme des rapports d’erreurs avec Sentry, des logs structurés (au format fluentd), des mesures métiers avec les exports prometheus pour les technos asynchrones, et statsd pour les autres.
Routage
C’est bien, d’avoir plein de conteneurs, mais vous allez exposer tout ça, dans la plupart des cas avec une seule IP et un seul port, 443 à priori (pour bien faire, cette IP sera flottante, et vous la collerez à une machine virtuelle en vie).
Il va donc falloir un peu de coordination entre les conteneurs et des règles de routages (et de la répartition de charge).
Persistance
C’est bien d’avoir des conteneurs “quelque part”, qui bougent en fonction des vagues. Mais cette mobilité n’est possible qu’avec des conteneurs sans état, état qui va bien devoir être quelque part. Pour ça, il faut une solution de stockage distribué, en mode blob, à la S3 (ou Minio), ou en mode block, à la iSCSI. Donc, du Ceph ou une offre SAAS spécifique et propriétaire que votre fournisseur de nuage vous proposera gentiment.
Orchestration
Pour gérer un ensemble de conteneurs sur plusieurs machines, il faut de l’orchestration. L’orchestrateur a la charge de décider comment les conteneurs vont être répartis sur les différentes machines, que faire quand un noeud disparait ou quand un nouveau apparait.
L’orchestrateur va s’appuyer sur service clef/valeur distribué (comme etcd) pour mettre à disposition de tout le monde tout ce qui est paramétrage et maintenir une carte de l’existant.
L’orchestrateur va réagir à un ensemble d’évènements, et en réponse à des commandes, déclencher des évènements (et des actions) comme les stratégies de mises à jour des conteneurs, ou la mythique promesse du Cloud : lancer des VM pour empiler encore plus de conteneurs pour accompagner un pic de charge, puis les éteindre tranquillement une fois la vague passée.
Voilà, là, vous êtes à l’étape Kubernetes, et pour ça, vous avez pieusement suivi toutes les étapes précédentes, car vous avez besoin de plusieurs serveurs pour héberger votre application.
La vieille promesse de l’élastique du nuage n’est possible qu’avec de l’orchestration, et donc des conteneurs, de l’intégration continue, de l’observabilité, une séparation nette entre l’expression de l’intention (sous la responsabilité des devs) et les choix de son implémentation (sous la responsabilité des ops).