Сети Docker глубже: bridge, overlay, macvlan и iptables

Рейтинг: 61% · 6 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Сети Docker глубже: bridge, overlay, macvlan и iptables

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables (вы здесь)
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Боль, с которой ты живёшь, пока не разобрался в сети Docker

Контейнер не видит соседа по имени. Порт опубликовал, а снаружи глухо. Поднял firewalld - и весь проброс портов рассыпался, хотя docker network ls показывает всё на месте. Подсеть Docker внезапно совпала с офисной 172.17.0.0/16, и VPN до прода отвалился у всей команды. Знакомо? Сеть Docker выглядит как магия ровно до того момента, пока что-то не сломается, и тогда внезапно надо знать, что такое veth, DNAT и почему DNS живёт на 127.0.0.11.

Хорошая новость: никакой магии тут нет. Docker network - это тонкая обёртка над тремя примитивами ядра Linux: network namespaces (изолированный сетевой стек на каждый контейнер), veth-пары (виртуальный кабель между namespace и хостом) и iptables/nftables (NAT, фильтрация, проброс портов). Разберёшь эти три кирпича - и вся docker сеть станет прозрачной: ты будешь чинить её через ip, brctl и iptables -t nat, а не методом тыка.

Изображение

Драйверы сети: bridge, host, none, macvlan/ipvlan, overlay

Драйвер - это способ, которым контейнер подключается к внешнему миру. Их немного, и каждый решает свою задачу.

bridge - дефолт для одного хоста. Контейнер получает приватный IP во внутренней подсети, наружу ходит через NAT. Это то, что включается само, если ты не указал --network. Именно про docker bridge дальше будет самый подробный разбор.

host - контейнер шарит сетевой namespace хоста. Никакой изоляции, никакого NAT: процесс слушает порт прямо на хостовом интерфейсе, как будто запущен без Docker. Максимальная пропускная и нулевой оверхед, но порт 80 в контейнере = порт 80 на хосте, и два таких контейнера подерутся. На Docker Desktop (Mac/Windows) host работает иначе - там контейнеры крутятся в VM, так что "хост" - это linux-VM, а не твоя macOS.

none - есть только loopback, наружу ноль. Для air-gapped задач: посчитать что-то и записать в volume, без единого сетевого пакета.

macvlan/ipvlan - контейнер получает свой собственный MAC и IP прямо в физической сети. Для остальной LAN он выглядит как отдельная железка, а не как процесс за NAT. docker macvlan берут, когда легаси-софт хочет быть полноценным узлом сети (DHCP, обнаружение по броадкасту, прослушка VLAN). ipvlan - брат-близнец, но раздаёт IP без раздачи отдельных MAC (режим L2/L3), что спасает, когда у свитча включён port security или провайдер лимитирует число MAC на порту.

overlay - мультихостовая сеть поверх нескольких машин. docker overlay строит виртуальный L2-сегмент через VXLAN-туннели, так что контейнеры на разных серверах общаются по приватным IP, будто в одном свитче. Это основа сети Docker Swarm и кластеров; вне Swarm overlay тоже можно поднять, но нужен внешний key-value store, и в 2026 так почти никто не делает - либо Swarm, либо уже Kubernetes со своим CNI.

bridge под капотом: docker0, veth-пары и namespace

Разберём дефолтную docker сеть по косточкам. После установки Docker на хосте появляется мост docker0 - это виртуальный коммутатор в ядре. Когда ты запускаешь контейнер, Docker создаёт veth-пару: два виртуальных интерфейса, соединённых как два конца одного патч-корда. Один конец уезжает внутрь network namespace контейнера и там зовётся eth0, второй остаётся на хосте и втыкается в docker0.

Посмотрим на хост. Поднимем контейнер и глянем мост:

Код: Выделить всё

docker run -d --name web nginx:alpine
ip -br link show master docker0
# vethadf3c1a@if12  UP  6a:1f:0c:8e:22:b4 <BROADCAST,MULTICAST,UP,LOWER_UP>
Этот vethadf3c1a - хостовый конец кабеля. Суффикс @if12 значит: на том конце интерфейс с индексом 12, и он в другом namespace. Зайдём внутрь контейнера и сверим:

Код: Выделить всё

docker exec web ip -br addr show eth0
# eth0@if13  UP  172.17.0.2/16
eth0 внутри имеет адрес 172.17.0.2 из подсети моста 172.17.0.0/16, а его @if13 указывает обратно на хостовый veth. Вот он, кабель: один конец в контейнере (eth0), другой на мосту (veth...). Шлюз по умолчанию внутри контейнера - это IP самого моста docker0, обычно 172.17.0.1. Весь трафик наружу контейнер шлёт на шлюз, дальше включается NAT.

Дефолтный мост против user-defined. Запомни главное практическое правило: на стандартном docker0 нет DNS по именам - контейнеры видят друг друга только по IP. Как только ты создаёшь свою сеть через docker network create mynet, включается встроенный DNS и контейнеры резолвят друг друга по имени. Поэтому в реальной эксплуатации почти всегда делают свою bridge-сеть, а не сидят на docker0.

Встроенный DNS на 127.0.0.11 - как контейнеры находят друг друга

Внутри каждого контейнера в user-defined сети Docker поднимает встроенный DNS-сервер на адресе 127.0.0.11. Загляни в resolv.conf:

Код: Выделить всё

docker network create app-net
docker run -d --name db --network app-net postgres:16-alpine
docker run --rm --network app-net alpine cat /etc/resolv.conf
# nameserver 127.0.0.11
# options ndots:0
Когда контейнер делает запрос к db, запрос летит на 127.0.0.11, встроенный резолвер смотрит в свою таблицу имён сети и отдаёт приватный IP контейнера db. Имена, которых он не знает (например google.com), он форвардит на DNS-серверы хоста из /etc/resolv.conf хоста. Механизм держится на двух хитростях: внутри namespace стоит iptables-правило, заворачивающее трафик с 127.0.0.11 на реальный порт демона, а сам Docker по событиям подключения/отключения контейнеров на лету правит записи. Поэтому масштабируешь сервис - имена резолвятся сразу, без перезапусков.

Грабля номер один тут: 127.0.0.11 работает только на user-defined сетях. На голом docker0 этого DNS нет, и --link - это древний костыль, забудь про него. Вторая грабля - ndots:0 в примере выше (поведение зависит от версии): неаккуратные ndots в больших инсталляциях порождают лавину лишних DNS-запросов, потому что резолвер дописывает search-домены к каждому короткому имени.

Публикация портов: docker сеть, iptables, DNAT и docker-proxy

Теперь самое тёмное место - почему -p 8080:80 пускает мир в контейнер. Контейнер сидит в приватной подсети, снаружи его IP не маршрутизируется. Чтобы внешний запрос на хостовый 8080 дошёл до контейнерного 80, Docker вставляет правило DNAT (destination NAT) в таблицу nat. docker iptables - это не пугающая абстракция, это набор цепочек, которые ты можешь прочитать глазами:

Код: Выделить всё

docker run -d -p 8080:80 --name pub nginx:alpine
iptables -t nat -L DOCKER -n
# Chain DOCKER (2 references)
# target  prot opt source     destination
# RETURN  all  --  0.0.0.0/0  0.0.0.0/0
# DNAT    tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 to:172.17.0.2:80
Читаем последнюю строку: любой TCP-пакет с назначением на порт 8080 переписывается так, что новый адрес назначения - 172.17.0.2:80, то есть контейнер. Это и есть проброс. Цепочка DOCKER вызывается из PREROUTING (для трафика снаружи) и из OUTPUT (для трафика с самого хоста на localhost). На свежем Docker (ветка 29 и новее) появилась поддержка nftables-бэкенда: тогда правила лежат не в iptables, а в nft-таблицах ip docker-bridges / ip6 docker-bridges. На большинстве актуальных дистрибутивов iptables и так уже nft-обёртка, так что iptables-nft и nft показывают одно и то же разными словами.

А что за docker-proxy? Рядом с DNAT Docker запускает пользовательский процесс docker-proxy на каждый опубликованный порт. Многие думают, что трафик идёт через него - это миф. В нормальном случае работает DNAT в ядре, а docker-proxy нужен для краевых случаев: обращения с самого хоста на localhost:8080 (где PREROUTING не срабатывает в части конфигураций) и хост-нетворкинга на старых ядрах. Увидеть его легко:

Код: Выделить всё

ps aux | grep docker-proxy
# /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 80
Практический вывод: при тысячах портов докер-прокси жрёт память и файловые дескрипторы. В демоне его можно отключить флагом userland-proxy: false в /etc/docker/daemon.json, оставшись на чистом DNAT - но тогда теряешь часть обращений на localhost, проверяй свой кейс.

Изоляция, --internal сети, MTU и доступ в host-сеть

Изоляция. Контейнеры в разных user-defined сетях по умолчанию не видят друг друга - между мостами стоит DROP в цепочке DOCKER-ISOLATION. Контейнеры в одной сети видят друг друга по всем портам напрямую, без всякого -p (публикация нужна только для доступа снаружи хоста). Это частая путаница новичков: чтобы web ходил в db, порт публиковать НЕ надо, достаточно общей сети.

--internal. Если создать сеть с флагом --internal, Docker не добавляет маршрут наружу и MASQUERADE - контейнеры в ней общаются между собой, но в интернет не выйдут. Идеально для бэкенд-сегмента (база, кеш), который не должен иметь исходящего доступа:

Код: Выделить всё

docker network create --internal backend
MTU - тихий убийца. По умолчанию мост берёт MTU 1500. Но overlay поверх VXLAN добавляет 50 байт оверхеда: если физический MTU 1500, то внутри overlay должно быть 1450 или меньше, иначе крупные пакеты молча режутся, и ты получаешь зависающие соединения, которые "иногда работают". Симптом классический: ping и мелкие запросы летают, а большой POST или git clone виснет. Лечится выставлением MTU драйвера сети под реальный канал (особенно важно в облаках и поверх туннелей).

Доступ из контейнера в host-сеть. Иногда контейнеру надо достучаться до сервиса, который крутится на самом хосте (БД на хосте, dev-сервер). Внутри контейнера используй специальное имя host.docker.internal - на Docker Desktop оно резолвится автоматически, на Linux его надо явно прокинуть: --add-host host.docker.internal:host-gateway. Это безопаснее, чем сажать контейнер в --network host.

Типичные грабли и антипаттерны
  • Конфликт подсетей. Docker по умолчанию выедает 172.17.0.0/16 и дальше из пула 172.16-172.31. Если у тебя VPN или офисная сеть в этом диапазоне - маршруты дерутся, и пропадает связь. Лечение: задать свои диапазоны в daemon.json через default-address-pools, а не ловить совпадения по факту.
  • firewalld и nftables против Docker. Docker сам пишет свои правила в iptables/nftables. Перезапуск firewalld или ручной iptables -F вычищает докеровские цепочки, и проброс портов умирает до docker restart или docker network reconnect. Не трогай руками цепочки DOCKER, DOCKER-USER, DOCKER-ISOLATION - для своих правил есть специально оставленная цепочка DOCKER-USER, которая выполняется ПЕРЕД докеровскими. Туда и вешай ограничения доступа.
  • -p 8080:80 публикует на 0.0.0.0. То есть порт виден всему миру, а не только локально. На сервере с белым IP это дыра. Привязывай явно: -p 127.0.0.1:8080:80, если сервис только для локального reverse-proxy.
  • macvlan: хост не видит свои контейнеры. Фундаментальное ограничение драйвера - сам хост НЕ может общаться со своими macvlan-контейнерами (трафик уходит на физику и не возвращается). Это не баг, это дизайн. Нужна связь хост-контейнер - городи дополнительный macvlan-интерфейс на хосте или используй другой драйвер.
  • Сидеть на docker0 в проде. Нет DNS по именам, нет нормальной изоляции. Всегда создавай user-defined сети под каждый стек (Compose делает это сам).
Безопасность сети - коротко и по делу

Минимизируй атакуемую поверхность. Не публикуй порты, которые нужны только внутри стека - общая сеть и так даёт связь между контейнерами. Бэкенд-сегменты делай --internal, чтобы база не имела исходящего доступа в интернет (это режет утечки при компрометации). Вешай ограничения в DOCKER-USER, а не поверх докеровских цепочек. Для публикации наружу биндись на конкретный интерфейс, а не на 0.0.0.0. И помни: --network host снимает сетевую изоляцию полностью - применяй точечно и осознанно, а не "чтобы заработало".

Мини-лаба: собери и препарируй сеть руками
  • Создай свою сеть с фиксированной подсетью: docker network create --subnet 10.55.0.0/24 lab. Подними два контейнера alpine в ней (--network lab, с именами a и b).
  • Из контейнера a пингани b по имени: docker exec a ping -c2 b. Убедись, что встроенный DNS на 127.0.0.11 отдал IP (загляни в /etc/resolv.conf внутри).
  • Опубликуй порт у nginx (-p 8080:80 --network lab) и найди DNAT-правило: iptables -t nat -L DOCKER -n. Разбери поля dpt и to: - какой порт куда переписывается.
  • Найди veth-пару: на хосте ip -br link show master br-... (имя моста для своей сети возьми из ip link), внутри контейнера docker exec a ip -br addr show eth0. Сопоставь индексы @ifN.
  • Сделай вторую сеть и контейнер в ней, проверь, что он НЕ пингует контейнеры из lab (изоляция мостов). Потом подключи его второй сетью: docker network connect lab othercontainer - и убедись, что связь появилась.
Контрольные вопросы
  • Чем отличается поведение DNS на дефолтном docker0 и на user-defined bridge, и какой адрес отвечает за резолв имён внутри контейнера?
  • Что происходит с пакетом при -p 8080:80: какая таблица и какое правило его переписывают, и зачем рядом живёт docker-proxy?
  • Почему overlay требует уменьшенного MTU и как проявляется проблема, если этого не сделать?
  • Куда вешать свои firewall-правила, чтобы их не стёр Docker, и почему нельзя править цепочку DOCKER напрямую?
Итог

Сеть Docker - это namespace плюс veth плюс iptables, и больше ничего волшебного. bridge для одного хоста, host для скорости без изоляции, macvlan/ipvlan чтобы стать узлом физической сети, overlay для мультихоста. Имена резолвит встроенный DNS на 127.0.0.11, но только в своих сетях. Порты пробрасывает DNAT в таблице nat. Знаешь эти механизмы - и docker network перестаёт быть чёрным ящиком: ты читаешь её через ip, iptables -t nat и docker network inspect, а не гадаешь.
👍4 ❤️3 🔥1 😄 🤔3
Аватара пользователя
msdana
Сообщения: 1
Зарегистрирован: 31 май 2026, 05:27

Re: Сети Docker глубже: bridge, overlay, macvlan и iptables

Сообщение msdana »

Блин, вот про docker-proxy реально откровение - всегда думал что весь трафик через него течёт, а оно DNAT в ядре. Спасибо, теперь понятно зачем он память жрёт
👍1 ❤️ 🔥1 😄 🤔1
Аватара пользователя
nixos_hacker
Сообщения: 1
Зарегистрирован: 30 май 2026, 03:05

Re: Сети Docker глубже: bridge, overlay, macvlan и iptables

Сообщение nixos_hacker »

А можно подробнее про DOCKER-USER? У меня firewalld после рестарта убивает проброс портов, правила в обычные цепочки вешал. Видимо в этом и косяк был
👍 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Отладка контейнеров: exec, attach, nsenter, debug
Следующая глава →
Docker Compose глубже: profiles, healthcheck-зависимости, override

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: как установить docker на ubuntu и linuxdocker команды шпаргалка для новичкаdocker run как запустить контейнер с флагамиdocker images как посмотреть и удалить образыdocker desktop и wsl2 на windows как настроитьdocker no space left on device как очистить

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость