Добавлена локальная локальная балансировка
This commit is contained in:
1 parent
afd4f057be
commit
d80a6c44b0
17 files changed
+1628
-369
No files matched your search
@@ -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`.
|
||||
Reference in new issue
Block a user