4. Docker networking
Сетевая подсистема Docker: от виртуальных мостов и iptables до ловушки ndots в Go
Контейнеры изолируют сетевые процессы с помощью пространств имен Network Namespaces (как мы выяснили в лекции 6. Networking в Linux). Однако изолированный бэкенд не может функционировать автономно: реальному Go-сервису требуется принимать входящие HTTP/gRPC-запросы клиентов, совершать вызовы к пулу PostgreSQL, сохранять кэш в Redis и обмениваться сообщениями с соседними микросервисами.
Сетевая подсистема Docker (Docker Networking) — это программная виртуальная фабрика коммутации, которая связывает изолированные сетевые пространства имен (netns) между собой, маршрутизирует трафик через ядро хоста и выводит пакеты в открытый интернет.
1. Сеть по умолчанию (Default Bridge) и виртуальный мост docker0
При установке Docker автоматически создает на хосте программный виртуальный сетевой коммутатор (мост второго уровня L2) — интерфейс docker0 (обычно с подсетью 172.17.0.0/16 и IP-адресом 172.17.0.1).
Если запустить контейнер командой docker run без явного указания сети, он автоматически подключается к этому дефолтному мосту:
- Создание виртуального кабеля (veth pair): Демон Docker создает пару связанных виртуальных сетевых интерфейсов — программный аналог двустороннего патч-корда.
- Подключение к мосту: Один конец пары (например,
veth1234) подключается к мостуdocker0на хосте. - Изоляция в netns: Второй конец переносится внутрь сетевого пространства контейнера и переименовывается в стандартный
eth0. - Конфигурация IP и маршрутизации: Контейнеру выделяется приватный IP-адрес (например,
172.17.0.2), а шлюзом по умолчанию (default gateway) назначается IP мостаdocker0(172.17.0.1).
flowchart LR
classDef process fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
classDef success fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
classDef error fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
classDef warning fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
classDef entry fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
classDef aux fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
classDef system fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
classDef data fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
classDef network fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
classDef accent fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
A["Go App Container 1"]:::process -->|"eth0 - 172.17.0.2"| B["veth pair"]
B --> C["docker0 Bridge - 172.17.0.1"]
D["Go App Container 2"]:::process -->|"eth0 - 172.17.0.3"| E["veth pair"]
E --> C
C -->|"NAT / iptables"| F["Host eth0 - Внешняя сеть"]
2. Пользовательские сети (Custom Bridge) и встроенный DNS
Чтобы контейнеры могли безопасно и прозрачно находить друг друга по доменным именам (Service Discovery), Docker требует создания пользовательских сетей (Custom Bridge Networks):
docker network create my-app-network
При запуске сервисов с флагом --network my-app-network вступает в действие встроенный механизм обнаружения сервисов Docker:
- Docker поднимает для этой сети изолированный DNS-сервер на локальном адресе
127.0.0.11. - Теперь любой Go-сервис может безопасно обращаться к зависимостям по именам сервисов:
http://redis:6379илиpostgres:5432.
3. Проброс портов (Port Mapping) и цепочки iptables
Каким образом внешний клиент из публичного интернета может достучаться до Go-сервиса, если тот заперт в изолированной приватной подсети 172.17.0.0/16?
Эту задачу решает подсистема ядра Linux Netfilter и таблицы iptables.
Когда вы запускаете контейнер с флагом публикации порта -p 8080:80, демон Docker создает правило DNAT (Destination NAT) в цепочке PREROUTING таблицы nat:
- Входящий TCP-пакет от клиента приходит на порт 8080 физического интерфейса хоста (
eth0). - Модуль ядра
iptablesперехватывает пакет и перезаписывает его Destination IP: адрес назначения хоста подменяется на внутренний IP контейнера (172.17.0.2:80). - Ядро маршрутизирует модифицированный пакет через коммутатор
docker0прямо вnetnsконтейнера. - Исходящий ответ от Go-сервиса на обратном пути проходит через правило SNAT (Source NAT / Masquerade): ядро подменяет исходный адрес
172.17.0.2на публичный IP хоста, создавая для внешнего клиента полную иллюзию прямого диалога с хост-машиной.
4. Сеть Host: Обход изоляции
При указании флага --network host демон Docker не создает отдельный Network Namespace. Go-процесс запускается непосредственно в сетевом пространстве хостовой операционной системы.
- Плюсы: Полный отказ от виртуальных мостов, veth-интерфейсов и правил iptables NAT. Нулевые накладные расходы — пакеты с сетевой карты хоста попадают прямо в сетевой сокет Go-приложения без единой микросекунды задержки.
- Минусы: Абсолютная потеря сетевой изоляции. Если Go-сервис забиндит порт 8080, никакой другой процесс на хосте не сможет его использовать. Запустить два одинаковых контейнера на одном сервере невозможно без переопределения конфигурации портов.
В промышленной эксплуатации сеть host используется для инфраструктурных агентов мониторинга (Prometheus Node Exporter, DaemonSets сбора логов), а также для сверхнагруженных сетевых прокси.
5. Специфика резолвинга в Go и коварная ловушка ndots:5
Пожалуй, самая распространенная сетевая аномалия при переносе Go-микросервисов в контейнеры Docker и кластеры Kubernetes связана с параметром ndots и особенностями встроенного резолвера Go.
В контейнерных окружениях (особенно в Kubernetes) файл /etc/resolv.conf конфигурируется с параметром:
options ndots:5
search default.svc.cluster.local svc.cluster.local cluster.local
Директива options ndots:5 инструктирует резолвер: если в запрошенном доменном имени содержится меньше 5 точек, имя считается неполным (не FQDN). Резолвер обязан сначала попытаться найти имя, последовательно подставляя все домены поиска из списка search, и только если все попытки вернут отрицательный результат (NXDOMAIN), отправить запрос на поиск имени как абсолютного.
В чем опасность для чистого Go?
Если в проекте скомпилирован полностью статический бинарник (CGO_ENABLED=0), рантайм Go использует собственный встроенный Pure Go DNS-клиент.
Когда ваш код обращается к базе данных по короткому имени db (в имени 0 точек):
- Go отправляет UDP-запрос:
db.default.svc.cluster.local-> Ждет ответа. - Если не нашел:
db.svc.cluster.local-> Ждет ответа. - Затем:
db.cluster.local-> Ждет ответа. - И только в самом конце: прямой запрос
db!
Каждый такой холостой круг генерирует 3–5 последовательных сетевых запросов к DNS-серверу (CoreDNS), создавая колоссальный всплеск задержек (в сотни миллисекунд) при установке соединений и перегружая инфраструктуру DNS-запросами.
6. Сеть None: Абсолютная изоляция
При флаге --network none контейнер получает персональный сетевой неймспейс, в котором активен исключительно loopback-интерфейс lo (127.0.0.1). Внешние интерфейсы не создаются вовсе.
Этот режим незаменим для:
- Изолированных криптографических вычислений и генерации закрытых ключей.
- Обработки конфиденциальных данных (batch processing), где законодательно или архитектурно требуется гарантировать невозможность утечки в сеть.
- Сервисов, общающихся с внешними процессами исключительно через локальные разделяемые тома и UNIX Domain Sockets.
Итог
- Default Bridge опасен: В нем отсутствует автоматический DNS-сервер; для надежного Service Discovery всегда создавайте Custom Bridge сети.
- Port Mapping через iptables: Проброс портов
-pработает через механизм DNAT и модуль ядраconntrack, что добавляет накладные расходы на отслеживание соединений. - Сеть Host для экстремального I/O: Устраняет оверхед трансляции адресов ценой полной потери сетевой изоляции.
- Коварство ndots:5: Статический DNS-резолвер Go при коротких именах может генерировать цепочки холостых запросов; защищайтесь использованием FQDN и замыкающих точек.
- Сеть None: Гарантирует абсолютную сетевую изоляцию процессов для критических задач.
Сетевое взаимодействие налажено. Однако контейнеры эфемерны: при падении или перезапуске все изменения в слое контейнера бесследно стираются. В следующей лекции мы детально изучим надежное сохранение данных и высокопроизводительный дисковый ввод-вывод: 5. Volumes и хранение данных.