Quand vous lancez docker run nginx, que se passe-t-il vraiment ? Docker ne crée pas une machine virtuelle — il utilise des mécanismes du noyau Linux pour isoler un processus et limiter ses ressources. Ce guide vous explique les 4 piliers de cette isolation : namespaces, cgroups, capabilities et seccomp. Comprendre ces concepts vous permettra de mieux sécuriser vos conteneurs et de diagnostiquer certains problèmes.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Namespaces : comment Docker isole ce que le conteneur voit (processus, réseau, fichiers)
- Cgroups : comment Docker limite ce que le conteneur utilise (CPU, mémoire, I/O)
- Capabilities : comment Linux fragmente les privilèges root en unités granulaires
- Seccomp : comment filtrer les appels système autorisés
Conteneur vs Machine virtuelle
Section intitulée « Conteneur vs Machine virtuelle »Avant de plonger dans les mécanismes, comprenons la différence fondamentale entre un conteneur et une VM.
| Aspect | Machine virtuelle | Conteneur |
|---|---|---|
| Isolation | OS invité complet + hyperviseur | Namespaces + cgroups |
| Kernel | Séparé (chaque VM a le sien) | Partagé avec l’hôte |
| Démarrage | Secondes à dizaines de secondes | Centaines de ms à quelques secondes |
| Mémoire | Go par VM (OS inclus) | Mo (juste l’app) |
| Sécurité | Forte (frontière hardware) | Moindre (kernel partagé) |
| Cas d’usage | Isolation forte, OS différents | Microservices, CI/CD |
Namespaces : isoler ce que le conteneur voit
Section intitulée « Namespaces : isoler ce que le conteneur voit »Les namespaces sont des mécanismes du noyau Linux qui créent des vues isolées de certaines ressources système. Chaque conteneur a ses propres namespaces.
| Namespace | Ce qu’il isole | Exemple concret |
|---|---|---|
| PID | Processus | Le conteneur voit son processus comme PID 1 |
| NET | Réseau | Le conteneur a sa propre pile réseau, IP, ports |
| MNT | Montages | Le conteneur a son propre filesystem root |
| UTS | Hostname | Le conteneur peut avoir son propre nom d’hôte |
| IPC | Communication inter-processus | Files de messages isolées |
| USER | Utilisateurs | Le root du conteneur peut être mappé sur un UID non-root |
| CGROUP | Cgroups | Vue isolée des cgroups (Linux 4.6+) |
Ce que ça signifie concrètement : quand vous faites ps aux dans un conteneur, vous ne voyez que les processus de ce conteneur. Le processus principal apparaît comme PID 1, même si l’hôte lui attribue un autre numéro (ex: PID 12345).
# Sur l'hôte : voir le vrai PID du processus conteneurisédocker inspect --format '{{.State.Pid}}' mon_conteneur# Résultat : 12345
# Dans le conteneur : le processus se voit comme PID 1docker exec mon_conteneur ps aux# USER PID COMMAND# root 1 nginxUser namespaces et conteneurs rootless
Section intitulée « User namespaces et conteneurs rootless »Le user namespace est crucial pour la sécurité. Il permet de mapper le UID 0 (root) à l’intérieur du conteneur vers un utilisateur non privilégié sur l’hôte. Deux approches existent :
| Mode | Daemon Docker | Conteneurs | Cas d’usage |
|---|---|---|---|
| userns-remap | Root | UID remappé | Durcir des conteneurs existants |
| Rootless | Non-root | UID remappé | Réduire la surface d’attaque du daemon |
# Vérifier si Docker utilise les user namespaces (userns-remap)docker info | grep -i userns
# Installer Docker en mode rootless (daemon + conteneurs sans root)dockerd-rootless-setuptool.sh install
# Attention : rootless a des limitations (ports < 1024, overlay, etc.)Inspecter les namespaces d’un conteneur
Section intitulée « Inspecter les namespaces d’un conteneur »# Trouver le PID du processus principalPID=$(docker inspect --format '{{.State.Pid}}' mon_conteneur)
# Lister les namespaces utilisésls -la /proc/$PID/ns/# lrwxrwxrwx 1 root root 0 ... cgroup -> 'cgroup:[4026532xxx]'# lrwxrwxrwx 1 root root 0 ... ipc -> 'ipc:[4026532xxx]'# lrwxrwxrwx 1 root root 0 ... mnt -> 'mnt:[4026532xxx]'# lrwxrwxrwx 1 root root 0 ... net -> 'net:[4026532xxx]'# lrwxrwxrwx 1 root root 0 ... pid -> 'pid:[4026532xxx]'# lrwxrwxrwx 1 root root 0 ... user -> 'user:[4026531837]'# lrwxrwxrwx 1 root root 0 ... uts -> 'uts:[4026532xxx]'Les numéros entre crochets sont les identifiants des namespaces. Deux processus avec le même ID partagent la même vue.
Cgroups : limiter ce que le conteneur utilise
Section intitulée « Cgroups : limiter ce que le conteneur utilise »Les cgroups (Control Groups) permettent de limiter et comptabiliser les ressources consommées par un groupe de processus.
| Ressource | Option Docker | Exemple |
|---|---|---|
| Mémoire | --memory | --memory=512m (limite à 512 Mo) |
| CPU | --cpus | --cpus=1.5 (limite à 1,5 cœurs) |
| I/O disque | --device-read-bps | Limite la bande passante disque |
| Nombre de processus | --pids-limit | --pids-limit=100 |
Pourquoi c’est important : sans cgroups, un conteneur défaillant (fuite mémoire, boucle infinie) pourrait monopoliser toutes les ressources de l’hôte et faire tomber les autres conteneurs.
# Lancer un conteneur avec des limitesdocker run -d --name web \ --memory=256m \ --cpus=0.5 \ nginx
# Voir les limites appliquéesdocker stats web --no-streamCgroups v1 vs v2 : quelle version utilisez-vous ?
Section intitulée « Cgroups v1 vs v2 : quelle version utilisez-vous ? »Linux supporte deux versions de cgroups. La v2 est désormais le standard recommandé.
| Aspect | Cgroups v1 | Cgroups v2 |
|---|---|---|
| Architecture | Hiérarchies multiples (une par contrôleur) | Hiérarchie unifiée |
| Gestion mémoire | Moins précise | Meilleur accounting, PSI metrics |
| Kubernetes | Support historique | Standard depuis 1.25 |
| Statut | Maintenance mode (K8s 1.31+) | Actif, recommandé |
# Vérifier quelle version est activemount | grep cgroup# cgroup2 on /sys/fs/cgroup type cgroup2 → v2# cgroup on /sys/fs/cgroup/cpu type cgroup → v1
# Alternative : via Dockerdocker info | grep -i cgroup# Cgroup Driver: systemd# Cgroup Version: 2Inspecter les cgroups d’un conteneur
Section intitulée « Inspecter les cgroups d’un conteneur »# Méthode universelle : trouver le cgroup via /procPID=$(docker inspect --format '{{.State.Pid}}' mon_conteneur)cat /proc/$PID/cgroup# Exemple avec driver systemd (chemin typique)CONTAINER_ID=$(docker inspect --format '{{.Id}}' mon_conteneur)cat /sys/fs/cgroup/system.slice/docker-$CONTAINER_ID.scope/memory.maxcat /sys/fs/cgroup/system.slice/docker-$CONTAINER_ID.scope/memory.currentCapabilities : fragmenter les privilèges root
Section intitulée « Capabilities : fragmenter les privilèges root »Historiquement, Linux avait deux niveaux de privilèges : root (tout-puissant) ou utilisateur normal (limité). Les capabilities fragmentent les privilèges root en ~40 unités indépendantes.
Capabilities conservées par Docker par défaut
Section intitulée « Capabilities conservées par Docker par défaut »Docker retire la plupart des capabilities dangereuses mais en conserve typiquement 14 pour permettre aux applications courantes de fonctionner (la liste exacte peut varier selon les versions) :
| Capability | Ce qu’elle permet | Conservée par défaut |
|---|---|---|
CAP_CHOWN | Changer propriétaire de fichiers | ✅ |
CAP_DAC_OVERRIDE | Ignorer les permissions fichiers | ✅ |
CAP_FOWNER | Ignorer la propriété pour certaines ops | ✅ |
CAP_FSETID | Conserver setuid/setgid après modif | ✅ |
CAP_KILL | Envoyer des signaux aux processus | ✅ |
CAP_SETGID | Changer de groupe | ✅ |
CAP_SETUID | Changer d’utilisateur | ✅ |
CAP_SETPCAP | Modifier les capabilities | ✅ |
CAP_NET_BIND_SERVICE | Écouter sur ports < 1024 | ✅ |
CAP_NET_RAW | Utiliser RAW sockets (ping) | ✅ |
CAP_SYS_CHROOT | Utiliser chroot | ✅ |
CAP_MKNOD | Créer des fichiers spéciaux | ✅ |
CAP_AUDIT_WRITE | Écrire dans le journal d’audit | ✅ |
CAP_SETFCAP | Définir capabilities sur fichiers | ✅ |
Capabilities retirées (dangereuses)
Section intitulée « Capabilities retirées (dangereuses) »| Capability | Ce qu’elle permet | Risque |
|---|---|---|
CAP_SYS_ADMIN | Presque tout (mount, namespace, ptrace…) | Critique |
CAP_NET_ADMIN | Configurer le réseau (iptables, interfaces) | Élevé |
CAP_SYS_PTRACE | Débugger d’autres processus | Élevé |
CAP_SYS_MODULE | Charger des modules kernel | Critique |
CAP_SYS_RAWIO | Accès I/O brut | Critique |
CAP_SYS_TIME | Modifier l’heure système | Moyen |
# Voir les capabilities effectives d'un conteneurdocker run --rm alpine cat /proc/1/status | grep Cap# CapEff: 00000000a80425fb
# Décoder les capabilities (nécessite libcap-ng sur l'hôte)# apt install libcap-ng-utils (Debian/Ubuntu) ou dnf install libcap-ng-utils (RHEL)capsh --decode=00000000a80425fb
# Ajouter une capability spécifique (si vraiment nécessaire)docker run --cap-add=NET_ADMIN alpine ip link
# Retirer toutes les capabilities puis ajouter le minimumdocker run --cap-drop=ALL --cap-add=CHOWN --cap-add=SETUID alpine
# Conteneur ultra-restreint (recommandé si possible)docker run --cap-drop=ALL --read-only --security-opt=no-new-privileges alpineSeccomp : filtrer les appels système
Section intitulée « Seccomp : filtrer les appels système »Seccomp (Secure Computing Mode) filtre les appels système qu’un processus peut effectuer. Docker applique par défaut un profil seccomp qui bloque plusieurs dizaines de syscalls jugés à risque, tout en conservant la compatibilité avec la majorité des applications.
Syscalls bloqués par défaut (exemples)
Section intitulée « Syscalls bloqués par défaut (exemples) »| Syscall | Effet potentiel | Pourquoi bloqué |
|---|---|---|
reboot | Redémarrer le système | Impact hôte |
mount / umount | Monter/démonter filesystems | Évasion conteneur |
ptrace | Débugger d’autres processus | Injection de code |
clock_settime | Modifier l’heure système | Impact autres conteneurs |
kexec_load | Charger un nouveau kernel | Prise de contrôle |
bpf | Programmes kernel eBPF | Surveillance/modification kernel |
add_key / keyctl | Manipuler le keyring kernel | Accès secrets |
acct | Activer process accounting | Écriture sur l’hôte |
pivot_root | Changer le root filesystem | Évasion conteneur |
swapon / swapoff | Gérer le swap | Impact performances hôte |
Pourquoi c’est utile : même si un attaquant compromet un conteneur, il ne pourra pas exécuter certaines actions dangereuses car le kernel refusera l’appel système avec EPERM ou EACCES.
# Lancer avec le profil seccomp par défaut (implicite)docker run alpine
# Voir le profil seccomp utilisédocker inspect --format '{{.HostConfig.SecurityOpt}}' mon_conteneur
# Lancer sans profil seccomp (dangereux, uniquement pour debug)docker run --security-opt seccomp=unconfined alpine
# Utiliser un profil personnalisédocker run --security-opt seccomp=/path/to/profile.json alpineCréer un profil seccomp personnalisé
Section intitulée « Créer un profil seccomp personnalisé »Un profil seccomp est un fichier JSON qui définit les syscalls autorisés :
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [ { "names": ["read", "write", "exit", "exit_group"], "action": "SCMP_ACT_ALLOW" } ]}Et aussi : LSM (AppArmor/SELinux)
Section intitulée « Et aussi : LSM (AppArmor/SELinux) »Les 4 piliers ci-dessus ne sont pas les seuls mécanismes de sécurité. En durcissement réel, on ajoute souvent un Linux Security Module (LSM) :
| LSM | Distribution | Option Docker |
|---|---|---|
| AppArmor | Ubuntu, Debian | --security-opt apparmor=docker-default |
| SELinux | RHEL, Fedora, CentOS | --security-opt label=type:container_t |
Ces LSM définissent des politiques d’accès obligatoire (MAC) qui s’appliquent même au root. Docker utilise un profil AppArmor par défaut sur les distributions compatibles.
# Vérifier si AppArmor est actifcat /sys/module/apparmor/parameters/enabled# Y = actif
# Voir le profil AppArmor d'un conteneurdocker inspect --format '{{.AppArmorProfile}}' mon_conteneurComment ces mécanismes fonctionnent ensemble
Section intitulée « Comment ces mécanismes fonctionnent ensemble »Quand vous lancez docker run nginx, les 4 piliers de l’isolation s’activent simultanément :
- Docker demande au kernel de créer de nouveaux namespaces (PID, NET, MNT…)
- Le processus nginx démarre dans ces namespaces isolés
- Les cgroups limitent les ressources (mémoire, CPU) disponibles
- Les capabilities sont réduites au minimum nécessaire
- Seccomp filtre les appels système autorisés
Le résultat : nginx s’exécute comme s’il était seul sur le système, sans pouvoir impacter l’hôte ni les autres conteneurs.
Récapitulatif visuel
Section intitulée « Récapitulatif visuel »| Pilier | Question | Mécanisme |
|---|---|---|
| Namespaces | Que voit le conteneur ? | Isolation de la vue système |
| Cgroups | Combien le conteneur utilise ? | Limitation des ressources |
| Capabilities | Que peut faire le conteneur ? | Fragmentation des privilèges |
| Seccomp | Quels syscalls sont autorisés ? | Filtrage au niveau kernel |
Testez vos connaissances
Section intitulée « Testez vos connaissances »Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
📋 Récapitulatif de vos réponses
Vérifiez vos réponses avant de soumettre. Cliquez sur une question pour la modifier.
Détail des réponses
À retenir
Section intitulée « À retenir »- Un conteneur n’est pas une VM : il partage le kernel de l’hôte (vulnérabilité kernel = tous les conteneurs exposés)
- Namespaces = isolation de la vue (processus, réseau, fichiers)
- Cgroups = limitation des ressources (mémoire, CPU) — v2 est le standard actuel
- Capabilities = fragmentation des privilèges root (typiquement 14 conservées)
- Seccomp = filtrage des appels système (plusieurs dizaines bloqués par défaut)
- LSM (AppArmor/SELinux) = couche supplémentaire de politiques d’accès
- User namespaces / rootless = root mappé sur UID non-privilégié
- Principe du moindre privilège :
--cap-drop=ALL,--read-only,--security-opt no-new-privileges
Pour aller plus loin
Section intitulée « Pour aller plus loin »Prochaine étape
Section intitulée « Prochaine étape »Maintenant que vous comprenez comment Docker isole les conteneurs, apprenez à diagnostiquer les problèmes quand un conteneur ne démarre pas ou redémarre en boucle.