Files
hpnn-proto/docs/STEP3_IMPLEMENTATION_PLAN.md

9.9 KiB
Raw Permalink Blame History

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

Дата: 2026-08-17 Предыдущий шаг: STEP2_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.