Docker est l'implémentation de référence pour gérer des conteneurs : le moyen simple et systématique de créer un service, sur son poste de travail et/ou dans de l'intégration continue, de le livrer, pour finalement le lancer dans un contexte bordé.
Une application est rarement constituée d'un service unique, mais d'un ensemble de services, avec souvent une base de données dans la boucle.
Docker-compose
Au départ nommé Fig, le projet est rapidement absorbé par Docker pour devenir docker-compose.
Docker-Compose permet de décrire un ensemble de services, pouvant être dépendants les uns des autres. En suivant le paradigme 12 factors, l'utilisateur pousse des variables d'environnement pour avoir des paramètres différents en fonction de la cible de déploiement (développement local, préprod, prod…).
Le projet est écrit en python, et clairement, il fait le job.
Kubernetes
Très vite, un an après Docker, apparait Kubernetes, qui mise tout sur l'environnement de production, constitué de plusieurs machines, avec une automatisation de la répartition des conteneurs en cas d'incidents matériels, ou d'ajouts de nouveaux noeuds au cluster.
Kubernetes, k8s pour les intimes, cible des projets de taille conséquente, et permet plein de réglages liés à l'exploitation, bien au-delà de la définition du service par le développeur.
K8s utilise une abstraction réseau (enfin, une interface, c'est à vous de choisir l'implémentation), qui est souvent le point douloureux de k8s, mais ne vous inquiétez pas, les offres des gros du Cloud utilisent leur propre abstraction réseau, ce qui de fait, met le ticket d'entrée trop haut pour pas mal d'hébergeurs.
AWS, Google Cloud et Azure ont rapidement proposé des offres infogérées de k8s.
Vous avez un VLAN, un load-balancer qui pointera vers votre Ingress, des disques distants, un stockage objet à la S3, un rangement des logs.
Vous pouvez ajouter des VMs dans votre cluster, et k8s va ventiler les conteneurs sur les VMs.
Profiter d'une offre managée est clairement le moyen le plus serein de faire du k8s, mais clairement pas celui de déployer du conteneur.
Il existe une passerelle, Kompose qui permet de convertir un docker-compose.yml pour l'utiliser dans Kubernetes.
Docker Swarm
Docker a travaillé sur Swarm, qui permet de déployer son docker-compose sur un cluster, mais sans arriver à sortir de l'ombre de k8s.
Nomad
Hashicorp propose une approche inverse, avec Nomad, un outil de gestion de cluster qui peut, entre autres, utiliser des conteneurs. Principal différence avec k8s, il n'y a pas d'abstraction réseau, c'est à vous de gérer ça. Il n'y pas non plus de YAML, mais bon, il y a du HCL le concurrent maison.
Comme Nomad est poli, il expose toutes les options pour lancer un conteneur, tout comme Docker-Compose; il est donc aisé de mouliner un docker-compose.yml vers Nomad, mais si il n'existe pas d'outils tout prêt: vous allez devoir prendre des décisions liées aux choix techniques que vous avez pris pour votre Cluster.
Retour à Compose
(Photo Katieleeosborne, CC BY SA, trouvée ici)
OK, il y a plein de façons de déployer ses conteneurs, mais il y a clairement un moyen évident de définir la topologie de son application : Compose.
Le fichier docker-compose.yml est devenu le standard de fait pour décrire l'architecture de son application, utilisable par l'équipe de développement, et comme contrat pour l'hébergement.
AWS et Azure ont découvert qu'il y avait aussi un marché pour le conteneur raisonnable, et que le plus simple était d'exposer directement du docker-compose.yml (et une registry privée pour les images).
C'est ces offres qui ont poussé le développement de compose v2, dans le README, à la section remerciements, il y a les noms de gens du Cloud qui ont bossé dessus.
L'écosystème de Docker (mais aussi celui de k8s, Nomad…) est intimement lié à Golang, pourtant, pour des raisons historique, Docker-Compose v1 est écrit en python. Python s'est toujours intégré avec élégance dans l'environnement Docker, le client python docker est beau, mais… ce serait quand même plus simple d'avoir un compose en golang, directement intégré au cli docker.
Il y a eut un libcompose jamais abouti, toujours stressé de gérer les avancées du Docker-Compose v1.
Le plus simple est d'avoir un Compose en golang, qui remplace la V1 en Python et qui prends le lead.
Normaliser Docker
Docker est parti très vite, très fort, avec des décisions clivantes.
Une fois la concurrence éradiquée (RKT, LXC …), les partenaires de l'écosystème (k8s essentiellement), ont collé la pression pour normaliser tout ça.
Docker a créé son pendant libre Moby qui contient les couches basses avec plein de kits, et surtout containerd, la couche basse et consensuelle sur laquelle s'appuie Docker et sa série de choix opiniated.
Kubernetes a récemment décidé de remplacer Docker par la couche bas niveau Containerd. Rien de révolutionnaire, on continue à pousser des images docker dans une Registry pour déployer dans son cluster, mais c'est plus simple. K8s gère ses conteneurs avec des RPC adéquat (grpc et ttrpc), plutôt que du REST.
Ensuite, pas mal de spécifications (avec des implémentations de référence) :
Création de l'OCI, Open Container Initiative, aka opencontainers
image-spec, les images, un gros chroot avec un manifest, rien de bien compliqué
runtime-spec, un runtime, le kernel Linux avec ses Namespaces, Cgroup et autres outils. Les specs évoquent aussi Solaris.
runtime-spec précise comment lancer un conteneur, avec runc comme implémentation de référence, mais il a des alternatives, comme crun, gVisor, youki. Oui, pour le dernier, Gotainer n'assume pas la référence.
distribution-spec qui définit la livraison et le stockage des images (la Registry, quoi).
image-spec définit le format des images avec artifacts, l'implémentation de référence.
compose-spec normalise la définition d'une application composée de services, ce qui a permis l'émergence de podman-compose ou de nerdctl compose (le cli qui utilise directement Containerd).
RedHat a profité de tout ça pour sortir son Podman qui se focalise du conteneur rootless, Buildah qui permet de construire des images sans fichier Dockerfile, et OpenShift une surcouche à K8s.
Nerdctl propose aussi du rootless (tout comme Docker maintenant), mais aussi du stream d'images, leur partage en P2P, leur chiffrement…
Tout ces outils ne sont pas forcément super convaincants, mais c'est toujours bien d'avoir plusieurs implémentations d'une norme, cela permet de valider la spécification, et d'essayer des choses qui profiteront à tout le monde.
Desktop
Docker a bien stabilisé son produit sur le poste de développeurs, avec Docker Desktop, un gros effort sur Mac et Windows, pour gérer la virtualisation native, mais surtout proposer du réseau (vpnkit) et exposer des fichiers (avec grpc-FUSE, et plus tard virtio-FS). Dans la machine virtualisée, un docker daemon docker tourne, et un docker client natif peut lui causer depuis l'hôte.
Toute cette gestion de virtualisation n'est pas utile sur un poste Linux, qui va tout simplement utiliser le daemon et client Docker, avec peut-être des pinaillages comme btrfs pour les layers.
Docker-Desktop est maintant disponible en béta pour Linux.
Docker-Desktop intègre un ensemble de services autour de Docker, ainsi qu'une interface minimaliste. AMHA, la réelle utilité de Docker-Desktop est de permettre un cycle rapide de mises à jour, et de proposer des fonctionnalités en béta, pour avoir rapidement des retours utilisateurs.
Un Docker-Desktop sous Linux est prévu à courte échéance, sans toute la stack de virtualisation, mais avec une petite UI... curieux de voir ce que ça apportera.
Compose 2
Dans les nouveautés importantes fournies par Docker-Desktop, il y a la mise en place de Compose v2, aka docker compose (et non plus docker-compose).
Compose arrête d'embêter les gens en exigeant un numéro de version. Le format est maintenant stable et spécifié.
On peut faire les fous à distance avec des offres clouds.
docker compose convert normalise le fichier, en utilisant les formats longs et explicites. C'est indispensable pour le confier à une validation ou conversion.
Les profiles permettent de définir des services outils qui ne seront pas déployés en production.
Sous le capot
Docker-compose v1 fait le job, mais il n'a jamais proposé une API stable, permettant de coder des applications qui lancent des grappes de services. Le plus sage est de passer par un subprocess.Popen qui va lancer le cli, en se branchant sur STDIN/STDOUT/STDERR. Pragmatique, mais rustre.
Docker-compose v2, lui, part de l'autre pied : API first.
Il y a un projet dédié au modèle objet qui implémente les spécifications : compose-spec/compose-go.
Le typage fort de Golang adore cette spécification de bout en bout, tout le bazar JSON/YAML est définit dans des structs ou des interfaces, laissant très peu de place aux interprétations.
Pour créer un compose, compose.NewComposeService part d'un client Docker avec sa configuration, pour créer un api.Service.
Vous avez là un projet compose théorique, impalpable. Vous pouvez naviguer dans le graphe acyclique des services avec un compose.NewGraph.
Pour rattacher votre compose au monde réel, vous allez ajouter à votre api.Service un types.ConfigDetails, soit un dossier de travail, des variables d'environnement, et une suite de fichiers docker-compose.yml.
On est sur un mapping un pour un avec le cli : vous êtes dans un dossier avec un .env et un docker-compose.yml.
On load ensuite son types.ConfigDetails dans un loader.Load où l'on peut spécifier le nom du projet, d'activer l'interpolation et d'autres options, pour obtenir un types.Project
Il est possible de bidouiller le projet, lire les labels, changer les chemins des volumes, débrancher des services, ajouter des contraintes, tout ce que permet Docker en fait.
Le types.Project permet d'utiliser des api.Service qui est l'équivalent des actions disponibles à docker compose, comme run, up, start… avec un io.Writer pour STDOUT et un autre pour STDERR.
Pour modifier des actions "juste à temps", il est possible d'utiliser un api.ServiceProxy plutôt que directement un api.Service.
L'API permet d'utiliser et de maitriser de bout en bout un projet Compose.
Microdensity
Que peut-on faire avec une si belle API?
Microdensity est un simple service REST permettant de mettre à disposition des outils d'analyse fonctionnelle de sites webs, et de fournir des badges.
Un docker-compose.yml contient l'outil d'analyse (pa11y, sitespeed, lighthouse…).
Un bout de javascript va valider et convertir les arguments venant du body HTTP POST en ENV, ou en fichiers de conf.
Les arguments, env et conf, sont confiés au docker compose run, les résultats seront écrits dans un volume, qui sera exposé en HTTP, pour qu'ils puissent être lus.
Ces outils peuvent utiliser un Chrome en boîte fournit par browserless, avec du CPU dédié, pour avoir des mesures reproductibles.
µdensity ne se contente pas d'exposer en REST des Docker-compose, mais propose une intégration simple à Gitlab, profitant du jeton JWT mis à disposition dans la CI. Ceci nous permet d'avoir des analyses de qualité, asynchrones et donc non bloquantes pour se soucier de la qualité sur un temps plus long que le rythme de développement avec ses tests unitaires et fonctionnels.
Compose a clairement sa place dans la chaine de développement d'un site/service web, du poste de dev à la production.
Une application est rarement constituée d'un service unique, mais d'un ensemble de services, avec souvent une base de données dans la boucle.
Docker-compose
Au départ nommé Fig, le projet est rapidement absorbé par Docker pour devenir docker-compose.
Docker-Compose permet de décrire un ensemble de services, pouvant être dépendants les uns des autres. En suivant le paradigme 12 factors, l'utilisateur pousse des variables d'environnement pour avoir des paramètres différents en fonction de la cible de déploiement (développement local, préprod, prod…).
Le projet est écrit en python, et clairement, il fait le job.
Kubernetes
Très vite, un an après Docker, apparait Kubernetes, qui mise tout sur l'environnement de production, constitué de plusieurs machines, avec une automatisation de la répartition des conteneurs en cas d'incidents matériels, ou d'ajouts de nouveaux noeuds au cluster.
Kubernetes, k8s pour les intimes, cible des projets de taille conséquente, et permet plein de réglages liés à l'exploitation, bien au-delà de la définition du service par le développeur.
K8s utilise une abstraction réseau (enfin, une interface, c'est à vous de choisir l'implémentation), qui est souvent le point douloureux de k8s, mais ne vous inquiétez pas, les offres des gros du Cloud utilisent leur propre abstraction réseau, ce qui de fait, met le ticket d'entrée trop haut pour pas mal d'hébergeurs.
AWS, Google Cloud et Azure ont rapidement proposé des offres infogérées de k8s.
Vous avez un VLAN, un load-balancer qui pointera vers votre Ingress, des disques distants, un stockage objet à la S3, un rangement des logs.
Vous pouvez ajouter des VMs dans votre cluster, et k8s va ventiler les conteneurs sur les VMs.
Profiter d'une offre managée est clairement le moyen le plus serein de faire du k8s, mais clairement pas celui de déployer du conteneur.
Il existe une passerelle, Kompose qui permet de convertir un docker-compose.yml pour l'utiliser dans Kubernetes.
Docker Swarm
Docker a travaillé sur Swarm, qui permet de déployer son docker-compose sur un cluster, mais sans arriver à sortir de l'ombre de k8s.
Nomad
Hashicorp propose une approche inverse, avec Nomad, un outil de gestion de cluster qui peut, entre autres, utiliser des conteneurs. Principal différence avec k8s, il n'y a pas d'abstraction réseau, c'est à vous de gérer ça. Il n'y pas non plus de YAML, mais bon, il y a du HCL le concurrent maison.
Comme Nomad est poli, il expose toutes les options pour lancer un conteneur, tout comme Docker-Compose; il est donc aisé de mouliner un docker-compose.yml vers Nomad, mais si il n'existe pas d'outils tout prêt: vous allez devoir prendre des décisions liées aux choix techniques que vous avez pris pour votre Cluster.
Retour à Compose
(Photo Katieleeosborne, CC BY SA, trouvée ici)
OK, il y a plein de façons de déployer ses conteneurs, mais il y a clairement un moyen évident de définir la topologie de son application : Compose.
Le fichier docker-compose.yml est devenu le standard de fait pour décrire l'architecture de son application, utilisable par l'équipe de développement, et comme contrat pour l'hébergement.
AWS et Azure ont découvert qu'il y avait aussi un marché pour le conteneur raisonnable, et que le plus simple était d'exposer directement du docker-compose.yml (et une registry privée pour les images).
C'est ces offres qui ont poussé le développement de compose v2, dans le README, à la section remerciements, il y a les noms de gens du Cloud qui ont bossé dessus.
L'écosystème de Docker (mais aussi celui de k8s, Nomad…) est intimement lié à Golang, pourtant, pour des raisons historique, Docker-Compose v1 est écrit en python. Python s'est toujours intégré avec élégance dans l'environnement Docker, le client python docker est beau, mais… ce serait quand même plus simple d'avoir un compose en golang, directement intégré au cli docker.
Il y a eut un libcompose jamais abouti, toujours stressé de gérer les avancées du Docker-Compose v1.
Le plus simple est d'avoir un Compose en golang, qui remplace la V1 en Python et qui prends le lead.
Normaliser Docker
Docker est parti très vite, très fort, avec des décisions clivantes.
Une fois la concurrence éradiquée (RKT, LXC …), les partenaires de l'écosystème (k8s essentiellement), ont collé la pression pour normaliser tout ça.
Docker a créé son pendant libre Moby qui contient les couches basses avec plein de kits, et surtout containerd, la couche basse et consensuelle sur laquelle s'appuie Docker et sa série de choix opiniated.
Kubernetes a récemment décidé de remplacer Docker par la couche bas niveau Containerd. Rien de révolutionnaire, on continue à pousser des images docker dans une Registry pour déployer dans son cluster, mais c'est plus simple. K8s gère ses conteneurs avec des RPC adéquat (grpc et ttrpc), plutôt que du REST.
Ensuite, pas mal de spécifications (avec des implémentations de référence) :
Création de l'OCI, Open Container Initiative, aka opencontainers
image-spec, les images, un gros chroot avec un manifest, rien de bien compliqué
runtime-spec, un runtime, le kernel Linux avec ses Namespaces, Cgroup et autres outils. Les specs évoquent aussi Solaris.
runtime-spec précise comment lancer un conteneur, avec runc comme implémentation de référence, mais il a des alternatives, comme crun, gVisor, youki. Oui, pour le dernier, Gotainer n'assume pas la référence.
distribution-spec qui définit la livraison et le stockage des images (la Registry, quoi).
image-spec définit le format des images avec artifacts, l'implémentation de référence.
compose-spec normalise la définition d'une application composée de services, ce qui a permis l'émergence de podman-compose ou de nerdctl compose (le cli qui utilise directement Containerd).
RedHat a profité de tout ça pour sortir son Podman qui se focalise du conteneur rootless, Buildah qui permet de construire des images sans fichier Dockerfile, et OpenShift une surcouche à K8s.
Nerdctl propose aussi du rootless (tout comme Docker maintenant), mais aussi du stream d'images, leur partage en P2P, leur chiffrement…
Tout ces outils ne sont pas forcément super convaincants, mais c'est toujours bien d'avoir plusieurs implémentations d'une norme, cela permet de valider la spécification, et d'essayer des choses qui profiteront à tout le monde.
Desktop
Docker a bien stabilisé son produit sur le poste de développeurs, avec Docker Desktop, un gros effort sur Mac et Windows, pour gérer la virtualisation native, mais surtout proposer du réseau (vpnkit) et exposer des fichiers (avec grpc-FUSE, et plus tard virtio-FS). Dans la machine virtualisée, un docker daemon docker tourne, et un docker client natif peut lui causer depuis l'hôte.
Toute cette gestion de virtualisation n'est pas utile sur un poste Linux, qui va tout simplement utiliser le daemon et client Docker, avec peut-être des pinaillages comme btrfs pour les layers.
Docker-Desktop est maintant disponible en béta pour Linux.
Docker-Desktop intègre un ensemble de services autour de Docker, ainsi qu'une interface minimaliste. AMHA, la réelle utilité de Docker-Desktop est de permettre un cycle rapide de mises à jour, et de proposer des fonctionnalités en béta, pour avoir rapidement des retours utilisateurs.
Un Docker-Desktop sous Linux est prévu à courte échéance, sans toute la stack de virtualisation, mais avec une petite UI... curieux de voir ce que ça apportera.
Compose 2
Dans les nouveautés importantes fournies par Docker-Desktop, il y a la mise en place de Compose v2, aka docker compose (et non plus docker-compose).
Compose arrête d'embêter les gens en exigeant un numéro de version. Le format est maintenant stable et spécifié.
On peut faire les fous à distance avec des offres clouds.
docker compose convert normalise le fichier, en utilisant les formats longs et explicites. C'est indispensable pour le confier à une validation ou conversion.
Les profiles permettent de définir des services outils qui ne seront pas déployés en production.
Sous le capot
Docker-compose v1 fait le job, mais il n'a jamais proposé une API stable, permettant de coder des applications qui lancent des grappes de services. Le plus sage est de passer par un subprocess.Popen qui va lancer le cli, en se branchant sur STDIN/STDOUT/STDERR. Pragmatique, mais rustre.
Docker-compose v2, lui, part de l'autre pied : API first.
Il y a un projet dédié au modèle objet qui implémente les spécifications : compose-spec/compose-go.
Le typage fort de Golang adore cette spécification de bout en bout, tout le bazar JSON/YAML est définit dans des structs ou des interfaces, laissant très peu de place aux interprétations.
Pour créer un compose, compose.NewComposeService part d'un client Docker avec sa configuration, pour créer un api.Service.
Vous avez là un projet compose théorique, impalpable. Vous pouvez naviguer dans le graphe acyclique des services avec un compose.NewGraph.
Pour rattacher votre compose au monde réel, vous allez ajouter à votre api.Service un types.ConfigDetails, soit un dossier de travail, des variables d'environnement, et une suite de fichiers docker-compose.yml.
On est sur un mapping un pour un avec le cli : vous êtes dans un dossier avec un .env et un docker-compose.yml.
On load ensuite son types.ConfigDetails dans un loader.Load où l'on peut spécifier le nom du projet, d'activer l'interpolation et d'autres options, pour obtenir un types.Project
Il est possible de bidouiller le projet, lire les labels, changer les chemins des volumes, débrancher des services, ajouter des contraintes, tout ce que permet Docker en fait.
Le types.Project permet d'utiliser des api.Service qui est l'équivalent des actions disponibles à docker compose, comme run, up, start… avec un io.Writer pour STDOUT et un autre pour STDERR.
Pour modifier des actions "juste à temps", il est possible d'utiliser un api.ServiceProxy plutôt que directement un api.Service.
L'API permet d'utiliser et de maitriser de bout en bout un projet Compose.
Microdensity
Que peut-on faire avec une si belle API?
Microdensity est un simple service REST permettant de mettre à disposition des outils d'analyse fonctionnelle de sites webs, et de fournir des badges.
Un docker-compose.yml contient l'outil d'analyse (pa11y, sitespeed, lighthouse…).
Un bout de javascript va valider et convertir les arguments venant du body HTTP POST en ENV, ou en fichiers de conf.
Les arguments, env et conf, sont confiés au docker compose run, les résultats seront écrits dans un volume, qui sera exposé en HTTP, pour qu'ils puissent être lus.
Ces outils peuvent utiliser un Chrome en boîte fournit par browserless, avec du CPU dédié, pour avoir des mesures reproductibles.
µdensity ne se contente pas d'exposer en REST des Docker-compose, mais propose une intégration simple à Gitlab, profitant du jeton JWT mis à disposition dans la CI. Ceci nous permet d'avoir des analyses de qualité, asynchrones et donc non bloquantes pour se soucier de la qualité sur un temps plus long que le rythme de développement avec ses tests unitaires et fonctionnels.
Compose a clairement sa place dans la chaine de développement d'un site/service web, du poste de dev à la production.