Добавлена локальная локальная балансировка

This commit is contained in:
ayurishchev committed 2026-08-17 16:59:16 +03:00
1 parent afd4f057be
commit d80a6c44b0
17 files changed
+1628 -369

No files matched your search

+149
View File
@@ -0,0 +1,149 @@
# Шаг 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`.