Docker macvlan: No puedo alcanzar el contenedor — Soluciona la clásica trampa de enrutamiento

¿Te fue útil?

Has creado una red macvlan para que tu contenedor viva como una “máquina real” en la LAN. Obtiene su propia IP, aparece en los registros DHCP,
y otros hosts pueden alcanzarlo. Luego intentas curl desde el host Docker y… nada. No hay ping, no hay conexión TCP, no hay alegría.

Esta es la clásica trampa de enrutamiento de macvlan: el host no puede alcanzar a sus hijos macvlan a través de la misma interfaz física por defecto.
Parece un problema de firewall. Huele a problema de ARP. No es ninguno de los dos—hasta que lo conviertes en uno por adivinar.

Lo que ves (y por qué es tan confuso)

El modo de fallo suele ser muy específico:

  • El contenedor tiene una IP en tu LAN (por ejemplo 192.168.10.50).
  • Otros equipos en la LAN pueden hacer ping/curl sin problema.
  • El host Docker no puede hacer ping/curl al contenedor.
  • A veces, el contenedor tampoco puede alcanzar al host en la IP LAN del host.

Si vienes de redes bridge (docker0) esperas que el host pueda hablar con todo.
Macvlan viola esa expectativa a propósito. No es “Docker comportándose raro”; es el driver macvlan de Linux haciendo exactamente
lo que fue diseñado para hacer: crear múltiples interfaces virtuales con direcciones MAC únicas, pero con propiedades específicas de aislamiento de tráfico.

La trampa: pones el contenedor “directamente” en la LAN, pero no pusiste al host en ese mismo segmento L2 de forma que permita comunicación host↔hijo.
La NIC física del host es el padre, y las interfaces macvlan son hijos. En modos comunes de macvlan, Linux no permitirá que el tráfico
vuelva desde el padre a sus propios hijos macvlan.

Broma #1: macvlan es como darle a tus contenedores sus propias puertas de entrada—y luego darte cuenta de que el casero cerró el pasillo interno.

Comportamiento de macvlan en cristiano: el host no “está” en esa red

Cuando creas una red macvlan en Docker, Docker le pide al kernel crear interfaces macvlan vinculadas a una interfaz padre
(por ejemplo, eth0 o enp3s0). Esas interfaces macvlan viven en los namespaces de los contenedores, cada una con su propia dirección MAC.
Para el switch, parecen máquinas separadas conectadas al mismo puerto.

La clave es la entrega local: un host Linux no necesariamente enruta/bridgea paquetes desde su interfaz padre hacia sus propios hijos macvlan.
Así que el host intenta alcanzar 192.168.10.50, hace ARP por eth0 y espera la respuesta. Pero las reglas del kernel para macvlan pueden impedir
que ese tráfico vuelva a entrar en la interfaz macvlan que pertenece al contenedor. Es una limitación deliberada: macvlan está pensado principalmente
para dar identidades L2 a endpoints, no para hacer que el padre les hable.

Por eso ves la extraña asimetría:
otros hosts de la LAN pueden alcanzar el contenedor porque su tráfico llega por el cable y se entrega en la interfaz macvlan.
Pero el tráfico originado por el host se origina “por encima” de la interfaz padre y está sujeto a semánticas de filtrado local.

Modos de macvlan y el que Docker suele implicar

Linux admite varios modos de macvlan: bridge, private, vepa, passthru.
El driver macvlan de Docker por defecto se asemeja al comportamiento bridge para los endpoints macvlan, pero la limitación host↔endpoint sigue siendo la pega clásica.

Si quieres conectividad host↔contenedor, necesitas añadir explícitamente una interfaz en el host en la red macvlan (un macvlan “shim” en el host),
y enrutar la subred de los contenedores a través de ella. O cambiar a ipvlan L3 o usar otro modelo de networking.

Datos e historia interesantes que realmente importan

  1. Macvlan es una función del kernel de Linux, no una invención de Docker; Docker solo lo invoca. Eso importa cuando depuras: piensa en el kernel, no en “magia de Docker”.
  2. Macvlan se popularizó en virtualización y cargas telco donde muchos endpoints necesitan identidades L2 distintas en un uplink compartido.
  3. “Un puerto de switch, muchas MACs” puede activar funciones de seguridad de switch empresarial como límites de MAC en port-security. Por eso macvlan funciona en laboratorio y falla estrepitosamente en un armario corporativo.
  4. La limitación de comunicación host↔hijo macvlan es conocida y antigua; no es una regresión. Es un efecto secundario de cómo macvlan se engancha en la ruta RX/TX.
  5. Ipvlan se añadió más tarde como alternativa que puede reducir la proliferación de MAC: múltiples endpoints comparten la MAC del padre, desplazando la identidad a L3. A veces es la opción madura.
  6. Hairpinning es un concepto separado del aislamiento host de macvlan. Puedes habilitar hairpin en bridges; no “arregla” automáticamente la accesibilidad del host en macvlan.
  7. El modo promiscuo suele ser necesario en hipervisores (VMware, algunas VPCs cloud) para pasar múltiples MACs por una NIC virtual. Sin ello, macvlan descarta paquetes silenciosamente y culpas a Docker.
  8. El ARP flux y el enrutamiento asimétrico aparecen más con macvlan porque ahora tienes múltiples direcciones en un mismo segmento físico y el kernel debe elegir IPs de origen y rutas con cuidado.

Guía de diagnóstico rápido (comprueba primero/segundo/tercero)

El objetivo es responder tres preguntas rápidamente:
(1) ¿Está el contenedor realmente en la LAN? (2) ¿Está el camino host↔contenedor bloqueado por la trampa macvlan?
(3) ¿Hay algo más (VLAN, seguridad del switch, filtrado del hipervisor, firewall) que lo empeore?

Primero: confirma que el síntoma es la trampa clásica

  • Desde otro host de la LAN, haz ping/curl a la IP del contenedor.
  • Desde el host Docker, haz ping/curl a la IP del contenedor.
  • Si funciona desde otros hosts pero no desde el host Docker, probablemente estás en la trampa.

Segundo: valida lo básico L2/L3 (no “arregles” lo que no está roto)

  • Confirma que la IP del contenedor, máscara de subred y gateway sean correctos.
  • Confirma que la tabla de rutas del host no contenga ya una ruta en conflicto.
  • Revisa el comportamiento ARP en el host para la IP del contenedor (¿está INCOMPLETE? ¿MAC errónea?).

Tercero: comprueba las restricciones del entorno

  • ¿Estás en una VM que bloquea múltiples MACs? Si sí, habilita promisc / forged transmits / MAC spoofing según corresponda.
  • ¿Estás en un switch gestionado con port-security o límites de direcciones MAC? Si sí, puede que necesites aumentar el límite o evitar macvlan.
  • ¿Usas trunks VLAN? Verifica que el etiquetado sea correcto y que la interfaz padre sea la subinterfaz VLAN adecuada.

Cuarto: elige un patrón de solución

  • Si necesitas tráfico host↔contenedor: añade una interfaz macvlan en el host y una ruta.
  • Si necesitas menos MACs: considera ipvlan L3 y enruta en su lugar.
  • Si solo necesitas exponer servicios simples: considera bridge networking + publicación de puertos, y sigue con tu vida.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son las tareas que realmente ejecuto cuando alguien dice “macvlan está roto.” Cada una incluye una salida representativa y qué decisión toma.
Ajusta nombres de interfaz, subredes e IDs de contenedor a tu entorno.

Tarea 1: Confirma que la red Docker es realmente macvlan y aporta su padre

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

Decisión: si Driver no es macvlan o parent es incorrecto (NIC equivocada, subinterfaz VLAN equivocada),
detente y corrige eso primero. Padre equivocado = teatro de depuración.

Tarea 2: Confirma la IP del contenedor y la interfaz dentro del 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

Decisión: si el contenedor no tiene IP, o no está en la subred esperada, tu problema es IPAM/DHCP/configuración estática, no la trampa de enrutamiento.

Tarea 3: Prueba la alcanzabilidad desde otro host de la LAN (prueba de control)

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 salida anterior representa el resultado “malo” desde el propio host. Ejecuta lo mismo desde otra máquina de la LAN si la tienes.

Decisión: si nadie en la LAN puede alcanzarlo, probablemente tienes filtrado de MAC en el switch/hipervisor, VLAN equivocada o firewall del contenedor.
Si otros pueden alcanzarlo pero el host no, continúa: trampa clásica.

Tarea 4: Revisa la tabla de rutas del host para la subred del contenedor

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

Decisión: si la ruta a 192.168.10.0/24 apunta a enp3s0, el host hará ARP en la interfaz padre.
Eso es normal. También crea la trampa: el host piensa que puede alcanzar directamente al contenedor en la red, pero la entrega local a hijos macvlan está bloqueada.

Tarea 5: Inspecciona el estado ARP/neighbor mientras haces ping al contenedor

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

Decisión: INCOMPLETE sugiere que las solicitudes ARP no están siendo respondidas de una manera que el host acepte.
En la trampa macvlan, la respuesta ARP puede nunca ser entregada al stack del host.

Tarea 6: Captura ARP en la interfaz padre del host mientras haces 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

Decisión: si ves requests pero no replies, o las replies no aparecen, o las respuestas no son emitidas (contenedor no las ve) o son filtradas
por el host/hipervisor/switch.

Tarea 7: Captura ARP dentro del contenedor mientras el host hace ping

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

Decisión: si el contenedor ve la request ARP, el camino L2 está bien. Si el host aún muestra INCOMPLETE,
estás frente a la limitación host↔macvlan o filtrado local de firewall.

Tarea 8: Verifica que el host no esté bloqueando silenciosamente con 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
  }
}

Decisión: si tu política forward es drop y dependes del reenvío entre interfaces del host,
necesitarás reglas explícitas. Nota: la clásica trampa macvlan persiste incluso con firewall permisivo, pero un firewall estricto puede añadir fallos adicionales.

Tarea 9: Confirma que Docker no colocó reglas iptables conflictivas (configuraciones legacy)

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

Decisión: si FORWARD es DROP, entiende qué debe ser reenviado.
Para host↔macvlan, normalmente resolvemos con una interfaz macvlan en el host y enrutamiento, no reenviando a través de docker0.

Tarea 10: Crea una interfaz macvlan en el host (“shim”) y asigna una IP

Esta es la corrección central para la trampa de enrutamiento. Creamos una interfaz macvlan en el host, atada al mismo padre,
le damos una IP en la misma subred (o un /32 dedicado más una ruta), y luego enrutar la IP del contenedor vía ese 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

Decisión: una vez que esta interfaz esté arriba, el host tiene una presencia L2 que puede hablar con hijos macvlan.
Si tu LAN ya usa .254 para algo, elige una IP libre. No te apropies de la dirección del router a menos que disfrutes las noches en vela.

Tarea 11: Añade una ruta específica (o ruta por política) para que el tráfico vaya por el shim

Si las IPs de los contenedores están dentro de la misma subred que el host (común), el host seguirá considerándolas “on-link” vía enp3s0.
Necesitamos empujar al enrutamiento para que las IPs del contenedor se alcancen a través de 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

Decisión: si ip route get muestra dev macvlan0, tu host ahora enviará tráfico al contenedor a través del shim.
Para múltiples contenedores, enruta un rango entero o usa un rango IPAM dedicado a contenedores macvlan para poder enrutar un prefijo, no docenas de /32.

Tarea 12: Valida la conectividad host↔contenedor después del 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

Decisión: si ping funciona, TCP debería funcionar. Si ping funciona pero TCP no, ahora es momento de revisar bind de servicios y firewalls del contenedor.

Tarea 13: Confirma que el contenedor puede alcanzar el host vía la IP del shim

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

Decisión: indica a las aplicaciones en el host que hablen al contenedor vía la IP del contenedor, y a los contenedores que hablen de vuelta al host vía 192.168.10.254.
No es bonito. Es fiable.

Tarea 14: Comprueba filtrado MAC o problemas de promisc (pista VM/hipervisor)

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

Decisión: en metal desnudo, promiscuity 0 puede seguir siendo válido porque la NIC recibe tramas para las MAC que se le anuncian.
En una VM, si las tramas macvlan se filtran upstream, verás comportamiento “funciona a veces”. Entonces la solución está fuera de Linux: habilitar MAC spoofing/forged transmits/promisc en el vSwitch/port group.

Tarea 15: Verifica la interfaz padre VLAN si haces 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

Decisión: si tus contenedores pertenecen a VLAN 30, el parent macvlan de Docker debería ser enp3s0.30, no enp3s0.
VLAN equivocada equivale a “inaccesible”, y perderás tiempo culpando a macvlan.

Patrones de solución: elige la opción menos mala

No hay una solución universal porque macvlan suele elegirse por una razón: quieres adyacencia L2, IPs separadas y que otros dispositivos LAN traten a los contenedores como ciudadanos de primera clase.
Pero también quieres que el host los administre. Esos objetivos están algo en conflicto.

Patrón A (recomendado): Shim macvlan en el host + ruta

Esta es la corrección estándar para la trampa de enrutamiento. Hace al host un par en el segmento macvlan de una forma que el kernel aceptará.
Es explícito. Es observable. Es reversible.

Cómo hacerlo bien:

  • Asigna una IP dedicada para el shim (p. ej., 192.168.10.254).
  • Usa un rango IP dedicado para contenedores (p. ej., 192.168.10.128/25) para poder enrutar un prefijo a macvlan0 en lugar de añadir muchas /32.
  • Persiste la configuración (systemd-networkd, NetworkManager o un script de arranque). Los ip link add ad-hoc desaparecen al reiniciar.

Patrón B: Usa ipvlan L3 en lugar de macvlan

Si tu necesidad principal es “los contenedores tienen IPs en la LAN y son alcanzables”, ipvlan L3 puede ser más limpio.
Reduce la proliferación de direcciones MAC porque los endpoints pueden compartir la MAC del padre, y el enrutamiento es explícito en L3.

La contrapartida: ahora te preocupas más por el enrutamiento y menos por la semántica de broadcast L2. Algunos protocolos de descubrimiento que dependen de broadcast L2
no se comportarán igual. A cambio, evitas drama con port-security del switch y ajustes de MAC spoofing en el hipervisor.

Patrón C: No uses macvlan; usa bridge + puertos publicados

A veces la respuesta correcta es: deja de intentar que los contenedores parezcan máquinas físicas. Si solo expones HTTP, DNS o unos pocos puertos TCP,
el networking bridge con publicación de puertos es más simple y es menos probable que enfurezca a tu equipo de red.

Macvlan es una herramienta. No es una personalidad.

Patrón D: Pon al host en una subinterfaz VLAN y deja los contenedores en otra

En algunas organizaciones, lo más limpio es hacer que el host “viva” en una VLAN de gestión y poner a los contenedores macvlan en una VLAN de servicio vía trunk.
Así puedes enrutar explícitamente entre ellas y tratar al host como un endpoint router más.

A menudo así es como la gente hace funcionar macvlan en sistemas multi-tenant sin crear un host medio-adjuntado raro.

Patrón E: Usa una NIC dedicada para cargas macvlan

Si tienes hardware y puerto, dedicar una NIC física a cargas macvlan puede reducir conflictos con la identidad LAN del host.
También hace los dominios de fallo más limpios: puedes resetear la NIC macvlan sin perder SSH al host.

Broma #2: cuando macvlan se rompe en producción, nunca es “la red,” hasta que lo es—y entonces siempre es tu ticket de cambio.

Errores comunes: síntoma → causa raíz → solución

1) El host no puede alcanzar el contenedor, pero otros hosts sí

Síntoma: la LAN funciona, el host falla (ping/curl desde el host caduca).

Causa raíz: aislamiento clásico host↔hijo macvlan en la interfaz padre.

Solución: crea una interfaz macvlan shim en el host y añade rutas para que las IPs de los contenedores vayan por el shim.

2) Nadie puede alcanzar el contenedor, el contenedor no llega a nada

Síntoma: el contenedor tiene IP, pero está muerto en la red.

Causa raíz: filtrado upstream de múltiples MACs (ajustes del hipervisor, port-security del switch) o parent VLAN equivocada.

Solución: habilita MAC spoofing/promisc/forged transmits en el hipervisor/vSwitch; aumenta el límite de MAC en el switch; asegura que el parent sea eth0.VLAN al trunkear.

3) El contenedor es accesible hasta que despliegas un segundo, luego inestabilidad

Síntoma: el primer contenedor funciona; añadir más causa ARP intermitente o alcance aleatorio.

Causa raíz: límite de MAC en port-security del switch, churn de la tabla CAM, o IPs duplicadas por asignaciones estáticas descuidadas.

Solución: revisa port-security del switch; asigna rangos IP correctamente; usa IPAM de Docker con un rango controlado; considera ipvlan para reducir el conteo de MAC.

4) Alcance desde el host “arreglado”, pero solo para ping

Síntoma: ping funciona tras el shim; conexión TCP falla.

Causa raíz: servicio ligado a localhost, firewall del contenedor o firewall del host bloqueando puertos específicos.

Solución: valida ss -lntp dentro del contenedor; confirma que el servicio liga a 0.0.0.0 o a la IP del contenedor; ajusta reglas nftables/iptables.

5) El contenedor no puede alcanzar servicios del host en la IP LAN del host

Síntoma: el contenedor puede alcanzar internet, pero no 192.168.10.20 (host).

Causa raíz: mismo aislamiento, solo invertido: el tráfico contenedor→host dirigido a la IP del padre llega al stack del host de una forma que puede no devolver bien.

Solución: hace que los contenedores apunten a la IP shim del host (p. ej., 192.168.10.254), o diseña con VLANs separadas y enrutamiento.

6) Las tramas aparecen en tcpdump, pero las aplicaciones aún no conectan

Síntoma: tcpdump ve SYNs llegando; la aplicación caduca.

Causa raíz: enrutamiento asimétrico o rp_filter que descarta respuestas porque el kernel cree que la ruta de retorno es “incorrecta”.

Solución: revisa sysctl net.ipv4.conf.*.rp_filter; considera routing por políticas o asegura que la ruta a las IPs de contenedor sea vía el shim macvlan.

7) Red Docker creada con gateway que en realidad no es alcanzable

Síntoma: el contenedor es alcanzable on-link pero no puede salir de la subred.

Causa raíz: gateway equivocado (error tipográfico, VLAN equivocada) o firewall en el gateway que bloquea ese rango de contenedores.

Solución: valida la ruta por defecto del contenedor; valida ARP del gateway; coordina con el equipo de red para ACLs.

Tres micro-historias del mundo corporativo desde las trincheras de macvlan

Micro-historia 1: El incidente causado por una suposición equivocada

Una empresa mediana quería ejecutar un puñado de servicios de red en contenedores: DNS interno, un relé NTP y un par de appliances de proveedores
que asumían IPs “reales”. Alguien propuso macvlan: “Será limpio. Los contenedores obtienen IPs, sin mapeo de puertos, todo parece un host normal.”
Pasó el piloto con un contenedor. Todo bien.

El incidente comenzó tras un reinicio de mantenimiento. El monitoring gritó que DNS estaba caído—pero solo desde el propio host Docker.
Otros servidores aún resolvían. El ingeniero on-call hizo lo normal: reinició el contenedor, revisó logs y luego revisó el firewall.
Nadie sospechó el problema real porque se asumía que el host siempre puede alcanzar las cargas que ejecuta. Esa suposición suele ser correcta, hasta que eliges macvlan.

Escalaron al equipo de red (claro). Ellos vieron ARP desde el host y ninguna respuesta. El contenedor sí veía las requests ARP, pero el host nunca aprendió la entrada neighbor.
Todos miraron capturas durante una hora, convencidos de un bug de switch. El “bug” era local: la interfaz padre del host no podía hablar con su hijo macvlan.

La solución fue un shim macvlan en el host más una ruta /32 para la IP DNS. Los chequeos DNS desde el host se pusieron verdes de inmediato.
La lección quedó: macvlan no está roto, es opinativo. Si no conoces su opinión, la expresará a las 3 a.m.

Micro-historia 2: La optimización que salió mal

Otra organización ejecutaba una flota de hosts Docker en una plataforma virtualizada. Alcanzaron un límite: demasiadas MACs aprendidas en un puerto de switch top-of-rack.
Alguien propuso “una optimización”: mantener macvlan pero reciclar contenedores e IPs agresivamente para reducir el conteo steady-state de MACs. En papel parece ingenioso.
En realidad, así es como enseñas a la red a odiarte.

A medida que los contenedores churneaban, el switch aprendía y envejecía entradas MAC constantemente. Algunas capas del hipervisor también cacheaban filtros MAC.
El resultado no fue un fallo limpio. Fue lo peor: alcanzabilidad intermitente.
Un contenedor sería alcanzable desde algunas subredes pero no desde otras. Las tablas ARP diferían entre hosts. Handshakes TCP se colgaban a medias.

La revisión del incidente reveló que la “optimización” aumentó el churn justo donde quieres estabilidad: identidad L2 y descubrimiento de vecinos.
El equipo de red no estaba contento, y con razón. L2 está feliz cuando es aburrida.

La corrección fue migrar la carga a ipvlan L3 en un segmento enrutado y mantener vidas útiles de contenedor estables.
También reservaron un rango IP dedicado y dejaron de reciclar direcciones agresivamente. La “optimización” no fue más rápida; solo fue más ruidosa.

Micro-historia 3: La práctica aburrida pero correcta que salvó el día

Una firma financiera tenía una regla estricta: cada red no-bridge usaba un rango de direcciones dedicado, documentado en una hoja interna “IPAM lite”.
No glamoroso. No cutting-edge. Muy efectivo.

Cuando introdujeron macvlan para un servicio legacy que necesitaba adyacencia L2, asignaron un /27 contiguo para IPs de contenedores y reservaron una dirección para el shim del host.
Crearon la interfaz shim vía systemd-networkd, no con un script efímero. También escribieron un runbook de una página: “Si el host no alcanza el contenedor, verifica la ruta al /27 vía macvlan0.”

Meses después, una actualización de kernel y un upgrade de Docker ocurrieron durante el parcheo rutinario. Un ingeniero junior notó que los cheques de monitoring desde el host comenzaron a fallar.
No “intentaron cosas”. Siguieron el runbook: verificaron que existiera la interfaz shim, verificaron la ruta, verificaron ARP. Un reinicio había eliminado la interfaz porque un archivo de configuración no estaba habilitado.
Habilitaron la configuración, recargaron la red y el servicio se recuperó rápido.

No pasó nada heroico. Ese es el punto. La práctica que salvó el día fue aburrida: rangos dedicados, configuración persistente y un runbook que asume que los humanos están cansados.

Listas de comprobación / plan paso a paso

Checklist: antes de elegir macvlan en producción

  • Confirma que realmente necesitas identidades L2. Si solo necesitas TCP/UDP entrante, bridge + puertos publicados suele ser mejor.
  • Haz la pregunta de red temprano: ¿puede este puerto aceptar múltiples MACs? ¿Hay port-security o límites de MAC? ¿Características NAC?
  • Decide de dónde vienen las IPs: rango IPAM estático o DHCP (Docker macvlan a menudo usa asignación estática vía Docker IPAM).
  • Reserva una IP shim del host y documenta esto.
  • Planifica un rango dedicado para contenedores para poder enrutar un prefijo al shim.
  • Confirma diseño VLAN: si trunkeas, crea subinterfaces VLAN y úsalas como padres macvlan.

Paso a paso: implementar macvlan con acceso desde el host (la forma sensata)

  1. Crea la red Docker macvlan con una subred definida y (idealmente) un rango IP restringido.

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

    Decisión: si esto falla, probablemente no tienes permisos, el parent no existe o NetworkManager te está peleando.

  2. Ejecuta un contenedor y asigna una IP (o deja que Docker escoja del pool).

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

    Decisión: si Docker rechaza la IP, tienes conflictos o la IP está fuera de la subred.

  3. Crea la interfaz shim en el host y asigna una 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

    Decisión: si la interfaz no sube, revisa el estado del padre y restricciones del driver.

  4. Añade rutas para las IPs de contenedores (preferiblemente un prefijo).

    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 salida anterior muestra que la ruta aún va por enp3s0 porque 192.168.10.50 no está dentro de 192.168.10.128/25.
    Decisión: alinea tu rango IP de contenedores con tu ruta. No enrutes la mitad equivocada de la subred y llames a eso “networking.”

  5. Enruta el rango correcto o añade rutas /32 para contenedores específicos.

    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

    Decisión: si el enrutamiento es correcto, prueba la conectividad de la aplicación. Si aún falla, ya no estás en la categoría “trampa macvlan”.

  6. Persiste el shim y las rutas usando el sistema de gestión de red del host.

    No lo dejes efímero. Reinicios son inevitables, como reuniones sobre por qué ocurrieron reinicios.

Una cita que deberías tener en tu panel mental

“La esperanza no es una estrategia.” — Vince Lombardi (citada a menudo en círculos de ops/confiabilidad)

Preguntas frecuentes

1) ¿Por qué otros hosts de la LAN pueden alcanzar mi contenedor macvlan, pero el host Docker no?

Porque macvlan típicamente impide que la interfaz padre hable directamente con sus hijos macvlan. El tráfico de otros hosts llega por el cable y se entrega en la interfaz hija.
El tráfico originado por el host no se hairpinea de la misma forma.

2) ¿Es esto un bug de Docker?

No. Docker usa el driver macvlan del kernel. El comportamiento es una propiedad conocida del networking macvlan en Linux.
Trátalo como diseño del kernel de red, no como una regresión de la aplicación.

3) ¿Cuál es la solución más limpia si necesito comunicación host↔contenedor?

Crea una interfaz macvlan en el host atada al mismo parent, asígnale una IP y enruta las IPs de los contenedores por esa interfaz.
Esto hace al host un par de primera clase en ese segmento L2.

4) ¿Debería enrutar una subred completa al shim o usar rutas /32 por contenedor?

Enruta un prefijo dedicado si puedes. Es operacionalmente sensato: menos rutas, menos sorpresas y más fácil de documentar.
Las rutas /32 están bien para pocos contenedores o soluciones tácticas.

5) ¿Ipvlan evitaría este problema?

A menudo sí—especialmente ipvlan L3. Ipvlan cambia el modelo: menos identidad L2, más enrutamiento L3 explícito.
También puede reducir la proliferación de MACs y evitar problemas de port-security.

6) ¿Necesito modo promiscuo?

En metal desnudo, normalmente no. En entornos virtualizados, a menudo sí—porque el hipervisor/vSwitch puede descartar tramas para MACs “desconocidas”.
Si macvlan funciona en un host físico pero falla en una VM, sospecha filtrado MAC del hipervisor inmediatamente.

7) Mi switch tiene port-security. ¿Puedo usar macvlan?

Quizás, pero necesitas conocer el límite de MAC en ese puerto y cómo macvlan cambia el conteo de MACs.
Si no puedes subir el límite o conseguir una excepción, considera ipvlan o bridge networking.

8) ¿Cómo hago que los contenedores alcancen al host?

Haz que los contenedores hablen con el host a través de la IP shim macvlan del host, no a la IP del parent.
Alternativamente, separa la gestión del host y las redes de servicio de contenedores con VLANs y enruta entre ellas.

9) ¿Macvlan es seguro para servicios de almacenamiento stateful?

Puede serlo, pero no confundas “tiene su propia IP” con “está aislado”. Aún compartes la misma NIC física, colas y camino upstream.
Para servicios de almacenamiento, haz explícitos los dominios de fallo: NICs/VLANs dedicadas, MTU predecible y failover probado.

10) ¿Macvlan rompe multicast o descubrimiento broadcast?

Macvlan en sí no “rompe” automáticamente el broadcast en la LAN, pero tu entorno podría: límites de VLAN, IGMP snooping o controles de seguridad pueden cambiar el comportamiento.
Si tu app depende de descubrimiento L2, pruébala con switches reales, no solo con un portátil y optimismo.

Conclusión: próximos pasos prácticos

Si estás atascado en “no puedo alcanzar el contenedor”, deja de mirar logs de Docker. Esto casi siempre es ruteo y semántica L2.
Confirma el síntoma asimétrico (LAN funciona, host falla). Luego implementa la solución que se adapte a tus restricciones:
un shim macvlan en el host con rutas explícitas, o cambiar a ipvlan si la proliferación de MAC te va a despertar por la noche.

Pasos que puedes hacer hoy:

  • Decide un rango IP dedicado para contenedores y reserva una IP shim.
  • Implementa la interfaz shim y una ruta que cubra realmente tus IPs de contenedores.
  • Persiste la configuración para que los reinicios no resuciten el problema.
  • Escribe un runbook de dos minutos: “Si el host no alcanza el contenedor, verifica la ruta al rango vía macvlan0.”
← Anterior
Proxmox Ceph Slow Ops: localizar el cuello de botella (disco, red o CPU)
Siguiente →
Debian 13: El pinning de paquetes me salvó el servidor — cómo usar apt preferences sin caos

Deja un comentario