71 points par GN⁺ 2026-01-21 | 9 commentaires | Partager sur WhatsApp
  • 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 -r affiche la version du noyau de l’hôte, tandis que seul /etc/os-release indique 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écuter apt update ainsi qu’installer des paquets
  • Pourtant, si l’on exécute uname -r dans 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-release indique 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() ou fork(), 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:latest affichent 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() et exec()
    • 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
  • 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
  • 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 : ulimit par 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

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
  • 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

 
bbulbum 2026-01-22

Noyau + outils = distribution
Alors dans ce cas, c’est aussi bien Ubuntu, non..

 
sacredshine 2026-01-21

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.

 
dongho42 2026-01-21

C’est instructif.

 
seunggi 2026-01-21

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.

 
euphcat 2026-01-21

Acktuallly, c’est en fait un peu plus proche de systemd-nspawn ☝️🤓

 
hohemian 2026-01-22

En fait

 
euphcat 2026-01-22

Sarcasme / autodérision

 
crawler 2026-01-21

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.

 
minsuchae 2026-01-22

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.