- Depuis son lancement initial en 2013, Docker a profondément transformé la manière dont les développeurs construisent, déploient et exécutent leurs applications ; cet article de l’ACM met en lumière son évolution technique et retrace plusieurs décennies de recherche sur les systèmes cachées derrière une simple CLI
- Le socle technique central de Docker repose sur l’utilisation des namespaces Linux pour obtenir l’isolation des processus sans machine virtuelle, en combinant 7 types de namespaces ajoutés progressivement depuis 2001 afin d’implémenter des conteneurs légers
- Pour prendre en charge macOS et Windows, Docker a adopté une architecture contre-intuitive consistant à embarquer un library virtual machine monitor (HyperKit) directement dans l’application desktop, afin d’exécuter Linux à l’intérieur d’un processus utilisateur plutôt qu’avec un hyperviseur classique
- Aujourd’hui, Docker prend en charge des matériels hétérogènes comme ARM et RISC-V ainsi que les workloads IA, et s’est imposé comme une infrastructure de développement standard sur le cloud, le desktop et l’edge
- Avec la montée des workloads IA, la gestion des dépendances GPU est devenue un nouveau défi ; l’évolution de Docker se poursuit avec la prise en charge des GPU via CDI (Container Device Interface) et l’intégration des TEE (environnements d’exécution de confiance)
Origines techniques
- Au début des années 2000, il était courant d’installer manuellement de nombreuses dépendances sur une distribution Linux et de compiler puis configurer soi-même les logiciels ; l’essor du cloud computing en 2010 a encore complexifié ce processus
- Docker a simplifié cela en permettant aux développeurs de packager une application et toutes ses dépendances dans une image de système de fichiers (« conteneur »), exécutable sur n’importe quelle machine où Docker est installé
- Contrairement à une machine virtuelle, l’exécution ne nécessite pas l’installation complète d’un système d’exploitation et peut se faire avec seulement quelques commandes
Workflow habituel
- Le développeur écrit un Dockerfile, qui définit un processus de build étape par étape fondé sur une syntaxe proche du shell
- Exemple d’un site web Python : on commence par
FROM python:3, puis on décrit dans un seul fichier l’installation des dépendances, la copie du code, l’exposition du port et la commande d’exécution
docker build crée l’image du conteneur, puis docker push la pousse vers Docker Hub
- L’exécution se fait en précisant par exemple le montage d’un volume de données et l’exposition de ports réseau, comme dans
docker run -v data:/app/data -p 80:80
- Depuis 2013, la CLI s’est fortement étendue et le backend a été entièrement repensé, mais le workflow de base est resté constant : écrire un Dockerfile →
docker build → docker run
- Plus de 3,4 millions de Dockerfiles ont été trouvés à la racine de dépôts publics sur GitHub
Fonctionnement interne : les namespaces Linux
- Le noyau de l’OS isole la mémoire des processus, mais de nombreuses ressources système, comme le système de fichiers, les fichiers de configuration ou les bibliothèques dynamiques, restent partagées
- Il est très difficile d’installer plusieurs applications sur une même machine lorsqu’elles ont des exigences incompatibles en bibliothèques dynamiques
- Des interférences non désirées entre processus peuvent aussi survenir, comme des conflits de ports réseau
- Exécuter chaque application dans sa propre machine virtuelle (VM) résout le problème, mais c’est très lourd à cause de la duplication du noyau, du système de fichiers, du cache ou encore du réseau bridge
- Comme chaque OS invité fonctionne indépendamment, il est aussi difficile d’éliminer la redondance en stockage et en mémoire
- Le
chroot() d’Unix v7 en 1978 permettait déjà d’utiliser un système de fichiers racine distinct, mais sans prendre en charge la composition de systèmes de fichiers pour plusieurs applications
- Nix et Guix contournent cela via le repackaging par répertoire applicatif et l’édition de liens dynamique, mais cette approche s’applique mal aux logiciels propriétaires et ne résout pas les conflits de ports réseau
- Docker a choisi les namespaces Linux : ils permettent à chaque processus de contrôler individuellement sa manière d’accéder aux ressources partagées comme les fichiers et les répertoires
- Exemple : deux processus dans des namespaces différents peuvent interpréter
/etc/passwd respectivement comme /alice/etc/passwd et /bob/etc/passwd
- Le namespace ne s’applique qu’au moment d’ouvrir la ressource ; ensuite, le descripteur de fichier fonctionne comme une ressource noyau ordinaire, sans surcoût supplémentaire
- Historique de l’introduction des namespaces
- 2001, Linux 2.5.2 : namespace de montage
- 2006, Linux 2.6.19 : namespace IPC
- 2007, Linux 2.6.24 : namespace de pile réseau
- Au total, 7 types de namespaces sont pris en charge
- Contrairement à Plan 9, les namespaces n’ont pas été conçus dès l’origine mais ajoutés progressivement, ce qui les rendait bas niveau et difficiles à utiliser
- Des fonctions similaires sur FreeBSD et Solaris ne sont pas non plus devenues d’usage courant
- La contribution centrale de Docker en 2013 : trouver un compromis pratique entre la lourde isolation des VM et la facilité d’usage des primitives natives de l’OS
Structure d’exécution des conteneurs Linux dans Docker
- Docker repose sur une architecture client-serveur, composée d’un démon serveur exécuté sur l’hôte (
dockerd) et d’un client CLI qui envoie ses requêtes via une API RESTful
- Vers 2015, le démon monolithique a été décomposé en composants spécialisés
- BuildKit : assemblage des images de système de fichiers
- containerd : instanciation des images en conteneurs en cours d’exécution et gestion des ressources réseau et stockage
Images de conteneur
- Lors de l’appel à
docker build, Docker crée une image de système de fichiers en couches représentant les exécutables et les données décrits dans le Dockerfile
- Couche la plus basse : une distribution d’OS comme Debian ou Alpine Linux (ou un assemblage manuel à partir d’une archive tar)
- Couches suivantes : les différences de système de fichiers produites par l’exécution de chaque commande du Dockerfile
- Les images sont stockées dans un système de stockage adressé par le contenu, où le hash de l’image du système de fichiers sert de clé
- Cela permet une déduplication efficace, garantit l’immuabilité et permet de vérifier toute altération via le hash
- En 2016, l’Open Container Initiative (OCI) a standardisé le format d’image, et il existe aujourd’hui de nombreuses implémentations indépendantes
- Les systèmes de fichiers Linux overlayfs, btrfs et ZFS sont utilisés pour effectuer efficacement snapshots et clones de couches copy-on-write
- La récupération différée d’images (lazy-pulling) est prise en charge via le snapshotter de stockage
stargz
Instances de conteneur
- Lors de l’appel à
docker run, des ressources système sont allouées afin de créer, à partir d’une image OCI, un processus isolé par namespaces (« conteneur »)
containerd configure dynamiquement les namespaces nécessaires à chaque conteneur et exécute notamment les opérations suivantes :
- définir des cgroups (groupes de contrôle) pour l’isolation des ressources et la limitation du débit d’E/S
- remapper les ports réseau locaux du conteneur vers des ports exposés sur l’interface de l’hôte
- connecter des volumes de stockage mutables du système de fichiers hôte pour conserver l’état persistant de l’application
- isoler l’arbre des processus du conteneur avec un namespace PID
- mapper, avec un namespace utilisateur, les UID locaux du conteneur vers d’autres UID sur l’hôte (par exemple UID 1000 dans le conteneur → UID 12345 ou 23456 sur l’hôte)
- La configuration des namespaces introduit un léger surcoût, mais bien inférieur au lancement d’une VM Linux complète, et reste dans la plupart des cas sous la seconde
- Le noyau Linux récupère les conteneurs arrêtés comme il le ferait pour des processus ordinaires
Au-delà de Linux : prise en charge de macOS et Windows
- Grâce à l’architecture client-serveur, la CLI peut envoyer des commandes à une instance Docker distante via une connexion réseau sécurisée
- En 2015, alors que Docker était largement adopté pour le développement Linux, il s’est heurté à une barrière d’ergonomie pour les développeurs macOS et Windows, incapables d’exécuter des conteneurs Linux
- La majorité des développeurs utilisent macOS ou Windows comme environnement de développement principal, mais les images de système de fichiers Docker ne peuvent s’exécuter que sur un noyau Linux
- Avec l’essor du cloud public, Linux est devenu l’environnement privilégié pour le déploiement
Construction de l’application Docker for Mac
- Contrainte clé : pour les développeurs habitués à Docker sur Linux, cela devait fonctionner sans configuration supplémentaire et permettre d’exécuter les mêmes images Docker
- En renversant l’approche existante (faire tourner Linux séparément à côté de l’OS de bureau), l’hyperviseur a été embarqué dans une application en espace utilisateur sur macOS/Windows, avec Linux exécuté à l’intérieur
- Inspiration tirée de la recherche sur les unikernels : elle a démontré qu’il était possible d’intégrer de manière souple des composants d’OS dans de plus grandes applications
- HyperKit : une bibliothèque VMM qui utilise les extensions de virtualisation matérielle des CPU Intel pour exécuter un noyau Linux dans un processus utilisateur ordinaire
- Le noyau Linux embarqué exécute le démon Docker, qui gère les conteneurs et joue le rôle d’un endpoint serveur Docker standard
- Tous les détails d’administration Linux sont masqués dans l’application desktop →
docker build et docker run sur le bureau sont transmis à l’instance Linux embarquée pour que « ça fonctionne, tout simplement »
- Cette approche a ensuite été adoptée par d’autres systèmes de conteneurs comme Podman, et s’est imposée comme la méthode standard pour exécuter des conteneurs sur macOS/Windows
LinuxKit : une distribution Linux sur mesure embarquée
- Une distribution Linux personnalisée conçue pour être intégrée comme composant d’une autre application, et non pour fonctionner de manière autonome
- Construction d’un espace utilisateur sur mesure ne contenant que les composants minimaux nécessaires à l’exécution de conteneurs Docker, afin de réduire au maximum le temps de démarrage de l’application
- Exécution de chaque composant isolé dans un conteneur, sans rien exécuter dans l’espace de noms racine utilisé au démarrage
- Utilisation du même système de fichiers copy-on-write et des espaces de noms réseau que ceux employés par les conteneurs Docker
- La combinaison LinuxKit + HyperKit permet de démarrer des processus Linux à une vitesse presque identique à celle de processus natifs macOS
- Lancé en 2016 dans les applications Docker for Mac et Windows
Problèmes réseau et solution SLIRP
- Les connexions réseau entre les conteneurs Linux embarqués et macOS/Windows se sont révélées un problème plus difficile que prévu
- La méthode existante de pont Ethernet exigeait une gestion réseau complexe, et les pare-feu et antivirus des postes d’entreprise la détectaient comme un trafic potentiellement malveillant, entraînant des milliers de rapports de bugs de la part des bêta-testeurs
- Solution SLIRP : un outil d’abord utilisé au milieu des années 1990 pour connecter au web les PDA Palm Pilot
- Lors du handshake TCP d’un conteneur, les trames Ethernet sont envoyées à l’hôte via le protocole virtio sur mémoire partagée
- Des bibliothèques unikernel de MirageOS sont utilisées pour convertir les requêtes réseau Linux en appels de sockets natifs macOS/Windows
- La pile TCP/IP en espace utilisateur vpnkit, écrite en OCaml, les reçoit sur l’OS hôte et appelle la syscall macOS
connect()
- Du point de vue des politiques VPN, le trafic sortant est vu comme provenant de l’application Docker, et non d’une machine distincte
- Après le déploiement de vpnkit durant la bêta en 2016, les rapports de bugs des utilisateurs en entreprise ont chuté de plus de 99 %
- L’approche SLIRP a ensuite été adoptée dans le domaine du cloud serverless, où une ancienne technique de réseau dial-up a servi à résoudre un nouveau problème de gestion des conteneurs
Gestion du trafic réseau entrant
- Lorsqu’un conteneur Linux écoute sur un port, il n’est pas automatiquement exposé à Internet si cela n’est pas explicitement demandé dans le CLI (par exemple
docker run -p 80:80 nginx)
- Expérience utilisateur idéale : le port du conteneur apparaît directement sur l’IP du bureau et devient accessible via
http://localhost:8080 dans le navigateur
- Les solutions de virtualisation desktop existantes comme VMware Fusion exposaient une IP intermédiaire temporaire au lieu de
localhost
- Installation d’un programme eBPF personnalisé dans le noyau LinuxKit → déclenchement de la création d’un socket en écoute correspondant sur l’hôte desktop → activation du redirecteur de ports
- Résultat : lorsqu’un conteneur Linux tourne sur Mac, il devient immédiatement accessible via
localhost, avec la même expérience développeur que sur une machine Linux native
Stockage
- Les développeurs doivent pouvoir modifier leur code en local tout en exécutant ce code et les tests dans des conteneurs
- Sous Linux, on utilise des bind mounts via
docker run -v /host:/container pour un accès direct aux fichiers
- Sur macOS et Windows, cela ne fonctionne pas car les noyaux sont différents
- Docker utilise virtio-fs, un protocole de mémoire partagée issu de l’hyperviseur KVM, pour transmettre au système hôte les opérations sur le système de fichiers (sous forme de requêtes FUSE)
- L’hôte reçoit ces requêtes et exécute les syscalls correspondantes
open, read, write
- Le code et les données du développeur restent sur le système de fichiers hôte, ce qui les rend accessibles à des outils de sauvegarde et de recherche comme Time Capsule et Spotlight d’Apple
Windows Services for Linux (WSL)
- En 2017, Microsoft lance WSL, qui permet d’exécuter directement des applications Linux sur Windows
- La première version suivait une approche de library OS, convertissant dynamiquement les syscalls des binaires Linux en syscalls Windows au lieu d’utiliser la virtualisation
- Cela a bien fonctionné pour de nombreuses applications, mais il manquait des syscalls prises en charge pour exécuter des conteneurs Docker
- En 2018, sortie de WSL2 : refonte vers une architecture exécutant une VM Linux complète en arrière-plan, similaire à Docker for Mac
- Docker sur WSL2 exécute le démon et les conteneurs utilisateur dans une distribution LinuxKit WSL
- Il gère l’API Docker et le port forwarding réseau depuis Windows lui-même et depuis d’autres distributions Linux
- L’architecture clé qui a permis l’évolution cross-platform des conteneurs Docker : une approche de library OS réutilisant comme bibliothèques en espace utilisateur du code traditionnellement réservé au noyau pour l’embarquer dans d’autres applications
- Le succès de cette architecture se prouve par son caractère invisible mais omniprésent : des millions de développeurs utilisent Docker chaque jour sans se soucier de l’OS sur lequel il s’exécute
Nouveau workflow développeur : architectures CPU multiples
- Aux débuts de Docker, la plupart des workloads cloud reposaient sur l’architecture Intel
- La situation a changé avec l’arrivée du processeur Amazon Graviton ARM en 2018 et du CPU Apple M1 ARM en 2020
- Exécuter des workloads sur ARM permet de réduire les coûts et d’améliorer les performances
- Il est devenu nécessaire de prendre en charge plusieurs architectures CPU dans une même image Docker : Intel, ARM, POWER, RISC-V, etc.
- Côté serveur, le format d’image OCI a été étendu avec des manifestes multi-architecture (multiarch manifests)
- Pour construire sur un seul hôte des images destinées à plusieurs architectures CPU, on utilise la fonctionnalité Linux
binfmt_misc
- QEMU assure une conversion transparente entre binaires ARM et Intel
- La surcharge se produit surtout à l’étape de build, tandis que l’image multi-architecture résultante s’exécute ensuite nativement sur n’importe quel hôte compatible
- Apple a introduit avec Rosetta un support matériel et logiciel pour la traduction de jeux d’instructions CPU, facilement intégrable à l’architecture Docker
- Aujourd’hui, exécuter des conteneurs Intel et ARM côte à côte fait partie du workflow développeur courant
Gestion des secrets via les environnements d’exécution de confiance (TEE)
- Dans un environnement de conteneurs, la gestion des secrets comme les mots de passe ou les clés API reste toujours un défi
- Ils doivent être injectés dynamiquement sans être intégrés à l’image du système de fichiers
- Docker prend en charge le socket forwarding, ce qui permet de monter un socket de domaine local dans un conteneur
- Dans le cas de Docker for Mac/Windows, le socket forwarding s’étend jusqu’à la VM Linux
- Cela permet d’utiliser dans le conteneur des systèmes de gestion de clés comme
ssh-agent sans exposer directement les clés
- Le socket forwarding offre un niveau de protection de base, mais face aux malwares dans une chaîne logistique logicielle de plus en plus vaste, davantage de couches de défense sont nécessaires
- Une protection par hyperviseur est appliquée directement dans le runtime de conteneurs afin d’améliorer le niveau de protection entre conteneurs
- Environnement d’exécution de confiance (TEE) : des fonctionnalités matérielles des CPU modernes permettent de créer des VM confidentielles (confidential VMs) capables de protéger les données secrètes même vis-à-vis de l’OS hôte
- Il devient possible d’imposer des restrictions d’accès aux données au-delà des frontières de l’application, du noyau et de l’hyperviseur
- Mais la configuration et l’usage des TEE présentent aussi une complexité de gestion comparable à celle de la virtualisation au niveau de l’OS
- Le groupe de travail Confidential Containers développe des applications exécutées dans des TEE et gérées avec Docker
- Le Docker CLI assure un transfert de messages chiffrés depuis le TEE local du poste de travail, via l’hôte, jusqu’à un environnement TEE distant dans le cloud
- Les développeurs peuvent ainsi s’authentifier dans des environnements cloud sensibles sans se déplacer sur site, tandis que les identifiants sont stockés en sécurité dans l’enclave du poste de travail
Prise en charge GPGPU pour les charges de travail IA
- L’essor des charges de travail IA fait apparaître un défi entièrement nouveau : les charges de travail de machine learning s’exécutent majoritairement sur GPU
- Problème central : les charges GPU nécessitent un pilote GPU du noyau et des bibliothèques en espace utilisateur strictement correspondants, alors que plusieurs conteneurs s’exécutent sur un même noyau partagé
- Si deux applications exigent des versions différentes du même pilote GPU du noyau, on retrouve le même conflit fondamental que Docker cherchait justement à résoudre à l’origine
- Depuis mars 2023, Docker prend en charge CDI (Container Device Interface)
- Personnalisation de l’image du système de fichiers au moment du démarrage du conteneur
- Montage bind des fichiers de périphérique GPU et des bibliothèques dynamiques dédiées au GPU, puis régénération du cache
ld.so
- CDI garantit la portabilité des images entre certaines classes et certains fournisseurs de GPU, mais cela reste loin d’être totalement fluide entre différents OS et marques de matériel
- Les bibliothèques dynamiques ajoutées par CDI définissent des API différentes ; il n’existe donc rien de comparable à l’interface traditionnelle des conteneurs, à savoir l’ABI stable des appels système Linux
- Si les applications pour GPU Nvidia peinent à s’exécuter sur des CPU Apple série M, c’est parce que la prise en charge de la virtualisation GPU n’est pas encore assez mature pour traduire les instructions vectorielles entre matériels variés
- Des travaux sont en cours avec la communauté des conteneurs et les fabricants de GPU pour développer une méthode plus flexible et plus sûre de gestion des dépendances liées aux GPU
- L’espoir est que l’initiative des interfaces portables aboutisse à un consensus
Conclusion
- Docker a été lancé en 2013 avec l’objectif d’aider les développeurs à créer, partager et exécuter plus facilement des applications
- Il est aujourd’hui profondément intégré aux workflows de développement standard sur le cloud et le desktop, utilisé chaque jour par des millions de développeurs dans le monde, et traite des milliards de requêtes chaque mois
- L’objectif constant est de maintenir une communauté open source active et diversifiée qui élabore des standards d’interopérabilité
- La CNCF (Cloud Native Computing Foundation) joue le rôle de gestionnaire pour plusieurs composants clés
- L’Open Container Initiative (au sein de la Linux Foundation) est le gestionnaire du format d’image
- Aujourd’hui, de nombreuses implémentations de ces éléments sont actives, et leur déploiement augmente dans des environnements de périphérie comme le cloud, le desktop, l’automobile, le mobile et les vaisseaux spatiaux
- En 2025, le workflow type des développeurs intègre les tests et déploiements continus, les serveurs de langage des IDE et une assistance IA via le codage agentique
- Du point de vue de Docker, le workflow fondamental « build and run » reste très proche de celui d’il y a dix ans, mais le support système pour réduire les frictions dans des environnements variés s’est fortement renforcé
- L’objectif est de faire de Docker un compagnon invisible qui aide les développeurs à déployer leur code plus rapidement, avec une conception capable d’évoluer pour s’adapter aux workflows modernes de codage assisté par IA
Aucun commentaire pour le moment.