Перейти к содержанию

5. Сеть Docker

Цель темы: уметь настраивать и диагностировать сеть контейнеров: понимать драйверы (bridge, host, none, overlay, macvlan), port mapping, связь контейнер↔контейнер и контейнер↔хост, DNS внутри Docker и роль iptables (цепочки DOCKER, NAT, FORWARD).


Определения терминов

Сетевой драйвер Docker

Сетевой драйвер определяет, как контейнер подключается к сети: какой тип виртуального устройства используется (bridge, veth, macvlan и т.д.) и как настроены маршрутизация и изоляция. По умолчанию используется драйвер bridge: контейнеры подключаются к виртуальному коммутатору (bridge) на хосте и получают IP из подсети этого bridge.

Bridge (мост)

Bridge — виртуальный коммутатор уровня 2 в ядре Linux. Docker создаёт bridge docker0 (или пользовательскую сеть с драйвером bridge). Каждый контейнер получает пару veth: один конец в network namespace контейнера (интерфейс eth0), другой привязан к bridge. Контейнеры в одном bridge могут общаться по IP; для выхода в интернет и приёма трафика с хоста используются NAT и правила iptables.

Port mapping (publish)

Port mapping — проброс порта хоста на порт контейнера. При обращении к <host_ip>:<host_port> ядро (через iptables или docker-proxy) перенаправляет пакеты в контейнер на указанный порт. Задаётся флагом -p или --publish, например -p 8080:80.

Overlay-сеть

Overlay — сеть, размазанная по нескольким хостам (Docker Swarm, Kubernetes). Трафик между контейнерами на разных нодах инкапсулируется (VXLAN и др.) и доставляется поверх физической сети. На одной ноде overlay не обязателен; для многопоточной оркестрации он нужен для «сети между подами/сервисами» поверх кластера.

macvlan

macvlan — драйвер, создающий у контейнера виртуальный интерфейс с собственным MAC-адресом, привязанный к физическому интерфейсу хоста. Контейнер выглядит как отдельное устройство в той же L2-сети, что и хост (свой IP из подсети сети). Удобно, когда контейнер должен быть «рядом» с хостом в сети (например, для legacy-интеграций).


Драйверы сетей

bridge (по умолчанию)

Контейнеры подключаются к bridge (по умолчанию docker0 или к пользовательской сети с драйвером bridge). Изоляция между разными сетями; внутри одной сети контейнеры видят друг друга по IP и по имени (если включён встроенный DNS).

docker network create mynet
docker run -d --name web --network mynet nginx
docker run -it --rm --network mynet alpine ping web

host

Контейнер использует сетевой стек хоста напрямую: общие с хостом интерфейсы, порты, нет изоляции. Подходит для высокопроизводительных или специфичных сценариев (например, сбор метрик с хоста); на Windows/macOS поведение ограничено.

docker run --network host myapp

none

У контейнера нет сетевых интерфейсов (кроме loopback). Полная сетевая изоляция; доступ только с хоста через другие каналы (volumes, exec).

docker run --network none alpine

overlay

Используется в Swarm и при необходимости в Compose с несколькими нодами. Создаётся через docker network create -d overlay .... На одной ноде для связи контейнеров достаточно bridge.

macvlan

Контейнер получает свой MAC и IP в подсети, к которой подключён хост. Требует указания родительского интерфейса и подсети.

docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 macnet
docker run --network macnet --ip=192.168.1.100 myapp

Практика

Для большинства сценариев на одной машине достаточно пользовательской bridge-сети (по умолчанию встроенный DNS по имени контейнера) и port mapping для доступа с хоста/извне.


Port mapping

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

docker run -d -p 8080:80 nginx
# хост:контейнер
docker run -d -p 127.0.0.1:8080:80 nginx   # только localhost
docker run -d -p 80:80/tcp -p 443:443/tcp nginx

Трафик на <host>:8080 перенаправляется в контейнер на порт 80. Правила создаются в iptables (цепочки DOCKER, NAT PREROUTING/OUTPUT).

Проверка проброса

docker port <container>
# 80/tcp -> 0.0.0.0:8080

Контейнер → контейнер

Контейнеры в одной пользовательской сети (или в одной default bridge до определённых версий) могут общаться по имени контейнера: встроенный DNS резолвит имя в IP. На default bridge раньше имена не резолвились — использовали --link (deprecated). Рекомендация: создавать пользовательскую сеть и подключать контейнеры к ней.

docker network create appnet
docker run -d --name db --network appnet postgres
docker run -d --name app --network appnet -e DB_HOST=db myapp

Внутри контейнера app имя db разрешается в IP контейнера postgres.


Контейнер → хост

  • Обращение к сервисам на хосте: с Linux при стандартной bridge IP хоста с точки зрения контейнера — это шлюз bridge (например, 172.17.0.1 для docker0). Можно использовать этот IP или специальный DNS-имя host.docker.internal (на Docker Desktop для Mac/Windows). На Linux host.docker.internal может быть добавлен вручную или через extra_hosts.
  • Обращение с хоста к контейнеру: через port mapping (-p) или, если контейнер в пользовательской сети, по IP контейнера в этой сети (доступ с хоста к подсети bridge обычно разрешён).
docker run --add-host=host.docker.internal:host-gateway app

На части установок host-gateway подставляется в IP хоста.


DNS внутри Docker

Пользовательские сети предоставляют встроенный DNS: имя контейнера (и имя сети, если заданы алиасы) резолвится в IP контейнера. Запросы идут на резолвер демона (127.0.0.11). Настройка DNS контейнера: --dns, --dns-search; при проблемах с резолвингом проверяют resolv.conf внутри контейнера и доступность 127.0.0.11.

docker run --dns 8.8.8.8 alpine

--link раньше использовался для связи контейнеров по имени и для передачи переменных окружения. Недостатки: работает только между двумя контейнерами, при перезапуске контейнера ссылки ломаются, не масштабируется. Вместо этого используют пользовательские сети: контейнеры в одной сети резолвят друг друга по имени без --link. Знать про --link нужно только чтобы не использовать его в новом коде.


iptables: DOCKER, NAT, FORWARD

Как контейнер получает IP?

При создании контейнера в bridge-сети Docker (через демон и сетевой драйвер): создаётся пара veth, один конец попадает в network namespace контейнера и получает IP из подсети bridge (например, 172.18.0.2/16); в контейнере прописываются default gateway (IP bridge на хосте) и resolv.conf. IP выдаётся из пула подсети, назначенной этой сети.

Почему контейнер не видит внешний мир?

Типичные причины:

  1. Нет NAT/masquerade для исходящего трафика: пакеты из контейнера должны выходить с IP хоста (SNAT). Правила в iptables в цепочке POSTROUTING (masquerade для подсети bridge). Если правил нет (например, после ручной очистки iptables или смены firewall) — исходящий трафик не уходит или ответы не возвращаются.
  2. FORWARD закрыт: пакеты между контейнером и внешней сетью проходят через хост; цепочка FORWARD должна разрешать трафик для интерфейсов Docker. Обычно Docker добавляет правила сам; при жёстком firewall их могут удалять или блокировать.
  3. DNS: контейнер не может резолвить имена (проблемы с 127.0.0.11 или с upstream DNS). Проверка: docker run --rm alpine nslookup google.com.

Цепочки DOCKER и NAT

Docker создаёт цепочки в таблицах nat и filter:

  • nat: PREROUTING, OUTPUT — направляют трафик к контейнерам при port mapping; POSTROUTING — masquerade для исходящего трафика контейнеров.
  • filter: цепочка DOCKER и правила в FORWARD — разрешают трафик к/от контейнеров.

Просмотр (на хосте):

sudo iptables -t nat -L -n -v
sudo iptables -L DOCKER -n -v

Внимание

Ручное изменение iptables может конфликтовать с правилами Docker. Восстановление: перезапуск демона или пересоздание сетей. В production изменения firewall делают с учётом правил, которые создаёт Docker.


Паттерны использования

Паттерн Описание
Пользовательские сети вместо default bridge Изоляция групп сервисов, встроенный DNS по имени контейнера.
Port mapping только при необходимости Публиковать только те порты, которые должны быть доступны с хоста/извне.
Не использовать --link Везде пользовательские сети и резолвинг по имени.
Диагностика: смотреть с хоста и из контейнера Проверять iptables, docker network inspect, ping/nslookup из контейнера.

Антипаттерны

Антипаттерн Почему плохо Что делать
Полагаться на default bridge и --link Нет DNS по имени, устаревший механизм. Создавать сети и подключать контейнеры к ним.
Открывать все порты (-p без ограничений) Увеличение поверхности атаки. Публиковать только нужные порты, при возможности привязывать к 127.0.0.1.
Игнорировать firewall на хосте Docker добавляет правила; жёсткая политика может их блокировать. Согласовывать правила firewall с работой Docker (FORWARD, цепочки DOCKER).

Вопросы на собеседовании

Как контейнер получает IP?
При подключении к bridge-сети Docker создаёт пару veth, один конец в network namespace контейнера. Этому интерфейсу назначается IP из подсети сети (пул выдаёт демон); в контейнере настраиваются default gateway (IP bridge) и DNS (резолвер демона 127.0.0.11).

Почему контейнер не видит внешний мир?
Проверить: 1) NAT/masquerade для подсети контейнеров (iptables POSTROUTING); 2) разрешён ли FORWARD для интерфейсов Docker; 3) DNS (резолвинг имён). На хосте посмотреть iptables и логи демона; из контейнера — ping шлюза и nslookup.


Примеры из production

После обновления firewall контейнеры потеряли доступ наружу

Часто причина — сброс или изменение правил FORWARD и NAT. Нужно восстановить правила Docker (перезапуск демона) или добавить в скрипты firewall исключения для интерфейсов Docker и цепочек DOCKER.

Контейнеры в одной сети не резолвят имена

Проверить, что оба контейнера в одной пользовательской сети (docker network inspect <net>), что резолвер 127.0.0.11 доступен из контейнера и что демон не перезапускался с очисткой сетей. При использовании Compose убедиться, что сервисы в одной network.

Port mapping не срабатывает

Проверить, что порт не занят на хосте; что привязка к 0.0.0.0 или к нужному IP; что правила iptables на месте (цепочка DOCKER, PREROUTING). На некоторых системах docker-proxy слушает порт; при его отключении полагаются только на iptables.


Дополнительные материалы