Skip to main content

Як працює bridge networking в Docker?

Docker bridge networking це дефолтний режим для спілкування container на одному host. Під капотом це Linux software bridge плюс пара віртуальних інтерфейсів на container, усе зв'язано iptables NAT-правилами. Знання цього стеку дає змогу дебажити «чому container не говорять?» за 30 секунд замість 30 хвилин.

Теорія

TL;DR

  • Docker створює Linux software bridge на host (дефолтне ім'я docker0; user-defined bridge отримують ім'я br-<id>).
  • Для кожного container Docker створює veth-пару: один кінець всередині network namespace container як eth0, інший приєднано до bridge.
  • Усі container на тому самому bridge на приватній підмережі (наприклад, 172.17.0.0/16) і дістають один одного напряму на будь-якому порту.
  • Host дістає container через їхній bridge IP (але не по імені container з host).
  • iptables MASQUERADE-правила NAT'ять вихідний container-трафік на IP host. iptables DNAT-правила реалізують -p.
  • User-defined bridge додають embedded DNS по імені container. Дефолтний docker0 цього не має.

Швидкий приклад

bash
$ docker network create mynet $ docker run -d --name web --network mynet nginx # На host бачимо bridge $ ip link show | grep -E 'br-|docker0' 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ... 7: br-1234abcd: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ... # Бачимо який veth приєднано $ brctl show br-1234abcd # або: bridge link bridge name bridge id STP enabled interfaces br-1234abcd 8000.0242a3f9d2b8 no vethabcd123 # Всередині container $ docker exec web ip a show eth0 5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 inet 172.18.0.2/24 brd 172.18.0.255 scope global eth0

Bridge br-1234abcd на host, кінець vethabcd123, відповідний eth0@if6 всередині container, три в'юхи на той самий провід.

Архітектура veth-пари

Host network namespace Container network namespace +--------------------+ +----------------------+ | | | | | docker0 / br-X |═══vethXXX═══eth0│ процес | | (Linux bridge) | | container | | | | | +--------------------+ +----------------------+ | v iptables MASQUERADE для вихідного v iptables DNAT для -p<host>:<container> +--------------------+ | host eth0 / wlan0 | ↔ зовнішня мережа +--------------------+

veth це по суті віртуальний кабель. Один кінець живе у host (приєднано до bridge); інший у namespace container (перейменовано на eth0). Пакети, що входять у один кінець, виходять з іншого.

Як тече трафік між container

Container A (172.18.0.2) Container B (172.18.0.3) | | | eth0 | eth0 | vethA | vethB +----------------+ +------------+ v v br-1234abcd (bridge)

A шле пакет на IP B. Bridge-логіка kernel форвардить з vethA на vethB. Без NAT, без host'а на L3 крім routing.

На user-defined bridge A може ще дістати B по імені (db, api), Docker додає 127.0.0.11 (свій embedded DNS) у /etc/resolv.conf container, і цей DNS знає про усі container-імена на тому самому bridge.

Як працює вихідний трафік

Container хоче дістатися https://api.github.com. Пакет:

  1. Виходить з eth0 container (172.18.0.2 → 140.82.x.x).
  2. Прибуває на bridge.
  3. Маршрутизується на вихідний інтерфейс host.
  4. iptables-правило MASQUERADE переписує source IP на IP host.
  5. Виходить в інтернет.

Зворотний трафік використовує conntrack, щоб знайти шлях назад до container.

Як працює -p (port publishing)

З docker run -p 8080:80 nginx:

  1. iptables DNAT-правило: host:8080 → 172.18.0.2:80.
  2. Зовнішній запит б'є у host:8080.
  3. iptables переписує destination на container.
  4. Bridge форвардить container'у.
  5. nginx відповідає. Зворотний шлях.

docker-proxy userspace-процес теж стартує як fallback для деяких IPv6 / loopback кейсів. Більшість трафіку йде iptables-шляхом.

Дефолтний bridge vs user-defined

Дефолтний bridgeUser-defined bridge
СтворюєтьсяDocker при інсталіdocker network create <name>
Ім'яbridgeбудь-яке за вибором
Linux-інтерфейсdocker0br-<random-id>
DNS по імені containerНі (тільки legacy --link)Так (embedded resolver)
Ізоляція між projectЖодної (кожен container тут за замовчуванням)Кожна мережа ізольована
Auto-cleanupПостійнийdocker network rm, коли не used
Рекомендовано длямайже нічого новогоусього

Якщо container не задає --network, він приземляється на дефолтний bridge. Завжди задавай user-defined bridge для будь-якого нетривіального use.

Типові помилки

Спроба дістати container по імені з host

bash
$ curl http://web # host намагається резолвити container-ім'я curl: (6) Could not resolve host: web

Embedded DNS обслуговує лише container на тому самому bridge, не host. З host бери опублікований порт (localhost:8080) або bridge-IP container (172.18.0.2).

Два container на різних bridge, очікуючи, що говоритимуть

bash
$ docker network create net-a && docker network create net-b $ docker run -d --name api --network net-a myapp $ docker run -d --name db --network net-b postgres:16 $ docker exec api ping db # падає

Bridge ізольовані. Або постав обидва на ту саму мережу, або docker network connect net-b api, щоб приєднати api до обох.

Забути, що container на docker0 не мають DNS

Свіжа інсталяція + docker run -d --name x ... приземляє x на docker0. Зсередини ping x не працює. Сучасна порада: завжди створюй мережу спочатку.

iptables flush ламає Docker networking

Docker керує своїми iptables-правилами у chain DOCKER. Запуск iptables -F або деяких firewall-менеджерів (UFW з дефолтними правилами) може стерти правила Docker і зламати port publishing. Рестартуй Docker (systemctl restart docker), щоб їх перестворити.

Inspecting і debugging

bash
# Які bridge керує Docker? $ docker network ls --filter driver=bridge # Що на конкретній мережі? $ docker network inspect mynet # IP container $ docker inspect web --format '{{.NetworkSettings.Networks.mynet.IPAddress}}' # Live трафік на bridge $ sudo tcpdump -i br-1234abcd -n # iptables-правила, що Docker налаштував $ sudo iptables -t nat -L DOCKER -n -v

Реальне застосування

  • Compose: авто-створює user-defined bridge на project (<projectname>_default). Service-to-service трафік відбувається тут, з DNS по service-імені.
  • Single-host production: явний docker network create appnet, усі container приєднані, лише entry point опубліковано через -p.
  • Кілька ізольованих стеків на одному host: один bridge на стек. Postgres на appnet1 випадково не дістати з container на appnet2.
  • Reverse-proxy патерни: Traefik або nginx-proxy приєднується до кількох bridge, щоб маршрутизувати трафік між стеками.

Питання для поглиблення

Q: Чому мій container має IP 172.17.x.x, коли очікував 172.18.x.x?


A: Він на дефолтному bridge (172.17.0.0/16), не на user-defined. Задай --network <yourname> при запуску.

Q: Як знайти IP container з host?


A: docker inspect <name> --format '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}'. Container може бути на кількох мережах; формат вище друкує усі IP.

Q: Чому трафік з мого container повільний?


A: Bridge networking має overhead vs host-режим (NAT, додатковий хоп через bridge, можливо docker-proxy). Для сирого throughput --network host найшвидше. Для типових web/API навантажень overhead незначний.

Q: Чи можна кастомізувати bridge-підмережу?


A: Так, при створенні: docker network create --subnet 10.0.0.0/24 --gateway 10.0.0.1 mynet. Корисно, коли дефолтний 172.17/16 колізиться з VPN або офісними підмережами.

Q: (Senior) Як дебажити пакет, що прибув на host, але ніколи не дістає container?


A: Трасуй iptables-шлях. sudo iptables -t nat -L DOCKER -n -v --line-numbers, щоб побачити DNAT-правила для опублікованого порту. Потім sudo iptables -L FORWARD -n -v для підтвердження, що forward-chain приймає container-bound трафік. Запусти sudo tcpdump -i any -n port 80, щоб побачити, де пакет зупиняється. Поширені винуватці: host-firewall (UFW, firewalld) додає deny-правила вище Docker, або проблема docker-proxy на IPv6.

Приклади

Трасування -p mapping end-to-end

bash
$ docker run -d --name web -p 8080:80 nginx # 1. iptables-правило, що робить DNAT $ sudo iptables -t nat -L DOCKER -n | grep 8080 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80 # 2. IP container $ docker inspect web --format '{{.NetworkSettings.IPAddress}}' 172.17.0.2 # 3. З host дістаємо напряму через bridge-IP (без -p для цього) $ curl http://172.17.0.2/ # 4. Або через опублікований mapping $ curl http://localhost:8080/

Один пакет, два валідних шляхи. DNAT-правило це міст між localhost:8080 і 172.17.0.2:80.

Двоконтейнерний застосунок на user-defined bridge

bash
$ docker network create appnet $ docker run -d --name db --network appnet \ -e POSTGRES_PASSWORD=devpass postgres:16 $ docker run -d --name api --network appnet \ -e DATABASE_URL=postgres://postgres:devpass@db:5432/app \ myapp $ docker exec api nslookup db Server: 127.0.0.11 Name: db Address 1: 172.18.0.2 db.appnet

db резолвиться по імені тільки тому, що обидва container на user-defined appnet. На дефолтному bridge той самий setup провалиться.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів