Docker Avancé

Maîtriser les Internals de Docker : Performance, Debugging et Architecture Avancée

Plongez dans les mécanismes profonds de Docker pour optimiser vos conteneurs, déboguer les problèmes complexes et architecturer des solutions cloud-native robustes. Un cours expert couvrant les namespaces, cgroups, storage drivers et orchestration distribuée.

Preparetoi.academy 60 min

Maîtriser les Internals de Docker : Performance, Debugging et Architecture Avancée

Sommaire

  1. Isolation Kernel : Plongée dans les Namespaces Linux
  2. Gestion des Ressources : Cgroups v1 vs v2 et Quotas
  3. Système de Fichiers Union : Drivers de Storage et Performance I/O
  4. Stack Réseau Internes : Bridges, NAT et iptables
  5. Architecture du Daemon : Socket UNIX et API Remote
  6. Optimisation des Images : BuildKit et Cache Stratégique
  7. Sécurité Système : Profils Seccomp et AppArmor
  8. Debugging Profond : eBPF, strace et Inspection Proc
  9. Réseau Distribué : Overlay Networks et VXLAN
  10. Patterns Orchestration : Health Checks et Résilience Production

1. Isolation Kernel : Plongée dans les Namespaces Linux

Definition approfondie

Les namespaces constituent le fondement de l'isolation dans l'écosystème Docker. Au niveau du kernel Linux, un namespace est une fonctionnalité qui permet de partitionner les ressources globales du système pour qu'un ensemble de processus ait sa propre vue isolée de ces ressources. Docker utilise principalement six types de namespaces : PID (identification des processus), NET (interfaces réseau), IPC (communication inter-processus), MNT (points de montage), UTS (nom d'hôte et domaine) et USER (mapping des utilisateurs). Lorsqu'un conteneur est lancé, le daemon Docker invoque l'appel système clone() avec les flags appropriés pour créer ces namespaces. Cela signifie que le processus PID 1 à l'intérieur du conteneur est isolé du PID 1 de l'hôte, bien qu'il s'agisse techniquement d'un processus standard sur le kernel hôte. Cette abstraction est cruciale pour la sécurité et la stabilité, car elle empêche un processus conteneurisé d'interférer avec les processus système critiques ou d'autres conteneurs. Comprendre les namespaces est essentiel pour déboguer les problèmes de visibilité où un conteneur ne peut pas résoudre un service ou voir un fichier spécifique.

Explication avec analogie

Imaginez un grand immeuble de bureaux partagé par plusieurs entreprises concurrentes. Chaque entreprise dispose de son propre étage avec ses propres salles de réunion, son propre standard téléphonique et sa propre liste d'employés. Bien que tous les employés travaillent dans le même bâtiment physique (le kernel Linux) et utilisent les mêmes ascenseurs et la même structure fondamentale, une entreprise ne peut pas voir les employés d'une autre entreprise dans son annuaire interne, ni accéder à leurs salles de réunion privées. Les namespaces agissent comme les cloisons invisibles et les standards téléphoniques indépendants qui créent cette illusion d'un bâtiment dédié à chaque entreprise, alors qu'ils partagent tous la même infrastructure physique sous-jacente.

Bloc code commente ligne par ligne

# Lancement d'un conteneur avec isolation spécifique pour inspection
docker run -d --name ns-inspect --pid=host alpine sleep 3600

# Inspection des namespaces via le système de fichiers proc
# On récupère le PID du processus principal du conteneur sur l'hôte
CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' ns-inspect)

# Affichage des liens symboliques vers les namespaces du processus
# Cela montre quels namespaces sont partagés ou uniques
ls -l /proc/$CONTAINER_PID/ns/

# Comparaison avec le namespace de l'hôte pour vérifier l'isolation PID
# Si les inodes diffèrent, l'isolation est active
ls -l /proc/1/ns/pid
ls -l /proc/$CONTAINER_PID/ns/pid

# Nettoyage de la ressource pour éviter les fuites
docker rm -f ns-inspect

Cas usage reel

Dans le cadre de la digitalisation des services publics, imaginez une plateforme hébergeant plusieurs microservices pour l'état civil, les impôts et la santé sur le même cluster physique. Pour des raisons de conformité RGPD, le service de santé ne doit jamais pouvoir lister les processus du service des impôts. En configurant explicitement les namespaces PID et NET, on garantit qu'une compromission du service des impôts ne permet pas à l'attaquant de voir les processus voisins pour lancer des attaques latérales. Cela est vital lors de l'audit de sécurité où l'on doit prouver l'isolation stricte entre les données sensibles de différentes administrations.

Type de Namespace Ressource Isolée Impact Sécurité Complexité
PID Identifiants Processus Empêche le kill d-processus hôte Faible
NET Interfaces & Ports Isolement trafic réseau Moyenne
MNT Système de fichiers Empêche modification root host Élevée
USER UID/GID Mapping root conteneur != root host Élevée

Astuce : Utilisez docker run --pid=host uniquement pour le debugging d'un conteneur spécifique, jamais en production car cela brise l'isolation PID.

Attention : Le namespace USER est complexe à configurer ; une erreur de mapping peut rendre les fichiers inaccessibles ou donner des privilèges involontaires.

2. Gestion des Ressources : Cgroups v1 vs v2 et Quotas

Definition approfondie

Les Control Groups (cgroups) sont un mécanisme du kernel Linux qui limite, comptabilise et isole l'utilisation des ressources (CPU, mémoire, I/O disque, réseau) d'un groupe de processus. Docker s'appuie lourdement sur cgroups pour empêcher un conteneur "bruyant" (noisy neighbor) de consommer toutes les ressources de l'hôte. Cgroups v1 est la version historique, organisée en hiérarchies multiples par ressource, tandis que Cgroups v2, plus récent, unifie ces hiérarchies pour une gestion plus cohérente et sécurisée. Dans Docker, cela se traduit par les flags --memory, --cpus, et --blkio-weight. Sans ces limites, un conteneur exécutant une boucle infinie ou une fuite mémoire peut faire tomber tout le serveur, impactant tous les autres services publics hébergés. La compréhension de cgroups est indispensable pour le capacity planning et pour garantir la Qualité de Service (QoS) dans des environnements multi-locataires où la prédictibilité des performances est contractuelle.

Explication avec analogie

Considérez un immeuble résidentiel avec un compteur électrique général. Sans sous-compteurs (cgroups), si un appartement laisse tous ses appareils allumés en permanence, il peut faire sauter le disjoncteur général, plongeant tout l'immeuble dans le noir. Les cgroups agissent comme des disjoncteurs individuels et des compteurs intelligents pour chaque appartement. Ils garantissent que chaque appartement dispose d'un budget énergétique maximal défini. Même si un occupant essaie de brancher trop d'appareils, son alimentation sera limitée à son quota sans affecter la luminosité des voisins. Cela assure une équité de ressources et une stabilité globale du bâtiment.

Bloc code commente ligne par ligne

# Lancement d'un conteneur avec limites strictes de CPU et Mémoire
# --memory=512m : Limite la RAM à 512 Mo, le conteneur sera tué (OOM) si dépassé
# --cpus=1.5 : Limite à 1.5 cœur de processeur, utile pour les calculs intensifs
docker run -d --name resource-limited --memory=512m --cpus=1.5 nginx

# Vérification des limites appliquées via l'inspect Docker
docker inspect resource-limited --format '{{.HostConfig.Memory}}'

# Inspection directe dans le cgroup filesystem (path peut varier selon v1/v2)
# Cela permet de voir la consommation réelle au niveau kernel
cat /sys/fs/cgroup/memory/docker/*/memory.usage_in_bytes

# Mise à jour dynamique des limites sans redémarrage du conteneur
# Crucial pour ajuster les ressources lors de pics de charge imprévus
docker update --memory=1g resource-limited

# Nettoyage
docker rm -f resource-limited

Cas usage reel

Pour un portail administratif de dépôt de dossiers en ligne, les périodes de déclaration fiscale génèrent des pics de charge massifs. Sans cgroups, un script de traitement de PDF mal optimisé dans un conteneur pourrait monopoliser 100% du CPU, rendant le portail inaccessible pour les autres utilisateurs. En appliquant des limites cgroups strictes, on garantit que le service reste réactif même si un traitement individuel est lent. De plus, en surveillant les événements OOM (Out Of Memory), les équipes DevOps peuvent identifier les fuites mémoire dans le code avant qu'elles ne causent des interruptions de service critiques pour les citoyens.

Version Cgroups Structure Sécurité Compatibilité Docker
Cgroups v1 Hiérarchies multiples Moyenne (échappements possibles) Standard historique
Cgroups v2 Hiérarchie unifiée Élevée (contrôle granulaire) Requis pour Kubernetes récent
Sans Limites Aucun contrôle Nulle (risque de DoS) Déconseillé en prod

Astuce : Configurez toujours une limite mémoire inférieure à la RAM physique totale pour laisser de la place au système d'exploitation hôte.

Attention : Une limite CPU trop stricte peut augmenter la latence perçue par l'utilisateur final, testez sous charge réelle.

3. Système de Fichiers Union : Drivers de Storage et Performance I/O

Definition approfondie

Le système de stockage de Docker repose sur des systèmes de fichiers en couches (Union File Systems) comme Overlay2, AUFS ou Btrfs. Chaque image Docker est constituée de plusieurs couches en lecture seule, et un conteneur ajoute une couche writable au sommet. Lorsqu'un conteneur modifie un fichier existant dans une couche inférieure, un mécanisme appelé "Copy-on-Write" (CoW) est déclenché : le fichier est copié dans la couche writable avant d'être modifié. Cela optimise l'espace disque et le temps de démarrage, car les images sont partagées. Cependant, le CoW a un coût en performance I/O, particulièrement pour les opérations d'écriture intensive ou les nombreux petits fichiers. Le choix du storage driver impacte directement la vitesse de build, le démarrage des conteneurs et les performances des bases de données tournant dans Docker. Pour les systèmes d'information publics gérant de gros volumes de documents, comprendre ces mécanismes est vital pour éviter la dégradation des performances disque.

Explication avec analogie

Imaginez une pile de calques transparents superposés sur une table de dessin. Les calques du bas contiennent des dessins de base (l'image Docker). Vous posez un dernier calque vierge au-dessus (le conteneur). Si vous voulez modifier un trait situé sur le calque du bas, vous ne pouvez pas le toucher directement. Vous devez recopier ce trait sur votre calque du dessus, puis le modifier là. Cela préserve les originaux en dessous pour d'autres utilisations, mais cela prend du temps de recopier le trait avant de le changer. Si vous devez recopier des milliers de traits (fichiers) pour chaque modification, le processus devient lent. C'est le principe du Copy-on-Write.

Bloc code commente ligne par ligne

# Vérification du driver de stockage utilisé par le daemon Docker
# Overlay2 est le standard actuel sur la plupart des distributions Linux
docker info | grep "Storage Driver"

# Inspection des couches d'une image pour analyser la taille
# Permet d'identifier les couches lourdes qui ralentissent le pull
docker image history --no-trunc nginx:latest

# Création d'un conteneur avec inspection du mount point
docker create --name storage-test nginx
# Récupération du chemin de la couche writable sur l'hôte
docker inspect storage-test --format '{{.GraphDriver.Data.MergedDir}}'

# Test de performance I/O simple à l'intérieur du conteneur
docker run --rm alpine dd if=/dev/zero of=/tmp/test bs=1M count=100

# Nettoyage
docker rm storage-test

Cas usage reel

Dans une architecture de gestion documentaire pour une administration, les conteneurs doivent générer et stocker des milliers de petits fichiers PDF quotidiennement. Si le storage driver n'est pas optimisé ou si le système de fichiers sous-jacent est lent, l'opération CoW pour chaque fichier peut saturer les IOPS du disque. En analysant les couches avec docker history, on peut refactoriser le Dockerfile pour réduire le nombre de couches et la taille totale. De plus, pour les bases de données, il est recommandé d'utiliser des volumes montés directement (bind mounts) plutôt que la couche writable du conteneur pour bypasser le CoW et gagner en performance et persistance.

Storage Driver Performance Stabilité Usage Recommandé
Overlay2 Élevée Très Stable Production Standard (Linux)
AUFS Moyenne Stable Legacy, éviter nouveau projet
Devicemapper Faible Complexe Obsolète, ne pas utiliser
ZFS/Btrfs Variable Expérimental Cas spécifiques snapshots

Astuce : Pour les bases de données, montez toujours un volume externe (-v) pour éviter la perte de données et contourner le CoW.

Attention : Ne stockez jamais de données persistantes dans la couche writable du conteneur, elle est éphémère.

4. Stack Réseau Internes : Bridges, NAT et iptables

Definition approfondie

Par défaut, Docker crée un réseau bridge virtuel nommé docker0 sur l'hôte. Chaque conteneur connecté à ce réseau reçoit une adresse IP privée dans un sous-réseau défini (ex: 172.17.0.0/16). La communication entre conteneurs se fait directement via ce bridge, mais la communication vers l'extérieur passe par un mécanisme de NAT (Network Address Translation) géré par les règles iptables du kernel Linux. Docker manipule dynamiquement ces règles pour mapper les ports exposés (-p 80:80) vers les IP internes des conteneurs. Cette abstraction simplifie la connectivité mais introduit une complexité de debugging réseau. Pour les environnements sécurisés des services publics, il est crucial de comprendre comment Docker modifie iptables pour ne pas entrer en conflit avec les firewalls existants (comme UFW ou firewalld) et pour assurer que le trafic sensible ne soit pas exposé inadvertamment sur des interfaces publiques.

Explication avec analogie

Considérez un standard téléphonique interne dans une entreprise (le bridge Docker). Chaque employé (conteneur) a un poste interne (IP 172.x.x.x) qui peut appeler les autres employés directement sans passer par l'extérieur. Pour appeler un client externe (Internet), l'appel passe par la ligne principale de l'entreprise qui masque le numéro interne de l'employé (NAT). Si un client veut appeler un employé spécifique, il doit composer un numéro général qui est transféré vers le poste interne (Port Mapping). Le standardiste (iptables) gère toutes ces règles de transfert. Si le standardiste fait une erreur, les appels externes n'arrivent pas ou sont dirigés vers la mauvaise personne.

Bloc code commente ligne par ligne

# Inspection du réseau bridge par défaut pour voir le sous-réseau
docker network inspect bridge --format '{{.IPAM.Config}}'

# Affichage des règles iptables gérées par Docker (nécessite sudo)
# La chaîne DOCKER gère le NAT et le forwarding des ports
sudo iptables -L DOCKER -n -v

# Lancement d'un conteneur sur un réseau isolé personnalisé
# Meilleure pratique que le bridge par défaut pour la sécurité
docker network create --driver bridge --subnet=10.0.0.0/24 secure-net

# Connexion d'un conteneur à ce réseau avec une IP statique
docker run -d --name web --net secure-net --ip 10.0.0.5 nginx

# Vérification de la connectivité depuis un autre conteneur
docker run --rm --net secure-net busybox ping -c 2 10.0.0.5

Cas usage reel

Pour une application de vote électronique, l'isolation réseau est primordiale. Le serveur de base de données ne doit être accessible que par le serveur d'application, et jamais directement depuis Internet. En créant un réseau Docker personnalisé sans publication de ports (-p), on s'assure que la base de données n'est joignable que par nom de service depuis le réseau interne Docker. Les règles iptables générées automatiquement bloquent tout accès externe par défaut. Cela réduit la surface d'attaque et simplifie la conformité aux exigences de sécurité nationale, car le réseau est segmenté logiquement au niveau de l'hôte sans besoin de hardware supplémentaire.

Type de Réseau Isolation Performance Cas d'Usage
Bridge (Default) Faible (tous conteneurs) Élevée Dev, Test isolé
Bridge (Custom) Moyenne (par réseau) Élevée Production Microservices
Host Nulle (partage stack) Maximale Performance critique
None Totale (aucun réseau) N/A Sécurité maximale

Astuce : Utilisez toujours des réseaux personnalisés (docker network create) pour bénéficier de la résolution DNS automatique entre conteneurs.

Attention : Redémarrer le service Docker peut flusher les règles iptables personnalisées, prévoyez un script de restauration.

5. Architecture du Daemon : Socket UNIX et API Remote

Definition approfondie

Docker fonctionne selon une architecture client-serveur. Le client Docker (CLI) communique avec le Docker Daemon (dockerd) via une API REST. Par défaut, cette communication se fait via un socket UNIX (/var/run/docker.sock), ce qui est sécurisé car limité aux utilisateurs du groupe docker. Cependant, le daemon peut être configuré pour écouter sur un socket TCP, permettant une gestion remote. Le daemon est responsable de la gestion des objets Docker (images, conteneurs, réseaux, volumes). Il est unique par hôte et constitue un point de défaillance unique (SPOF). Comprendre cette architecture est vital pour développer des outils d'orchestration maison ou des systèmes de monitoring qui interrogent directement l'API. Pour les systèmes d'information, exposer l'API TCP sans authentification TLS mutuelle équivaut à donner un accès root à l'hôte, ce qui est une faille de sécurité critique.

Explication avec analogie

Le Docker Daemon est comme le gardien principal d'un immeuble de sécurité maximale. Le client Docker (votre terminal) est un résident qui fait des demandes au gardien via un tube pneumatique interne sécurisé (Socket UNIX). Le gardien exécute les demandes (ouvrir une porte, livrer un colis). Si vous installez un interphone externe (Socket TCP), n'importe qui depuis l'extérieur peut sonner. Si l'interphone n'a pas de code secret (TLS/Auth), n'importe qui peut ordonner au gardien d'ouvrir toutes les portes. Le gardien centralise toutes les opérations, donc s'il s'endort (daemon crash), personne ne peut entrer ou sortir jusqu'à son réveil.

Bloc code commente ligne par ligne

# Interaction directe avec l'API Docker via le socket UNIX avec curl
# Évite d'installer le CLI Docker sur les machines de monitoring
curl --unix-socket /var/run/docker.sock http://localhost/containers/json

# Récupération des métriques en temps réel d'un conteneur spécifique
# Utile pour l'intégration dans des dashboards personnalisés (Grafana)
curl --unix-socket /var/run/docker.sock http://localhost/containers/top/ns-inspect

# Configuration du daemon pour écouter sur TCP (DÉCONSEILLÉ sans TLS)
# Modification de /etc/docker/daemon.json pour ajout de "hosts": ["tcp://0.0.0.0:2375"]
# Ne jamais faire cela sur un réseau public non sécurisé
sudo systemctl restart docker

# Vérification des versions API supportées par le daemon
curl --unix-socket /var/run/docker.sock http://localhost/version

Cas usage reel

Dans une infrastructure de digitalisation, il est souvent nécessaire de créer un dashboard de supervision unifié qui affiche l'état de santé de tous les services conteneurisés. Plutôt que d'installer le CLI Docker sur le serveur de monitoring, on utilise l'API directe via le socket UNIX pour récupérer les états des conteneurs. Cela permet de développer des scripts légers en Python ou Go qui alertent les administrateurs en cas de redémarrage intempestif. De plus, comprendre l'API permet d'automatiser le déploiement de nouveaux services administratifs via des scripts CI/CD qui interagissent directement avec le daemon sans passer par des outils lourds.

Méthode Communication Sécurité Latence Usage
Socket UNIX Élevée (local only) Minimale Standard Local
TCP sans TLS Nulle (Root équivalent) Faible Dev Local uniquement
TCP avec TLS Élevée (Auth mutuelle) Moyenne Swarm, Remote Management
SSH Tunnel Élevée Élevée Accès temporaire sécurisé

Astuce : Pour sécuriser l'API remote, utilisez toujours des certificats TLS générés par Docker ou une autorité de confiance interne.

Attention : L'ajout d'un utilisateur au groupe docker lui donne implicitement les privilèges root sur la machine hôte.

6. Optimisation des Images : BuildKit et Cache Stratégique

Definition approfondie

BuildKit est le backend de construction d'images moderne de Docker, remplaçant l'ancien builder legacy. Il introduit un moteur de cache plus intelligent, une exécution parallèle des étapes de build, et la possibilité de secrets mountés temporairement sans les laisser dans les couches finales. L'optimisation des images est cruciale pour réduire la surface d'attaque, accélérer les déploiements et minimiser l'utilisation du stockage. Une image optimisée ne contient que le strict nécessaire pour exécuter l'application (principe du moindre privilège). BuildKit permet d'utiliser des fonctionnalités avancées comme --mount=type=cache pour persister le cache des gestionnaires de paquets (npm, pip, apt) entre les builds sans alourdir l'image finale. Pour les services publics, des images légères signifient des transferts plus rapides sur les réseaux gouvernementaux potentiellement limités et une réduction des vulnérabilités logicielles.

Explication avec analogie

La construction d'une image Docker est comme la préparation d'un colis pour expédition. L'ancien méthode empilait chaque objet dans une boîte scellée définitivement. Si vous changiez un objet au milieu, tout le reste devait être remballé. BuildKit est comme un entrepôt logistique intelligent : il garde les objets courants (dépendances) dans un zone de stockage temporaire accessible rapidement, mais ne les met dans le colis final que si nécessaire. Il permet aussi d'utiliser des outils d'emballage (secrets) qui sont détruits immédiatement après usage, ensuring qu'aucun outil dangereux ne soit expédié dans le colis final au client.

Bloc code commente ligne par ligne

# Syntaxe obligatoire pour activer BuildKit features
# syntax=docker/dockerfile:1

FROM node:18-alpine AS builder

# Mount de cache pour npm : accélère les builds suivants sans laisser de traces
RUN --mount=type=cache,target=/root/.npm \
    npm set progress=false && npm install -g typescript

WORKDIR /app
COPY package*.json ./

# Installation des dépendances avec cache persistant entre les builds
RUN --mount=type=cache,target=/root/.npm \
    npm ci --only=production

COPY . .
RUN npm run build

# Image finale minimale (Multi-stage build)
FROM node:18-alpine
WORKDIR /app
# Copie uniquement des artefacts buildés, pas le code source
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules

CMD ["node", "dist/main.js"]

Cas usage reel

Lors de la mise en place d'une chaîne CI/CD pour une application citoyenne, le temps de build impacte la fréquence des mises en production. En utilisant BuildKit avec le cache mount, le temps d'installation des dépendances passe de 5 minutes à 30 secondes après le premier build. De plus, l'utilisation de multi-stage builds permet de réduire la taille de l'image de 1 Go à 150 Mo. Cela réduit considérablement la bande passante consommée lors du déploiement sur plusieurs serveurs régionaux et diminue le temps de démarrage des nouveaux conteneurs lors d'un scaling automatique en période de forte affluence.

Feature Legacy Builder BuildKit Gain
Cache Couche par couche Granulaire & Mount x10 Vitesse
Secrets Injections Env (risque) Mount temporaire Sécurité
Parallélisme Séquentiel Graphe DAG Optimisation CPU
Output Image Docker OCI, Local, SSH Flexibilité

Astuce : Activez BuildKit par défaut avec export DOCKER_BUILDKIT=1 ou dans la config du daemon pour tous les utilisateurs.

Attention : Les mounts de cache sont locaux au builder, ne comptez pas dessus pour la persistance de données applicatives.

7. Sécurité Système : Profils Seccomp et AppArmor

Definition approfondie

La sécurité des conteneurs ne se limite pas à l'isolation réseau. Seccomp (Secure Computing Mode) est un mécanisme du kernel Linux qui filtre les appels système (syscalls) qu'un processus peut effectuer. Docker applique un profil Seccomp par défaut qui bloque environ 44 syscalls dangereux (comme mount, reboot, ptrace). AppArmor (AppArmor Mandatory Access Control) fonctionne au niveau du chemin de fichier et des capacités réseau. Combinés, ils réduisent drastiquement la surface d'attaque. Si un attaquant compromet une application dans un conteneur, ces profils l'empêchent d'interagir avec le kernel hôte ou d'accéder à des fichiers sensibles hors du conteneur. Pour les systèmes critiques de l'État, il est recommandé de créer des profils personnalisés encore plus restrictifs que le profil par défaut, autorisant uniquement les syscalls strictement nécessaires à l'application métier.

Explication avec analogie

Imaginez un laboratoire de recherche haute sécurité. Le chercheur (conteneur) a besoin d'utiliser certains équipements (syscalls). Le profil Seccomp est une liste blanche accrochée au poignet du chercheur : s'il essaie d'utiliser une machine non listée (comme un broyeur industriel dangereux), la machine se verrouille immédiatement. AppArmor est comme un badge d'accès qui l'empêche d'entrer dans les salles archives interdites (fichiers système). Même si le chercheur est mal intentionné, ces contraintes physiques et logiques l'empêchent de causer des dégâts majeurs au laboratoire (l'hôte).

Bloc code commente ligne par ligne

// Exemple de profil Seccomp personnalisé (fichier profile.json)
// On autorise explicitement les syscalls nécessaires, on bloque le reste
{
    "defaultAction": "SCMP_ACT_ERRNO",
    "archMap": [{"architecture": "SCMP_ARCH_X86_64", "subArchitectures": ["SCMP_ARCH_X86"]}],
    "syscalls": [
        {
            "names": ["accept", "bind", "connect", "listen", "socket"],
            "action": "SCMP_ACT_ALLOW"
        },
        {
            "names": ["open", "read", "write", "close"],
            "action": "SCMP_ACT_ALLOW"
        }
    ]
}
# Lancement du conteneur avec le profil de sécurité personnalisé
# Réduit les capacités au strict minimum requis par l'application
docker run -d --security-opt seccomp=profile.json --name secure-app mon-app

# Vérification du profil appliqué sur le conteneur en cours d'exécution
docker inspect secure-app --format '{{.HostConfig.SecurityOpt}}'

Cas usage reel

Pour un service de traitement de pièces d'identité, l'application n'a besoin d'aucun accès réseau sortant ni d'aucun accès au système de fichiers hôte. En appliquant un profil Seccomp strict qui bloque tous les syscalls réseau sauf ceux nécessaires à l'écoute du port 80, on empêche toute exfiltration de données en cas de compromission du code. De même, AppArmor peut restreindre l'écriture uniquement dans le dossier /uploads temporaire. Cela satisfait aux exigences d'homologation de sécurité (HDS) requises pour les données de santé ou d'identité en France, en prouvant que le conteneur est "jailed" au niveau du kernel.

Profil Niveau Protection Performance Complexité Config
Default Docker Bonne Nulle Aucune
Unconfined Nulle (Dangereux) Maximale Aucune
Custom Seccomp Excellente Faible overhead Élevée
AppArmor Très Bonne Faible overhead Moyenne

Astuce : Utilisez docker run --rm --cap-add SYS_ADMIN uniquement pour déboguer des problèmes de montage, jamais en production.

Attention : Un profil trop restrictif peut faire planter l'application silencieusement si un syscall nécessaire est bloqué.

8. Debugging Profond : eBPF, strace et Inspection Proc

Definition approfondie

Le debugging dans un environnement conteneurisé est complexe car les outils traditionnels peuvent ne pas voir à l'intérieur des namespaces. strace permet de tracer les appels système d'un processus, utile pour comprendre pourquoi un programme plante ou se bloque. Cependant, l'outil le plus puissant actuellement est eBPF (Extended Berkeley Packet Filter), qui permet d'exécuter des programmes sandboxés dans le kernel pour observer le comportement système sans impact sur la performance. Des outils comme bcc ou bpftrace permettent de voir les latences disque, les erreurs réseau ou les appels système bloqués en temps réel. Pour un développeur IT dans le secteur public, maîtriser ces outils signifie pouvoir résoudre des incidents critiques en production sans avoir à redémarrer les services, garantissant la continuité du service public.

Explication avec analogie

Débugger un conteneur sans outils appropriés, c'est comme essayer de réparer un moteur de voiture sans ouvrir le capot, en écoutant seulement le bruit. strace est comme brancher un diagnostic électronique sur le port OBD de la voiture pour voir les erreurs moteur précises. eBPF est comme installer des capteurs internes dans chaque pièce du moteur qui envoient des données en temps réel sur un tableau de bord sans ralentir la voiture. Vous voyez exactement quel piston chauffe ou quelle valve se bloque pendant que la voiture roule à pleine vitesse.

Bloc code commente ligne par ligne

# Lancement d'un conteneur avec un processus qui plante silencieusement
docker run -d --name debug-target alpine sh -c "while true; do sleep 1; done"

# Récupération du PID hôte pour inspection directe
PID=$(docker inspect -f '{{.State.Pid}}' debug-target)

# Utilisation de strace pour voir les appels système du processus
# -p attache au PID, -f suit les forks, -o écrit dans un fichier
sudo strace -f -p $PID -o /tmp/trace.log

# Analyse des appels système avec eBPF (outil execsnoop pour voir les exec)
# Nécessite l'installation des tools bpfcc
sudo /usr/sbin/execsnoop

# Inspection des descripteurs de fichiers ouverts par le processus
# Utile pour voir quels fichiers sont verrouillés ou manquants
ls -l /proc/$PID/fd

Cas usage reel

Une application de paiement en ligne commence à ralentir sporadiquement sans erreur visible dans les logs applicatifs. En utilisant strace sur le processus conteneurisé, on découvre qu'il effectue des milliers d'appels stat sur un fichier de configuration situé sur un montage NFS lent. Cela identifie la racine du problème : une mauvaise gestion du cache de configuration. Sans ces outils internals, l'équipe aurait passé des jours à analyser le code Java/Python sans trouver la cause système. La résolution rapide minimise l'impact financier et la perte de confiance des usagers du service public.

Outil Niveau Impact Perf Usage Principal
Docker Logs Applicatif Nul Logs standards
strace Syscall Moyen (ralentit) Debug précis processus
eBPF/bpftrace Kernel Très Faible Monitoring prod temps réel
/proc/fs Fichier Nul Inspection état statique

Astuce : Pour utiliser eBPF sur Docker Desktop, assurez-vous que le kernel Linux sous-jacent supporte eBPF (version 4.19+).

Attention : strace peut ralentir considérablement le processus tracé, évitez de l'utiliser sur des services temps réel critiques.

9. Réseau Distribué : Overlay Networks et VXLAN

Definition approfondie

Lorsque Docker passe à l'échelle avec Swarm ou des solutions orchestrées, les conteneurs doivent communiquer entre différents hôtes physiques. Le driver de réseau overlay utilise une technique d'encapsulation appelée VXLAN (Virtual Extensible LAN). Il crée un tunnel virtuel entre les hôtes, encapsulant les paquets Ethernet des conteneurs dans des paquets UDP pour les transporter sur le réseau physique sous-jacent. Chaque paquet reçoit un VNI (VXLAN Network Identifier) pour isoler le trafic. Cela permet de créer un réseau plat logique où un conteneur sur le serveur A peut parler à un conteneur sur le serveur B comme s'ils étaient sur la même carte réseau. Pour les architectures cloud-native de l'administration, cela permet la haute disponibilité et la répartition de charge géographique tout en maintenant une isolation logique stricte.

Explication avec analogie

Imaginez deux bureaux gouvernementaux dans deux villes différentes. Ils veulent créer un réseau privé commun sans louer une ligne physique dédiée coûteuse. Ils utilisent le réseau internet public mais mettent chaque courrier dans une enveloppe sécurisée supplémentaire (encapsulation VXLAN) avec une étiquette spéciale (VNI). Le facteur (réseau physique) voit seulement l'enveloppe extérieure et la livre à l'autre ville. Là, l'enveloppe extérieure est retirée et le courrier interne est livré au destinataire exact. Les deux bureaux ont l'impression d'être dans le même bâtiment, bien qu'ils soient séparés par des kilomètres.

Bloc code commente ligne par ligne

# Initialisation d'un Swarm pour activer le réseau distribué
docker swarm init --advertise-addr 192.168.1.10

# Création d'un réseau overlay chiffré pour la communication inter-nœuds
# --opt encrypted active le chiffrement IPSec pour les tunnels VXLAN
docker network create -d overlay --opt encrypted secure-overlay

# Lancement d'un service répliqué sur le réseau overlay
# Les conteneurs seront répartis sur les nœuds disponibles
docker service create --name api --network secure-overlay --replicas 3 mon-api

# Inspection du réseau pour voir les nœuds connectés et les tunnels
docker network inspect secure-overlay --verbose

# Vérification de la connectivité entre conteneurs sur des hôtes différents
docker exec -it <container_id_on_node_A> ping <container_id_on_node_B>

Cas usage reel

Pour un service national de réservation de rendez-vous, la charge doit être répartie sur plusieurs datacenters régionaux pour assurer la résilience. En utilisant un réseau overlay, la base de données peut être répliquée et les services applicatifs peuvent communiquer de manière transparente quel que soit le datacenter où ils tournent. Si un datacenter tombe en panne, le trafic est routé vers les autres nœuds sans reconfiguration IP complexe. Le chiffrement du réseau overlay garantit que les données de rendez-vous transitant entre les régions ne peuvent pas être interceptées sur le réseau public inter-datacenter.

Feature Bridge Local Overlay Distribué
Portée Hôte unique Multi-hôtes (Cluster)
Encapsulation Aucune VXLAN (UDP)
Performance Native Légère overhead CPU
Usage Dev / Single Node Prod / Swarm / K8s

Astuce : Le réseau overlay nécessite que le port 4789 (UDP) soit ouvert entre tous les nœuds du cluster pour le trafic VXLAN.

Attention : Le chiffrement overlay ajoute une charge CPU, surveillez les performances lors de l'activation sur des instances petites.

10. Patterns Orchestration : Health Checks et Résilience Production

Definition approfondie

En production, un conteneur peut être en cours d'exécution (status UP) mais l'application à l'intérieur peut être bloquée (deadlock, base de données inaccessible). Docker propose des "Health Checks" qui exécutent une commande à l'intérieur du conteneur à intervalles réguliers pour vérifier sa santé réelle. Si le check échoue plusieurs fois, le conteneur est marqué "unhealthy". Couplé à des politiques de redémarrage (restart-policy) et à un orchestrateur, cela permet l'auto-guérison (self-healing). Pour les services publics, la disponibilité est contractuelle. Configurer correctement ces checks permet de détecter les pannes silencieuses et de déclencher des alertes ou des redémarrages automatiques avant que les utilisateurs ne signalent le problème.

Explication avec analogie

Un conteneur sans health check est comme un employé présent physiquement à son bureau (processus running) mais qui dort devant son écran. Le manager (Docker) voit qu'il est là et pense que tout va bien. Le health check est comme un superviseur qui passe toutes les minutes demander un rapport d'activité. Si l'employé ne répond pas trois fois de suite, le superviseur le signale comme inactif et appelle un remplaçant (redémarrage). Cela garantit que le service rendu aux citoyens est toujours assuré par quelqu'un de réellement opérationnel.

Bloc code commente ligne par ligne

# Exemple Docker Compose avec health checks robustes
version: '3.8'
services:
  db:
    image: postgres:13
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      # Commande réelle pour tester la connexion DB, pas juste un ping
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  web:
    image: nginx
    depends_on:
      # Attend que la DB soit healthy avant de démarrer
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s

Cas usage reel

Lors du déploiement d'une nouvelle version d'un portail administratif, il arrive que l'application démarre mais échoue à se connecter à son cache Redis. Sans health check, le load balancer envoie du trafic vers ce conteneur défaillant, générant des erreurs 500 pour les utilisateurs. Avec un health check configuré pour tester l'endpoint /health de l'application, le conteneur est marqué unhealthy et retiré automatiquement du pool de load balancing. L'orchestrateur le redémarre ensuite. Cela assure une expérience utilisateur fluide et évite la propagation d'erreurs lors des déploiements continus.

Policy Comportement Usage
no Jamais de redémarrage Debug manuel
on-failure Seulement si exit code != 0 Batch jobs
always Toujours redémarrer Services critiques
unless-stopped Toujours sauf si stoppé manuellement Production Standard

Astuce : Utilisez start_period dans le health check pour éviter les redémarrages en boucle pendant le démarrage lent de l'application (warm-up).

Attention : Un health check trop fréquent ou trop lourd peut consommer des ressources et aggraver la panne qu'il tente de détecter.


Conclusion

Tu as maintenant maitrise :

  • L'architecture kernel des namespaces et cgroups pour l'isolation et les ressources.
  • Les mécanismes de stockage Union FS et l'optimisation des images avec BuildKit.
  • La sécurisation avancée via Seccomp, AppArmor et les réseaux overlay chiffrés.
  • Le debugging profond avec strace et eBPF pour la résolution d'incidents complexes.
  • Les patterns de résilience et health checks pour la haute disponibilité production.
Accédez à des centaines d'examens QCM — Découvrir les offres Premium