Comprendre les images de base Docker : l’Ubuntu dans le conteneur n’est pas un vrai Ubuntu
(oneuptime.com)- Même en exécutant
docker run ubuntu, le conteneur partage le noyau Linux de l’hôte et Ubuntu ne fournit que les outils de l’espace utilisateur - Le résultat de
uname -raffiche la version du noyau de l’hôte, tandis que seul/etc/os-releaseindique des informations Ubuntu - Chaque VM possède son propre noyau et met plusieurs minutes à démarrer, alors qu’un conteneur démarre en quelques millisecondes et partage le noyau de l’hôte via une isolation au niveau du système d’exploitation, sans virtualisation matérielle, ce qui réduit l’overhead
- Grâce à la stabilité de l’ABI des appels système Linux, des conteneurs issus de distributions variées peuvent fonctionner sur un même noyau
- Dans un environnement avec 16 Go de RAM, la limite pratique se situe autour de 50 à 100 conteneurs légers, 10 à 30 de taille moyenne et 5 à 10 grands conteneurs
- Comprendre cette architecture est essentiel, car une vulnérabilité du noyau affecte tous les conteneurs, et le choix de l’image de base a un impact direct sur la compatibilité et la sécurité
Ce que signifie « exécuter Ubuntu »
- En lançant
docker run ubuntu:22.04, on obtient un prompt bash qui ressemble à Ubuntu, et on peut exécuterapt updateainsi qu’installer des paquets - Pourtant, si l’on exécute
uname -rdans le conteneur, c’est la version du noyau de l’hôte (par ex. 6.5.0-44-generic) qui s’affiche - Le fichier
/etc/os-releaseindique Ubuntu 22.04, mais le noyau est celui de la machine hôte, et la partie « Ubuntu » n’est rien d’autre que le système de fichiers qui compose l’espace utilisateur
Conteneurs vs machines virtuelles : comparaison d’architecture
- Les VM virtualisent le matériel, tandis que les conteneurs virtualisent le système d’exploitation
- Principales différences :
- Noyau : chaque VM possède son propre noyau, les conteneurs partagent celui de l’hôte
- Temps de démarrage : plusieurs minutes pour une VM, quelques millisecondes pour un conteneur
- Overhead mémoire : 512 Mo à 4 Go pour une VM, 1 à 10 Mo pour un conteneur
- Utilisation disque : 10 à 100 Go pour une VM, 10 à 500 Mo pour une image de conteneur
- Niveau d’isolation : niveau matériel pour les VM, niveau processus pour les conteneurs
- Performances : environ 5 à 10 % d’overhead pour une VM, performances proches du natif pour les conteneurs
Ce que contient réellement une image de base
- Contenu du tarball téléchargé lors d’un pull de
ubuntu:22.04: -
1. Binaires essentiels (
/bin,/usr/bin)/bin/bash(shell),/bin/ls(liste des fichiers),/bin/cat(affichage des fichiers)/usr/bin/apt(gestionnaire de paquets),/usr/bin/dpkg(outil de paquets Debian)
-
2. Bibliothèques partagées (
/lib,/usr/lib)- glibc et d’autres bibliothèques partagées auxquelles les programmes se lient
/lib/x86_64-linux-gnu/libc.so.6(bibliothèque C, base de tous les programmes C)- Bibliothèques essentielles comme
libpthread.so.0,libm.so.6, etc.
-
3. Fichiers de configuration (
/etc)/etc/apt/sources.list(dépôts de paquets)/etc/passwd(base des utilisateurs)/etc/resolv.conf(configuration DNS, généralement montée depuis l’hôte)
-
4. Base de données des paquets
/var/lib/dpkg/status(paquets installés)/var/lib/apt/lists/(cache des paquets disponibles)
- Le noyau, le bootloader et les pilotes ne sont pas inclus
Le noyau ne change pas, tout le reste oui
- Le noyau Linux fournit : ordonnancement des processus, gestion mémoire, opérations sur le système de fichiers, pile réseau, pilotes de périphériques, appels système
- Quand un processus dans un conteneur appelle
open(),read()oufork(), l’appel est transmis directement au noyau de l’hôte - Le noyau ne sait pas — et ne se soucie pas — si ce processus appartient à un « conteneur Ubuntu » ou à un « conteneur Alpine »
-
Stabilité de l’interface des appels système
- L’ABI des syscalls Linux est très stable
- Pourquoi un binaire compilé avec glibc 2.31 (Ubuntu 20.04) fonctionne aussi sur un noyau Ubuntu 24.04 :
- le noyau maintient la rétrocompatibilité
- les numéros d’appels système ne changent pas
- de nouvelles fonctions sont ajoutées, mais les anciennes sont rarement supprimées
- C’est aussi la raison pour laquelle on peut exécuter un conteneur Ubuntu 18.04 sur un hôte avec le noyau 6.5
Démonstration : même noyau, espace utilisateur différent
- En interrogeant le même noyau depuis plusieurs images de base, on constate que toutes les images partagent le noyau de l’hôte
ubuntu:22.04,debian:12,alpine:3.19,fedora:39,archlinux:latestaffichent toutes la même version de noyau (6.5.0-44-generic)- Ce qui change selon le conteneur, ce sont le binaire
uname, la libc et, plus largement, la composition du userland
Pourquoi les conteneurs sont si efficaces
-
1. Pas de duplication du noyau
- Chaque VM charge un noyau complet en mémoire (environ 100 à 500 Mo)
- 10 VM consomment la mémoire de 10 noyaux, alors que 10 conteneurs n’utilisent qu’un seul noyau
-
2. Démarrage immédiat
- Séquence de boot d’une VM : BIOS → bootloader → noyau → système d’init → services
- Un conteneur existe en quelques millisecondes grâce à de simples appels
fork()etexec() - Boot typique d’une VM : 30 à 60 secondes / démarrage d’un conteneur : environ 0,347 s
-
3. Couches d’image partagées
- Si l’on exécute 100 conteneurs à partir de
ubuntu:22.04, les couches de l’image de base n’existent qu’une seule fois sur le disque - Chaque conteneur ne reçoit qu’une fine couche copy-on-write pour ses modifications
- Si l’on exécute 100 conteneurs à partir de
-
4. Partage mémoire via le noyau
- Le page cache du noyau est partagé
- Si 50 conteneurs lisent le même fichier, le noyau ne le met en cache qu’une seule fois
- Avec les mêmes bibliothèques partagées, il est aussi possible de partager des pages mémoire via copy-on-write
Calcul des limites d’exécution des conteneurs
-
Analyse mémoire (VM avec 16 Go de RAM)
- RAM totale : 16 384 Mo
- Overhead de l’OS hôte : -1 024 Mo
- Démon Docker : -256 Mo
- Overhead du runtime des conteneurs : -512 Mo
- Mémoire disponible pour les conteneurs : 14 592 Mo
-
Utilisation mémoire par type de conteneur
- Minimal (
sleep) : environ 1 Mo - Alpine + petite application : environ 25 Mo
- Ubuntu + application Python : environ 120 Mo
- Ubuntu + application Java : environ 500 Mo
- Service Node.js : environ 200 Mo
- Minimal (
-
Maximum théorique
- Conteneur minimal (1 Mo) : 14 592
- Alpine + petite application (25 Mo) : 583
- Ubuntu + Python (120 Mo) : 121
- Microservice Java (500 Mo) : 29
-
Limites réelles
- Autres facteurs à considérer au-delà de la mémoire :
- Ordonnancement CPU : trop de conteneurs en concurrence peut provoquer des pics de latence
- Descripteurs de fichiers :
ulimitpar défaut à 1024 - Ports réseau : seulement 65 535 ports disponibles pour le mapping
- PIDs : limite de
/proc/sys/kernel/pid_max(par défaut : 32 768) - I/O disque : overhead d’OverlayFS et nécessité de parcourir de nombreuses couches
- Sur une VM de 16 Go exécutant de vraies charges de travail, les limites pratiques sont :
- Conteneurs légers (API, workers) : 50 à 100
- Conteneurs intermédiaires (DB, cache) : 10 à 30
- Grands conteneurs (modèles ML, applications JVM) : 5 à 10
- Autres facteurs à considérer au-delà de la mémoire :
Compatibilité entre distributions Linux
-
La promesse de l’ABI du noyau
- Linux conserve une interface de syscalls stable
- Des binaires compilés pour d’anciens noyaux fonctionnent sur des noyaux plus récents
- Un binaire Ubuntu 18.04 s’exécute normalement sur un noyau 6.5
-
Quand la compatibilité casse
- Exigences de fonctionnalités du noyau : si le conteneur a besoin d’une fonction absente du noyau (par ex. io_uring exige le noyau 5.1+)
- Dépendances à des modules noyau : Wireguard nécessite le module noyau wireguard, les conteneurs NVIDIA ont besoin du pilote noyau nvidia
- Restrictions seccomp/capabilities : si l’hôte bloque un syscall requis par le conteneur (par ex. pour utiliser
ptrace, il faut--cap-add SYS_PTRACE)
Guide de choix des images de base
| Image de base | Taille | Gestionnaire de paquets | Usage |
|---|---|---|---|
scratch |
0 Mo | Aucun | Binaires Go/Rust compilés statiquement |
alpine |
7 Mo | apk | Conteneurs minimaux, musl libc |
distroless |
20 Mo | Aucun | Axé sécurité, sans shell ni gestionnaire de paquets |
debian-slim |
80 Mo | apt | Équilibre entre taille et compatibilité |
ubuntu |
78 Mo | apt | Confort de développement |
fedora |
180 Mo | dnf | Paquets récents, SELinux |
-
Quand utiliser chaque image
- scratch : pour les binaires compilés statiquement, sans aucun OS, uniquement le binaire
- alpine : image minimale avec accès shell, utilise musl libc au lieu de glibc, ce qui peut poser certains problèmes de compatibilité
- distroless : image de production axée sécurité, sans shell ni gestionnaire de paquets ; plus difficile à déboguer mais plus sûre
La frontière entre espace utilisateur et noyau
-
Ce qui vient de l’image de base (espace utilisateur)
- shell (
/bin/bash,/bin/sh) - bibliothèque C (glibc, musl)
- gestionnaire de paquets (apt, apk, yum)
- utilitaires de base (ls, cat, grep)
- configuration du système d’init (mais généralement pas systemd lui-même)
- utilisateurs et groupes par défaut (
/etc/passwd) - chemins de bibliothèques et configuration
- shell (
-
Ce qui vient de l’hôte (noyau)
- ordonnancement des processus et gestion mémoire
- pile réseau (TCP/IP, routage)
- opérations sur le système de fichiers (lecture, écriture, montage)
- fonctions de sécurité (namespaces, cgroups, seccomp)
- pilotes de périphériques (GPU, réseau, stockage)
- gestion du temps et de l’horloge
- chiffrement et génération aléatoire
-
L’illusion créée par les namespaces
- Le noyau fournit des namespaces qui donnent au conteneur l’impression d’être isolé
- Le processus vu comme PID 1 à l’intérieur du conteneur existe sur l’hôte avec un PID plus élevé (par ex. 45678)
- Le noyau maintient ce mapping : PID 1 du conteneur → PID 45678 sur l’hôte
- C’est ainsi que l’isolation fonctionne sans virtualisation
Ce que cela implique en production
-
1. Une vulnérabilité du noyau affecte tous les conteneurs
- Si le noyau hôte présente une faille, tous les conteneurs y sont exposés
- Maintenir l’hôte à jour est indispensable
-
2. Le noyau de l’hôte limite les capacités du conteneur
- Pour utiliser io_uring, il faut un noyau hôte 5.1+
- Les fonctions eBPF exigent un noyau 4.15+ avec certaines options activées
-
3. L’importance de glibc vs musl
- Alpine utilise musl libc
- Certains binaires compilés pour glibc peuvent ne pas fonctionner
- Exemple : sur Alpine, l’exécution d’un binaire glibc peut échouer avec une erreur indiquant l’absence de
/lib/x86_64-linux-gnu/libc.so.6
-
4. Le « système d’exploitation » du conteneur est une notion purement organisationnelle
- Du point de vue du noyau, il n’y a aucune différence entre un « conteneur Ubuntu » et un « conteneur Debian »
- Ce sont simplement des processus qui émettent des syscalls
Idées reçues courantes
- ❌ « Les conteneurs sont des VM légères » : un conteneur est un processus avec une isolation avancée, alors qu’une VM virtualise le matériel et exécute un noyau distinct
- ❌ « Chaque conteneur possède son propre noyau » : tous les conteneurs partagent le noyau de l’hôte ; l’« OS » du conteneur n’est qu’un ensemble de fichiers d’espace utilisateur
- ❌ « Exécuter un conteneur Ubuntu = exécuter Ubuntu » : on exécute le noyau de l’hôte avec des outils Ubuntu ; si l’hôte est Debian, c’est en réalité un noyau Debian qui tourne
- ❌ « L’image de base contient un système d’exploitation complet » : l’image de base ne contient qu’un espace utilisateur minimal, sans noyau, bootloader ni pilotes
- ❌ « Plus de conteneurs = plus de mémoire consommée » : avec les couches partagées et le page cache du noyau, les conteneurs partagent souvent efficacement la mémoire
Résumé essentiel
- Une image de base Docker est un instantané du système de fichiers des composants d’espace utilisateur d’une distribution Linux
- les binaires, bibliothèques et configurations qui donnent à Ubuntu son apparence d’Ubuntu
- Le véritable système d’exploitation, le noyau, est partagé avec l’hôte
- Cette architecture permet :
- un démarrage en quelques millisecondes (pas de boot du noyau)
- un overhead mémoire minimal (un seul noyau, pages partagées)
- une forte densité (des centaines de conteneurs par hôte)
- des performances proches du natif (syscalls directs vers le noyau)
- Le compromis est une isolation plus faible que celle des VM : comme les conteneurs partagent le noyau, un exploit noyau affecte tous les conteneurs
- Pour la plupart des workloads, ce compromis en vaut la peine
9 commentaires
Noyau + outils = distribution
Alors dans ce cas, c’est aussi bien Ubuntu, non..
Du coup, il existe aussi des tutoriels qui montrent comment recréer Docker directement sous Linux, en isolant les répertoires et en bricolant avec les utilisateurs, les groupes, etc.
C’est instructif.
https://fr.news.hada.io/topic?id=9531
Donc, pour le dire de façon un peu exagérée, on peut presque voir Docker comme
chroot + cgroup = docker.Acktuallly, c’est en fait un peu plus proche de
systemd-nspawn☝️🤓En fait
Sarcasme / autodérision
C’est vraiment fascinant.
L’article semble expliquer cela en se basant sur LINUX,
mais dans le cas d’une exécution sous Windows, cela veut dire qu’on partage aussi, comme dans l’article, un noyau virtuel créé avec WSL2, n’est-ce pas ?
Et si une vulnérabilité apparaissait dans Docker au point de permettre de toucher au noyau, je me demande s’il faudrait alors considérer que Windows, qui ajoute une couche de virtualisation par rapport à Linux, est potentiellement plus sûr.
J’ai été un peu surpris par la réaction dans le commentaire du dessus.
Je pensais qu’on l’utilisait en sachant déjà ça, naturellement.
Le noyau Linux est celui de l’hôte, et pour le reste on récupère les outils utilisés dans une distribution Linux.
WSL2, si je ne me trompe pas, fonctionne via une virtualisation sur Hyper-V.
Windows - Linux dans une machine virtuelle - puis à nouveau un conteneur à l’intérieur...
À la base, comme le root à l’intérieur d’un conteneur n’est pas le vrai root de tout le système, on ne peut pas manipuler le noyau arbitrairement.
En revanche, s’il y a une vulnérabilité, ça peut devenir très grave.
Du point de vue des performances, Windows est un peu plus lent pour cette raison aussi, parce qu’il y a une couche de virtualisation supplémentaire.