Docker macvlan: Не вдається дістатися до контейнера — виправте класичну пастку маршрутизації

Було корисно?

Ви створили мережу macvlan, щоб контейнер поводився як «справжній» хост у LAN. Він отримує власну IP‑адресу, з’являється в логах DHCP,
інші машини в мережі можуть до нього дістатися. Потім ви пробуєте зробити curl з Docker‑хоста і… нічого. Ні ping, ні TCP‑з’єднання, ні радості.

Це класична пастка macvlan‑маршрутизації: хост за замовчуванням не може дістатися до своїх дочірніх macvlan‑інтерфейсів через той самий фізичний інтерфейс.
Здається, ніби це проблема фаєрвола. Наче запах ARP‑проблеми. Насправді — ні те, ні інше, поки ви не почнете гадати й ускладнювати діагностику.

Що ви бачите (і чому це так плутає)

Режим відмови зазвичай дуже специфічний:

  • Контейнер має IP у вашій LAN (наприклад, 192.168.10.50).
  • Інші машини в мережі можуть нормально пінгувати/запитувати його.
  • Docker‑хост сам не може пінгувати/запитувати його.
  • Іноді контейнер також не може дістатися до хоста за LAN‑IP хоста.

Якщо ви звикли до bridge‑мереж (docker0), то очікуєте, що хост може спілкуватися з усім.
Macvlan навмисно порушує це очікування. Це не «дивний Docker»; це драйвер macvlan у Linux робить те, для чого його спроектували: створює кілька віртуальних інтерфейсів з унікальними MAC‑адресами, але з певними властивостями ізоляції трафіку.

Пастка: ви розмістили контейнер «безпосередньо» в LAN, але не розмістили хост на тому ж L2‑сегменті так, щоб дозволити двосторонній зв’язок host↔child.
Фізичний NIC хоста — це батько, а macvlan‑інтерфейси — діти. У поширених режимах macvlan Linux не дозволяє hairpin‑трафік
від батька до власних macvlan‑дочок.

Жарт №1: macvlan — як дати контейнерам власні вхідні двері — а потім зрозуміти, що власник замкнув внутрішній коридор.

Поведінка macvlan простою мовою: хост не «на» цій мережі

Коли ви створюєте macvlan‑мережу в Docker, Docker просить ядро створити macvlan‑інтерфейси прив’язані до батьківського інтерфейсу
(наприклад, eth0 або enp3s0). Ці macvlan‑інтерфейси живуть у неймспейсах контейнерів, кожен з власною MAC‑адресою.
Для комутатора вони виглядають як окремі машини, підключені до одного порту.

Ключова тонкість — локальна доставка: Linux не обов’язково маршрутизує/містить пакети від батьківського інтерфейсу до своїх macvlan‑дочок.
Тому хост, намагаючись дістатися 192.168.10.50, надсилає ARP через eth0 та очікує відповідь. Але правила macvlan у ядрі можуть перешкоджати
тому, щоб цей трафік повернувся всередину macvlan‑інтерфейсу контейнера. Це свідоме обмеження: macvlan в першу чергу
дає кінцевим точкам L2‑ідентичність, а не робить батька їхнім повноцінним співрозмовником.

Саме тому виникає дивна асиметрія:
інші хости LAN можуть дістатися контейнера, бо їхній трафік надходить із дроту і доставляється в macvlan‑інтерфейс.
Але трафік, що походить від хоста, генерується «вище» батьківського інтерфейсу і підпадає під інші локальні правила обробки.

Режими macvlan і той, що зазвичай має на увазі Docker

Linux підтримує кілька режимів macvlan: bridge, private, vepa, passthru.
Драйвер macvlan Docker за замовчуванням поводиться подібно до bridge для macvlan‑ендепойнтів, але обмеження host↔endpoint залишається класичним підводним каменем.

Якщо вам потрібен зв’язок host↔container, потрібно явно додати інтерфейс на боці хоста в ту ж macvlan‑мережу (так званий «shim» macvlan на хості),
і маршрутизувати підмережу контейнерів через нього. Або перейти на ipvlan L3 або іншу модель мережування.

Цікаві факти та історія, що мають значення

  1. Macvlan — це функція ядра Linux, а не винахід Docker; Docker лише звертається до неї. Це важливо під час налагодження: думайте про ядро, а не про «магію Docker».
  2. Macvlan стала популярною віртуалізацією та телеком‑навантеженнями, де багато кінцевих точок потребують окремої L2‑ідентичності на спільному uplink.
  3. «Один порт комутатора — багато MAC» може активувати корпоративні політики безпеки, як‑от ліміти port‑security на MAC. Ось чому macvlan працює в лабораторії, але може провалитись у корпоративному шафі.
  4. Обмеження host↔macvlan‑дочка відоме й довготривале; це не регресія. Це побічний ефект того, як macvlan підключається до RX/TX‑шляху.
  5. Ipvlan з’явився пізніше як альтернатива, що зменшує розмноження MAC: кілька кінцевих точок ділять MAC батька, а ідентичність переходить на L3. Це іноді доречніший вибір.
  6. Hairpin‑трафік — окрема концепція від ізоляції хоста в macvlan. Hairpin можна включити на мостах; це не завжди «вирішує» доступ хоста до macvlan.
  7. Потрібен promiscuous‑режим на гіпервізорах (VMware, деякі cloud VPC), щоб пропускати кілька MAC через віртуальний NIC. Без цього macvlan мовчки втрачає пакети і всі звинувачують Docker.
  8. ARP‑flux і асиметрична маршрутизація частіше проявляються з macvlan, бо у вас багато адрес на одному фізичному сегменті і ядро має ретельно обирати вихідні IP і маршрути.

Швидкий план діагностики (перевірити першим/другим/третім)

Мета — швидко відповісти на три питання:
(1) Чи справді контейнер у LAN? (2) Чи заблокований шлях host↔container пасткою macvlan?
(3) Чи щось інше (VLAN, безпека комутатора, фільтрація гіпервізора, фаєрвол) погіршує ситуацію?

Перше: підтвердьте, що це класична пастка

  • З іншого хоста в LAN пінгніть/запросіть IP контейнера.
  • З Docker‑хоста пінгніть/запросіть IP контейнера.
  • Якщо працює з інших хостів, але не з Docker‑хоста — ви, ймовірно, у пастці.

Друге: перевірте базові L2/L3 (не «ліпіть» виправлення, якщо нічого не зламано)

  • Переконайтеся, що IP контейнера, маска підмережі та шлюз вірні.
  • Перегляньте таблицю маршрутів хоста — чи немає конфліктуючих маршрутів.
  • Перевірте поведінку ARP на хості для IP контейнера (INCOMPLETE? неправильний MAC?).

Третє: перевірте обмеження середовища

  • Чи це VM, що блокує кілька MAC? Якщо так, увімкніть promisc / forged transmits / MAC spoofing відповідно.
  • Чи це керований комутатор з port‑security або лімітами MAC? Якщо так, підніміть ліміт або уникайте macvlan.
  • Чи використовуються VLAN‑транки? Потрібно перевірити, що тегування правильне і батьківський інтерфейс — потрібний VLAN‑сабінтерфейс.

Четверте: оберіть шаблон виправлення

  • Якщо потрібен host↔container трафік: додайте host‑side macvlan‑інтерфейс і маршрут.
  • Якщо потрібно менше MAC: розгляньте ipvlan L3 і маршрутизацію натомість.
  • Якщо треба лише опублікувати сервіс: використайте bridge‑мережі + порт‑паблішинг і живіть далі.

Практичні завдання: команди, виводи та рішення (12+)

Це завдання, які я справді виконую, коли хтось каже «macvlan не працює». Кожне містить прикладний вивід і яке рішення це підштовхує.
Налаштуйте імена інтерфейсів, підмережі та ID контейнерів під своє оточення.

Завдання 1: Підтвердити, що Docker‑мережа справді macvlan і записати її 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"
        }
      ]
    }
  }
]

Рішення: якщо Driver не macvlan або parent неправильний (невірний NIC, невірний VLAN‑сабінтерфейс),
зупиніться і виправте це спочатку. Неправильний parent = марна театральна діагностика.

Завдання 2: Підтвердити IP контейнера та інтерфейс у неймспейсі

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

Рішення: якщо контейнер не має IP або він не в очікуваній підмережі — ваша проблема в IPAM/DHCP/статичній конфігурації, а не в пастці маршрутизації.

Завдання 3: Перевірка досяжності з іншого LAN‑хоста (контрольний тест)

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

Наведений вивід представляє «поганий» результат з самого хоста. Запустіть те саме з іншої машини в LAN, якщо є така можливість.

Рішення: якщо ніхто в LAN не може дістатися контейнера, ймовірно, upstream‑фільтрація MAC, невірний VLAN або фаєрвол контейнера.
Якщо інші можуть, але хост не може — продовжуйте: класична пастка.

Завдання 4: Перевірити таблицю маршрутів хоста для підмережі контейнера

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

Рішення: якщо маршрут до 192.168.10.0/24 вказує на enp3s0, хост буде робити ARP на батьківський інтерфейс.
Це нормально. Але це й встановлює пастку: хост вважає, що може дістатися контейнера напряму, але локальна доставка до macvlan‑дочок може блокуватися.

Завдання 5: Переглянути стан ARP/neighbor при пінгу контейнера

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

Рішення: INCOMPLETE вказує, що ARP‑запити не отримують відповідей, які хост приймає.
У пастці macvlan ARP‑відповідь може ніколи не бути доставлена стеку хоста.

Завдання 6: Захопити ARP на батьківському інтерфейсі хоста під час пінгу

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

Рішення: якщо бачите запити, але немає відповідей — або відповідей не генерується (контейнер їх не бачить), або вони фільтруються шляхом хоста/гіпервізора/комутатора.

Завдання 7: Захопити ARP всередині контейнера під час пінгу з хоста

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

Рішення: якщо контейнер бачить ARP‑запит, L2‑шлях працює. Якщо хост усе ще показує INCOMPLETE,
ви дивитесь на обмеження host↔macvlan або локальну фільтрацію.

Завдання 8: Перевірити, чи хост не блокує тихо через 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
  }
}

Рішення: якщо політика forward — drop і ви покладаєтеся на форвардинг між інтерфейсами хоста,
потрібні явні правила. Зауважте: класична пастка macvlan зберігається навіть при ліберальному фаєрволі, але строгий фаєрвол може додати додаткові відмови.

Завдання 9: Переконатися, що Docker не підсипав конфліктні iptables‑правила (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

Рішення: якщо FORWARDDROP, розберіться, що саме повинно форвардитись.
Для host↔macvlan зазвичай вирішують через host‑side macvlan shim і маршрутизацію, а не через форвардинг через docker0.

Завдання 10: Створити host‑side macvlan‑інтерфейс («shim») і призначити IP

Це основне виправлення для пастки маршрутизації. Ми створюємо macvlan‑інтерфейс на хості, прив’язаний до того самого parent,
даємо йому IP у тій самій підмережі (або виділений /32 плюс маршрут) і маршрутуємо IP контейнерів через цей 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

Рішення: коли цей інтерфейс піднятий, хост має L2‑присутність, яка може спілкуватися з macvlan‑дочками.
Якщо в LAN уже використовується .254, оберіть вільну адресу. Не привласнюйте адресу маршрутизатора, якщо не любите пізні ночі.

Завдання 11: Додати конкретний маршрут (або політичний маршрут), щоб трафік ішов через shim

Якщо IP контейнерів у тій самій підмережі, що й хост (поширено), хост все одно вважатиме їх «on‑link» через enp3s0.
Потрібно підштовхнути маршрутизацію, щоб IP контейнерів досягалися через 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

Рішення: якщо ip route get показує dev macvlan0, хост тепер надсилатиме трафік до контейнера через shim.
Для багатьох контейнерів маршрутуйте префікс або використайте виділений IPAM‑діапазон для macvlan, щоб маршрутизувати мережу, а не десятки /32.

Завдання 12: Перевірити зв’язок host↔container після підняття 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

Рішення: якщо ping працює, TCP теж має працювати. Якщо ping працює, але TCP ні — перевіряйте, як сервіс биндується і фаєрвол.

Завдання 13: Підтвердити, що контейнер може дістатися хоста через IP 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

Рішення: повідомте додаткам на хості звертатися до контейнера по його IP, а контейнерам — до хоста через 192.168.10.254.
Це не красиво. Це надійно.

Завдання 14: Перевірити фільтрацію MAC або проблеми promiscuity (підказка для VM/гіпервізора)

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

Рішення: на bare‑metal promiscuity 0 часто буває нормальним, бо NIC отримує кадри лише для MAC, про які його інформують.
У VM, якщо macvlan‑кадри фільтруються upstream, ви побачите «іноді працює» поведінку. Тоді виправлення за межами Linux: увімкнути MAC spoofing/forged transmits/promisc на vSwitch/порт‑групі.

Завдання 15: Перевірити VLAN‑parent, якщо ви використовуєте транк

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

Рішення: якщо ваші контейнери мають належати до VLAN 30, parent для Docker macvlan має бути enp3s0.30, а не enp3s0.
Неправильний VLAN = «недоступно», і ви витратите час на звинувачення macvlan.

Шаблони виправлення: оберіть найменш проблемний варіант

Універсального виправлення немає, бо macvlan зазвичай обирають з причини: потрібно L2‑сусідство, окремі IP і щоб інші пристрої LAN трактували контейнери як повноцінні хости.
Але вам також потрібно, щоб хост їх керував. Ці цілі трохи суперечать одна одній.

Шаблон A (рекомендовано): Host‑side macvlan shim + маршрут

Це стандартне виправлення для пастки маршрутизації. Воно робить хост рівноправним піром у macvlan‑сегменті в спосіб, який ядро приймає.
Це явне, спостережуване та зворотне.

Як зробити це правильно:

  • Виділіть присвячену IP для shim (наприклад, 192.168.10.254).
  • Використовуйте виділений діапазон IP для контейнерів (наприклад, 192.168.10.128/25), щоб можна було маршрутизувати префікс до macvlan0, а не додавати багато /32.
  • Персистуйте конфігурацію (systemd‑networkd, NetworkManager або скрипт при завантаженні). Тимчасові ip link add зникнуть після перезавантаження.

Шаблон B: Використайте ipvlan L3 замість macvlan

Якщо ваша головна потреба — «контейнери мають IP у LAN і до них можна дістатися», ipvlan L3 може бути чистішим рішенням.
Він зменшує розмноження MAC, бо кінцеві точки можуть ділити MAC батька, а маршрутизація робиться явною на L3.

Компроміс: тепер доведеться більше думати про маршрути, менше — про L2‑бродкасти. Деякі discovery‑протоколи, що покладаються на L2‑брауадкасти,
поводитимуться інакше. Взамін ви уникаєте драм із port‑security і налаштуваннями MAC‑спуфінгу на гіпервізорі.

Шаблон C: Не використовуйте macvlan; застосуйте bridge + опубліковані порти

Іноді правильна відповідь: припинити намагатись зробити контейнери схожими на фізичні машини. Якщо ви лише відкриваєте HTTP, DNS або кілька TCP‑портів,
bridge‑мережа з порт‑паблішингом простіша і менше роздратує вашу мережеву команду.

Macvlan — інструмент. Це не статус особистості.

Шаблон D: Розмістіть сам хост у VLAN‑сабінтерфейсі і тримайте контейнери в іншому

В деяких організаціях найчистіший підхід — зробити хост «на» management VLAN, а macvlan‑контейнери помістити в сервісний VLAN через транк.
Так ви явно маршрутизуєте між ними і тримаєте хост як просто ще один маршрутизований кінцевий пункт.

Це часто використовують у мульти‑тенантних середовищах, щоб macvlan працював без дивних напівпідключених хостів.

Шаблон E: Використати присвячений NIC для macvlan‑навантажень

Якщо у вас є апаратні ресурси та порт, виділення фізичного NIC для macvlan зменшує конфлікти з ідентичністю хоста в LAN.
Також це робить домени збоїв чистішими: можна піднімати/скидати macvlan‑NIC, не втрачаючи SSH‑зʼєднання до хоста.

Жарт №2: коли macvlan ламається в продакшні, це ніколи не «мережа», допоки не стане — а тоді це завжди ваш change‑тикет.

Типові помилки: симптом → коренева причина → виправлення

1) Хост не може дістатися контейнера, але інші LAN‑хости можуть

Симптом: LAN працює, хост — ні (ping/curl з хоста тайм‑аутиться).

Коренева причина: класична ізоляція host↔child у macvlan на батьківському інтерфейсі.

Виправлення: створити host‑side macvlan shim‑інтерфейс і додати маршрути, щоб IP контейнерів проходили через shim.

2) Ніхто не може дістатися контейнера, контейнер ні до кого

Симптом: контейнер має IP, але він мертвий у мережі.

Коренева причина: upstream‑фільтрація кількох MAC (налаштування гіпервізора, port‑security комутатора) або невірний VLAN‑parent.

Виправлення: увімкнути MAC spoofing/promisc/forged transmits на гіпервізорі/vSwitch; підвищити ліміт MAC на комутаторі; переконатися, що parent — це eth0.VLAN при trunk.

3) Контейнер доступний доти, поки ви не розгорнете другий, потім нестабільність

Симптом: перший контейнер працює; додавання інших викликає переривчастий ARP або випадкову досяжність.

Коренева причина: ліміт MAC на порті комутатора, часті зміни CAM‑таблиці або дублювання IP при статичних призначеннях.

Виправлення: перевірити port‑security; правильно виділяти IP‑діапазони; використовувати Docker IPAM з контрольованим діапазоном; розглянути ipvlan, щоб зменшити кількість MAC.

4) Доступ хоста «виправлено», але тільки для ping

Симптом: після shim ping працює; TCP‑зʼєднання — ні.

Коренева причина: сервіс биндується на localhost, фаєрвол контейнера або фаєрвол хоста блокує конкретні порти.

Виправлення: перевірити ss -lntp всередині контейнера; упевнитися, що сервіс биндується на 0.0.0.0 або IP контейнера; підправити nftables/iptables.

5) Контейнер не може дістатися сервісів хоста за LAN‑IP хоста

Симптом: контейнер може вийти в інтернет, але не до 192.168.10.20 (хост).

Коренева причина: та сама ізоляція, тільки в зворотному напрямку: трафік контейнера до IP батька доходить до стеку хоста у вигляді, що може не повернутися коректно.

Виправлення: змусити контейнери звертатися до shim‑IP хоста (наприклад, 192.168.10.254), або розділити мережі VLAN і маршрутизувати між ними.

6) Пакети видно в tcpdump, але додатки все одно не підʼєднуються

Симптом: tcpdump бачить SYN; додаток тайм‑аутиться.

Коренева причина: асиметрична маршрутизація або rp_filter, який відкидає відповіді, бо ядро вважає, що шлях назад «неправильний».

Виправлення: перевірити sysctl net.ipv4.conf.*.rp_filter; розглянути політичну маршрутизацію або забезпечити, щоб маршрут до IP контейнерів ішов через macvlan shim.

7) Docker‑мережа створена з gateway, який насправді недоступний

Симптом: контейнер доступний on‑link, але не може виходити за межі підмережі.

Коренева причина: неправильний gateway (описка, невірний VLAN) або фаєрвол на шлюзі блокує цей діапазон контейнерів.

Виправлення: перевірити дефолтний маршрут контейнера; перевірити ARP шлюзу; координуватися з мережею щодо ACL.

Три міні‑історії з корпоративного життя про macvlan

Міні‑історія 1: Інцидент через неправильні припущення

Середня компанія хотіла запустити кілька мережевих сервісів у контейнерах: внутрішній DNS, NTP‑релей і кілька пристроїв постачальника,
які припускали «реальні» IP. Хтось запропонував macvlan: «Гарно. Контейнери отримають IP, без порт‑мапінгу, все виглядатиме як звичайні хости.»
Пілот пройшов з одним контейнером. Усі задоволені.

Інцидент почався після перезавантаження під час обслуговування. Моніторинг зауважив, що DNS впав — але тільки з Docker‑хоста.
Інші сервери могли резолвити. On‑call інженер перезапустив контейнер, перевірив логи, глянув фаєрвол.
Ніхто не підозрював справжню причину, бо вважали, що хост завжди може дістатися сервісів, які він запускає. Це припущення зазвичай правильне — поки ви не оберете macvlan.

Вони підняли мережу до мережників (звісно), які побачили ARP‑запити від хоста і відсутність відповідей. Контейнер бачив ARP‑запити, але хост ніколи не навчився запису neighbor.
Усі годину дивилися на захоплення, переконані, що це баг комутатора. «Баг» виявився локальним: батьківському інтерфейсу хоста заборонено спілкуватися з його macvlan‑дочкою.

Виправлення — host‑side macvlan shim плюс /32 маршрут на IP DNS‑контейнера. Перевірки DNS з хоста відразу зелені.
Урок закарбувався: macvlan не зламана, вона просто має свою думку. Якщо ви не знаєте її думку, вона висловиться о 3:00.

Міні‑історія 2: Оптимізація, що відкотилася

Інша організація тримала Docker‑хости у віртуалізованому середовищі. Вони стикнулися з лімітом: надто багато MAC на порту ToR.
Хтось запропонував «оптимізацію»: лишити macvlan, але агресивно перерозподіляти контейнери та IP, щоб зменшити постійну кількість MAC. На папері — геніально.
Насправді це як навчити мережу ненавидіти вас.

Контейнери активно змінювалися, комутатор постійно навчав і старів записи MAC. Деякі шари гіпервізора також кешували фільтри MAC.
Результат — не чистий провал, а найгірший вид: переривчаста досяжність.
Контейнер міг бути досяжним з деяких підмереж, але не з інших. ARP‑таблиці різнилися між хостами. TCP‑руки висіли наполовину.

Рев’ю інциденту показало, що «оптимізація» збільшила churn саме там, де потрібна стабільність: L2‑ідентичність і neighbor discovery.
Мережна команда не була в захваті — і правильно. L2‑мережі люблять стабільність.

Остаточне рішення — міграція на ipvlan L3 на маршрутизованому сегменті і збереження стабільних життєвих циклів контейнерів.
Також виділили присвячений IP‑діапазон і припинили агресивне перерозподілення адрес. «Оптимізація» не зробила швидше; вона просто зробила голосніше.

Міні‑історія 3: Нудна, але правильна практика, що врятувала день

Фінансова компанія мала суворе правило: кожна не‑bridge мережа використовує виділений діапазон, задокументований у внутрішньому «IPAM light» документі.
Нудно. Не модно. Дуже ефективно.

Коли вони ввели macvlan для легасі‑сервісу, який потребував L2‑сусідства, виділили суцільний /27 для IP контейнерів і зарезервували одну адресу для host‑shim.
Вони створили shim через systemd‑networkd, а не через скрипт у терміналі. Також написали односторінковий runbook: «Якщо хост не може дістатися контейнера, перевірити маршрут до /27 через macvlan0.»

Через кілька місяців оновили ядро і Docker під час рутинних патчів. Юніор‑інженер помітив, що моніторинг з хоста почав падати.
Вони не «пробували щось» — виконали runbook: перевірили існування shim‑інтерфейсу, маршрут, ARP. Один reboot показав, що інтерфейс впав, бо конфіг файл не було ввімкнено.
Вони увімкнули конфіг, перезавантажили мережу і сервіс швидко відновився.

Геройства не було. У цьому й справа. Практика, що врятувала день, була нудною: виділені діапазони, персистентна конфігурація і runbook для втомлених людей.

Чек‑листи / покроковий план

Чек‑лист: перед тим, як обрати macvlan у продакшні

  • Переконайтеся, що вам дійсно потрібна L2‑ідентичність. Якщо потрібні лише вхідні TCP/UDP‑порти, bridge + published ports зазвичай кращі.
  • Запитайте мережу заздалегідь: чи може цей порт приймати кілька MAC? Чи є port‑security або ліміти MAC? Чи є NAC‑функції?
  • Визначте, звідки IP: статичний IPAM‑діапазон чи DHCP (Docker macvlan часто використовує статичне виділення через Docker IPAM).
  • Зарезервуйте IP для host shim і документуйте це.
  • Сплануйте виділений контейнерний діапазон, щоб можна було маршрутизувати префікс до shim.
  • Підтвердіть VLAN‑дизайн: якщо ви використовуєте trunks, створіть VLAN‑сабінтерфейси і використайте їх як parent для macvlan.

Покроково: впровадження macvlan з доступом хоста (розумний підхід)

  1. Створити Docker macvlan‑мережу з визначеною підмережею і (ідеально) обмеженим діапазоном IP.

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

    Рішення: якщо це не вдається, ймовірно у вас немає дозволів, parent не існує або NetworkManager заважає.

  2. Запустити контейнер і призначити IP (або дозволити Docker вибрати з пулу).

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

    Рішення: якщо Docker відмовляє в IP, є конфлікти або IP поза підмережею.

  3. Створити host‑shim інтерфейс і призначити йому 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

    Рішення: якщо інтерфейс не піднімається — перевірте стан parent і обмеження драйвера.

  4. Додати маршрути для IP контейнерів (бажано префікс).

    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

    Вивід вище показує, що маршрут все ще йде через enp3s0, бо 192.168.10.50 не входить в 192.168.10.128/25.
    Рішення: узгодьте діапазон IP контейнерів з маршрутом. Не маршрутуйте неправильну половину підмережі і не називайте це «мережевим рішенням».

  5. Маршрутуйте правильний діапазон або додайте /32 маршрути для конкретних контейнерів.

    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

    Рішення: якщо маршрути коректні — перевірте підключення додатків. Якщо все ще не працює, ви вже не в «пастці macvlan».

  6. Персистуйте shim і маршрути через систему управління мережею хоста.

    Не залишайте це епізодичним. Перезавантаження неминучі, як наради про те, чому перезавантаження відбулися.

Цитата, яку варто тримати на думці

«Надія — це не стратегія.» — Вінс Ломбарді (часто цитують в операціях/надійності)

FAQ

1) Чому інші LAN‑хости можуть дістатися мого macvlan‑контейнера, а Docker‑хост — ні?

Тому що macvlan зазвичай забороняє батьківському інтерфейсу напряму спілкуватися зі своїми macvlan‑дочками. Трафік інших хостів приходить по дроту і доставляється в дочірній інтерфейс.
Трафік, згенерований на хості, не проходить hairpin таким самим чином.

2) Це баг Docker?

Ні. Docker використовує драйвер macvlan ядра. Поведінка — відома властивість macvlan у Linux.
Розглядайте це як дизайн ядра мережі, а не як регресію програми.

3) Яке найчистіше виправлення, якщо потрібен зв’язок host↔container?

Створіть host‑side macvlan‑інтерфейс, привʼяжіть його до того самого parent, призначте IP і маршрутуйте IP контейнерів через цей інтерфейс.
Це робить хост повноцінним peer у тому L2‑сегменті.

4) Маршрутувати цілу підмережу до shim чи використовувати /32 на контейнер?

Маршруйте виділений префікс, якщо можете. Це операційно розумно: менше маршрутів, менше сюрпризів, простіше документувати.
/32‑маршрути підходять для невеликої кількості контейнерів або тактичних виправлень.

5) Чи уникне цього проблема ipvlan?

Часто так — особливо ipvlan L3. Ipvlan змінює модель: менше L2‑ідентичності, більше явної L3‑маршрутизації.
Також зменшує розмноження MAC і уникає проблем port‑security.

6) Чи потрібен promiscuous‑режим?

На bare‑metal зазвичай ні. У віртуальних середовищах часто — так, бо гіпервізор/vSwitch може відкидати кадри для «невідомих» MAC.
Якщо macvlan працює на фізичному хості, але не в VM — підозрівайте фільтрацію MAC у гіпервізорі.

7) Мій комутатор має port‑security. Чи можна все одно використовувати macvlan?

Можливо, але треба знати ліміт MAC на цьому порту і як macvlan змінює кількість MAC.
Якщо ви не можете підвищити ліміт або отримати виняток — розгляньте ipvlan або bridge‑мережі.

8) Як зробити, щоб контейнери дісталися до хоста?

Нехай контейнери звертаються до shim‑IP хоста, а не до батьківського IP хоста.
Альтернативно, розділіть мережі хоста та контейнерів VLAN‑ами і маршрутуйте між ними.

9) Чи безпечний macvlan для stateful‑storage сервісів?

Може бути, але не змішуйте «має власний IP» з «ізольований». Ви все одно ділите фізичний NIC, черги і upstream‑шлях.
Для storage зробіть домени збоїв явними: присвячений NIC/VLAN, передбачуваний MTU і протестоване відновлення.

10) Чи ламає macvlan мультикаст або broadcast‑discovery?

Сам по собі macvlan не обов’язково «ламав» broadcast у LAN, але ваше середовище може: VLAN‑межі, IGMP snooping або політики безпеки можуть змінити поведінку.
Якщо ваш додаток покладається на L2‑discovery — тестуйте на реальних комутаторах, а не лише на ноуті та оптимізмі.

Висновок: практичні наступні кроки

Якщо ви застрягли в «не можу дістатися контейнера», припиніть дивитися Docker‑логи. Це майже завжди питання маршрутизації та L2‑семантики.
Підтвердіть асиметричний симптом (LAN працює, хост ні). Потім реалізуйте виправлення, що відповідає вашим обмеженням:
host‑side macvlan shim з явними маршрутами, або перехід на ipvlan, якщо розмиття MAC спричинить виклики від мережі.

Наступні кроки, які ви можете зробити сьогодні:

  • Визначте присвячений діапазон IP для контейнерів і зарезервуйте shim‑IP.
  • Реалізуйте shim‑інтерфейс і маршрут, який дійсно покриває IP контейнерів.
  • Персистуйте конфігурацію, щоб перезавантаження не відтворювали проблему.
  • Напишіть двоххвилинний runbook: «Якщо хост не може дістатися контейнера, перевірити маршрут до діапазону через macvlan0.»
← Попередня
Повільні операції Ceph у Proxmox: знайти вузьке місце (диск, мережа або CPU)
Наступна →
Debian 13: Закріплення пакетів врятувало мій сервер — як використовувати apt preferences без хаосу

Залишити коментар