Docker macvlan : Impossible d’atteindre le conteneur — Corriger le piège classique de routage

Cet article vous a aidé ?

Vous avez créé un réseau macvlan pour que votre conteneur puisse vivre comme une « machine réelle » sur le LAN. Il obtient sa propre IP, apparaît dans les logs DHCP,
et les autres hôtes peuvent l’atteindre. Puis vous essayez de le curl depuis l’hôte Docker et… rien. Pas de ping, pas de connexion TCP, pas de joie.

C’est le piège classique du routage macvlan : l’hôte ne peut pas atteindre ses enfants macvlan via la même interface physique par défaut.
On dirait un problème de pare-feu. Ça sent le problème ARP. Ce n’en est pas un — jusqu’à ce que vous compliquiez les choses en devinant la cause.

Ce que vous observez (et pourquoi c’est si déroutant)

Le mode d’échec est généralement très spécifique :

  • Le conteneur a une IP sur votre LAN (par exemple 192.168.10.50).
  • D’autres machines sur le LAN peuvent le pinguer/curl sans problème.
  • L’hôte Docker lui-même ne peut pas le pinguer/curl.
  • Parfois, le conteneur ne peut pas non plus atteindre l’hôte sur l’IP LAN de l’hôte.

Si vous venez des réseaux en bridge (docker0) vous vous attendez à ce que l’hôte puisse parler à tout. Macvlan viole volontairement cette attente.
Ce n’est pas « Docker qui est bizarre » ; c’est le driver macvlan du noyau Linux qui fait exactement ce pour quoi il a été conçu : créer plusieurs interfaces virtuelles avec des adresses MAC uniques, mais avec des propriétés d’isolation du trafic spécifiques.

Le piège : vous avez placé le conteneur « directement » sur le LAN, mais vous n’avez pas mis l’hôte sur ce même segment L2 d’une manière qui permette la communication hôte↔enfant.
la NIC physique de l’hôte est le parent, et les interfaces macvlan sont les enfants. Dans les modes macvlan courants, Linux n’autorise pas le hairpin du trafic
depuis le parent vers ses propres enfants macvlan.

Blague n°1 : macvlan, c’est comme donner aux conteneurs leur propre porte d’entrée — puis réaliser que le propriétaire a fermé le couloir intérieur à clé.

Comportement du macvlan en clair : l’hôte n’est pas « sur » ce réseau

Lorsque vous créez un réseau macvlan dans Docker, Docker demande au noyau de créer des interfaces macvlan liées à une interface parent
(par exemple eth0 ou enp3s0). Ces interfaces macvlan vivent dans les namespaces des conteneurs, chacune avec sa propre adresse MAC.
Pour le switch, elles ressemblent à des machines séparées branchées sur le même port.

La nuance clé est la livraison locale : un hôte Linux ne relaiera/bridge pas nécessairement les paquets depuis son interface parent vers ses propres enfants macvlan.
Ainsi l’hôte tente d’atteindre 192.168.10.50, fait une ARP sur eth0 et attend la réponse. Mais les règles macvlan du noyau peuvent empêcher
que ce trafic fasse demi-tour dans l’interface macvlan appartenant au conteneur. C’est une limitation délibérée : macvlan vise principalement
à donner aux points de terminaison des identités L2, pas à faire dialoguer automatiquement le parent avec eux.

Voilà pourquoi vous observez cette asymétrie étrange :
les autres hôtes du LAN peuvent atteindre le conteneur parce que leur trafic arrive par le câble et est livré dans l’interface macvlan.
Mais le trafic originaire de l’hôte provient « au-dessus » de l’interface parent et est soumis à une sémantique de filtrage local.

Modes macvlan et celui que Docker implique généralement

Linux prend en charge plusieurs modes macvlan : bridge, private, vepa, passthru.
Le driver macvlan de Docker utilise par défaut un comportement proche de bridge pour les endpoints macvlan, mais la limitation hôte↔endpoint reste le piège classique.

Si vous voulez une connectivité hôte↔conteneur, vous devez ajouter explicitement une interface côté hôte sur le réseau macvlan (un « shim » macvlan sur l’hôte),
et router le sous‑réseau des conteneurs via cette interface. Ou bien vous passez à ipvlan L3 ou utilisez un autre modèle réseau.

Faits et historique intéressants qui importent vraiment

  1. Macvlan est une fonctionnalité du noyau Linux, pas une invention de Docker ; Docker s’en sert simplement. Cela importe lors du débogage : pensez noyau, pas « magie Docker ».
  2. Macvlan a été popularisé par la virtualisation et les workloads télécom où de nombreux endpoints requièrent des identités L2 distinctes sur un uplink partagé.
  3. « Un port de switch, plusieurs MAC » peut déclencher des fonctionnalités de sécurité d’entreprise comme les limites de port-security. C’est pourquoi macvlan fonctionne en labo et échoue spectaculairement dans un placard réseau d’entreprise.
  4. La limitation hôte↔enfant macvlan est connue et ancienne ; ce n’est pas une régression. C’est un effet secondaire de la façon dont macvlan s’accroche dans le chemin RX/TX.
  5. Ipvlan a été ajouté plus tard comme alternative qui peut réduire la prolifération d’MAC : plusieurs endpoints partagent la MAC du parent, déplaçant l’identité vers la couche L3. C’est parfois le choix mature.
  6. Le hairpinning est un concept distinct de l’isolation hôte macvlan. Vous pouvez activer le hairpin sur des bridges ; cela ne « corrige » pas automatiquement la portée hôte de macvlan.
  7. Le mode promiscuous est souvent requis sur les hyperviseurs (VMware, certains VPC cloud) pour faire passer plusieurs adresses MAC via une NIC virtuelle. Sans cela, macvlan laisse tomber les paquets silencieusement et vous blâmez Docker.
  8. ARP flux et routage asymétrique apparaissent plus souvent avec macvlan parce que vous avez maintenant plusieurs adresses sur un même segment physique et le noyau doit choisir soigneusement IP source et routes.

Playbook de diagnostic rapide (vérifier d’abord/deuxièmement/troisièmement)

L’objectif est de répondre rapidement à trois questions :
(1) Le conteneur est-il réellement sur le LAN ? (2) Le chemin hôte↔conteneur est-il bloqué par le piège macvlan ?
(3) Quelque chose d’autre (VLAN, sécurité du switch, filtrage hyperviseur, pare-feu) aggrave-t-il la situation ?

Première étape : confirmer que le symptôme est le piège classique

  • Depuis un autre hôte du LAN, pinguez/curl l’IP du conteneur.
  • Depuis l’hôte Docker, pinguez/curl l’IP du conteneur.
  • Si cela fonctionne depuis d’autres hôtes mais pas depuis l’hôte Docker, vous êtes probablement dans le piège.

Deuxième étape : valider les bases L2/L3 (ne « réparez » pas ce qui n’est pas cassé)

  • Confirmez que l’IP du conteneur, le masque de sous‑réseau et la passerelle sont corrects.
  • Confirmez que la table de routage de l’hôte ne contient pas déjà une route conflictuelle.
  • Vérifiez le comportement ARP sur l’hôte pour l’IP du conteneur (est‑elle INCOMPLETE ? mauvais MAC ?).

Troisième étape : vérifier les contraintes de l’environnement

  • Êtes‑vous sur une VM qui bloque plusieurs MAC ? Si oui, activez promisc / forged transmits / spoofing MAC selon le cas.
  • Êtes‑vous sur un switch managé avec port-security ou limite de MACs ? Si oui, il faudra augmenter la limite ou éviter macvlan.
  • Utilisez‑vous des trunks VLAN ? Vérifiez que le marquage est correct et que l’interface parent est bien la sous‑interface VLAN appropriée.

Quatrième étape : choisir un schéma de correction

  • Si vous avez besoin de trafic hôte↔conteneur : ajoutez une interface macvlan côté hôte et une route.
  • Si vous souhaitez moins de MACs : envisagez ipvlan L3 et faites du routage à la place.
  • Si vous avez juste besoin d’exposer un service simple : pensez au réseau bridge + publication de ports, et passez à autre chose.

Tâches pratiques : commandes, sorties et décisions (12+)

Voici les tâches que j’exécute réellement quand quelqu’un dit « macvlan est cassé ». Chacune inclut une sortie représentative et la décision qu’elle guide.
Ajustez les noms d’interfaces, sous‑réseaux et IDs de conteneurs à votre environnement.

Task 1: Confirm the Docker network is really macvlan and note its parent

cr0x@server:~$ docker network ls
NETWORK ID     NAME           DRIVER    SCOPE
a1b2c3d4e5f6   bridge         bridge    local
d4e5f6a1b2c3   host           host      local
e5f6a1b2c3d4   none           null      local
f6a1b2c3d4e5   lan-macvlan    macvlan   local
cr0x@server:~$ docker network inspect lan-macvlan | sed -n '1,120p'
[
  {
    "Name": "lan-macvlan",
    "Id": "f6a1b2c3d4e5...",
    "Driver": "macvlan",
    "Options": {
      "parent": "enp3s0"
    },
    "IPAM": {
      "Config": [
        {
          "Subnet": "192.168.10.0/24",
          "Gateway": "192.168.10.1"
        }
      ]
    }
  }
]

Décision : si Driver n’est pas macvlan ou si parent est incorrect (mauvaise NIC, mauvaise sous‑interface VLAN),
arrêtez et corrigez cela d’abord. Parent incorrect = théâtre de débogage.

Task 2: Confirm the container’s IP and interface inside the namespace

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.ID}}\t{{.Networks}}'
NAMES         ID            NETWORKS
dns01         7c1d2a9f0b11  lan-macvlan
cr0x@server:~$ docker exec -it dns01 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
2: eth0@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:c0:a8:0a:32 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.50/24 brd 192.168.10.255 scope global eth0

Décision : si le conteneur n’a pas d’IP, ou si elle n’est pas dans le sous‑réseau attendu, votre problème est IPAM/DHCP/configuration statique, pas le piège de routage.

Task 3: Test reachability from a different LAN host (control test)

cr0x@server:~$ ping -c 2 192.168.10.50
PING 192.168.10.50 (192.168.10.50) 56(84) bytes of data.
From 192.168.10.20 icmp_seq=1 Destination Host Unreachable
From 192.168.10.20 icmp_seq=2 Destination Host Unreachable

--- 192.168.10.50 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss

La sortie ci‑dessus représente le résultat « mauvais » depuis l’hôte lui‑même. Exécutez la même commande depuis un autre poste du LAN si vous en avez un.

Décision : si personne sur le LAN ne peut atteindre le conteneur, vous avez probablement un filtrage MAC côté switch/hyperviseur, un VLAN incorrect, ou un pare‑feu du conteneur.
Si d’autres peuvent l’atteindre mais pas l’hôte, continuez : piège classique.

Task 4: Check the host route table for the container subnet

cr0x@server:~$ ip route show
default via 192.168.10.1 dev enp3s0 proto dhcp src 192.168.10.20 metric 100
192.168.10.0/24 dev enp3s0 proto kernel scope link src 192.168.10.20 metric 100

Décision : si la route vers 192.168.10.0/24 pointe vers enp3s0, l’hôte va ARP sur l’interface parent.
C’est normal. Cela installe aussi le piège : l’hôte pense pouvoir atteindre directement le conteneur on‑link, mais la livraison locale aux enfants macvlan est bloquée.

Task 5: Inspect ARP/neighbor state when pinging the container

cr0x@server:~$ ip neigh show 192.168.10.50
192.168.10.50 dev enp3s0 INCOMPLETE

Décision : INCOMPLETE suggère que les requêtes ARP ne reçoivent pas une réponse que l’hôte accepte.
Dans le piège macvlan, la réponse ARP peut ne jamais être délivrée à la pile de l’hôte.

Task 6: Capture ARP on the host parent interface while you ping

cr0x@server:~$ sudo tcpdump -ni enp3s0 arp and host 192.168.10.50
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp3s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
12:10:01.123456 ARP, Request who-has 192.168.10.50 tell 192.168.10.20, length 28
12:10:02.125001 ARP, Request who-has 192.168.10.50 tell 192.168.10.20, length 28

Décision : si vous voyez des requêtes mais pas de réponses, soit les réponses ne sont pas émises (le conteneur ne voit pas la requête), soit elles sont filtrées
par le chemin hôte/hyperviseur/switch.

Task 7: Capture ARP inside the container while the host pings it

cr0x@server:~$ docker exec -it dns01 tcpdump -ni eth0 arp and host 192.168.10.20
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
12:10:01.123789 ARP, Request who-has 192.168.10.50 tell 192.168.10.20, length 28

Décision : si le conteneur voit la requête ARP, le chemin L2 est correct. Si l’hôte montre toujours INCOMPLETE,
vous êtes face à la limitation hôte↔macvlan ou à un filtrage local par pare‑feu.

Task 8: Verify the host isn’t silently blocking with nftables/iptables

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy accept;
  }
  chain forward {
    type filter hook forward priority 0; policy drop;
    ct state established,related accept
    iifname "docker0" oifname "docker0" accept
  }
}

Décision : si votre politique forward est drop et que vous comptez sur le forwarding entre interfaces hôtes,
vous aurez besoin de règles explicites. Note : le piège macvlan persiste même avec un pare‑feu permissif, mais un pare‑feu strict peut ajouter des échecs supplémentaires.

Task 9: Confirm Docker didn’t sneak in conflicting iptables rules (legacy setups)

cr0x@server:~$ sudo iptables -S | sed -n '1,80p'
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER

Décision : si FORWARD est DROP, comprenez ce qui est censé être forwardé.
Pour l’hôte↔macvlan, nous résolvons généralement avec une interface macvlan côté hôte et du routage, pas via le forward sur docker0.

Task 10: Create a host-side macvlan interface (“shim”) and assign an IP

C’est la correction centrale pour le piège de routage. Nous créons une interface macvlan sur l’hôte, attachée au même parent,
lui attribuons une IP sur le même sous‑réseau (ou un /32 dédié plus une route), puis routons les IP conteneurs via ce shim.

cr0x@server:~$ sudo ip link add macvlan0 link enp3s0 type macvlan mode bridge
cr0x@server:~$ sudo ip addr add 192.168.10.254/24 dev macvlan0
cr0x@server:~$ sudo ip link set macvlan0 up
cr0x@server:~$ ip addr show macvlan0
20: macvlan0@enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 8a:1c:2e:11:22:33 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.254/24 scope global macvlan0

Décision : une fois cette interface montée, l’hôte a une présence L2 pouvant parler aux enfants macvlan.
Si votre LAN utilise déjà .254 pour quelque chose, choisissez une IP libre. Ne volez pas l’adresse du routeur à moins d’apprécier les nuits blanches.

Task 11: Add a specific route (or policy route) so traffic goes via the shim

Si les IP conteneurs sont dans le même sous‑réseau que l’hôte (cas courant), l’hôte continuera de les considérer « on‑link » via enp3s0.
Il faut orienter le routage pour que les IP conteneurs soient atteintes via macvlan0.

cr0x@server:~$ sudo ip route add 192.168.10.50/32 dev macvlan0
cr0x@server:~$ ip route get 192.168.10.50
192.168.10.50 dev macvlan0 src 192.168.10.254 uid 1000
    cache

Décision : si ip route get affiche dev macvlan0, votre hôte enverra désormais le trafic vers le conteneur via le shim.
Pour plusieurs conteneurs, routez un préfixe entier ou utilisez une plage IP dédiée pour macvlan afin de router un préfixe, pas des dizaines de /32.

Task 12: Validate host↔container connectivity after the shim

cr0x@server:~$ ping -c 2 192.168.10.50
PING 192.168.10.50 (192.168.10.50) 56(84) bytes of data.
64 bytes from 192.168.10.50: icmp_seq=1 ttl=64 time=0.451 ms
64 bytes from 192.168.10.50: icmp_seq=2 ttl=64 time=0.389 ms

--- 192.168.10.50 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.389/0.420/0.451/0.031 ms

Décision : si le ping fonctionne, le TCP devrait fonctionner. Si le ping fonctionne mais pas le TCP, il est temps de vérifier les bind d’applications et les pare‑feux du conteneur.

Task 13: Confirm the container can reach the host via the shim IP

cr0x@server:~$ docker exec -it dns01 ping -c 2 192.168.10.254
PING 192.168.10.254 (192.168.10.254) 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=0.312 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=0.298 ms

--- 192.168.10.254 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.298/0.305/0.312/0.007 ms

Décision : demandez aux applications sur l’hôte de parler au conteneur via l’IP du conteneur, et demandez aux conteneurs de répondre à l’hôte via 192.168.10.254.
Ce n’est pas joli. C’est fiable.

Task 14: Check for MAC filtering or promisc problems (VM/hypervisor clue)

cr0x@server:~$ ip -d link show enp3s0 | sed -n '1,40p'
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 3c:52:82:aa:bb:cc brd ff:ff:ff:ff:ff:ff
    promiscuity 0

Décision : sur du bare metal, promiscuity 0 peut rester acceptable car la NIC reçoit les trames destinées aux MAC qu’on lui annonce.
Dans une VM, si les trames macvlan sont filtrées en amont, vous verrez un comportement « ça marche parfois ». La correction est hors Linux : activez le spoofing MAC / forged transmits / promisc sur le vSwitch/port group.

Task 15: Verify VLAN parent interface if you’re trunking

cr0x@server:~$ ip link show | egrep 'enp3s0|enp3s0\.'
2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
15: enp3s0.30@enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

Décision : si vos conteneurs appartiennent à la VLAN 30, le parent macvlan Docker doit être enp3s0.30, pas enp3s0.
Mauvaise VLAN = « inaccessible », et vous perdrez du temps à blâmer macvlan.

Schémas de correction : choisissez l’option la moins mauvaise

Il n’y a pas de correction universelle parce que macvlan est généralement choisi pour une raison : vous voulez l’adjacence L2, des IP séparées, et la capacité pour les autres appareils du LAN de traiter les conteneurs comme des hôtes de première classe.
Mais vous voulez aussi que l’hôte puisse les gérer. Ces objectifs sont légèrement contradictoires.

Schéma A (recommandé) : Shim macvlan côté hôte + route

C’est la correction standard pour le piège de routage. Elle rend l’hôte pair sur le segment macvlan d’une manière que le noyau acceptera.
C’est explicite. C’est observable. C’est réversible.

Comment bien le faire :

  • Allouez une IP dédiée pour le shim (ex. 192.168.10.254).
  • Utilisez une plage IP dédiée pour les conteneurs (ex. 192.168.10.128/25) afin de router un préfixe vers macvlan0 plutôt que d’ajouter de nombreux /32.
  • Persistez la configuration (systemd-networkd, NetworkManager, ou un script de démarrage). Les ip link add ad‑hoc disparaissent au reboot.

Schéma B : Utiliser ipvlan L3 au lieu de macvlan

Si votre besoin principal est « les conteneurs ont des IP sur le LAN et sont atteignables », ipvlan L3 peut être plus propre.
Il réduit la prolifération d’adresses MAC parce que les endpoints peuvent partager la MAC du parent, et le routage est explicite en L3.

Le compromis : vous vous souciez désormais davantage du routage et moins des sémantiques de broadcast L2. Certains protocoles de découverte qui reposent sur les broadcasts L2
ne se comporteront pas de la même façon. En échange, vous évitez le drame du port‑security du switch et les réglages de spoofing MAC de l’hyperviseur.

Schéma C : Ne pas utiliser macvlan ; utiliser bridge + ports publiés

Parfois la bonne réponse est : arrêtez d’essayer de faire ressembler les conteneurs à des machines physiques. Si vous exposez juste HTTP, DNS, ou quelques ports TCP,
le réseau bridge avec publication de ports est plus simple et moins susceptible d’irriter votre équipe réseau.

Macvlan est un outil. Ce n’est pas une personnalité.

Schéma D : Mettre l’hôte lui‑même sur une sous‑interface VLAN et garder les conteneurs ailleurs

Dans certaines organisations, la manière la plus propre est de faire « vivre » l’hôte sur une VLAN de gestion et de placer les conteneurs macvlan sur une VLAN de service via un trunk.
Ainsi vous pouvez router explicitement entre elles et traiter l’hôte comme un simple endpoint routeur.

C’est souvent la façon dont on fait macvlan fonctionner sur des systèmes multi‑locataires sans créer un hôte à moitié attaché.

Schéma E : Utiliser une NIC dédiée pour les workloads macvlan

Si vous avez le matériel et le port, dédier une NIC physique aux macvlan peut réduire les conflits avec l’identité LAN propre à l’hôte.
Cela rend aussi les domaines de panne plus propres : vous pouvez redémarrer la NIC macvlan sans perdre le SSH vers l’hôte.

Blague n°2 : quand macvlan casse en production, ce n’est jamais « le réseau », jusqu’à ce que ça le soit — et alors c’est toujours votre ticket de changement.

Erreurs courantes : symptôme → cause racine → correction

1) L’hôte ne peut pas atteindre le conteneur, mais les autres hôtes du LAN le peuvent

Symptôme : le LAN fonctionne, l’hôte échoue (ping/curl depuis l’hôte timeout).

Cause racine : isolation classique hôte↔enfant macvlan sur l’interface parent.

Correction : créez une interface shim macvlan côté hôte et ajoutez des routes pour que les IP conteneurs passent par le shim.

2) Personne ne peut atteindre le conteneur, le conteneur ne peut rien atteindre

Symptôme : le conteneur a une IP, mais il est mort sur le réseau.

Cause racine : filtrage en amont de plusieurs adresses MAC (réglages hyperviseur, port‑security switch) ou mauvais parent VLAN.

Correction : activez le spoofing MAC/promisc/forged transmits sur l’hyperviseur/vSwitch ; augmentez la limite MAC du switch ; assurez‑vous que le parent est eth0.VLAN lors d’un trunk.

3) Le conteneur est joignable jusqu’au déploiement d’un second, puis instabilité

Symptôme : le premier conteneur fonctionne ; en ajouter un autre cause des ARP intermittents ou une reachabilité aléatoire.

Cause racine : limite de MAC du switch, churn de la table CAM, ou IP dupliquées dues à des assignations statiques bâclées.

Correction : vérifiez le port‑security du switch ; allouez correctement les plages IP ; utilisez Docker IPAM avec une plage contrainte ; envisagez ipvlan pour réduire le nombre de MAC.

4) La reachabilité depuis l’hôte est « réparée », mais seulement pour le ping

Symptôme : le ping fonctionne après le shim ; la connexion TCP échoue.

Cause racine : service lié à localhost, pare‑feu du conteneur, ou pare‑feu de l’hôte bloquant certains ports.

Correction : validez ss -lntp dans le conteneur ; confirmez que le service écoute sur 0.0.0.0 ou sur l’IP du conteneur ; ajustez les règles nftables/iptables.

5) Le conteneur ne peut pas atteindre les services de l’hôte sur l’IP LAN de l’hôte

Symptôme : le conteneur peut atteindre l’internet, mais pas 192.168.10.20 (l’hôte).

Cause racine : même isolation, juste inversée : le trafic conteneur→hôte visant l’IP du parent arrive dans la pile de l’hôte d’une manière qui peut ne pas renvoyer correctement.

Correction : faites pointer les conteneurs vers l’IP shim de l’hôte (ex. 192.168.10.254), ou concevez avec des VLANs séparés et du routage.

6) Les paquets apparaissent dans tcpdump, mais les applications ne se connectent toujours pas

Symptôme : tcpdump voit des SYN arrivants ; l’application timeout.

Cause racine : routage asymétrique ou rp_filter qui jette les réponses parce que le noyau pense que le chemin de retour est « mauvais ».

Correction : vérifiez sysctl net.ipv4.conf.*.rp_filter ; envisagez du policy routing ou assurez‑vous que la route vers les IP conteneurs passe par le shim macvlan.

7) Le réseau Docker créé avec une gateway qui n’est pas réellement joignable

Symptôme : le conteneur est joignable on‑link mais ne peut pas sortir du sous‑réseau.

Cause racine : mauvaise passerelle (typo, VLAN incorrect) ou pare‑feu sur la passerelle bloquant cette plage conteneur.

Correction : validez la route par défaut du conteneur ; validez l’ARP de la passerelle ; coordonnez‑vous avec l’équipe réseau pour les ACLs.

Trois mini-histoires issues du terrain macvlan en entreprise

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

Une entreprise de taille moyenne voulait exécuter quelques services réseau en conteneurs : DNS interne, relais NTP, et quelques appliances vendors
qui supposaient des « IP réelles ». Quelqu’un a proposé macvlan : « Ce sera propre. Les conteneurs ont des IP, pas de NAT, tout ressemble à un hôte normal. »
Le pilote a réussi avec un conteneur. Coche verte partout.

L’incident a commencé après un reboot de maintenance. Le monitoring a hurlé que le DNS était down — mais seulement depuis l’hôte Docker lui‑même.
Les autres serveurs continuaient de résoudre. L’ingénieur on‑call a fait l’habituel : redémarrer le conteneur, vérifier les logs, puis le pare‑feu.
Personne n’a soupçonné la vraie cause parce qu’on suppose que l’hôte peut toujours atteindre les workloads qu’il exécute. Cette hypothèse est généralement vraie, jusqu’à ce que vous choisissiez macvlan.

Ils ont escaladé vers l’équipe réseau (évidemment), qui a vu des ARP venant de l’hôte sans réponses. Le conteneur voyait les requêtes ARP, mais l’hôte n’apprenait jamais l’entrée neighbor.
Tout le monde a inspecté les captures pendant une heure, convaincu qu’il y avait un bug de switch. Le « bug » était local : l’interface parent de l’hôte n’était pas autorisée à parler à son enfant macvlan.

La correction a été un shim macvlan côté hôte plus une route /32 pour l’IP DNS du conteneur. Les vérifications DNS depuis l’hôte sont redevenues vertes immédiatement.
La leçon est restée : macvlan n’est pas cassé, il a juste une opinion. Si vous ne connaissez pas son opinion, elle s’exprimera à 3 h du matin.

Mini‑histoire 2 : L’optimisation qui s’est retournée contre eux

Une autre organisation exécutait une flotte d’hôtes Docker sur une plateforme virtualisée. Ils ont atteint une limite : trop d’adresses MAC apprises sur un port de top‑of‑rack.
Quelqu’un a proposé une « optimisation » : garder macvlan mais recycler agressivement les conteneurs et les IPs pour réduire le nombre steady‑state de MACs. Sur le papier, c’était malin.
En réalité, c’est la façon de faire détester le réseau.

Au fur et à mesure du churn, le switch apprenait et vieillissait constamment les entrées MAC. Certaines couches d’hyperviseur cachaient aussi des filtres MAC.
Le résultat n’était pas une panne nette. C’était le pire genre : reachabilité intermittente.
Un conteneur était joignable depuis certains sous‑réseaux mais pas d’autres. Les tables ARP différaient entre hôtes. Les handshakes TCP se bloquaient à mi‑chemin.

La revue d’incident a révélé que l’« optimisation » augmentait le churn exactement là où vous voulez de la stabilité : l’identité L2 et la découverte des voisins.
L’équipe réseau n’a pas apprécié, et elle n’aurait pas dû. L2 est heureux quand c’est ennuyeux.

La correction finale a été de migrer le workload vers ipvlan L3 sur un segment routé et de stabiliser la durée de vie des conteneurs.
Ils ont aussi alloué une plage IP dédiée et ont cessé le recyclage agressif d’adresses. L’optimisation de performance n’était pas plus rapide ; elle était juste plus bruyante.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une société de services financiers avait une règle stricte : chaque réseau non‑bridge utilisait une plage d’adresses dédiée, documentée dans un tableau interne « IPAM lite ».
Pas glamour. Pas cutting‑edge. Très efficace.

Lorsqu’ils ont introduit macvlan pour un service legacy nécessitant L2 adjacency, ils ont alloué un /27 contigu pour les IP conteneurs et réservé une adresse pour le shim hôte.
Ils ont créé l’interface shim via systemd‑networkd, pas un script shell post‑it. Ils ont aussi rédigé un runbook d’une page : « Si l’hôte ne peut pas atteindre un conteneur, vérifier la route vers le /27 via macvlan0. »

Des mois plus tard, une mise à jour du noyau et une mise à jour Docker ont eu lieu pendant le patching de routine. Un ingénieur junior a remarqué que les checks depuis l’hôte avaient commencé à échouer.
Ils n’ont pas « essayé des choses ». Ils ont suivi le runbook : vérifié que le shim existait, vérifié la route, vérifié l’ARP. Un reboot avait fait disparaître l’interface parce qu’un fichier de configuration n’était pas activé.
Ils ont corrigé l’activation, rechargé la config réseau, et le service est revenu rapidement.

Rien d’héroïque n’est arrivé. C’est le point. La pratique qui a sauvé la mise était ennuyeuse : plages dédiées, config persistante, et un runbook qui suppose que les humains sont fatigués.

Listes de contrôle / plan étape par étape

Checklist : avant de choisir macvlan en production

  • Confirmez que vous avez vraiment besoin d’identités L2. Si vous avez juste besoin de TCP/UDP entrants, bridge + publication de ports est généralement préférable.
  • Posez la question réseau tôt : ce port peut‑il accepter plusieurs MACs ? Des limites port‑security ? Des fonctionnalités NAC ?
  • Décidez d’où viennent les IP : plage IPAM statique ou DHCP (Docker macvlan utilise souvent une allocation statique via Docker IPAM).
  • Réservez une IP shim pour l’hôte et documentez‑la.
  • Planifiez une plage dédiée pour les conteneurs afin de pouvoir router un préfixe vers le shim.
  • Confirmez la conception VLAN : si vous trunkez, créez des sous‑interfaces VLAN et utilisez‑les comme parents macvlan.

Étape par étape : implémenter macvlan avec accès depuis l’hôte (la façon raisonnable)

  1. Créez le réseau Docker macvlan avec un sous‑réseau défini et (idéalement) une plage IP contrainte.

    cr0x@server:~$ docker network create -d macvlan \
      --subnet=192.168.10.0/24 --gateway=192.168.10.1 \
      -o parent=enp3s0 lan-macvlan
    f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5

    Décision : si cela échoue, vous n’avez probablement pas les permissions, le parent n’existe pas, ou NetworkManager vous gêne.

  2. Exécutez un conteneur et assignez une IP (ou laissez Docker choisir dans la pool).

    cr0x@server:~$ docker run -d --name dns01 --network lan-macvlan --ip 192.168.10.50 alpine sleep 1d
    7c1d2a9f0b11b7f9a3b6c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2

    Décision : si Docker refuse l’IP, vous avez des conflits ou l’IP est hors du sous‑réseau.

  3. Créez l’interface shim côté hôte et assignez‑lui une IP.

    cr0x@server:~$ sudo ip link add macvlan0 link enp3s0 type macvlan mode bridge
    cr0x@server:~$ sudo ip addr add 192.168.10.254/24 dev macvlan0
    cr0x@server:~$ sudo ip link set macvlan0 up
    cr0x@server:~$ ip -br addr show macvlan0
    macvlan0@enp3s0     UP             192.168.10.254/24

    Décision : si l’interface ne monte pas, vérifiez l’état du parent et les restrictions du driver.

  4. Ajoutez des routes pour les IP conteneurs (de préférence un préfixe).

    cr0x@server:~$ sudo ip route add 192.168.10.128/25 dev macvlan0
    cr0x@server:~$ ip route get 192.168.10.50
    192.168.10.50 dev enp3s0 src 192.168.10.20 uid 1000
        cache

    La sortie ci‑dessus montre que la route passe toujours par enp3s0 parce que 192.168.10.50 n’est pas dans 192.168.10.128/25.
    Décision : alignez votre plage IP conteneurs avec votre route. Ne routez pas la mauvaise moitié du sous‑réseau et appelez cela du « networking ».

  5. Routez la plage correcte ou ajoutez des routes /32 pour des conteneurs spécifiques.

    cr0x@server:~$ sudo ip route add 192.168.10.50/32 dev macvlan0
    cr0x@server:~$ ip route get 192.168.10.50
    192.168.10.50 dev macvlan0 src 192.168.10.254 uid 1000
        cache

    Décision : si le routage est correct, testez la connectivité applicative. Si ça échoue encore, vous n’êtes plus dans le cas « piège macvlan ».

  6. Persistez le shim et les routes en utilisant le système de gestion réseau de l’hôte.

    Ne laissez pas cela éphémère. Les reboots arrivent inévitablement, comme les réunions sur pourquoi les reboots ont eu lieu.

Une citation à garder sur votre tableau de bord mental

« L’espoir n’est pas une stratégie. » — Vince Lombardi (souvent cité dans les cercles ops/fiabilité)

FAQ

1) Pourquoi d’autres hôtes LAN peuvent atteindre mon conteneur macvlan, mais pas l’hôte Docker ?

Parce que macvlan empêche typiquement l’interface parent de parler directement à ses enfants macvlan. Le trafic d’autres hôtes arrive depuis le câble et est livré dans l’interface enfant.
Le trafic originaire de l’hôte ne se loopback pas de la même façon.

2) Est‑ce un bug Docker ?

Non. Docker utilise le driver macvlan du noyau. Le comportement est une propriété connue du réseau macvlan sur Linux.
Traitez‑le comme une conception réseau du noyau, pas une régression applicative.

3) Quelle est la correction la plus propre si j’ai besoin de communication hôte↔conteneur ?

Créez une interface macvlan côté hôte attachée au même parent, attribuez‑lui une IP, et routez les IP conteneurs via cette interface.
Cela rend l’hôte pair de première classe sur ce segment L2.

4) Dois‑je router tout un sous‑réseau vers le shim ou utiliser des /32 par conteneur ?

Routez un préfixe dédié si possible. C’est opérationnellement sain : moins de routes, moins de surprises, plus simple à documenter.
Les /32 conviennent pour un petit nombre de conteneurs ou pour des corrections tactiques.

5) Est‑ce qu’ipvlan éviterait ce problème ?

Souvent oui — en particulier ipvlan L3. Ipvlan change le modèle : moins d’identité L2, plus de routage L3 explicite.
Il peut aussi réduire la prolifération d’adresses MAC et éviter les problèmes de port‑security.

6) Ai‑je besoin du mode promiscuous ?

Sur du bare metal, généralement non. Dans les environnements virtualisés, souvent oui — car l’hyperviseur/vSwitch peut supprimer les trames pour des adresses MAC « inconnues ».
Si macvlan fonctionne sur un hôte physique mais échoue dans une VM, suspectez immédiatement le filtrage MAC de l’hyperviseur.

7) Mon switch a du port‑security. Puis‑je quand même utiliser macvlan ?

Peut‑être, mais vous devez connaître la limite de MAC sur ce port et comment macvlan modifie le nombre de MACs.
Si vous ne pouvez pas augmenter la limite ou obtenir une exception, envisagez ipvlan ou le réseau bridge.

8) Comment faire pour que les conteneurs atteignent l’hôte ?

Faites en sorte que les conteneurs parlent à l’hôte via l’IP shim macvlan de l’hôte, pas via l’IP parent de l’hôte.
Alternativement, séparez la gestion hôte et le réseau service des conteneurs avec des VLANs et routez entre eux.

9) Le macvlan est‑il sûr pour des services de stockage stateful ?

Ça peut l’être, mais ne confondez pas « a sa propre IP » avec « est isolé ». Vous partagez toujours la même NIC physique, les mêmes queues et le même chemin en amont.
Pour des services de stockage, explicitez vos domaines de panne : NIC/VLAN dédiés, MTU prévisible, et bascule testée.

10) Le macvlan casse‑t‑il le multicast ou la découverte broadcast ?

Macvlan en soi ne « casse » pas automatiquement le broadcast sur le LAN, mais votre environnement peut le faire : frontières VLAN, IGMP snooping, ou contrôles de sécurité peuvent modifier le comportement.
Si votre application dépend de la découverte L2, testez‑la sur de vrais switches, pas seulement sur un laptop et de l’optimisme.

Conclusion : prochaines étapes pratiques

Si vous êtes coincé dans « impossible d’atteindre le conteneur », arrêtez de regarder les logs Docker. Il s’agit presque toujours de routage et de sémantique L2.
Confirmez le symptôme asymétrique (le LAN fonctionne, l’hôte échoue). Ensuite implémentez la correction qui correspond à vos contraintes :
un shim macvlan côté hôte avec des routes explicites, ou un passage à ipvlan si la prolifération d’MAC va déclencher l’équipe réseau.

Prochaines étapes que vous pouvez faire aujourd’hui :

  • Décidez d’une plage IP dédiée pour les conteneurs et réservez une IP shim.
  • Implémentez l’interface shim et une route qui couvre réellement vos IP conteneurs.
  • Persistez la configuration pour que les reboots ne ressuscitent pas le problème.
  • Rédigez un runbook de deux minutes : « Si l’hôte ne peut pas atteindre un conteneur, vérifiez la route vers la plage via macvlan0. »
← Précédent
Proxmox Ceph Slow Ops : localiser le goulot (disque, réseau ou CPU)
Suivant →
Debian 13 : Le pinning de paquets m’a sauvé le serveur — utiliser apt preferences sans chaos

Laisser un commentaire