Docker Network Architecture
Quand tu lances docker run nginx, Docker ne crée pas juste un processus isolé — il crée un environnement réseau entièrement séparé pour ce container. Comprendre comment cela fonctionne explique pourquoi les containers peuvent se parler, pourquoi les ports doivent être "publiés", et pourquoi un container peut accéder à internet sans configuration particulière.
L'analogie : un immeuble d'appartements
Imagine que chaque container est un appartement dans un immeuble :
- Chaque appartement a sa propre boîte aux lettres (adresse IP, ex:
172.17.0.2) - Il y a un couloir commun par étage (le bridge réseau)
- Le concierge (iptables) gère le trafic entrant/sortant
- La rue (réseau physique de la machine hôte) est partagée entre tous
Les Network Namespaces : l'isolation de base
Linux fournit les network namespaces : chaque namespace est une pile réseau complète et indépendante (interfaces, tables de routage, règles iptables, ports). Docker crée un namespace par container.
# Voir les namespaces réseau actifs sur l'hôte
ip netns list
# (Docker ne les affiche pas toujours ici, mais ils existent)
# Lancer un container et trouver son namespace
docker run -d --name test nginx
docker inspect test --format '{{.NetworkSettings.SandboxKey}}'
# → /var/run/docker/netns/abc123...
Ce que ça signifie concrètement : si le container écoute sur le port 80, ce port 80 n'est PAS le même que le port 80 de ta machine. Ils sont dans des namespaces différents. C'est pourquoi tu dois faire -p 8080:80 pour exposer le port.
Les veth pairs : le câble virtuel
Pour connecter le namespace du container à l'hôte, Docker crée des paires veth (virtual ethernet). Une paire veth, c'est deux interfaces réseau virtuelles reliées comme les deux bouts d'un câble :
Container namespace Hôte (namespace global)
┌────────────────────┐ ┌────────────────────┐
│ eth0 (container) │======│ veth3a2b (hôte) │
│ 172.17.0.2 │ │ branché sur docker0│
└────────────────────┘ └────────────────────┘
# Voir les paires veth créées par Docker
ip link show | grep veth
# → veth3a2b@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
# Voir combien de containers sont connectés
brctl show docker0
# ou
bridge link show
Un paquet qui sort de eth0 dans le container arrive automatiquement sur veth3a2b côté hôte — comme si les deux interfaces étaient reliées par un câble physique.
Le bridge docker0 : le switch virtuel
docker0 est un bridge Linux (switch virtuel logiciel) créé par Docker au démarrage. Tous les containers du réseau bridge par défaut y sont connectés via leurs veth pairs.
# Voir le bridge et son adresse IP
ip addr show docker0
# → docker0: <BROADCAST,MULTICAST,UP,LOWER_UP>
# → inet 172.17.0.1/16 brd 172.17.255.255
# Voir les interfaces connectées au bridge
brctl show docker0
# bridge name bridge id STP enabled interfaces
# docker0 8000.024238c32591 no veth3a2b
# vethf1c9
172.17.0.1 est l'adresse IP de docker0 sur l'hôte — c'est la gateway par défaut de tous les containers.
Le Container Network Model (CNM)
Docker formalise son architecture réseau avec le CNM :
| Composant | Rôle | Exemple concret |
|---|---|---|
| Sandbox | Namespace réseau isolé du container | Le namespace avec eth0, routes, DNS |
| Endpoint | Interface virtuelle connectant le sandbox à un réseau | La paire veth (côté container = eth0) |
| Network | Groupe d'endpoints pouvant communiquer | Le bridge docker0 ou my-app-net |
Un container peut avoir plusieurs endpoints (donc plusieurs IPs) s'il est connecté à plusieurs réseaux. Exemple : un container API connecté à la fois au réseau frontend et au réseau backend.
libnetwork est la bibliothèque Go qui implémente le CNM dans le daemon Docker. Les drivers réseau (bridge, overlay, macvlan…) sont des plugins de libnetwork.
Le parcours complet d'un paquet
Scénario 1 : Container → Internet
Container (172.17.0.2)
│ eth0
│ (veth pair)
│ veth côté hôte
▼
docker0 bridge (172.17.0.1)
│ ip_forward activé
▼
eth0 de l'hôte (ex: 192.168.1.10)
│ iptables MASQUERADE (NAT)
│ → remplace src IP 172.17.0.2 par 192.168.1.10
▼
Internet
# Vérifier que ip_forward est activé (requis pour que Docker fonctionne)
cat /proc/sys/net/ipv4/ip_forward
# → 1
# Voir la règle NAT que Docker crée automatiquement
sudo iptables -t nat -L POSTROUTING -n -v
# → MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16
# "tout ce qui vient de 172.17.x.x et ne va pas vers 172.17.x.x → masquerade"
Scénario 2 : Container → Container (même réseau)
Container A (172.17.0.2)
│ eth0 → vethA → docker0
│ docker0 switche directement
▼
Container B (172.17.0.3)
│ docker0 → vethB → eth0
Le trafic ne quitte jamais l'hôte. docker0 joue le rôle d'un switch L2.
Scénario 3 : Accès depuis l'extérieur (port publishing)
Quand tu fais docker run -p 8080:80 nginx :
# Docker crée cette règle iptables DNAT :
sudo iptables -t nat -L DOCKER -n -v
# → DNAT tcp -- !docker0 docker0 tcp dpt:8080 to:172.17.0.2:80
# "toute requête TCP sur port 8080 → redirige vers 172.17.0.2:80"
Requête externe → port 8080 de l'hôte
│ iptables DNAT (règle DOCKER chain)
│ dst: hôte:8080 → container:80
▼
docker0 → veth → eth0 du container (port 80)
Les chaînes iptables de Docker
Docker crée plusieurs chaînes iptables automatiquement :
sudo iptables -L -n --line-numbers
# DOCKER-USER ← tes règles custom (jamais écrasées par Docker)
# DOCKER ← règles de port publishing
# DOCKER-ISOLATION-STAGE-1 ← isolation inter-réseaux
# DOCKER-ISOLATION-STAGE-2 ← suite de l'isolation
Important : si tu dois ajouter des règles iptables personnalisées, utilise toujours la chaîne
DOCKER-USER. Docker ne la touche jamais.
Observer l'architecture en direct
# 1. Lancer deux containers
docker run -d --name c1 --network bridge alpine sleep 3600
docker run -d --name c2 --network bridge alpine sleep 3600
# 2. Voir leurs IPs
docker inspect c1 --format '{{.NetworkSettings.IPAddress}}'
docker inspect c2 --format '{{.NetworkSettings.IPAddress}}'
# 3. Voir les veth sur l'hôte
ip link show | grep veth
# 4. Voir le bridge
brctl show docker0
# 5. Tester la connectivité
docker exec c1 ping -c2 $(docker inspect c2 --format '{{.NetworkSettings.IPAddress}}')
Résumé
- Chaque container a son propre network namespace (pile réseau isolée)
- Les veth pairs connectent le namespace du container au bridge de l'hôte
- docker0 est le bridge par défaut — tous les containers s'y connectent
- Le trafic vers internet passe par NAT (MASQUERADE) sur l'interface de l'hôte
- Le trafic entrant est redirigé via DNAT (règles iptables)
- Le CNM (Sandbox, Endpoint, Network) est l'abstraction formelle de Docker
- libnetwork implémente le CNM ; les drivers réseau sont des plugins