Add FRR anycast lab: HLD, containerized topology, docs
Реализует HLD-дизайн (hld.md) в виде развёртываемого Docker Compose окружения: 5 FRR-роутеров (A1-A3 anycast 10.200.200.1/32 на AS 65001, B1/B2 на AS 65002/65003) + 6 клиентских контейнеров, ECMP-балансировка трафика клиентов площадки B к anycast-адресу площадки A. - docker-compose.yml, frr/*, entrypoints/* — топология и конфиги FRR - scripts/verify.sh, scripts/traffic-test.sh — проверка BGP/ECMP и трафика - hld.md — HLD-дизайн + Mermaid-диаграмма топологии - README.md — запуск, структура, команды проверки на роутерах и клиентах - docs/ — планы внедрения и summary по каждому изменению Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bc3HkMiDwooSyLBLoMKNyF
This commit is contained in:
commit
eaa577e6b7
30 files changed
+1035
No files matched your search
@@ -0,0 +1,40 @@
|
||||
# Summary: контейнерный POC anycast-топологии FRR
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
**План:** [2026-09-03-anycast-lab-plan.md](2026-09-03-anycast-lab-plan.md)
|
||||
|
||||
## Что сделано
|
||||
|
||||
Топология из `hld.md` полностью развёрнута в Docker Compose (`docker-compose.yml`): 5 FRR-контейнеров (a1/a2/a3 — AS 65001, площадка A; b1/b2 — AS 65002/65003, площадка B) + 6 клиентских контейнеров (`nicolaka/netshoot`) на площадке B. 8 Docker-сетей воспроизводят все линки HLD (6 межплощадочных + 2 клиентских сегмента).
|
||||
|
||||
Созданные файлы: `docker-compose.yml`, `frr/{a1,a2,a3,b1,b2}/{daemons,frr.conf,vtysh.conf}`, `entrypoints/{a-entrypoint.sh,b-entrypoint.sh,client-entrypoint.sh}`, `scripts/{verify.sh,traffic-test.sh}`, `README.md`.
|
||||
|
||||
## Отклонения от первоначального плана (обнаружены и устранены в процессе)
|
||||
|
||||
1. **`/30` → `/29` на межплощадочных линках.** Docker всегда резервирует первый usable-адрес подсети под gateway моста, даже при `internal: true`. При буквальном `/30` (только 2 usable-адреса) это конфликтовало с адресом `.1`, который нужен был роутеру A (`Error: Address already in use`). Решение — расширить линковые подсети до `/29` и явно вынести gateway на неиспользуемый адрес (`.6`); адресация роутеров (`.1`/`.2`) осталась как в hld.md. Аналогично для клиентских `/24`-сетей gateway вынесен на `.254`, чтобы не конфликтовать с адресом B-роутера (`.1`).
|
||||
2. **`sysctl -w` в entrypoint не работал** (`Read-only file system`) — `/proc/sys` внутри контейнера доступен на запись только для sysctl'ов, заданных декларативно через Docker (`sysctls:` в compose), а не вызовом `sysctl` из процесса внутри контейнера. Перенесли `net.ipv4.ip_forward=1` в `docker-compose.yml`.
|
||||
3. **`cap_sys_admin` требуется FRR для namespace-операций** — с `cap_add: [NET_ADMIN, NET_RAW]` zebra/bgpd не стартовали (`privs_init: initial cap_set_proc failed`). Добавили `SYS_ADMIN` в `cap_add`.
|
||||
4. **ECMP не реагировал на src-port.** С дефолтной политикой ядра `net.ipv4.fib_multipath_hash_policy=0` next-hop хешируется только по src/dst IP — один клиент всегда получал один и тот же anycast-узел, что противоречило описанной в hld.md 5-tuple-логике. Установили `net.ipv4.fib_multipath_hash_policy=1` на b1/b2.
|
||||
5. **Убраны `interface ethN` из frr.conf.** Раз Docker Compose уже назначает статические IP через `ipv4_address`, а zebra сам подхватывает существующие адреса интерфейсов, ручное дублирование адресов в frr.conf было источником риска (несовпадение имени интерфейса `ethN` с ожидаемым линком). Оставлен только `dummy0` (единственный интерфейс, которого нет "из коробки" у Docker).
|
||||
|
||||
## Результаты проверки
|
||||
|
||||
- **BGP:** все 10 eBGP-сессий (a1/a2/a3 × b1/b2) в состоянии Established.
|
||||
- **ECMP:** у b1 и у b2 — по 3 равнозначных пути (weight 1) до `10.200.200.1/32`, через a1/a2/a3 соответственно.
|
||||
- **Связность клиент → anycast:** `ping`/`traceroute`/`curl` с любого клиента (c1…c6) до `10.200.200.1` проходят.
|
||||
- **Распределение трафика (`scripts/traffic-test.sh`):** серии запросов с варьированием src-port показывают ответы от всех трёх anycast-узлов в примерно равной пропорции, например с `c4` (30 запросов): `a1: 13, a2: 2, a3: 15`; с `c2` (24 запроса): `a1: 10, a2: 7, a3: 7`. Ответ каждого узла содержит его hostname (мини HTTP-responder на dummy0), что визуально подтверждает, какой именно anycast-узел обслужил запрос.
|
||||
- **Идемпотентность:** `docker compose down && docker compose up -d` поднимает окружение с нуля без ручного вмешательства, BGP сходится за секунды.
|
||||
|
||||
## Как повторить
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
./scripts/verify.sh
|
||||
./scripts/traffic-test.sh c1 30
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## Дальнейшие возможные шаги (не выполнялись, вне рамок задачи)
|
||||
|
||||
- Симуляция отказа одного из A-узлов (`docker compose stop a2`) для проверки конвергенции ECMP на оставшихся 2 путях.
|
||||
- Нагрузочный тест через `iperf3` (уже есть в образе `nicolaka/netshoot`) вместо HTTP-responder.
|
||||
Reference in new issue
Block a user