Docker мережі: bridge vs host vs macvlan — оберіть те, що не підведе пізніше

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

Деякі відмови не починаються з краху. Вони починаються з «Ми лише змінили мережу». І раптом ваші контейнери не можуть достукатися до бази даних, моніторинг сліпне, а команда безпеки виявляє, що додаток слухає на кожному інтерфейсі, ніби повернувся 2009 рік.

Docker дає три спокусливі важелі для поведінки на локальному L2/L3 — bridge, host і macvlan. Усі три можуть працювати. Усі три можуть зруйнувати вам вихідні, якщо обрати неправильно. Давайте оберемо той, що добре старіє в продакшні.

Рішення спочатку: що обрати за 60 секунд

Якщо ви керуєте продакшн-системами, за замовчуванням оберігайте нудне. Нудні мережі тривожать менше.

Оберіть bridge коли

  • Потрібне публікування портів (-p) і розумна ізоляція.
  • Хост має залишатися хостом, а не «суп з контейнерів з root зверху».
  • На хості кілька сервісів і ви хочете, щоб вони співіснували без конфліктів портів.
  • Потрібен шлях до більш складних архітектур пізніше (кілька мереж, внутрішні мережі, контролі політик).

Оберіть host коли

  • Потрібна дуже низька затримка та накладні витрати і ви готові прийняти ризик поширення (фільтри пакетів, порти та простори імен спільні).
  • На хості працює одна велика мережева служба, яка вже прив’язує потрібні порти.
  • Є чітка стратегія файрволінгу й спостереження на хості.

Оберіть macvlan коли

  • Потрібно, щоб контейнери виглядали як повноцінні L2-учасники з власною MAC/IP у вашій фізичній LAN.
  • Інтеграція з системами, що очікують унікальні IP для кожного робочого навантаження (legacy ліцензування, ACL, upstream маршрутизатори, multicast, незграбний моніторинг, «апарати безпеки»).
  • Ви готові керувати реаліями L2: ARP, CAM-таблицями, політикою портів комутаторів і дисципліною IPAM.

Мій упереджений дефолт: починайте з user-defined bridge мереж. Вдавайтеся до host тільки коли можете обґрунтувати ризики письмово. Використовуйте macvlan лише коли потрібно інтегруватися з LAN як із реальним хостом і ви перевірили, що комутатор вас за це не покарає.

Ментальна модель, яка запобігає дурним інцидентам

Docker-мережі — це не магія. Це простір імен мережі Linux плюс трохи клею: віртуальні Ethernet пари (veth), пристрій мосту, правила маршрутизації і правила NAT/iptables/nftables. «Драйвер», який ви обираєте, здебільшого вирішує куди пакети прямують і хто володіє портами.

Три питання, які вирішують усе

  1. Де «живуть» порти? Ви відображаєте порти контейнера на порти хоста (bridge), чи контейнер ділиться простором портів хоста (host), чи контейнер отримує власну IP-адресу (macvlan)?
  2. Хто виконує політику L3/L4? Файрвол хоста + Docker-правила, чи upstream ACL, чи обидва?
  3. Яка ваша домен відмов? Чи хочете ви, щоб «один контейнер збожеволів» означало «збожеволів хост»?

Ще: продуктивність рідко перша проблема. Відлагоджуваність та передбачуваність важливіші. Драйвер мережі, що швидший на 3% але на 30% складніший для розслідування, — це не оптимізація. Це майбутній інцидент із запрошенням у календарі.

Одна цитата, яка справдилася у більшій кількості постмортемів, ніж мені хотілося б рахувати: парафраз: «Надія — не стратегія.» — Джин Кранц (парафраз)

Bridge мережі: значення за замовчуванням не випадкове

Bridge-режим — це «хочу, щоб контейнери мали свій маленький світ, але були доступні» від Docker. Контейнер отримує IP у приватному підмережі. Docker створює Linux-мост (наприклад docker0 або user-defined), потім підключає eth0 контейнера до нього через veth-пару. Вихідний трафік маршрутизується/маскується на інтерфейс хоста. Вхідний трафік зазвичай йде через опубліковані порти.

Чому bridge підходить для продакшну

  • Публікація портів явна. Ви відкриваєте лише те, що маєте на увазі. Це важливо, коли образ контейнера змінюється і раптом прив’язує додаткові порти.
  • Імена мають значення. User-defined bridge мережі дають вбудоване DNS для знаходження сервісів. Контейнери можуть звертатися за іменем без того, щоб ви клеїли IP у конфіги.
  • Ізоляція реальна — відносно. Це не повна межа VM, але це значна лінія утримання від випадкових конфліктів портів і деяких класів неправильних конфігурацій.
  • Налагодження передбачуване. Ви можете мислити потоками: container → veth → bridge → host → uplink; і правила NAT видимі.

Коли bridge підводить

  • Невідповідність MTU. Оверлеї, VPN або jumbo-кадри можуть зламати PMTUD і пакети зникатимуть.
  • Сюрпризи NAT. Зміна вихідної IP може ламати upstream ACL або плутати логи.
  • Поведінка hairpin. Трафік контейнер → хост → контейнер через опублікований порт може відрізнятися від прямого container → container.
  • Дрейф файрволу. Docker маніпулює iptables/nftables. Якщо ваша базова політика файрволу вважає, що вона повинна повністю контролювати правила, виникне turf-war.

Bridge-мережі — як надійний седан. Не гламурно, але заводиться взимку, запчастини дешеві, і всі знають, як його полагодити.

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

--network host скасовує претензії: контейнер ділиться простором мережі хоста. Ніякого NAT. Немає IP контейнера. Немає публікації портів. Якщо процес прив’язується до 0.0.0.0:443, він прив’язує порт 443 хоста. Це і є суть.

Для чого host mode справді підходить

  • Високошвидкісні навантаження, де наклад conntrack і NAT відчутний і болючий.
  • Мережеві апарати (маршрутизатори, BGP-спікери, DHCP-сервери), де потрібна пряма робота з інтерфейсами.
  • Прості хости з одним орендарем, де одне робоче навантаження володіє машиною.

Що host mode тихо ламає

  • Конфлікти портів стають «випадковими» збоями. Розгорніть два сервіси, що хочуть 8125/udp, і ви дізнаєтесь про це під час виконання.
  • Межі безпеки стираються. Контейнер може бачити інтерфейси хоста, іноді локальні сервіси хоста, і ваше припущення «це всередині Docker» помирає.
  • Спостережуваність ускладнюється. Інструменти, що очікують IP контейнера, втрачають орієнтир; атрибуція трафіку може вимагати інструментів, що знають про cgroup.

Короткий жарт №1: Host мережа — як дати контейнеру ключі від квартири, бо він пообіцяв лише помити машину.

Управління host mode, яке робить його терпимим

  • Використовуйте systemd або оркестратор, щоб запобігти двом контейнерам, що прив’язують один порт.
  • Наполягайте на явній політиці файрволу хоста; не покладайтеся на «гарні» дефолти Docker.
  • Документуйте власність портів на хості як контракт. Бо це ним і є.
  • Віддавайте перевагу host mode тільки для робочих навантажень, які його виправдовують: захоплення пакетів, агенти метрик, edge-проксі або реальні мережеві демони.

Macvlan: «виглядає як реальний хост» (і його пастки)

Macvlan призначає кожному контейнеру власну MAC-адресу та IP у вашому фізичному сегменті мережі. Для решти LAN контейнер — рівноправний вузол. Ніякої публікації портів, ніякого NAT. Просто «ще одна машина». Це привабливо, бо усуває категорію незручностей: upstream системи можуть говорити з контейнерами напряму без хитромудрих маніпуляцій з портами.

Коли macvlan — правильна відповідь

  • Legacy ACL та IP-бейзовані allowlist-и, коли ви не можете або не хочете переписувати політику навколо NAT.
  • Контейнери-«пристрої», яким потрібна власна IP-ідентичність для маршрутизації, моніторингу або сегментації мережі.
  • Програмне забезпечення, що залежить від multicast/broadcast, де семантика NAT/bridge болісна (з застереженнями; не все стає простішим).

Великий підводний камінь macvlan: трафік від хоста до контейнера

За замовчуванням з macvlan хост не може звертатися до своїх macvlan-«дiтей» на тому ж фізичному інтерфейсі. Це дивує людей щоразу. Пакети не «hairpin’яться» так, як ви очікуєте.

Зазвичай виправлення — створити macvlan суб-інтерфейс на хості (shim-інтерфейс) у тій же macvlan-мережі й маршрутизувати через нього. Це не складно, але ще один компонент, який треба зберегти через перезавантаження та керування конфігурацією.

Де macvlan підведе пізніше

  • Політика безпеки портів комутаторів і обмеження CAM-таблиці. Деякі комутатори не люблять, коли один фізичний порт починає емiтувати десятки MAC-адрес. Ви дізнаєтесь про це о 2:00 ночі під час інциденту, якщо не спитаєте заздалегідь.
  • ARP-шторм та хвильова зміна таблиць сусідів. Контейнери з’являються і зникають; ARP-кеші не завжди встигають за цим.
  • IPAM стає вашою проблемою. Docker IPAM може виділяти з діапазону, але він не буде домовлятися з вашим DHCP-сервером або таблицею мережевого відділу, якщо ви цього не організуєте.
  • Трудніше тестувати «на одній машині». Не можна просто запускати все на одному ноутбуці і очікувати, що LAN поведе себе однаково, особливо коли до справи входить Wi‑Fi.

Короткий жарт №2: Macvlan чудовий — поки ваш комутатор не побачить тридцять нових MAC-адрес і не вирішить попрактикувати уважність, відкидаючи трафік.

Цікавинки та трохи історії (корисне, не для вікторини)

  • Linux network namespaces (основа мереж контейнерів) з’явилися в ядрі наприкінці 2000-х, спочатку щоб ізолювати стек мережі для процесів без повної віртуалізації.
  • Ранні мережеві рішення Docker сильно покладалися на iptables NAT-правила; роками «Docker зламав мій файрвол» був обрядом посвяти, бо він автоматично вставляв свої ланцюжки.
  • User-defined bridge мережі додали вбудований DNS для сервіс-дискавері, що стало великим кроком уперед порівняно з застарілим механізмом --link, який запікав крихкі записи хостів у контейнери.
  • Macvlan як функція ядра передувала масовому використанню Docker; це Linux-драйвер, який дозволяє кільком віртуальним MAC-адресам ділитися одним фізичним інтерфейсом, історично використовувався для сегментації мереж і лабораторій.
  • Conntrack (відстеження з’єднань) — прихований податок у середовищах з великою кількістю NAT; високі швидкості з’єднань можуть виснажити таблиці conntrack і виглядати як випадкова втрата пакетів.
  • Проблеми з MTU погіршилися з поширенням оверлеїв/ VPN; «працює на одному хості, ламається між сайтами» часто пов’язане з Path MTU та поведінкою фрагментації.
  • Host networking фактично відмовляєтесь від однієї з практичних ізоляцій контейнерів: розділення простору портів. Ось чому багато оркестраторів ставлять це серед привілейованих виборів.
  • Bridge vs macvlan часто — це дебати про те, де живе ідентичність: у хості (bridge/NAT) або в LAN (macvlan). Жодне з рішень не є безкоштовним.

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

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

Завдання 1: Перелік Docker-мереж і помітні речі

cr0x@server:~$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
f6e5d4c3b2a1   host      host      local
112233445566   none      null      local
77aa88bb99cc   app-net   bridge    local

Що це значить: У вас є дефолтний bridge, плюс user-defined bridge (app-net). Це добре: user-defined bridge мережі дають кращу DNS-поведінку та розділення.

Рішення: Якщо ваші сервіси все ще використовують дефолтний bridge і застарілі патерни, перемістіть їх у user-defined мережу, якщо немає причини інакше.

Завдання 2: Інспект мережі і перевірка підмережі, шлюзу та опцій

cr0x@server:~$ docker network inspect app-net
[
  {
    "Name": "app-net",
    "Driver": "bridge",
    "IPAM": {
      "Config": [
        {
          "Subnet": "172.22.0.0/16",
          "Gateway": "172.22.0.1"
        }
      ]
    },
    "Options": {
      "com.docker.network.bridge.name": "br-77aa88bb99cc"
    }
  }
]

Що це значить: Цей bridge використовує виділений Linux-міст та визначену підмережу. Передбачувано.

Рішення: Якщо ця підмережа перекривається з вашим корпоративним VPN або діапазонами дата-центру, змініть її зараз. Перекриття викликає «працює тільки з деяких ноутбуків» інциденти.

Завдання 3: Перевірте, якою мережею фактично користується контейнер

cr0x@server:~$ docker inspect -f '{{json .NetworkSettings.Networks}}' api-1
{"app-net":{"IPAddress":"172.22.0.10","Gateway":"172.22.0.1","MacAddress":"02:42:ac:16:00:0a"}}

Що це значить: Контейнер в мережі app-net з внутрішньою IP.

Рішення: Якщо додаток очікує бути доступним з LAN без пробросу портів, bridge недостатньо; розгляньте macvlan або коректне ingress/proxy рішення.

Завдання 4: Підтвердьте опубліковані порти і де вони прив’язані

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES     PORTS
api-1     0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp
db-1      5432/tcp

Що це значить: api-1 експонований на порту хоста 8080. db-1 не опублікований; він тільки внутрішній (добре).

Рішення: Якщо бачите прив’язки 0.0.0.0, яких не очікували, закрийте їх (-p 127.0.0.1:... або файрвол), поки хтось інший цього не знайшов.

Завдання 5: Перевірте NAT-правила Docker (iptables) і чи вони існують

cr0x@server:~$ sudo iptables -t nat -S | sed -n '1,40p'
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-N DOCKER_OUTPUT
-N DOCKER_POSTROUTING
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER_OUTPUT
-A POSTROUTING -s 172.22.0.0/16 ! -o br-77aa88bb99cc -j MASQUERADE

Що це значить: Docker керує NAT для bridge-підмережі. Правило MASQUERADE — ваш шлях для вихідного трафіку.

Рішення: Якщо у вас система тільки на nftables, перевірте сумісність Docker з вашим стеком файрволу. Змішане використання інструментів викликає плутанину «правила є, але не застосовуються».

Завдання 6: Перевірте навантаження conntrack (болі NAT видно тут)

cr0x@server:~$ sudo conntrack -S
cpu=0 found=120384 invalid=42 ignore=0 insert=120410 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0

Що це значить: Invalid пакети низькі; падінь нема. Conntrack поки не горить.

Рішення: Якщо бачите зростання drops/insert_failed під навантаженням, підвищення лімітів conntrack або зменшення churn у NAT/з’єднаннях стає терміновим. Host mode іноді «вирішує» це, уникаючи NAT, але це компроміс.

Завдання 7: Перевірте маршрут і MTU зсередини контейнера

cr0x@server:~$ docker exec api-1 ip route
default via 172.22.0.1 dev eth0
172.22.0.0/16 dev eth0 proto kernel scope link src 172.22.0.10
cr0x@server:~$ docker exec api-1 ip link show eth0
2: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:ac:16:00:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0

Що це значить: Дефолтний маршрут — шлюз мосту; MTU — 1500.

Рішення: Якщо підстеля має MTU 1450 через VPN/оверлей, встановіть MTU мережі Docker або MTU хоста, щоб контейнер не відправляв пакети, які будуть поглинені.

Завдання 8: Подивіться veth на боці хоста і членство в мосту

cr0x@server:~$ ip link show br-77aa88bb99cc
7: br-77aa88bb99cc: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 02:42:11:22:33:44 brd ff:ff:ff:ff:ff:ff
cr0x@server:~$ bridge link | grep br-77aa88bb99cc | head
21: veth6d2b1a2@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> master br-77aa88bb99cc state forwarding priority 32 cost 2

Що це значить: Пара veth контейнера підключена до мосту і знаходиться в стані пересилання.

Рішення: Якщо інтерфейсу нема або він не пересилає, ймовірно проблема з ядром/мостом або контейнер у поганому стані простору імен мережі. Перезапуск Docker може «полагодити», але спершу зафіксуйте докази.

Завдання 9: Тест DNS всередині user-defined bridge мережі

cr0x@server:~$ docker exec api-1 getent hosts db-1
172.22.0.11    db-1

Що це значить: Вбудований DNS Docker працює; резольв контейнер→контейнер нормальний.

Рішення: Якщо тут DNS падає, не звинувачуйте корпоративний DNS спочатку. Перевірте, що контейнери в одній user-defined мережі; дефолтний bridge поводиться інакше.

Завдання 10: Підтвердьте поведінку host networking перевіряючи IP контейнера (його не буде)

cr0x@server:~$ docker run --rm --network host alpine ip addr show eth0 | sed -n '1,12p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 0c:de:ad:be:ef:01 brd ff:ff:ff:ff:ff:ff
    inet 10.20.30.40/24 brd 10.20.30.255 scope global eth0
       valid_lft forever preferred_lft forever

Що це значить: Ви бачите адресацію інтерфейсу хоста зсередини контейнера. Це і є «host network».

Рішення: Якщо вам потрібні окремі правила файрволу для контейнера або різні IP-ідентичності, host mode — не той інструмент.

Завдання 11: Створіть macvlan-мережу з контрольованим діапазоном IP

cr0x@server:~$ docker network create -d macvlan \
  --subnet=10.50.10.0/24 --gateway=10.50.10.1 \
  --ip-range=10.50.10.128/25 \
  -o parent=eno1 macvlan-net
8f9e0d1c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e

Що це значить: Контейнери можуть отримувати адреси в 10.50.10.128/25, тоді як решта підмережі залишається зарезервованою.

Рішення: Якщо ви не можете зарезервувати чистий діапазон і задокументувати його, не використовуйте macvlan. Конфлікти IP — повільні катастрофи.

Завдання 12: Запустіть контейнер на macvlan і підтвердіть, що він має LAN IP

cr0x@server:~$ docker run -d --name web-mv --network macvlan-net --ip 10.50.10.140 nginx:alpine
c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1
cr0x@server:~$ docker exec web-mv ip addr show eth0 | sed -n '1,10p'
2: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:0a:32:0a:8c brd ff:ff:ff:ff:ff:ff
    inet 10.50.10.140/24 brd 10.50.10.255 scope global eth0

Що це значить: Контейнер тепер — повноцінна кінцева точка LAN.

Рішення: Перевірте політику комутатора (port security, обмеження MAC). Якщо пакети падають після кількох контейнерів, це не «Docker нестабільний». Це ваше L2, що застосовує обмеження.

Завдання 13: Підтвердіть класичне обмеження macvlan: хост не може дістатися контейнера (за замовчуванням)

cr0x@server:~$ ping -c 2 10.50.10.140
PING 10.50.10.140 (10.50.10.140) 56(84) bytes of data.

--- 10.50.10.140 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1017ms

Що це значить: Трафік з хоста до macvlan-дитини не працює. Це очікувана поведінка в багатьох налаштуваннях.

Рішення: Якщо сервіси на хості (агенти резервного копіювання, локальні монітори, сайдкари) повинні спілкуватися з цими контейнерами, створіть macvlan shim на хості.

Завдання 14: Додайте macvlan shim-інтерфейс на хості, щоб дістатися до macvlan-контейнерів

cr0x@server:~$ sudo ip link add macvlan-shim link eno1 type macvlan mode bridge
cr0x@server:~$ sudo ip addr add 10.50.10.2/24 dev macvlan-shim
cr0x@server:~$ sudo ip link set macvlan-shim up
cr0x@server:~$ ping -c 2 10.50.10.140
PING 10.50.10.140 (10.50.10.140) 56(84) bytes of data.
64 bytes from 10.50.10.140: icmp_seq=1 ttl=64 time=0.412 ms
64 bytes from 10.50.10.140: icmp_seq=2 ttl=64 time=0.398 ms

--- 10.50.10.140 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms

Що це значить: Хост тепер може дістатися до macvlan-мережі через shim.

Рішення: Зробіть це персистентним (systemd-networkd/NetworkManager), інакше воно зникне після перезавантаження і ви знову натрапите на це обмеження в інциденті.

Завдання 15: Перевірте ARP/таблицю сусідів на хості

cr0x@server:~$ ip neigh show dev eno1 | head
10.50.10.1 lladdr 00:11:22:33:44:55 REACHABLE
10.50.10.140 lladdr 02:42:0a:32:0a:8c REACHABLE
10.50.10.141 lladdr 02:42:0a:32:0a:8d STALE

Що це значить: Хост вчиться сусідів. Якщо бачите багато FAILED або постійний churn, macvlan може створювати навантаження на L2/L3.

Рішення: Якщо churn сусідів корелює з втратою пакетів, зменшіть churn контейнерів, налаштуйте пороги GC або перерахуйте macvlan для цього середовища.

Завдання 16: Підтвердьте, який процес володіє портом (host mode і «таємні слухачі»)

cr0x@server:~$ sudo ss -lntp | grep ':8080'
LISTEN 0      4096         0.0.0.0:8080      0.0.0.0:*    users:(("nginx",pid=21457,fd=6))

Що це значить: Щось (тут, nginx) володіє портом 8080 у стеку мереж хоста.

Рішення: Якщо ви очікували публікацію порту Docker, але бачите прямого слухача, можливо ви в host network mode або сервіс запущено на хості випадково. Виправте модель розгортання перед тим, як шукати фантомні проблеми з файрволом.

Завдання 17: Швидко простежте шлях пакета за допомогою tcpdump (bridge vs host vs macvlan)

cr0x@server:~$ sudo tcpdump -ni br-77aa88bb99cc port 5432 -c 5
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on br-77aa88bb99cc, link-type EN10MB (Ethernet), snapshot length 262144 bytes
12:01:10.112233 IP 172.22.0.10.49822 > 172.22.0.11.5432: Flags [S], seq 123456789, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0

Що це значить: Ви бачите трафік контейнер→контейнер на мосту. Це підтверджує, що проблема не «пакети ніколи не покидали контейнер».

Рішення: Якщо трафік видно на мосту, але не на uplink, сфокусуйтеся на маршрутизації/NAT правилах. Якщо на uplink видно, але не доходить до призначення — це upstream.

Швидкий план діагностики

Це порядок дій, що економить час. Не порядок, який робить з вас мережевого чарівника.

Перше: визначте режим мережі і бажану доступність

  • Запустіть docker inspect для контейнера і підтвердіть, чи це bridge/host/macvlan.
  • Уточніть: відмова стосується контейнер → інтернет, контейнер → контейнер, LAN → контейнер або хост → контейнер?

Підказка-вузьке місце: Більшість «помилок мережі Docker» — це насправді «ваша ментальна модель хибна». Виправте модель, потім конфіг.

Друге: перевірте базові L3 речі (всередині контейнера і на хості)

  • У контейнері: ip addr, ip route, DNS через getent hosts.
  • На хості: стан мосту, наявність veth, маршрут до підмережі, таблиця сусідів (macvlan).

Підказка-вузьке місце: Відсутній дефолтний маршрут або поганий DNS викликає 80% скарг «не можу дістатися X».

Третє: валідуйте політику і трансляцію (iptables/nftables, прив’язки портів, conntrack)

  • Bridge: вхідні проблеми — перевірте опубліковані порти і NAT-ланцюжки iptables.
  • Host: перевірте, який процес володіє портом, і файрвол хоста.
  • Macvlan: перевірте ARP/статус сусідів і обмеження/політику комутатора.
  • Під навантаженням: перевірте статистику conntrack і журнали ядра.

Підказка-вузьке місце: Якщо трафік працює коротко, а потім ламається під навантаженням — припускайте тиск на conntrack або upstream L2 enforcement, перш ніж звинувачувати Docker.

Четверте: зніміть пакети на потрібному інтерфейсі

  • Bridge: tcpdump на інтерфейсі контейнера (всередині) і на мосту (br-*).
  • Host: tcpdump на інтерфейсі хоста і використовуйте інструменти для атрибуції процесів.
  • Macvlan: tcpdump на батьківському інтерфейсі і на macvlan shim, якщо він є.

Підказка-вузьке місце: Якщо пакети покидають контейнер, але не потрапляють на міст/ uplink — проблема у wiring простору імен. Якщо потрапляють на uplink — upstream.

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

1) «Контейнер не може дістатися інтернету, але DNS резолюється»

Симптом: getent hosts example.com працює; curl зависає або таймаутиться.

Корінна причина: Відсутній/некоректний дефолтний маршрут, зламане правило MASQUERADE, або файрвол хоста блокує форвардинг.

Виправлення: Перевірте ip route всередині контейнера; на хості підтвердіть iptables -t nat MASQUERADE і що IP forwarding увімкнений. Переконайтеся, що файрвол хоста дозволяє форвардинг з bridge-підмережі.

2) «Сервіс запущений, але ззовні до нього ніхто не підключається» (bridge)

Симптом: Контейнер слухає на 0.0.0.0:8080 всередині, але клієнти LAN не підключаються.

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

Виправлення: Перевірте docker ps на наявність 0.0.0.0:hostport->containerport. Якщо відсутній, додайте -p. Потім перевірте файрвол хоста для дозволу входу.

3) «Два контейнери постійно флапають, інколи один не стартує» (host)

Симптом: Петлі рестарту або періодичні помилки bind; логи говорять «address already in use».

Корінна причина: Host mode ділить простір портів; обидва сервіси хочуть один порт.

Виправлення: Припиніть використовувати host networking для обох, або перебудуйте порти. У host mode розглядайте виділення портів як глобальний ресурс на хості.

4) «Macvlan контейнери доступні з LAN, але хост їх не бачить»

Симптом: З іншої машини в підмережі підключитись можна; з Docker-хоста — ні.

Корінна причина: Поведінка macvlan за замовчуванням забороняє шлях host→child на тому ж parent.

Виправлення: Додайте macvlan shim-інтерфейс на хості з IP у тій же підмережі і маршрутизовуйте через нього (або розгляньте ipvlan L3, якщо підходить для вашого середовища).

5) «Все працювало, поки ми не додали більше контейнерів; потім почалися випадкові таймаути» (macvlan)

Симптом: Нові контейнери доступні інколи; ARP нестабільний; журнали комутатора сигналять.

Корінна причина: Політика port security комутатора або ліміти MAC, тиск на CAM-таблицю або обмеження ARP.

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

6) «Деякі запити зависають, особливо великі відповіді» (будь-який режим)

Симптом: Малі ping-и працюють; великі передачі зависають; TLS-руки іноді ламаються.

Корінна причина: Невідповідність MTU і зломаний Path MTU Discovery.

Виправлення: Виміряйте ефективний MTU, встановіть MTU мережі Docker або узгодьте MTU інтерфейсів, і перевірте за допомогою пінгів з DF-бітом.

7) «Container-to-container працює за IP, але не за іменем» (bridge)

Симптом: ping 172.22.0.11 працює; ping db-1 падає.

Корінна причина: Контейнери не в одній user-defined bridge мережі, або ви використовуєте дефолтний bridge без правильної DNS-поведінки.

Виправлення: Помістіть обидва контейнери в одну user-defined bridge мережу і використовуйте імена контейнерів (або явні псевдоніми) там.

Контрольні списки / поетапний план

Крок за кроком: безпечний вибір драйвера

  1. Напишіть матрицю досяжності. Хто з ким має спілкуватися (LAN → контейнер, контейнер → LAN, хост → контейнер, контейнер → інтернет).
  2. Визначте вимоги ідентичності. Чи вимагають upstream системи IP для кожного робочого навантаження? Якщо так — macvlan може бути потрібен; якщо ні — віддавайте перевагу bridge.
  3. Визначте модель експозиції. Бажаєте явне публікування портів (bridge) чи спільні порти хоста (host)? За замовчуванням — явне.
  4. Перевірте модель володіння файрволом. Якщо файрвол хоста централізовано керується і динамічні зміни Docker небажані, плануйте інтеграцію ретельно (bridge) або уникайте Docker-узагальнених NAT-патернів.
  5. Перевірте політику комутатора якщо macvlan у грі (ліміти MAC, port security, ARP inspection). Зробіть це перед розгортанням.
  6. Оберіть найпростіший варіант, що задовольняє вимоги. Потім задокументуйте його, щоб наступна людина не «оптимізувала» випадково.

Контрольний список для bridge в продакшн

  • Використовуйте user-defined bridge мережі, а не дефолтний bridge, для реальних додатків.
  • Обирайте підмережі, які не перекриваються з VPN/діапазонами дата-центрів.
  • Публікуйте тільки необхідні порти; прив’язуйте до конкретних IP хоста, коли це можливо.
  • Визначте, як ви керуватимете iptables/nftables (і тестуйте після оновлень ОС).
  • Проведіть валідацію MTU end-to-end і встановіть його свідомо.

Контрольний список для host в продакшн

  • Залишайте host mode для робочих навантажень, які це виправдовують (швидкість пакетів, мережеві демони, агенти хоста).
  • Підтримуйте мапу власності портів на кожному хості (або автоматизуйте це).
  • Загартовуйте правила файрволу хоста; не покладайтеся на дефолти контейнера.
  • Перевірте можливість прив’язати трафік до контейнера/ cgroup у системі спостереження.

Контрольний список для macvlan в продакшн

  • Резервуйте задокументований діапазон IP; уникайте змішування з DHCP, якщо ви точно не знаєте, що робите.
  • Підтвердіть, що політика комутатора дозволяє кілька MAC на порті і не буде флапити.
  • Сплануйте доступ хост→контейнер (shim-інтерфейс) якщо потрібно.
  • Моніторьте ARP/поведінку таблиці сусідів і ліміти швидкості.
  • Вирішіть, хто керує DNS: вам знадобляться прямі/зворотні записи, якщо корпоративні інструменти їх очікують.

Три корпоративні міні-історії (щоб ви їх не повторили)

Міні-історія 1: інцидент через неправильне припущення (macvlan і доступність з хоста)

Середня компанія контейнеризувала legacy сервіс звітності, який мав бути доступним для набору upstream batch-завдань. Вони обрали macvlan, бо кожне upstream-завдання мало хардкодований allowlist адрес, і переписати політику було політично складно.

У staging все виглядало добре. З інших машин у підмережі вони могли дістатися IP контейнерів напряму. Зміни пішли в продакшн у п’ятницю після обіду, бо звісно. І відразу на хості почали падати локальні health check-и. Оркестратор позначив інстанси як нездорові і перезапускав їх. Сервіс почав флапати, upstream-завдання частіше падали, що викликало ще більше повторних запусків і шуму.

Неправильне припущення: «Якщо LAN дістає — і хост дістане». У випадку macvlan шлях хост→дитина — відомий виняток у багатьох налаштуваннях. Їхні health check-и запускалися в просторі імен хоста і не могли потрапити на macvlan IP. Сервіс був робочим; просто хост його не бачив.

Виправлення було буденним: створити macvlan shim-інтерфейс на кожному хості, дати йому IP у macvlan-підмережі і оновити health check-и, щоб використовувати цей шлях. Також вони задокументували обмеження, щоб ніхто не «спростив» це згодом.

Міні-історія 2: оптимізація, що відплатилася (host networking щоб уникнути NAT)

Інша організація мала high-throughput pipeline збору метрик. Хтось помітив, що лічильники conntrack піднімаються під навантаженням і вирішив, що bridge NAT «їсть CPU». Пропозиція була проста: перевести ingest-контейнери в --network host, щоб пакети обминали NAT і conntrack.

У вузькому бенчмарку це спрацювало. CPU впала, латентність покращилася, і всі відчули, що знайшли чит-код. Зміна поступово розкатили по флоту.

Потім почалися дивності: на підмножині хостів після деплоїв з’явилися періодичні збої ingest. Не на всіх хостах, не завжди. Корінна причина — конфлікти портів між ingest-сервісом і відладним сайдкаром, який теж прив’язував UDP-порт. У bridge режимі вони могли співіснувати через різні простори імен. У host режимі другий сервіс просто не міг прив’язатися. Іноді порядковість деплойментів це маскувала; іноді вибухала.

Вони відкотили host networking для ingest-шару і замість цього налаштували conntrack і зменшили churn з’єднань батчуванням. Фактична проблема була не в «NAT повільний», а в «ми створили занадто багато короткотривалих потоків». Host mode вилікував симптом і додав новий клас відмов, який важче аналізувати.

Після цього вони ввели правило: будь-яка зміна, що збільшує blast radius (наприклад host networking), потребує письмової моделі загроз і плану відкату. Така політика була менш ефектною, ніж графік бенчмарку, але працювала.

Міні-історія 3: нудна, але правильна практика, що врятувала ситуацію (user-defined bridge + явне публікування)

Команда у фінансовому сервісі запускала кілька клієнтських додатків на спільних хостах. Вони стандартизувалися на user-defined bridge мережах для кожного стеку додатків і публікували порти явним чином, прив’язуючи до конкретних інтерфейсів хоста. Це не було модно. Це було також стійко.

Одного вечора оновлення образу від постачальника додало новий debug-listener всередині контейнера. Ніщо зловмисне; просто один із тих «упс, лишив включеним» дефолтів. Сам сервіс працював нормально.

Оскільки команда використовувала явну публікацію портів, новий внутрішній порт залишився внутрішнім. Він не з’явився на хості і не став доступним з LAN. Моніторинг не зазвучав, безпека не панікувала, і інцидент не став заголовком.

Їхній постмортем був коротким: «Нам не довелося перейматись, бо ми не експонували». Це та нудна перемога, яку помічаєш лише порівнюючи з альтернативним сценарієм.

FAQ

1) Чи bridge мережі завжди повільніші за host?

Ні. Bridge додає наклад (veth, lookup на мосту, часто NAT/conntrack), але для багатьох навантажень це не вузьке місце. Вимірюйте перш ніж «лікувати». Якщо ви не завантажуєте CPU на softirq/conntrack — bridge зазвичай підходить.

2) Чому краще віддавати перевагу user-defined bridge над дефолтним bridge?

User-defined bridge мережі дають кращу поведінку DNS/дискавері сервісів, чіткіше розділення і передбачуваніший multi-network сценарій. Дефолтний bridge — дружній до legacy, не до продакшну.

3) Коли --network host — хороша ідея?

Коли навантаження дійсно потребує прямих семантик мережі хоста: великі швидкості пакетів, мережеві демони або агенти хоста. Також коли хост фактично однопрофільний. Інакше host mode збільшує blast radius і складність відлагодження.

4) Чому хост не може дістатися macvlan контейнерів за замовчуванням?

Тому що macvlan ізолює трафік між parent інтерфейсом і macvlan-ендапойнтами так, що стек хоста не може напряму говорити з його «дітьми» на тому ж інтерфейсі. Загальне обхідне рішення — macvlan shim-інтерфейс на хості.

5) Чи можна запускати macvlan на Wi‑Fi?

Інколи, але часто це болісно. Багато драйверів Wi‑Fi і точки доступу не обробляють кілька source MAC на станцію так, як вам потрібно. Якщо мусите спробувати — робіть це в лабораторії і готуйтеся до розчарування.

6) Чи варто використовувати macvlan лише щоб уникнути конфліктів портів?

Зазвичай ні. Конфлікти портів краще вирішувати bridge-мережами з публікацією портів або ставити reverse proxy/ingress попереду. Macvlan міняє конфлікти портів на складність IPAM і L2.

7) Як зупинити Docker від маніпуляцій з моїм файрволом?

Ви можете обмежити поведінку Docker, але не уникнути необхідності правил, якщо потрібні NAT/опубліковані порти. Практичний підхід — вирішити, чи файрвол хоста «Docker-aware» і тестувати порядок правил після оновлень. Якщо середовище забороняє динамічні зміни файрволу, переробіть архітектуру навколо маршрутизованої мережі або upstream load balancer.

8) Який найнадійніший спосіб виставити сервіси з bridge режиму?

Публікуйте лише потрібні порти, прив’язуйте до конкретного IP хоста, коли доречно (наприклад лише до внутрішнього інтерфейсу), і застосовуйте правила файрволу хоста. Ставтеся до опублікованих портів як до зовнішнього API-поверху.

9) Як вирішити логування і видимість клієнтських IP при bridge NAT?

Ви можете втратити оригінальний IP клієнта, якщо трафік проксований/NAT-ований залежно від шляху. Використовуйте реверс-проксі, що передає X-Forwarded-For або proxy protocol, де це можливо, або уникайте NAT на цьому переході (macvlan/host або маршрутизована архітектура), якщо справжній IP клієнта обов’язковий.

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

  • Інвентаризуйте хости: перерахуйт
    е контейнери і зафіксуйте, які з них використовують host networking і чому. «Бо так працювало» — не причина.
  • Міґруйте один стек на user-defined bridge, якщо ви ще використовуєте дефолтний bridge. Перевірте іменування та дисципліну публікації портів.
  • Визначте план неперекривних підмереж для Docker bridge у середовищах (dev/stage/prod). Перекриття — повільна течія, що стає повінню.
  • Вирішіть політику macvlan: або «дозволено з перевіркою комутатора і резервуванням IP» або «не дозволено». Невизначеність — як macvlan потрапляє в продакшн без дорослих у кімнаті.
  • Напишіть односторінковий руkbук використовуючи Швидкий план діагностики вище і додайте точні команди, які ваш on-call може виконати без роздумів.

Якщо хочете правило, що витримає стрес: використовуйте bridge, доки не зможете назвати конкретне обмеження bridge, яке вам заважає. Тоді обирайте host або macvlan як свідоме виняток, а не як трендовий вибір.

← Попередня
Debian 13: Split-horizon DNS пішов не так — виправте внутрішні імена, не зламавши інтернет (випадок №33)
Наступна →
docker pull повільно працює: виправлення DNS, MTU та проксі, які справді працюють

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