Files

12 KiB
Raw Permalink Blame History

Шаг 3 — итоги: листенер в приватном сегменте

Дата: 2026-08-17 Статус: выполнено, проверено после холодного перезапуска (86 из 86 проверок) План: STEP3_IMPLEMENTATION_PLAN.md Предыдущий шаг: STEP2_SUMMARY.md

Что сделано

Балансировщик получил листенеры в приватных сегментах — 10.20.0.100:80 и 10.30.0.100:80 — и научился обслуживать клиента, который стоит в одном сегменте с бэкендами. Исходный IP клиента сохраняется, порт транслируется 80 → 8080, в ОС виртуальных машин не настраивается ничего.

  • br-lb стал коммутатором приватного сегмента. Каждая ВМ подключена отдельным портом (scripts/attach-segment.sh), порты p2/p3 остались аплинками сегмента к шлюзу Docker. Появились таблицы 0 (сегментация и обучение), 25 (FDB, broadcast и unknown flood) и 1 (классификация).
  • Обратная трансляция коммутируемого трафика — таблица 24. Ответ бэкенда клиенту-соседу уходит по L2 напрямую, но проходит через мост, где ct(nat) возвращает источник к VIP:80.
  • Выдача на члена своего сегмента — без маршрутизации. Таблица 12 при совпадении сегмента клиента и члена меняет только MAC назначения и отдаёт кадр в коммутацию: dec_ttl не выполняется, клиент и бэкенд остаются L2-соседями.
  • Пул общий с публичным листенером — та же таблица слотов, тот же pool_id, тот же дайджест 0a16713c5a9eb94d. Демон hcd не изменён ни строкой.
  • Переключатель PRIV_LISTENERS в topology.env: пусто — приватных листенеров нет, "p2" — один сегмент, "p2 p3" — оба.
  • Клиенты сегментов cli2 (10.20.0.10) и cli3 (10.30.0.10) — ВМ без единого маршрута в таблице маршрутизации.

Почему именно так

Клиент и бэкенд — L2-соседи, поэтому ответ бэкенда уходит клиенту напрямую. Снять DNAT можно только там, где этот кадр виден. Остальные пути отпадали по условиям задачи:

Вариант Почему отвергнут
SNAT уничтожает сохранение IP клиента
DSR, VIP на lo бэкенда требует настройки ОС ВМ, не транслирует порт
Изоляция портов сегмента блокирует внутрисегментный трафик, неприменима к ВМ
Дробление подсети маршрутами /25 вмешательство в ОС ВМ и в адресацию заказчика

Осталась единственная предпосылка, совпадающая с зоной влияния SDN: порты ВМ — порты OVS. В целевой среде это выполняется по построению; стенд приведён к той же модели.

Результаты проверок

Проверка Результат
ВМ сегментов подключены портами OVS 6 портов: be1, be3, cli2, be2, be4, cli3
ping 10.20.0.100 из cli2 отвечают ARP- и ICMP-респондеры OVS
Балансировка из приватного клиента 24 запроса, задействованы все четыре члена
IP клиента на бэкенде client=10.20.0.10 во всех ответах
Трансляция порта served=10.20.0.2:8080 при листенере на 80
Член в сегменте клиента обслуживает запросы, ответ транслируется таблицей 24
TTL ответа от VIP 64 для членов своего сегмента, 63 — для членов другого
В ОС клиента нет маршрутов ip route содержит только connected-запись
Внутрисегментная связность cli2 → be1 напрямую, be1 → be3, шлюз Docker — всё работает
Отказ члена под нагрузкой из cli2 слоты разошлись 341/342/341, 2 ошибки в окне обнаружения
Профиль A egress бэкенда адресован шлюзу Docker, не маршрутизатору сегмента
PRIV_LISTENERS="p2" листенер в priv3 выключен (счётчик drop растёт), связность цела
Регрессия шагов 1–2 публичный листенер, транзит, health-check, fail-close, дренаж — без изменений

Главное измерение: TTL как признак пути

Ответы от be1/be3 (сегмент клиента) приходят с TTL 64, от be2/be4 (другой сегмент) — с TTL 63. Это прямое доказательство того, что для соседей по сегменту балансировщик не стал L3-хопом: он подменил MAC назначения, оттранслировал адрес и отдал кадр коммутацией. С точки зрения ВМ ничего не изменилось — она общается с соседом по подсети, как и раньше.

Для членов из другого сегмента работает прежний маршрутизируемый путь с dec_ttl. Оба пути живут на одном пуле и одной таблице слотов, и клиент их не различает.

Отклонения от плана

  1. load:0->NXM_OF_IN_PORT[] не понадобился. План предполагал hairpin — выдачу в порт входа — и сброс in_port, чтобы OVS не подавил вывод. После перевода каждой ВМ на собственный порт hairpin исчез: клиент и бэкенд входят и выходят через разные порты. Риск снят самой архитектурой.

  2. Отдельная сеть для egress не потребовалась. План предусматривал служебную docker-сеть под default-маршрут. Оказалось достаточно оставить p2/p3 аплинками сегмента: бэкенд адресует кадр MAC-у шлюза Docker, мост его коммутирует, и профиль A сохраняется в прежнем виде.

  3. Появилась таблица 1. В плане классификация оставалась в таблице 0. Но таблица 0 занята определением сегмента и обучением (два действия learn на каждый порт), поэтому классификация вынесена отдельно — иначе правила пришлось бы дублировать на каждый порт сегмента.

  4. make больше не требует sudo. Под root его в системе может не быть; цели подготовки хоста теперь подставляют sudo только при запуске не от root.

  5. Проверки 7 и 9 в verify.sh переформулированы. Обе слушали аплинк p2, через который health-пробы и egress больше не проходят: пробы идут коммутацией на порт ВМ, а признаком профиля A стал MAC назначения (шлюз Docker, а не маршрутизатор сегмента), а не отсутствие трафика на p2.

Известные ограничения

  • Предпосылка архитектуры: порты ВМ должны быть портами OVS. Если между клиентом и бэкендом окажется коммутатор, который SDN не контролирует, кейс не решается ничем, кроме SNAT.
  • Хрупкость стенда: veth, созданные attach-segment.sh, исчезают при пересоздании контейнера — нужен make attach. В целевой среде порты создаёт гипервизор.
  • Весь внутрисегментный трафик проходит через br-lb — пропускная способность узла становится пропускной способностью сегмента. Замер k6 не выполнен: в системе нет клиента k6; цель для сравнения — http://10.20.0.100/ против http://192.168.5.21/.
  • Обратная трансляция опирается на ct — как и прежде. Stateless-вариант (nw_src=member, tp_src=8080 → VIP:80) остаётся заделом: в сегменте он потребует аккуратного исключения трафика, который бэкенд сам инициирует с порта 8080.
  • Unicast ARP внутри сегмента рассылается флудом, если MAC ещё не выучен — обычное поведение обучающегося коммутатора, но без таймера старения записей короче 300 с.
  • Приватный листенер только TCP и только IPv4, как и публичный. UDP- и L3-листенеров нет.
  • Кворум, control plane в OVSDB, ICMP-ошибки и фрагменты — по-прежнему вне рамок.

Задел на шаг 4

  1. Stateless-датапас для обоих путей: явный un-DNAT вместо ct(nat). На коммутируемом пути это снимет последнюю зависимость от состояния.
  2. Control plane: модель Load_Balancer → Listener → Pool → Member в OVSDB вместо topology.env. Сейчас листенер, сегмент и состав пула описаны в трёх местах, а добавление ВМ требует правки topology.env, hc-config.sh и запуска attach-segment.sh.
  3. Старение FDB и защита от переполнения — сейчас записи живут 300 с без ограничения на число MAC в сегменте.
  4. Второй узел LB: кворум наблюдений, генерации пулов, сравнение дайджестов.