Aller au contenu
high

Sous le capot : namespaces, cgroups, capabilities et seccomp

17 min de lecture

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.

  • 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

Avant de plonger dans les mécanismes, comprenons la différence fondamentale entre un conteneur et une VM.

Architecture VM vs Conteneur : la VM a un hyperviseur et OS invité par instance, le conteneur partage le kernel
AspectMachine virtuelleConteneur
IsolationOS invité complet + hyperviseurNamespaces + cgroups
KernelSéparé (chaque VM a le sien)Partagé avec l’hôte
DémarrageSecondes à dizaines de secondesCentaines de ms à quelques secondes
MémoireGo par VM (OS inclus)Mo (juste l’app)
SécuritéForte (frontière hardware)Moindre (kernel partagé)
Cas d’usageIsolation forte, OS différentsMicroservices, CI/CD

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.

NamespaceCe qu’il isoleExemple concret
PIDProcessusLe conteneur voit son processus comme PID 1
NETRéseauLe conteneur a sa propre pile réseau, IP, ports
MNTMontagesLe conteneur a son propre filesystem root
UTSHostnameLe conteneur peut avoir son propre nom d’hôte
IPCCommunication inter-processusFiles de messages isolées
USERUtilisateursLe root du conteneur peut être mappé sur un UID non-root
CGROUPCgroupsVue 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).

Fenêtre de terminal
# 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 1
docker exec mon_conteneur ps aux
# USER PID COMMAND
# root 1 nginx

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 :

ModeDaemon DockerConteneursCas d’usage
userns-remapRootUID remappéDurcir des conteneurs existants
RootlessNon-rootUID remappéRéduire la surface d’attaque du daemon
Fenêtre de terminal
# 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.)
Fenêtre de terminal
# Trouver le PID du processus principal
PID=$(docker inspect --format '{{.State.Pid}}' mon_conteneur)
# Lister les namespaces utilisés
ls -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.

Les cgroups (Control Groups) permettent de limiter et comptabiliser les ressources consommées par un groupe de processus.

RessourceOption DockerExemple
Mémoire--memory--memory=512m (limite à 512 Mo)
CPU--cpus--cpus=1.5 (limite à 1,5 cœurs)
I/O disque--device-read-bpsLimite 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.

Fenêtre de terminal
# Lancer un conteneur avec des limites
docker run -d --name web \
--memory=256m \
--cpus=0.5 \
nginx
# Voir les limites appliquées
docker stats web --no-stream

Linux supporte deux versions de cgroups. La v2 est désormais le standard recommandé.

AspectCgroups v1Cgroups v2
ArchitectureHiérarchies multiples (une par contrôleur)Hiérarchie unifiée
Gestion mémoireMoins préciseMeilleur accounting, PSI metrics
KubernetesSupport historiqueStandard depuis 1.25
StatutMaintenance mode (K8s 1.31+)Actif, recommandé
Fenêtre de terminal
# Vérifier quelle version est active
mount | grep cgroup
# cgroup2 on /sys/fs/cgroup type cgroup2 → v2
# cgroup on /sys/fs/cgroup/cpu type cgroup → v1
# Alternative : via Docker
docker info | grep -i cgroup
# Cgroup Driver: systemd
# Cgroup Version: 2
/system.slice/docker-abc123.scope
# Méthode universelle : trouver le cgroup via /proc
PID=$(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.max
cat /sys/fs/cgroup/system.slice/docker-$CONTAINER_ID.scope/memory.current

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.

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) :

CapabilityCe qu’elle permetConservée par défaut
CAP_CHOWNChanger propriétaire de fichiers
CAP_DAC_OVERRIDEIgnorer les permissions fichiers
CAP_FOWNERIgnorer la propriété pour certaines ops
CAP_FSETIDConserver setuid/setgid après modif
CAP_KILLEnvoyer des signaux aux processus
CAP_SETGIDChanger de groupe
CAP_SETUIDChanger d’utilisateur
CAP_SETPCAPModifier les capabilities
CAP_NET_BIND_SERVICEÉcouter sur ports < 1024
CAP_NET_RAWUtiliser RAW sockets (ping)
CAP_SYS_CHROOTUtiliser chroot
CAP_MKNODCréer des fichiers spéciaux
CAP_AUDIT_WRITEÉcrire dans le journal d’audit
CAP_SETFCAPDéfinir capabilities sur fichiers
CapabilityCe qu’elle permetRisque
CAP_SYS_ADMINPresque tout (mount, namespace, ptrace…)Critique
CAP_NET_ADMINConfigurer le réseau (iptables, interfaces)Élevé
CAP_SYS_PTRACEDébugger d’autres processusÉlevé
CAP_SYS_MODULECharger des modules kernelCritique
CAP_SYS_RAWIOAccès I/O brutCritique
CAP_SYS_TIMEModifier l’heure systèmeMoyen
Fenêtre de terminal
# Voir les capabilities effectives d'un conteneur
docker 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 minimum
docker 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 alpine

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.

SyscallEffet potentielPourquoi bloqué
rebootRedémarrer le systèmeImpact hôte
mount / umountMonter/démonter filesystemsÉvasion conteneur
ptraceDébugger d’autres processusInjection de code
clock_settimeModifier l’heure systèmeImpact autres conteneurs
kexec_loadCharger un nouveau kernelPrise de contrôle
bpfProgrammes kernel eBPFSurveillance/modification kernel
add_key / keyctlManipuler le keyring kernelAccès secrets
acctActiver process accountingÉcriture sur l’hôte
pivot_rootChanger le root filesystemÉvasion conteneur
swapon / swapoffGérer le swapImpact 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.

Fenêtre de terminal
# 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 alpine

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"
}
]
}

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) :

LSMDistributionOption Docker
AppArmorUbuntu, Debian--security-opt apparmor=docker-default
SELinuxRHEL, 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.

Fenêtre de terminal
# Vérifier si AppArmor est actif
cat /sys/module/apparmor/parameters/enabled
# Y = actif
# Voir le profil AppArmor d'un conteneur
docker inspect --format '{{.AppArmorProfile}}' mon_conteneur

Quand vous lancez docker run nginx, les 4 piliers de l’isolation s’activent simultanément :

Les 4 piliers de l'isolation : Namespaces, Cgroups, Capabilities, Seccomp
  1. Docker demande au kernel de créer de nouveaux namespaces (PID, NET, MNT…)
  2. Le processus nginx démarre dans ces namespaces isolés
  3. Les cgroups limitent les ressources (mémoire, CPU) disponibles
  4. Les capabilities sont réduites au minimum nécessaire
  5. 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.

PilierQuestionMécanisme
NamespacesQue voit le conteneur ?Isolation de la vue système
CgroupsCombien le conteneur utilise ?Limitation des ressources
CapabilitiesQue peut faire le conteneur ?Fragmentation des privilèges
SeccompQuels syscalls sont autorisés ?Filtrage au niveau kernel

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

7 questions
5 min.
80%

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

  1. Un conteneur n’est pas une VM : il partage le kernel de l’hôte (vulnérabilité kernel = tous les conteneurs exposés)
  2. Namespaces = isolation de la vue (processus, réseau, fichiers)
  3. Cgroups = limitation des ressources (mémoire, CPU) — v2 est le standard actuel
  4. Capabilities = fragmentation des privilèges root (typiquement 14 conservées)
  5. Seccomp = filtrage des appels système (plusieurs dizaines bloqués par défaut)
  6. LSM (AppArmor/SELinux) = couche supplémentaire de politiques d’accès
  7. User namespaces / rootless = root mappé sur UID non-privilégié
  8. Principe du moindre privilège : --cap-drop=ALL, --read-only, --security-opt no-new-privileges

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.