Сети в Docker: bridge, host и overlay — как контейнеры находят друг друга и когда какой драйвер нужен

Каждый контейнер Docker получает собственный сетевой namespace — изолированный стек с интерфейсами, таблицей маршрутизации и правилами iptables. То, как этот namespace подключается к внешнему миру и к другим контейнерам, определяет сетевой драйвер. Выбор драйвера влияет на три вещи: видимость контейнеров друг для друга, необходимость пробрасывать порты и уровень сетевой изоляции.

Bridge: изоляция с управляемым доступом​


Bridge — драйвер по умолчанию. При установке Docker создаёт виртуальный мост docker0 с подсетью 172.17.0.0/16. Каждый контейнер, запущенный без явного указания сети, подключается к этому мосту и получает IP из этой подсети.

Default bridge vs user-defined bridge​


Между ними принципиальная разница в обнаружении сервисов:

ВозможностьDefault bridge (docker0)User-defined bridge
Связь по IPДаДа
DNS-резолвинг по имени контейнераНетДа
Автоматическое подключениеДаНет, нужно указать явно
Изоляция между контейнерамиВсе видят всехТолько контейнеры в одной сети

На default bridge контейнеры могут обращаться друг к другу только по IP-адресу. Встроенный DNS-сервер Docker не работает в этой сети. Это делает default bridge непригодным для большинства реальных сценариев: IP-адреса меняются при перезапуске, а хардкодить их в конфигурации — прямой путь к хрупкой инфраструктуре.

User-defined bridge решает обе проблемы. Docker запускает встроенный DNS-сервер (на 127.0.0.11 внутри контейнера), который резолвит имена контейнеров в их текущие IP.

Создание и использование user-defined bridge​


Bash:
## Создание сети
docker network create myapp

## Запуск контейнеров в этой сети
docker run -d --name backend --network myapp nginx
docker run -d --name frontend --network myapp alpine sleep 3600

## Из контейнера frontend имя backend резолвится в его IP
docker exec frontend ping backend

Контейнер frontend обратится к backend по имени — Docker подставит актуальный IP. Если backend перезапустится и получит новый адрес, DNS обновится автоматически.

Подключение к нескольким сетям​


Контейнер может состоять в нескольких user-defined сетях одновременно. Это основной механизм сегментации:

Bash:
docker network create frontend-net
docker network create backend-net

## Nginx доступен из обеих сетей
docker run -d --name nginx --network frontend-net nginx
docker network connect backend-net nginx

## База данных только в backend-net
docker run -d --name postgres --network backend-net postgres

Контейнер postgres не виден из frontend-net. Контейнеры, которым нужен доступ к обоим слоям, подключаются к обеим сетям. Это заменяет файрвол-правила на уровне хоста.

Публикация портов​


Порт контейнера по умолчанию доступен только внутри его сети. Чтобы сделать сервис доступным с хоста или извне, используется флаг -p:

Bash:
docker run -d --name web --network myapp -p 8080:80 nginx

Здесь 8080 — порт на хосте, 80 — порт внутри контейнера. Контейнеры внутри myapp обращаются к web по имени и порту 80 без всякой публикации. Флаг -p нужен только для доступа снаружи сети.

Типичная ошибка: публиковать порты всех контейнеров, хотя они общаются между собой внутри bridge-сети. Каждый опубликованный порт — это правило в iptables и потенциальная поверхность атаки.

Host: без изоляции​


Драйвер host убирает сетевой namespace контейнера. Контейнер использует сетевой стек хоста напрямую: те же интерфейсы, те же порты, та же таблица маршрутизации.

Bash:
docker run -d --network host nginx

Nginx слушает порт 80 хоста. Флаг -p в этом режиме игнорируется — пробрасывать нечего, контейнер уже на хосте.

Когда host оправдан​


  • Высокая производительность сети. Нет overhead на NAT и виртуальный мост. Разница заметна на нагрузках с десятками тысяч соединений в секунду.
  • Протоколы, чувствительные к NAT. Некоторые приложения некорректно работают за NAT (отдельные реализации SIP, IPsec в режиме transport).
  • Мониторинг и агенты. Агенты, которым нужно видеть сетевой трафик хоста или слушать на определённых интерфейсах.

Ограничения host-режима​


  • Нет изоляции: два контейнера не могут слушать один порт.
  • Нет DNS-резолвинга по именам контейнеров — контейнеры не знают друг о друге.
  • Контейнер видит все интерфейсы хоста, включая служебные.
  • На Docker Desktop (macOS/Windows) --network host работает иначе, чем на Linux: контейнер получает сеть виртуальной машины, а не физического хоста.

Overlay: связь между хостами​


Overlay-сеть инкапсулирует трафик между контейнерами на разных физических или виртуальных машинах. Пакеты оборачиваются в VXLAN и передаются через underlying-сеть хостов.

Overlay работает в двух контекстах:

  1. Docker Swarm — создаётся автоматически или вручную, сервисы в одном overlay видят друг друга по имени.
  2. Ручное создание — через docker network create --driver overlay с указанием внешнего key-value store (Consul, etcd). Этот вариант используется редко, так как Swarm покрывает большинство сценариев.

Bash:
## В Swarm-режиме
docker network create -d overlay --attachable myapp-overlay

## Сервис подключается к overlay
docker service create --name api --network myapp-overlay myimage

Флаг --attachable позволяет подключать к overlay-сети не только Swarm-сервисы, но и обычные контейнеры через docker run --network.

Когда overlay не нужен​


Если все контейнеры работают на одном хосте, overlay избыточен. Bridge-сеть проще, не требует Swarm и не добавляет overhead на инкапсуляцию.

None: полная изоляция​


Драйвер none отключает сеть полностью. Контейнер получает только loopback-интерфейс. Используется для batch-задач, которые не требуют сети, или как дополнительный уровень изоляции.

Bash:
docker run --network none alpine ip addr
## Покажет только lo

Диагностика сетевых проблем​


Контейнер не резолвит имя другого контейнера​


Проверьте, что оба контейнера в одной user-defined сети:

Bash:
docker inspect <container> --format '{{json .NetworkSettings.Networks}}' | jq 'keys'

Если контейнер в default bridge — DNS не работает. Переподключите его к user-defined сети:

Bash:
docker network disconnect bridge <container>
docker network connect myapp <container>

Контейнер не доступен с хоста​


Убедитесь, что порт опубликован:

Bash:
docker port <container>

Если вывод пустой — порт не проброшен. Для доступа изнутри сети это нормально, для доступа с хоста нужен -p.

Конфликт подсетей​


Docker выбирает подсети для bridge-сетей автоматически. Если на хосте уже занята 172.17.0.0/16 или диапазон пересекается с корпоративной сетью, контейнеры станут недоступны. Проверить:

Bash:
docker network ls
docker network inspect <network> --format '{{.IPAM.Config}}'

При конфликте создайте сеть с явной подсетью:

Bash:
docker network create --subnet 10.10.0.0/24 myapp

iptables и файрвол хоста​


Docker управляет цепочками iptables напрямую. Если на хосте работает UFW или firewalld, правила могут конфликтовать. Docker добавляет правила в цепочки DOCKER, DOCKER-USER и FORWARD. Ручное изменение этих цепочек без понимания механики Docker — частая причина «пропавшей» связности.

Для диагностики:

Bash:
iptables -t nat -L DOCKER -n -v
iptables -L FORWARD -n -v

Выбор драйвера: краткая матрица​


СценарийДрайвер
Несколько сервисов на одном хосте, нужна связь по именамUser-defined bridge
Максимальная производительность сети, один сервис на портHost
Сервисы на разных хостах (Swarm)Overlay
Контейнер без сети (batch, изоляция)None
Быстрый тест, не продакшенDefault bridge

Default bridge подходит для docker run hello-world и одноразовых экспериментов. Для любой конфигурации из двух и более связанных контейнеров создавайте user-defined bridge — это занимает одну команду и устраняет класс проблем с DNS и изоляцией.

Источники​


 
Назад
Верх Низ