open atlas
↑ К треку
Docker: контейнеры как система DOCK · 03 · 03

Container-to-container: один хост, разные хосты и иллюзия overlay

Контейнеры на одном мосту говорят напрямую на L2; у дефолтного моста нет DNS. Между хостами overlay строит виртуальный L2 поверх VXLAN с распределённым control plane — а протечки (область DNS, расходы MTU, hairpin NAT) и есть то, где мульти-хостовая сеть ломается.

DOCK Senior ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior

Оно проходило каждый тест на одном узле и валилось в момент масштабирования до трёх. Приложение находило свою базу-соседа по имени контейнера, и на одной машине redis разрешался мгновенно. Перенеси кэш на другой хост в том же swarm-overlay — разрешение всё ещё работало, но тонкий ручеёк запросов отваливался по таймауту под нагрузкой, никогда воспроизводимо. Команда винила базу, потом приложение, потом балансировщик. Настоящим виновником был MTU. Overlay оборачивал каждый пакет в VXLAN-заголовок, ужимая полезную нагрузку с 1500 до 1450 байт, но контейнеры приложения всё ещё анонсировали MSS в 1500 байт. Мелкие запросы влезали и проходили; любой ответ больше 1450 байт требовал фрагментации, которую путь не разрешал, поэтому молча застревал. «Перемежающаяся проблема базы» была налогом в 50-байтовый заголовок на виртуальной L2-сети, притворяющейся плоской LAN. Сеть container-to-container проста на одном хосте и полна протекающих абстракций между хостами — и знание, где абстракция протекает, и есть вся работа.

Один хост: изоляция моста по умолчанию

Два контейнера на одном пользовательском bridge достигают друг друга напрямую: их хостовые veth делят один мост, поэтому кадр от одного коммутируется прямо к другому на L2 — без маршрутизации, без NAT, без опубликованного порта. Ты подключаешься к http://other-container:port, и встроенный DNS разрешает имя в bridge-IP соседа. Это счастливый путь, и он действительно прост.

Ловушка — дефолтный мост (docker0), ведущий себя иначе, чем создаваемые тобой сети:

  • На дефолтном мосту контейнеры не получают DNS-обнаружения сервисов (лишь устаревший --link, deprecated), а исторически связь между контейнерами управляется настройкой icc (inter-container communication) демона. Дефолтный мост — для разового docker run, а не для service mesh приложения.
  • На пользовательском мосту (docker network create app или любая сеть сервиса в Compose) ты получаешь встроенный DNS, автоматическое разрешение имён и — критически — изоляцию по сети: контейнер достигает лишь контейнеров в сети, к которой подключён. Помести базу в back-end-сеть, к которой публичный proxy не может подключиться, и база недостижима из proxy на L3 — сегментация, навязанная тем, к какому мосту ты подключаешься, а не правилом фаервола, которое можно забыть.

Senior-паттерн — одна сеть на границу доверия: front-end-сеть для proxy и API, back-end-сеть для API и базы, причём база никогда не во front-end-сети. API стоит на обеих (контейнер может подключаться к нескольким сетям, получая один veth и один IP на сеть); больше ничто не пересекает границу. Это реальная сегментация из примитива моста, и она бесплатна.

Викторина

Ты поместил 'db' в back-end-сеть, а API — в front-end и back-end. Публичный proxy только во front-end. Почему proxy не может достучаться до 'db', хотя все три на одном хосте?

Почему это работает

Почему предпочесть несколько пользовательских сетей более простому «всё в одной сети с правилами фаервола»? Потому что топология моста и есть фаервол — и это фаервол, который нельзя забыть применить. У контейнера без veth на сети базы нет возможного пути к ней; нет правила, чтобы настроить неверно, нет порядка, чтобы перепутать, нечему дрейфовать. Правила фаервола — это список allow/deny поверх полной связности; сегментация сети убирает саму связность. Первое падает open, когда правило отсутствует; второе падает closed по построению.

Между хостами: overlay и его протечки

Если один хост — счастливый путь, то между хостами всё идёт тихо не так — и обычно только под нагрузкой.

Один мост живёт на одном хосте; контейнеры на разных хостах не могут его делить. Драйвер overlay строит виртуальную L2-сеть, охватывающую хосты, инкапсулируя Ethernet-кадры контейнеров в VXLAN (UDP, дефолтный порт 4789) и туннелируя их между IP хостов. Контейнерам это выглядит как одна плоская LAN — они продолжают говорить с соседями по имени и IP, будто локально. Распределённый control plane (встроенное хранилище Swarm или внешний KV вроде etcd/Consul в обычном Docker) делит, какой IP контейнера на каком хосте живёт, чтобы data plane знал, куда слать каждый туннелированный кадр.

Абстракция убедительна, и это опасность — вот где она протекает:

  • Накладные расходы MTU. VXLAN-заголовок стоит 50 байт, поэтому эффективный MTU overlay — 1450, а не 1500. Если контейнеры анонсируют MSS в 1500 байт, а путь не может фрагментировать, крупные пакеты застревают — баг из вступления. Задай MTU overlay верно, и налог станет невидимым; проигнорируй, и получишь худший вид сбоя — перемежающийся, привязанный к размеру нагрузки.
  • Область DNS всё ещё per-network. Overlay не уплощает разрешение имён глобально — контейнер разрешает лишь имена в overlay-сетях, которые делит. Между хостами не значит между сетями.
  • Подложка должна пропускать VXLAN. UDP 4789 между IP хостов должен быть открыт; security group или фаервол, блокирующий его, оставляет control plane здоровым (контейнеры выглядят подключёнными, имена разрешаются), пока data plane молча дропает — связность, которая выглядит настроенной, но никогда не несёт пакет.
  • Инкапсуляция стоит ресурсов. Каждый межхостовый пакет оборачивается и разворачивается; без NIC-offload это измеримый CPU и задержка против L2 на том же хосте — одна из причин со-локации чувствительных к задержке соседей.

Вместе эти четыре протечки задают поверхность отказа любого overlay-развёртывания: задай MTU, помни об области DNS, проверь транспорт подложки и избегай hairpin-само-ссылки. Пропусти любую — и получишь перемежающийся, привязанный к нагрузке сбой из вступления, где абстракция выглядит здоровой, а проблема невидима до поры.

Ещё есть hairpin NAT (NAT loopback): контейнер, пытающийся достучаться до себя через собственный опубликованный host-IP:port — частое, когда приложение использует один внешне-маршрутизируемый URL для всего. Пакет должен уйти на хост, получить DNAT и вернуться в исходный контейнер, и сработает ли этот круг — зависит от наличия userland-proxy и hairpin-маршрутизации. Это классическая загадка «каждый другой контейнер достигает моего опубликованного порта, а я свой — нет», и фикс — использовать внутреннее имя/IP для само-ссылки, а не опубликованный порт хоста.

Викторина

В Swarm-overlay имена контейнеров разрешаются между хостами и 'docker network inspect' показывает каждый контейнер подключённым, но межхостовый трафик не приходит. Трафик на том же хосте в порядке. Какая причина наиболее вероятна?

Вспомните перед уходом
  1. 01
    Сопоставь дефолтный мост, пользовательский мост и overlay для связи container-to-container.
  2. 02
    Перечисли четыре способа протечки overlay и диагноз для каждого.
Итог

Сеть container-to-container чисто делится на один хост (просто, с одним мощным приёмом) и между хостами (иллюзия с известными протечками). На одном хосте два контейнера на одном пользовательском мосту говорят напрямую на Layer 2 — их хостовые veth делят мост, поэтому кадры коммутируются точка-в-точку без NAT и без опубликованного порта, а встроенный DNS разрешает имена в bridge-IP. Дефолтный мост docker0 — исключение: без DNS-обнаружения сервисов и для разовых запусков, а не для архитектуры приложения. Senior-ход на одном хосте — изоляция по сети: поскольку контейнер достигает лишь соседей в сети, к которой подключён, ты помещаешь базу в back-end-сеть, к которой публичный proxy никогда не подключается, даёшь API стоять на front-end и back-end по одному veth и IP на сеть, и получаешь сегментацию уровня фаервола из самой топологии — нет правила, чтобы забыть, и нет связности, чтобы настроить неверно, ведь у несоединённого контейнера нет пути вовсе. Между хостами драйвер overlay подделывает плоский L2, инкапсулируя кадры контейнеров в VXLAN, туннелируемый по UDP 4789 между IP хостов, с распределённым control plane, отслеживающим, какой IP контейнера на каком хосте. Иллюзия убедительна, и это опасность, ведь она протекает на четырёх предсказуемых швах: 50-байтовый VXLAN-заголовок режет эффективный MTU до 1450, вызывая перемежающиеся застревания по размеру нагрузки, когда контейнеры всё ещё анонсируют MSS в 1500 байт; область DNS остаётся per-network, поэтому между хостами не значит между сетями; подложка должна реально пропускать UDP 4789, иначе control plane выглядит здоровым, пока data plane дропает каждый кадр; а hairpin-само-подключение через опубликованный порт хоста требует особой маршрутизации, которой само-ссылка на внутреннее имя избегает целиком. Мульти-хостовая сеть не магия и не загадка, как только дебажишь на протечке, а не на приложении. Теперь, когда видишь перемежающийся таймаут, где мелкие запросы проходят, а крупные нет, — первое движение: проверить MTU overlay, а не открывать профайлер.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 6 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.