Разворачивает FRR на a1/a2/a3 (площадка A) через Ansible вместо
Docker-лабы, для реальных ВМ. Provisioning ВМ (3 NIC: внешний +
2 приватных) — Terraform, вне рамок этой задачи; playbook создаёт
только dummy0 (netplan) и конфигурирует FRR.
- ansible/site-a.env — единственный источник site-A-специфичных
значений (ASN, anycast-префикс, router-id/BGP-соседи по узлам,
имена интерфейсов); роль frr_anycast полностью параметризована
- ansible/roles/frr_anycast — задачи, handlers (netplan apply перед
restart frr), шаблоны frr.conf.j2/daemons.j2/dummy0.netplan.yml.j2
- ansible/tests/render-check.sh — офлайн golden-diff проверка
рендера конфигов против уже провалидированных в Docker-лабе
frr/a{1,2,3}/ (ВМ ещё не подняты, функциональный тест недоступен)
- README.md — раздел «Развёртывание площадки A на ВМ (Ansible)»
- docs/ — план и summary изменения
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bc3HkMiDwooSyLBLoMKNyF
11 KiB
poc_frr_anycast
POC anycast-топологии на FRR: две площадки, eBGP full-mesh, ECMP-балансировка трафика клиентов площадки B к anycast-адресу площадки A.
- Дизайн (HLD): hld.md (в т.ч. Mermaid-диаграмма топологии)
- История изменений: docs/ — на каждое изменение отдельный план внедрения и summary.
Lab-окружение (Docker Compose)
Топология из hld.md поднимается локально в Docker Compose: 5 FRR-роутеров (a1/a2/a3 — площадка A, b1/b2 — площадка B) + 6 клиентских контейнеров площадки B. Каждый линк HLD — отдельная Docker-сеть с фиксированной адресацией, 1:1 соответствующая схеме из hld.md.
Детали архитектуры и обоснование решений: docs/2026-09-03-anycast-lab-plan.md, результаты — docs/2026-09-03-anycast-lab-summary.md.
Запуск
docker compose up -d # поднять топологию (13 контейнеров)
./scripts/verify.sh # проверить BGP-сессии и ECMP-маршрут до 10.200.200.1/32
./scripts/traffic-test.sh [client] [count] # прогнать трафик к anycast, увидеть распределение по A1/A2/A3
docker compose down # разобрать окружение
Пример: ./scripts/traffic-test.sh c1 30 — 30 запросов с клиента c1 (площадка B1) к anycast-адресу с варьированием src-port; ответ каждого anycast-узла (a1/a2/a3) содержит его hostname, что показывает распределение трафика.
Структура
hld.md HLD-дизайн топологии
docker-compose.yml 13 контейнеров, 8 сетей (1 на линк)
frr/{a1,a2,a3,b1,b2}/ конфиги FRR (daemons, frr.conf, vtysh.conf)
entrypoints/ создание dummy0 + anycast-responder (A), маршрут по умолчанию (клиенты)
scripts/ verify.sh, traffic-test.sh
docs/ план внедрения + summary по каждому изменению
Проверка окружения
Всё это уже автоматизировано в ./scripts/verify.sh (BGP + маршруты) и ./scripts/traffic-test.sh (трафик), но ниже — те же проверки вручную, командами на самих узлах.
На маршрутизаторах (vtysh)
Выполняются как docker compose exec -T <node> vtysh -c "<command>" или интерактивно: docker compose exec <node> vtysh.
| Команда | Где | Что должно быть в норме |
|---|---|---|
show bgp summary |
все 5 узлов | все соседи в состоянии Established (число вместо Idle/Active в колонке State/PfxRcd) — 2 соседа на a1/a2/a3, 3 на b1/b2 |
show ip route 10.200.200.1/32 |
b1, b2 | 3 записи * <ip>, via ethN, weight 1 — ECMP-маршрут до anycast через a1/a2/a3 |
show bgp ipv4 unicast 10.200.200.1/32 |
b1, b2 | тот же префикс в BGP-таблице, 3 валидных пути, Local AS 65001 у всех |
show ip bgp neighbor <ip> advertised-routes |
a1/a2/a3 (<ip> — сосед в сторону B) |
среди анонсируемых — 10.200.200.1/32 |
show ip bgp neighbor <ip> advertised-routes |
b1, b2 (<ip> — сосед в сторону A) |
среди анонсируемых — 10.100.1.0/24 (b1) / 10.100.2.0/24 (b2) |
show interface brief |
любой | все интерфейсы up, IP соответствуют hld.md |
Пример:
docker compose exec -T b1 vtysh -c "show bgp summary"
docker compose exec -T b1 vtysh -c "show ip route 10.200.200.1/32"
docker compose exec -T a1 vtysh -c "show ip bgp neighbor 10.0.1.2 advertised-routes"
На клиентах (площадка B, доверенная сеть)
Выполняются как docker compose exec -T <client> <command>, где <client> — один из c1…c6.
| Команда | Что должно быть в норме |
|---|---|
ping -c 4 10.200.200.1 |
0% packet loss, ответы приходят с ttl=63 (1 транзитный хоп — B-роутер) |
traceroute -n 10.200.200.1 |
2 хопа: локальный B-роутер (10.100.1.1 / 10.100.2.1), затем сразу 10.200.200.1 |
curl -s http://10.200.200.1/ |
в ответе — hostname одного из a1/a2/a3 (мини-HTTP на dummy0, см. entrypoints/a-entrypoint.sh) |
серия curl с разным --local-port (см. scripts/traffic-test.sh) |
ответы приходят от разных anycast-узлов — подтверждает, что ECMP реально распределяет трафик по 5-tuple, а не всегда уходит в один и тот же узел |
Пример:
docker compose exec -T c1 ping -c 4 10.200.200.1
docker compose exec -T c1 traceroute -n 10.200.200.1
docker compose exec -T c1 curl -s http://10.200.200.1/
./scripts/traffic-test.sh c1 30 # готовый прогон с разбивкой по узлам
Если curl/ping не проходят — сначала проверить BGP (show bgp summary на затронутых узлах), затем ECMP-маршрут (show ip route 10.200.200.1/32 на b1/b2).
Важные технические нюансы
- Линки между A и B заведены как
/29(не/30, как в hld.md буквально) — Docker резервирует первый адрес подсети под gateway моста даже дляinternal: true-сетей, поэтому реальным peering-адресам (.1/.2, как в hld.md) нужно место рядом с ним. IP-адресация роутеров и логика BGP при этом полностью соответствуют hld.md. - ECMP-балансировка на b1/b2 требует
net.ipv4.fib_multipath_hash_policy=1(вdocker-compose.yml) — по умолчанию (0) Linux хеширует next-hop только по src/dst IP, без учёта портов, из-за чего один клиент всегда попадал бы в один и тот же anycast-узел.
Развёртывание площадки A на ВМ (Ansible)
ansible/ — playbook для конфигурации FRR на реальных ВМ площадки A (a1/a2/a3), продолжение того же HLD, но не в Docker, а «в проде». Provisioning ВМ (создание, 3 сетевых интерфейса: 1 внешний/управляющий + 2 приватных к B1/B2) выполняется отдельным Terraform-сценарием (вне рамок этого репозитория) — приватные интерфейсы уже сконфигурированы к моменту запуска playbook; Ansible создаёт dummy0 (anycast) и разворачивает FRR.
Все специфичные для площадки A значения (ASN, anycast-префикс, router-id и BGP-соседи каждой ноды, имена интерфейсов, SSH-доступ) вынесены в единый файл переменных окружения ansible/site-a.env — сама роль frr_anycast полностью параметризована и не содержит захардкоженных адресов/ASN. frr.conf.j2 при рендере воспроизводит уже проверенную в Docker-лабе BGP-конфигурацию (frr/a{1,2,3}/frr.conf), за вычетом блока interface dummy0 — в VM-варианте этот адрес назначается через netplan (dummy0.netplan.yml.j2), а не через frr.conf.
Детали: docs/2026-09-03-ansible-site-a-plan.md / docs/2026-09-03-ansible-site-a-summary.md.
Структура
ansible/
├── site-a.env источник всех site-A-специфичных значений
├── run.sh source site-a.env + ansible-playbook
├── inventory/site-a.yml a1/a2/a3, ansible_host из lookup('env', 'A{N}_MGMT_IP')
├── group_vars/site_a.yml site-wide переменные (ASN, anycast-префикс, интерфейсы)
├── host_vars/{a1,a2,a3}.yml per-node переменные (router-id, BGP-соседи)
├── site-a.yml playbook (role: frr_anycast)
├── roles/frr_anycast/ задачи, handlers, шаблоны (daemons.j2, frr.conf.j2, dummy0.netplan.yml.j2)
└── tests/render-check.sh офлайн-проверка шаблонов без реальных ВМ (см. ниже)
Запуск
cd ansible
./run.sh site-a.yml # применить конфигурацию к a1/a2/a3
./run.sh site-a.yml --check --diff # сухой прогон
Если ansible-playbook не установлен в системе — поставить локально в venv, не трогая системный Python: python3 -m venv venv && venv/bin/pip install ansible (каталог venv/ в .gitignore, не коммитится); run.sh сам подхватит venv/bin/ansible-playbook, если он есть.
Проверка без живых ВМ
На момент подготовки playbook ВМ площадки A ещё не подняты (Terraform-провижининг — отдельный шаг), поэтому функциональная проверка (vtysh, реальный apply) не выполнялась и не входит в текущую поставку. Доступна и выполнена офлайн-проверка:
ansible/run.sh site-a.yml --syntax-check # синтаксис
ansible/tests/render-check.sh # golden-diff: рендер frr.conf/daemons для a1/a2/a3
# сравнивается с уже работавшими в Docker-лабе frr/a{1,2,3}/
После того как Terraform поднимет ВМ — реальный прогон ./run.sh site-a.yml, затем те же vtysh-команды, что и в разделе «Проверка окружения» выше (заменить docker compose exec -T <node> vtysh ... на ssh <node> vtysh ... или ansible site_a -m command -a "vtysh -c 'show bgp summary'").