Сетевая модель Docker: маршрутизация, изоляция и управление трафиком через bridge, overlay и macvlan | AdminWiki

Сетевая модель Docker: маршрутизация, изоляция и управление трафиком через bridge, overlay и macvlan

19 июля 2026 13 мин. чтения

Сетевая модель Docker построена на изолированных пространствах имен (network namespaces) и виртуальных интерфейсах. Когда контейнер запускается, демон создает для него собственный сетевой стек: отдельные таблицы маршрутизации, правила iptables, интерфейсы. Это фундамент, на котором работают все драйверы - bridge, overlay и macvlan. Без понимания этого уровня невозможно ни диагностировать проблемы связности, ни настроить фильтрацию трафика.

Практическая задача администратора сводится к выбору драйвера под сценарий и управлению потоками данных через iptables. Bridge решает задачу изоляции групп контейнеров на одном хосте. Overlay связывает сервисы в кластере Swarm через зашифрованные туннели. Macvlan выводит контейнер напрямую в физическую сеть предприятия с собственным MAC-адресом. Каждый механизм имеет четкие границы применения, и в этом руководстве мы разберем их с пошаговыми примерами и готовыми командами.

Как Docker организует сеть: основы модели

Демон Docker при старте создает виртуальный мост docker0 и назначает ему подсеть по умолчанию 172.17.0.0/16. Этот мост выполняет роль коммутатора: все контейнеры, подключенные к сети bridge по умолчанию, получают IP из этой подсети и могут общаться друг с другом на втором уровне. Для выхода во внешнюю сеть Docker автоматически добавляет правила NAT в iptables - трафик маскируется под IP хоста.

Параллельно запускается встроенный DNS-сервер на адресе 127.0.0.11. Он разрешает имена контейнеров в IP-адреса внутри пользовательских сетей. В дефолтной сети bridge этот механизм не работает - там связь возможна только по IP, если не использовать устаревшие линки. Это первая причина отказаться от сети по умолчанию в пользу пользовательских bridge-сетей.

IPAM (IP Address Management) - подсистема, которая отвечает за выдачу адресов из пула. При создании пользовательской сети можно указать подсеть, шлюз, диапазон адресов. Docker отслеживает занятые IP и не допускает конфликтов. Если контейнер перезапускается, он может получить другой адрес, если только вы не назначили статический IP при создании.

Network namespace и veth-пары: изоляция на уровне ядра

Каждый контейнер получает собственный network namespace - изолированный экземпляр сетевого стека. Внутри namespace свои интерфейсы, таблица маршрутизации, правила iptables. Контейнер не видит интерфейсы хоста и не может напрямую взаимодействовать с процессами вне своего namespace. Это механизм ядра Linux, который Docker использует через вызовы clone() с флагом CLONE_NEWNET.

Связь между namespace контейнера и хостом организуется через veth-пару - два виртуальных интерфейса, соединенных как труба. Один конец помещается в контейнер (обычно eth0), второй остается на хосте с именем вида vethXXXXX и подключается к мосту docker0. Пакет, отправленный из контейнера, проходит через veth-пару, попадает на мост и дальше маршрутизируется по правилам хост-системы.

Проверить это можно командой на хосте:

ip netns list

Docker не регистрирует свои namespace в /var/run/netns по умолчанию, поэтому утилита ip netns их не покажет. Но можно создать симлинк вручную:

pid=$(docker inspect -f '{{.State.Pid}}' container_name)
ln -s /proc/$pid/ns/net /var/run/netns/container_name
ip netns exec container_name ip addr

Этот прием незаменим при отладке: вы видите сетевые интерфейсы глазами контейнера и можете проверить, дошел ли IP-адрес, корректна ли таблица маршрутизации.

Мост docker0 и NAT: как контейнер выходит в интернет

Когда контейнер отправляет пакет во внешнюю сеть, маршрут проходит цепочку: eth0 контейнера -> veth-пара -> мост docker0 -> eth0 хоста -> внешняя сеть. На хосте пакет проходит через правило маскарадинга в цепочке POSTROUTING таблицы nat. Посмотрите правило:

iptables -t nat -L POSTROUTING -n -v

Вы увидите правило с source-подсетью 172.17.0.0/16 и target MASQUERADE. Docker добавляет его автоматически при старте демона. Без этого правила пакеты с частными IP контейнеров были бы отброшены первым же маршрутизатором.

Для входящего трафика работает проброс портов. Когда вы указываете -p 8080:80, Docker создает правила DNAT в цепочке PREROUTING таблицы nat и правила фильтрации в цепочке DOCKER таблицы filter. Пакет, пришедший на порт 8080 хоста, перенаправляется на порт 80 контейнера. Все это происходит до того, как пакет достигает пользовательских правил, поэтому важно понимать архитектуру iptables Docker, которую мы разберем в отдельном разделе.

Bridge-драйвер: связность контейнеров на одном хосте

Bridge-драйвер - основной инструмент для организации сети на одном хосте. Пользовательская bridge-сеть отличается от дефолтной тремя критическими возможностями: автоматическое DNS-разрешение имен контейнеров, полная изоляция от других bridge-сетей, возможность назначать статические IP и тонко настраивать подсеть. Контейнеры из разных bridge-сетей не видят друг друга на сетевом уровне, если вы явно не настроите маршрутизацию между ними.

Драйвер работает на втором уровне OSI: все контейнеры в одной сети подключены к общему виртуальному мосту и могут общаться напрямую через MAC-адреса. Трафик между ними не покидает хост, что дает минимальную задержку и высокую пропускную способность. Для взаимодействия с внешним миром используется стандартный механизм NAT через iptables.

Создание пользовательской bridge-сети: пошаговый пример

Создадим сеть с явно заданной подсетью и проверим связность между контейнерами. Команда для создания сети:

docker network create \
  --driver bridge \
  --subnet=172.20.0.0/16 \
  --gateway=172.20.0.1 \
  my-bridge-net

Параметр --subnet задает адресное пространство, --gateway - адрес шлюза, который будет назначен интерфейсу моста на хосте. Если не указать эти параметры, Docker автоматически выберет свободную подсеть из диапазона 172.17.0.0/16 - 172.31.0.0/16.

Запускаем два контейнера в этой сети:

docker run -d --name web --network my-bridge-net nginx:alpine
docker run -d --name app --network my-bridge-net alpine sleep 3600

Проверяем связь по имени контейнера:

docker exec app ping web

Пинг проходит. Встроенный DNS-сервер разрешил имя web в IP-адрес 172.20.0.X. Это работает только в пользовательских сетях - в дефолтной bridge пришлось бы указывать IP явно или использовать устаревший механизм --link. Дополнительно можно назначить статический IP при запуске:

docker run -d --name web --network my-bridge-net --ip 172.20.0.100 nginx:alpine

Статический IP полезен для сервисов, которые должны быть доступны по фиксированному адресу, например, базы данных или балансировщики. Однако помните: если контейнер пересоздается, старый IP освобождается не мгновенно - Docker удерживает его некоторое время для предотвращения конфликтов.

Изоляция через bridge: зачем отказываться от сети по умолчанию

Сеть bridge по умолчанию имеет два фундаментальных ограничения. Первое: контейнеры общаются только по IP, DNS-разрешение имен отсутствует. Второе: чтобы изолировать контейнеры, нужно вручную управлять правилами iptables, потому что все контейнеры в дефолтной сети находятся в общем широковещательном домене. Пользовательская сеть решает обе проблемы.

Создайте две отдельные bridge-сети - frontend и backend. Контейнеры в frontend не смогут пинговать контейнеры в backend и наоборот. Это базовая модель изоляции для микросервисной архитектуры: веб-сервер в frontend принимает запросы извне, проксирует их в backend-сервисы, а база данных в backend вообще не имеет доступа к внешней сети. Проброс портов настраивается только для frontend-контейнера:

docker run -d --name proxy --network frontend -p 80:80 nginx:alpine

Контейнеры в backend остаются недоступными из интернета, но могут общаться друг с другом через DNS-имена. Это значительно безопаснее, чем выставлять порты каждого сервиса наружу.

Overlay-драйвер: сеть для контейнеров на разных хостах

Overlay-драйвер решает задачу связности контейнеров, разнесенных по разным физическим или виртуальным хостам. Он инкапсулирует L2-трафик в UDP-пакеты с помощью протокола VXLAN, создавая виртуальную сеть поверх физической инфраструктуры. Контейнеры на разных хостах видят друг друга так, будто подключены к одному коммутатору: общий широковещательный домен, единое адресное пространство, работающий DNS.

Для работы overlay требуется оркестратор, который управляет членством узлов в кластере и синхронизирует состояние сети. Docker Swarm выполняет эту роль из коробки. В более сложных сценариях можно использовать внешнее KV-хранилище (Consul, etcd), но для production-сред 2026 года Swarm - стандартный и поддерживаемый путь.

Overlay-сеть создает на каждом хосте виртуальный мост и VXLAN-туннель. Когда контейнер на хосте A отправляет пакет контейнеру на хосте B, демон Docker инкапсулирует L2-фрейм в UDP-пакет и отправляет его на IP-адрес хоста B. На принимающей стороне пакет декапсулируется и доставляется целевому контейнеру. Для приложений этот процесс прозрачен - они работают с обычными IP-адресами.

Настройка overlay-сети в Docker Swarm

Инициализируем Swarm на первом узле и добавим второй узел как worker:

# На manager-узле
docker swarm init --advertise-addr 192.168.1.10

# На worker-узле (команду покажет вывод init)
docker swarm join --token SWMTKN-1-... 192.168.1.10:2377

Создаем overlay-сеть с флагом --attachable, чтобы к ней можно было подключать обычные контейнеры, а не только сервисы:

docker network create \
  --driver overlay \
  --attachable \
  --subnet=10.0.0.0/24 \
  my-overlay-net

Запускаем контейнеры на разных узлах:

# На manager-узле
docker run -d --name web-overlay --network my-overlay-net nginx:alpine

# На worker-узле
docker run -d --name client-overlay --network my-overlay-net alpine sleep 3600

Проверяем связь с worker-узла:

docker exec client-overlay ping web-overlay

Пинг успешен. Контейнеры на разных физических хостах общаются по именам через общую overlay-сеть. Обратите внимание: без флага --attachable overlay-сеть доступна только для сервисов Swarm, запущенных через docker service create. Для отладки и тестирования attachable-режим удобнее.

Маршрутизация и шифрование в overlay

VXLAN инкапсулирует Ethernet-фреймы в UDP-дейтаграммы, добавляя 50 байт заголовков (VXLAN + UDP + IP + Ethernet). Это уменьшает эффективный MTU для контейнеров. Если ваши приложения используют jumbo-фреймы или чувствительны к фрагментации, увеличьте MTU на физических интерфейсах до 1550 или выше. Проблема проявляется как обрывы соединений при передаче больших пакетов - диагностируйте через ping -M do -s 1472 target_ip.

По умолчанию трафик между узлами в overlay-сети не шифруется. Для production-сред, особенно если узлы разнесены по разным дата-центрам, включите IPSec-шифрование:

docker network create \
  --driver overlay \
  --opt encrypted \
  --attachable \
  secure-overlay-net

Шифрование добавляет накладные расходы: примерно 10-15% к задержке и снижение пропускной способности на 20-30% в зависимости от нагрузки. Это плата за конфиденциальность трафика. Если узлы находятся в одном доверенном сегменте сети, шифрование можно отключить для производительности.

Macvlan-драйвер: контейнер как полноценный узел физической сети

Macvlan подключает контейнер напрямую к физической сети, назначая ему собственный MAC-адрес и IP из подсети организации. С точки зрения сети контейнер выглядит как отдельная физическая машина. Это востребовано для устаревших приложений, которые требуют прямого доступа к сетевому оборудованию, или для систем мониторинга, которые должны видеть весь трафик сегмента.

Драйвер работает поверх существующего физического интерфейса хоста, создавая виртуальные интерфейсы с уникальными MAC-адресами. Трафик от контейнера идет напрямую в физическую сеть, минуя NAT и мост docker0. Производительность близка к нативной - накладные расходы минимальны, так как нет трансляции адресов и дополнительных переходов между мостами.

Главное ограничение macvlan: контейнер не может общаться с хостом, на котором запущен. Это особенность реализации в ядре Linux - macvlan-интерфейсы изолированы от родительского интерфейса на уровне драйвера. Решение существует, и мы разберем его ниже.

Настройка macvlan для доступа к физической сети

Определите родительский интерфейс хоста, через который контейнеры будут выходить в сеть. Обычно это eth0 или ens18. Создайте macvlan-сеть с подсетью, соответствующей вашей физической сети:

docker network create \
  --driver macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  -o parent=eth0 \
  macnet

Запустите контейнер с явно указанным IP-адресом, чтобы избежать конфликтов с DHCP-сервером организации:

docker run -d --name legacy-app \
  --network macnet \
  --ip=192.168.1.200 \
  nginx:alpine

Контейнер получил IP 192.168.1.200. Проверьте с любой другой машины в сети: пинг до 192.168.1.200 должен проходить. Контейнер доступен по этому адресу без проброса портов - он полноценный участник физической сети. Это удобно для сервисов, которые должны быть доступны из корпоративной сети без дополнительного NAT.

Режимы macvlan: bridge (по умолчанию) - контейнеры на одном хосте могут общаться друг с другом через физический коммутатор; private - полная изоляция, контейнеры не видят даже соседей по хосту; passthru - контейнер получает прямой доступ к физическому интерфейсу, используется для мониторинга трафика.

Решение проблемы связи контейнера с хостом

Контейнер в macvlan-сети не может пинговать хост, потому что пакеты от macvlan-интерфейса не доставляются на родительский интерфейс на уровне ядра. Это не баг, а архитектурное ограничение. Обходной путь - создать macvlan-интерфейс на самом хосте в той же подсети:

ip link add macvlan0 link eth0 type macvlan mode bridge
ip addr add 192.168.1.201/24 dev macvlan0
ip link set macvlan0 up

Теперь хост имеет IP 192.168.1.201 на macvlan-интерфейсе, и контейнер может пинговать этот адрес. Трафик пойдет через физический коммутатор: контейнер -> eth0 хоста -> коммутатор -> eth0 хоста -> macvlan0 хоста. Это создает дополнительную нагрузку на коммутатор, но решает проблему связности.

Альтернативный подход - использовать второй физический интерфейс на хосте, если он доступен. Один интерфейс отдается под macvlan-контейнеры, второй остается для управления хостом. Это чище архитектурно, но требует дополнительного сетевого порта.

Управление трафиком с помощью iptables в Docker

Docker активно управляет iptables при запуске и остановке контейнеров. Он создает собственные цепочки DOCKER и DOCKER-USER в таблицах filter и nat, добавляет правила для проброса портов, маскарадинга и межконтейнерной фильтрации. Вмешиваться в цепочку DOCKER нельзя - демон перезаписывает ее при каждом изменении состояния контейнеров. Для пользовательских правил предназначена цепочка DOCKER-USER.

Ключевой момент: DOCKER-USER обрабатывается до DOCKER. Это означает, что ваши правила применяются первыми и могут отфильтровать трафик до того, как Docker начнет его обрабатывать. Если вы добавите правило в DOCKER-USER, которое дропает пакет, Docker не сможет его принять, даже если у него есть разрешающее правило в цепочке DOCKER.

Docker также добавляет правила в цепочку FORWARD таблицы filter. По умолчанию политика FORWARD - DROP, но Docker устанавливает правило, разрешающее трафик между мостом docker0 и внешними интерфейсами. Если вы вручную сбрасываете iptables, контейнеры теряют связность - восстановить правила можно перезапуском демона Docker.

Структура iptables в Docker: цепочки DOCKER и DOCKER-USER

Выполните команду для просмотра текущих правил:

iptables -L -n -v
iptables -t nat -L -n -v

Вы увидите цепочку DOCKER в таблице filter - здесь Docker добавляет правила для каждого проброшенного порта. В таблице nat цепочка DOCKER содержит правила DNAT для входящего трафика. Цепочка DOCKER-USER изначально пуста - это ваше пространство для кастомизации.

Порядок прохождения пакета из внешней сети к контейнеру: PREROUTING (nat) -> FORWARD (filter) -> DOCKER-USER -> DOCKER -> контейнер. Ваше правило в DOCKER-USER может дропнуть пакет на этапе FORWARD, и он никогда не достигнет цепочки DOCKER. Это дает полный контроль над фильтрацией.

Практические примеры фильтрации

Запрет исходящего трафика для конкретного контейнера. Определите IP контейнера:

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name

Добавьте правило в DOCKER-USER:

iptables -I DOCKER-USER -s 172.20.0.2 -j DROP

Контейнер потеряет доступ к внешней сети, но сохранит связность с другими контейнерами в той же bridge-сети, потому что этот трафик не проходит через FORWARD. Чтобы изолировать контейнер полностью, включая межконтейнерное общение, используйте правило в цепочке FORWARD напрямую или создайте отдельную bridge-сеть.

Разрешение доступа к порту контейнера только с определенного IP:

iptables -I DOCKER-USER -p tcp --dport 8080 ! -s 192.168.1.100 -j DROP

Это правило дропает все пакеты на порт 8080, кроме пришедших с IP 192.168.1.100. Правило добавляется в DOCKER-USER, поэтому обрабатывается до того, как Docker начнет маршрутизировать трафик в контейнер.

Изоляция контейнеров в одной bridge-сети. Если нужно запретить контейнеру A общаться с контейнером B, но разрешить с остальными:

iptables -I DOCKER-USER -s 172.20.0.2 -d 172.20.0.3 -j DROP

Правила в DOCKER-USER не переживают перезагрузку хоста. Для сохранения используйте пакет iptables-persistent или добавьте правила в скрипт, который запускается при старте системы. Docker восстановит свои правила автоматически, а ваши нужно применить повторно.

Выбор драйвера: bridge, overlay или macvlan?

Выбор сетевого драйвера определяется тремя факторами: масштаб развертывания, требования к изоляции и необходимость прямого доступа к физической сети. Для 90% сценариев на одном хосте достаточно пользовательского bridge. Он обеспечивает DNS-разрешение, изоляцию групп контейнеров и простую настройку проброса портов. Это базовый инструмент, с которого стоит начинать проектирование сети.

Overlay необходим, когда сервисы распределены по нескольким хостам и должны общаться напрямую. Docker Swarm с overlay-сетью - минимально достаточный стек для кластерных развертываний. Если вы используете Kubernetes, сетевой слой организуется через CNI-плагины, но понимание overlay помогает диагностировать проблемы на уровне инфраструктуры. Рекомендую изучить руководство по сетям Docker для микросервисов, где разобрана интеграция с Kubernetes и CNI.

Macvlan - нишевый инструмент для специфических требований: устаревшие приложения, системы мониторинга, прямой доступ к VLAN. Производительность macvlan выше, чем у bridge с NAT, но плата за это - ограничения в общении с хостом и более сложная настройка безопасности. Комбинировать драйверы можно и часто нужно: frontend-сервисы в bridge-сети с пробросом портов, backend в overlay для связи между хостами, а специфические сервисы - в macvlan для прямого доступа к корпоративной сети.

Сравнительная таблица драйверов:

КритерийBridgeOverlayMacvlan
МасштабируемостьОдин хостКластер (Swarm)Один хост
ИзоляцияВысокая (между сетями)Средняя (общая сеть)Низкая (физическая сеть)
ПроизводительностьВысокая (NAT)Средняя (VXLAN)Максимальная (прямой доступ)
Сложность настройкиНизкаяСредняяСредняя
DNS-разрешениеДа (пользовательские)ДаНет
Связь с хостомДаДаНет (требуется обход)

Для углубленного изучения безопасности контейнерных сред обратитесь к гайду по безопасности Docker. Там разобраны сканирование уязвимостей, отказ от root-прав и тонкая настройка cgroups. Если нужна пошаговая инструкция по созданию пользовательской bridge-сети, рекомендую полное руководство по настройке bridge-сети.

Практический навык настройки сетей Docker - фундамент для построения отказоустойчивой контейнерной инфраструктуры. Начните с bridge, освойте iptables для фильтрации, переходите к overlay при масштабировании, а macvlan оставьте для специфических случаев. Каждый драйвер решает свою задачу, и комбинация этих инструментов дает полный контроль над трафиком в контейнеризированной среде.

Поделиться:
Сохранить гайд? В закладки браузера