# Шаг 3 — план внедрения: листенер в приватном сегменте **Дата:** 2026-08-17 **Предыдущий шаг:** [STEP2_SUMMARY.md](STEP2_SUMMARY.md) **Итоги:** [STEP3_SUMMARY.md](STEP3_SUMMARY.md) ## Задача Поддержать листенер балансировщика **в приватном сегменте** — в одном сегменте или во всех сразу — при условиях: 1. листенер работает как публичный: принимает на порту 80 и транслирует на порт бэкенда 8080; 2. бэкенд может стоять в одном сегменте с клиентом, отправившим запрос; 3. в ОС клиента и бэкенда не вносится ничего — ни адресов, ни маршрутов, ни интерфейсов; 4. адресное пространство подсети задаёт заказчик, дробить её нельзя; 5. трафик внутри сегмента не блокируется; 6. единственный уровень влияния — OVS как data plane SDN и сама нода балансировщика. Ключевое требование: на бэкенд приходит пакет с исходным IP клиента. ## Почему прежняя схема не годится Клиент и бэкенд — L2-соседи в одной подсети. Ответ бэкенда уходит клиенту напрямую, минуя балансировщик: снять DNAT негде, клиент получает пакет от `10.20.0.2:8080` вместо `10.20.0.100:80` и рвёт соединение. Условия отсекают все обходные пути: | Вариант | Почему не подходит | |---|---| | SNAT | уничтожает сохранение IP клиента | | DSR (VIP на `lo` бэкенда) | нарушает условие 3, не транслирует порт (условие 1) | | Изоляция портов сегмента (private VLAN) | нарушает условие 5, неприменимо к ВМ | | Дробление подсети маршрутами `/25` на бэкенде | нарушает условия 3 и 4 | Остаётся единственная предпосылка, совпадающая с условием 6: **порты клиента и бэкенда — порты OVS**. Тогда кадр «бэкенд → клиент-сосед» проходит через OpenFlow-таблицы даже при прямом L2-обмене, и обратную трансляцию есть где выполнить. В целевой среде это выполняется по построению: ВМ подключены к vSwitch. Текущий стенд этому не соответствует — приватный сегмент коммутирует docker-бридж `hpnn-p2`, а `br-lb` подключён к нему одним портом. Шаг 3 приводит стенд к целевой модели. ## Решение `br-lb` становится **коммутатором приватного сегмента** и выполняет трансляцию на пути кадра. Для внутрисегментного трафика нет ни маршрутизации, ни `dec_ttl`: с точки зрения ВМ клиент и бэкенд остаются L2-соседями. **Прямой путь.** Клиент резолвит VIP через ARP-респондер OVS и шлёт кадр на MAC шлюза сегмента. Таблица листенера кладёт сессию в слот, таблица DNAT переписывает `VIP:80 → member:8080`, `src` не трогает, заменяет dst MAC на MAC члена и отдаёт кадр в коммутацию. **Обратный путь.** Бэкенд отвечает соседу напрямую: `src=member:8080`, `dst=клиент`. Кадр приходит на порт OVS, где `ct(nat)` снимает трансляцию — `src` возвращается к `VIP:80` — и кадр коммутируется в порт клиента. **Прочий трафик сегмента** (`be↔be`, клиент к бэкенду напрямую, egress к docker-шлюзу, ARP) коммутируется обычным образом, без трансляции. ## Топология после изменения Каждая ВМ сегмента — отдельный порт `br-lb`. Порты `p2`/`p3` остаются как аплинки сегмента к docker-шлюзу: через них идёт egress бэкендов (профиль A). | Сегмент | Порт | ofport | Адрес | |---|---|---|---| | priv2 `10.20.0.0/24` | `p2` (аплинк) | 2 | — | | | `hcif-p2` | 4 | `10.20.0.253` | | | `be1` | 10 | `10.20.0.2` | | | `be3` | 11 | `10.20.0.3` | | | `cli2` | 12 | `10.20.0.10` | | priv3 `10.30.0.0/24` | `p3` (аплинк) | 3 | — | | | `hcif-p3` | 5 | `10.30.0.253` | | | `be2` | 20 | `10.30.0.2` | | | `be4` | 21 | `10.30.0.3` | | | `cli3` | 22 | `10.30.0.10` | | pub `192.168.5.0/24` | `pub0` | 1 | маршрутизируемый, без изменений | Шлюз сегмента `10.20.0.1` и VIP `10.20.0.100` отвечают одним MAC — `$P2_MAC`. Это и есть признак, по которому пайплайн отличает L3-трафик (адресован маршрутизатору) от L2-трафика сегмента. ## Карта таблиц Новые — 1, 24, 25; остальные сохраняют назначение шага 2. | Таблица | Назначение | |---|---| | 0 | определение сегмента по `in_port` (`reg0`), обучение MAC и adjacency | | 1 | классификация: ARP, ICMP, листенер, L3-трафик к шлюзу, коммутация | | 5 | ARP-респондер (адреса узла и VIP), затем коммутация | | 6 | ICMP echo-респондер | | 10 | листенеры: публичный и приватные | | 11 | таблица слотов (заливает `hcd`, не меняется) | | 12 | применение члена пула: DNAT + L2-выдача либо DNAT + маршрутизация | | 15/16 | обратный путь L3 (публичный и межсегментный) | | 20/21 | маршрутизация и adjacency | | **24** | обратная трансляция коммутируемого трафика сегмента | | **25** | L2-коммутация сегмента: FDB, broadcast и unknown flood | Регистры: `reg0` — номер сегмента, `reg1` — слот, `reg2` — член пула. ## Порядок работ 1. `lb/topology.env` — порты и адреса участников сегментов, VIP приватных листенеров, переключатель `PRIV_LISTENERS`. 2. `scripts/attach-segment.sh` — veth из netns контейнера в `br-lb`, адрес, MAC и маршруты назначаются снаружи (эмуляция гипервизора и DHCP). `docker-compose.yml`: участники сегментов переводятся на `network_mode: none`; добавляются клиенты `cli2` и `cli3`. 3. `lb/pipeline.sh` — таблицы 0/1/5/25: сегментация, обучение, коммутация. Проверка связности сегмента до всякой балансировки. 4. Листенер: таблицы 10/12 и обратная трансляция в таблице 24. 5. `lb/lbctl.sh` — `trace` по приватному листенеру, `fdb`. 6. `scripts/verify.sh` — новая секция и регрессия существующих проверок. 7. Документация: `docs/STEP3_SUMMARY.md`, `README.md`, `docs/PRIVATE_LB_DATAFLOW.md`, `docs/MANUAL_TEST_PLAN.md`. ## Критерии приёмки - `curl http://10.20.0.100/` из `cli2` обслуживается всеми четырьмя членами; - во всех ответах `client=10.20.0.10` — исходный IP клиента; - `served=10.20.0.2:8080` — трансляция порта 80 → 8080 выполняется; - TTL ответа от VIP равен TTL прямого ответа соседа по сегменту — путь остался коммутируемым; - внутрисегментная связность не пострадала: `cli2 → be1:8080` напрямую, `be1 → be3:8080`, egress бэкенда мимо балансировщика; - `PRIV_LISTENERS="p2"` выключает листенер в priv3, не затрагивая связность; - все проверки шага 2 проходят. ## Риски - **`br-lb` получает функцию L2-коммутатора** — обучение, flood, изоляция сегментов. Новый класс правил и главный источник ошибок шага, поэтому этап 3 проверяется до подключения балансировки. - **Хрупкость стенда:** veth, добавленные вручную, теряются при пересоздании контейнера. `make attach` восстанавливает; в реальной среде порты создаёт гипервизор. - **Обратная трансляция опирается на `ct`**, как и прежде. Stateless-вариант — задел следующего шага. - **Нагрузка сегмента ложится на балансировщик**: весь внутрисегментный трафик проходит через `br-lb`.