12 KiB
Шаг 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. Оба пути живут на одном пуле и одной таблице слотов, и клиент их не
различает.
Отклонения от плана
-
load:0->NXM_OF_IN_PORT[]не понадобился. План предполагал hairpin — выдачу в порт входа — и сбросin_port, чтобы OVS не подавил вывод. После перевода каждой ВМ на собственный порт hairpin исчез: клиент и бэкенд входят и выходят через разные порты. Риск снят самой архитектурой. -
Отдельная сеть для egress не потребовалась. План предусматривал служебную docker-сеть под default-маршрут. Оказалось достаточно оставить
p2/p3аплинками сегмента: бэкенд адресует кадр MAC-у шлюза Docker, мост его коммутирует, и профиль A сохраняется в прежнем виде. -
Появилась таблица 1. В плане классификация оставалась в таблице 0. Но таблица 0 занята определением сегмента и обучением (два действия
learnна каждый порт), поэтому классификация вынесена отдельно — иначе правила пришлось бы дублировать на каждый порт сегмента. -
makeбольше не требуетsudo. Под root его в системе может не быть; цели подготовки хоста теперь подставляютsudoтолько при запуске не от root. -
Проверки 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
- Stateless-датапас для обоих путей: явный un-DNAT вместо
ct(nat). На коммутируемом пути это снимет последнюю зависимость от состояния. - Control plane: модель
Load_Balancer → Listener → Pool → Memberв OVSDB вместоtopology.env. Сейчас листенер, сегмент и состав пула описаны в трёх местах, а добавление ВМ требует правкиtopology.env,hc-config.shи запускаattach-segment.sh. - Старение FDB и защита от переполнения — сейчас записи живут 300 с без ограничения на число MAC в сегменте.
- Второй узел LB: кворум наблюдений, генерации пулов, сравнение дайджестов.