Сети Docker: bridge, host, overlay — что использовать
Стоит поднять больше двух-трёх контейнеров, как встаёт вопрос: а как они вообще должны общаться друг с другом и с внешним миром? Docker предлагает несколько драйверов сети, и выбор не самого подходящего часто оборачивается либо проблемами с безопасностью, либо контейнерами, которые не видят друг друга. Разберём три главных варианта — bridge, host и overlay — и когда какой использовать.
Тестовый стенд — сервер srv-docker01 для одиночного хоста и три ноды srv-swarm01, srv-swarm02, srv-swarm03 для примеров с overlay, все в сети 192.168.10.0/24, домен itpages.lab.local.
Bridge: сеть по умолчанию для одного хоста
Bridge — самый распространённый режим, используется по умолчанию для контейнеров на одной машине. Docker создаёт виртуальный мост (обычно docker0), контейнеры получают собственные IP из отдельной подсети и общаются через NAT с внешним миром.
Важно различать сеть bridge по умолчанию и пользовательскую bridge-сеть — это не одно и то же. В сети по умолчанию контейнеры не резолвят друг друга по имени, только по IP:
docker network ls
docker network inspect bridge
Пользовательская bridge-сеть даёт встроенный DNS и изоляцию от других проектов на том же хосте:
docker network create --driver bridge \
--subnet 172.20.0.0/24 \
app-net
docker run -d --name db01 --network app-net mysql:8.0
docker run -d --name app01 --network app-net node:20-alpine sleep infinity
docker exec app01 getent hosts db01
Когда использовать: почти всегда, если у тебя один хост (srv-docker01) и несколько связанных контейнеров одного проекта. Это дефолтный и самый безопасный выбор для большинства задач — каждый проект в своей сети, никаких пересечений портов и случайных обращений между несвязанными сервисами.
Host: без изоляции сети вообще
Режим host убирает сетевую изоляцию контейнера полностью — контейнер использует сетевой стек хоста напрямую, без NAT и проброса портов. Это даёт максимальную производительность (нет накладных расходов на трансляцию адресов), но и максимум риска.
docker run -d --name web01 --network host nginx:1.27-alpine
Обрати внимание: флаг -p здесь не нужен и не будет работать — контейнер и так слушает порты хоста напрямую. Если nginx внутри слушает 80-й порт, он займёт 80-й порт именно на srv-docker01, и конфликт с другим сервисом на этом же порту приведёт к ошибке запуска.
Когда использовать: узкий круг случаев — высоконагруженные прокси и балансировщики, где важна каждая доля миллисекунды задержки, или инструменты мониторинга, которым нужен доступ к сетевым интерфейсам хоста. Для типичного веб-приложения или API host-режим избыточен и убирает полезную изоляцию без ощутимой выгоды.
Overlay: сеть поверх нескольких хостов
Overlay-сеть решает задачу, которую bridge решить не может, — связать контейнеры, запущенные на разных физических или виртуальных машинах, в единую логическую сеть. Это основа Docker Swarm: overlay-сеть работает поверх обычной сети между нодами (в нашем случае — поверх 192.168.10.0/24) и инкапсулирует трафик контейнеров в VXLAN-туннели.
Overlay-сети создаются на инициализированном Swarm-кластере. Сначала инициализируем Swarm на srv-swarm01:
docker swarm init --advertise-addr 192.168.10.41
После присоединения srv-swarm02 и srv-swarm03 (подробности — в статье про Docker Swarm) создаём overlay-сеть:
docker network create \
--driver overlay \
--attachable \
--subnet 10.10.0.0/24 \
swarm-net
Флаг --attachable позволяет подключать к этой сети не только Swarm-сервисы, но и обычные standalone-контейнеры — удобно для отладки. Разворачиваем сервис в этой сети:
docker service create \
--name web \
--network swarm-net \
--replicas 3 \
nginx:1.27-alpine
Три реплики сервиса разъедутся по трём нодам кластера, но благодаря overlay-сети и встроенному DNS они будут видеть друг друга по имени сервиса web вне зависимости от того, на какой физической ноде находятся.
Когда использовать: как только у тебя больше одной ноды и контейнерам с разных нод нужно общаться напрямую — то есть практически всегда, если ты используешь Docker Swarm или аналогичный оркестратор.
Сравнение и практический выбор
Если формулировать коротко: bridge — для одного хоста и большинства повседневных задач, host — для редких случаев, где критична производительность и можно пожертвовать изоляцией, overlay — как только речь заходит о кластере из нескольких нод. На srv-docker01 в качестве одиночного хоста разработки и тестирования держись пользовательских bridge-сетей по проектам. Для боевого кластера srv-swarm01–03 overlay становится обязательным элементом архитектуры.
Проверить, к каким сетям подключён контейнер, и какие адреса он получил, можно так:
docker inspect app01 --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool
Режим none: полная изоляция от сети
Есть и четвёртый, редко используемый вариант — сеть none. Контейнер в этом режиме получает только loopback-интерфейс и никак не взаимодействует ни с другими контейнерами, ни с внешним миром:
docker run -d --name isolated01 --network none alpine sleep infinity
docker exec isolated01 ip addr
Применяется в узких сценариях — например, для пакетной обработки данных, которая читает и пишет файлы через volume, но по своей природе не должна иметь сетевого доступа вообще (соображения безопасности при работе с недоверенными данными).
Macvlan: собственный MAC-адрес и IP из сети хоста
Ещё один драйвер, который стоит знать — macvlan. Он выдаёт контейнеру собственный MAC-адрес и позволяет получить IP-адрес напрямую из физической сети, как будто это отдельное устройство, а не процесс на хосте. Это удобно, когда контейнер должен быть виден в сети 192.168.10.0/24 как полноценный сосед, например для легаси-приложений, ожидающих прямого сетевого доступа без NAT:
docker network create -d macvlan \
--subnet=192.168.10.0/24 \
--gateway=192.168.10.1 \
-o parent=eth0 \
macvlan-net
docker run -d --name legacy01 \
--network macvlan-net \
--ip 192.168.10.60 \
legacy-app:1.0
Минус в том, что по умолчанию хост и контейнер в режиме macvlan не видят друг друга напрямую (ограничение самого драйвера ядра Linux) — для большинства повседневных задач это делает macvlan менее удобным, чем bridge или overlay, и применять его стоит только когда действительно нужна именно такая топология.
Проброс портов подробнее
Раз уж речь зашла о сетях, стоит явно разобрать синтаксис проброса портов, с которым сталкивается каждый, кто работает с bridge-сетью. Базовый вариант публикует порт на всех интерфейсах хоста:
docker run -d -p 8080:80 nginx:1.27-alpine
Если нужно ограничить доступ только локальной сетью 192.168.10.0/24, а не всем интернетом (актуально для сервера с публичным IP), порт можно забиндить на конкретный внутренний адрес:
docker run -d -p 192.168.10.21:8080:80 nginx:1.27-alpine
Проброс диапазона портов и портов UDP делается так:
docker run -d -p 9000-9010:9000-9010 -p 5000:5000/udp myapp:1.0
Диагностика сетевых проблем между контейнерами
Когда два контейнера «не видят» друг друга, стандартный набор шагов диагностики выглядит так. Сначала убедись, что оба контейнера действительно в одной сети:
docker inspect app01 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect db01 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
Если сети совпадают, но связи всё равно нет, проверь резолвинг DNS изнутри контейнера и доступность порта:
docker exec app01 nslookup db01
docker exec app01 sh -c "nc -zv db01 3306"
Если nslookup вообще не находит утилиты внутри минималистичного alpine-образа, временно поставь диагностический набор:
docker exec app01 sh -c "apk add --no-cache bind-tools netcat-openbsd"
Для более глубокой диагностики трафика между хостами можно подключиться к сетевому namespace контейнера напрямую и снять дамп трафика tcpdump — это уже уровень сетевого инженера, но иногда без него не обойтись, если проблема не в самом Docker, а в firewall между srv-docker01 и srv-app01.
Взаимодействие Docker с iptables и firewalld
Docker по умолчанию сам управляет правилами iptables для проброса портов и NAT — и это регулярно вызывает путаницу у администраторов, которые параллельно настраивают firewalld или ufw. Правила Docker добавляются в отдельную цепочку DOCKER и обычно применяются раньше, чем правила ufw, из-за чего попытка заблокировать порт контейнера через ufw может просто не сработать:
sudo iptables -L DOCKER -n -v
Если действительно нужно ограничить доступ к пробрасываемому порту контейнера по IP, правильнее делать это либо через параметр -p 192.168.10.21:8080:80 при запуске (привязка к конкретному интерфейсу), либо через явные правила в самой цепочке DOCKER-USER, которую Docker специально оставляет пустой для пользовательских правил и не перезаписывает при перезапуске демона:
sudo iptables -I DOCKER-USER -s 192.168.20.0/24 -j DROP
Настройка собственного DNS для контейнеров
Иногда контейнерам нужно резолвить внутренние имена лабораторного домена itpages.lab.local, которых нет ни во встроенном DNS Docker, ни в публичном DNS. Задать собственный DNS-сервер можно как глобально для демона, так и для конкретного контейнера. Глобально — через daemon.json:
{
"dns": ["192.168.10.10", "8.8.8.8"]
}
Или точечно при запуске конкретного контейнера, если только ему нужно резолвить внутренние имена, а остальным — нет:
docker run -d --name app01 \
--dns 192.168.10.10 \
--dns-search itpages.lab.local \
myapp:1.0
Это особенно актуально, когда контейнер должен обращаться не к другому контейнеру, а к внешнему по отношению к Docker сервису — например, к LDAP-серверу или почтовому релею, которые существуют только во внутренней зоне DNS домена.
Частые ошибки и как их избежать
- Использование host-режима «на всякий случай». Это убирает сетевую изоляцию и повышает риск конфликтов портов между сервисами на одном хосте. Держи host-режим только для тех задач, где он реально обоснован.
- Смешивание разных проектов в одной bridge-сети по умолчанию. Если не создавать отдельные пользовательские сети под каждый проект, контейнеры разных приложений могут случайно достучаться друг до друга. Разделяй сети по проектам.
- Overlay-сеть без открытых нужных портов между нодами. Docker Swarm требует открытых портов 2377/tcp (управление), 7946/tcp и udp (discovery), 4789/udp (VXLAN для overlay). Если firewall между srv-swarm01–03 их блокирует, overlay-сеть работать не будет, а диагностировать это не всегда очевидно.
- Использование macvlan без понимания ограничения хост-контейнер. Если после настройки macvlan хост неожиданно не может достучаться до собственного контейнера напрямую — это не баг, а особенность драйвера. При необходимости такого доступа добавляй дополнительный macvlan-интерфейс на самом хосте.
Что дальше
Docker редко используют в одиночку — вот что обычно настраивают рядом: