Як працює 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цього не має.
Швидкий приклад
$ 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 eth0Bridge 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. Пакет:
- Виходить з
eth0container (172.18.0.2 → 140.82.x.x). - Прибуває на bridge.
- Маршрутизується на вихідний інтерфейс host.
- iptables-правило
MASQUERADEпереписує source IP на IP host. - Виходить в інтернет.
Зворотний трафік використовує conntrack, щоб знайти шлях назад до container.
Як працює -p (port publishing)
З docker run -p 8080:80 nginx:
- iptables DNAT-правило:
host:8080 → 172.18.0.2:80. - Зовнішній запит б'є у host:8080.
- iptables переписує destination на container.
- Bridge форвардить container'у.
- nginx відповідає. Зворотний шлях.
docker-proxy userspace-процес теж стартує як fallback для деяких IPv6 / loopback кейсів. Більшість трафіку йде iptables-шляхом.
Дефолтний bridge vs user-defined
Дефолтний bridge | User-defined bridge | |
|---|---|---|
| Створюється | Docker при інсталі | docker network create <name> |
| Ім'я | bridge | будь-яке за вибором |
| Linux-інтерфейс | docker0 | br-<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
$ curl http://web # host намагається резолвити container-ім'я
curl: (6) Could not resolve host: webEmbedded DNS обслуговує лише container на тому самому bridge, не host. З host бери опублікований порт (localhost:8080) або bridge-IP container (172.18.0.2).
Два container на різних bridge, очікуючи, що говоритимуть
$ 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
# Які 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
$ 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
$ 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.appnetdb резолвиться по імені тільки тому, що обидва container на user-defined appnet. На дефолтному bridge той самий setup провалиться.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів