9.9 KiB
Шаг 3 — план внедрения: листенер в приватном сегменте
Дата: 2026-08-17 Предыдущий шаг: STEP2_SUMMARY.md Итоги: STEP3_SUMMARY.md
Задача
Поддержать листенер балансировщика в приватном сегменте — в одном сегменте или во всех сразу — при условиях:
- листенер работает как публичный: принимает на порту 80 и транслирует на порт бэкенда 8080;
- бэкенд может стоять в одном сегменте с клиентом, отправившим запрос;
- в ОС клиента и бэкенда не вносится ничего — ни адресов, ни маршрутов, ни интерфейсов;
- адресное пространство подсети задаёт заказчик, дробить её нельзя;
- трафик внутри сегмента не блокируется;
- единственный уровень влияния — 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 — член пула.
Порядок работ
lb/topology.env— порты и адреса участников сегментов, VIP приватных листенеров, переключательPRIV_LISTENERS.scripts/attach-segment.sh— veth из netns контейнера вbr-lb, адрес, MAC и маршруты назначаются снаружи (эмуляция гипервизора и DHCP).docker-compose.yml: участники сегментов переводятся наnetwork_mode: none; добавляются клиентыcli2иcli3.lb/pipeline.sh— таблицы 0/1/5/25: сегментация, обучение, коммутация. Проверка связности сегмента до всякой балансировки.- Листенер: таблицы 10/12 и обратная трансляция в таблице 24.
lb/lbctl.sh—traceпо приватному листенеру,fdb.scripts/verify.sh— новая секция и регрессия существующих проверок.- Документация:
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.