Commit 0d5c64d9 authored by Vitaly Lipatov's avatar Vitaly Lipatov

memory: route-health fix + vici-socket lesson, igw changelog

Mark lesson_route_health_flush_hang resolved (bulk flush + 3m window shipped, deployed, timer re-enabled). Add lesson_vici_socket_acl_for_telegraf: the ikev2 churn root cause (check-vpn-status.sh as telegraf can't read the vici socket -> false vpn_status=0), why the sudoers approach is blocked on ALT (sudo is 4710 root:wheel), and the vici-socket ACL fix. Add objects/igw.md changelog. Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
parent 8144abe5
---
name: lesson_route_health_flush_hang
description: "ИСТОРИЯ (исправлено 01.08.2026): route-health.sh на igw вешал ВЕСЬ мониторинг шлюзов per-route flush 121k v6-маршрутов + окно 1m ложный dead feedback-loop. Фикс: bulk flush + окно 3m. Отдельный корень ikev2 churn см. [[lesson_vici_socket_acl_for_telegraf]]"
metadata:
node_type: memory
type: lesson
modified: 2026-08-01T19:28:55.619Z
originSessionId: c2840474-3dab-4156-9d46-6f9a238fcfe2
---
`router/route-health.sh` на igw (CT, timer `route-health.timer` OnUnitActiveSec=20s). Симптом (2026-08-01): все шлюзы в `health.json` замёржены на стейл-состоянии, gre.hetzner2 и ikev2.vdska показывают «dead» хотя оба реально здоровы. `route-health.service` висит в одной инвокации (activating 20+ мин, load avg 14+).
**Корень — три проблемы в скрипте (вместе = self-reinforcing collapse):**
1. **`flush_gw_routes()` (стр. 275) делает per-route delete**: `$ipcmd route show table "$t" | grep "via $dead_gw" | while read route; do … ip route del …`. Для шлюза с массой маршрутов (ikev2.vdska.v6 держал 121 913 v6-маршрутов в **общей** table 202) это ~20 мин тысяч netlink `RTM_DELROUTE`. Комментарий на стр. 283 сам признаёт: должно быть по per-gateway таблицам и один `ip route flush table`. На общей таблице это O(n) катастрофа.
2. **Узкое окно ping-запроса (стр. 50):** `SELECT last(percent_packet_loss) FROM ping WHERE time > now() - 1m GROUP BY gateway` + `curl --max-time 5`. Под нагрузкой (см. п.3) telegraf на egress-контейнерах недополучает CPU → ping-точки пишутся реже 1m → запрос возвращает **пусто**`get_health` для tunnel_gw: `loss=""`**«dead»** (ложный). Прямой запрос с окном 2m данные видит, с 1m — нет. 1m слишком тесно для мониторинга, который как раз нужен под нагрузкой.
3. **Feedback loop:** flush-шторм (тысячи netlock-опов) → load avg 14+ на igw/border → telegraf-агенты голодают → ping стареет >1m → ложный dead → **ещё flush** → ещё load. Перезапуск сервиса не лечит: свежий прогон тут же ловит ложный dead (stale ping) и возобновляет flush.
**Доп.:** без таймаута на per-gateway `ip`-операции одно зависшее `ip route show` (netlock contention от собственного flush'а) глушит инвокацию навсегда → `OnUnitActiveSec=20s` не перепланируется (сервис не exiting) → таймер NEXT="-".
**Что сделал 01.08.2026:** `systemctl stop route-health.timer` + `disable`, `systemctl kill -s SIGKILL route-health.service` — остановил разрушительный flush (маршруты ikev2.vdska.v6 сохранены, ~37k, и правильно — шлюз здоров). route-health оставлен **stopped+disabled** до починки скрипта. route-update (отдельный timer) продолжает работать.
**Рекомендация автору (Vitaly Lipatov):**
- `flush_gw_routes` → массовый `ip route flush table <per-gw>` (как задумано в комментарии стр. 283); per-gateway таблицы вместо общей 202.
- Окно ping-запроса 1m → 3–5m (или last-seen логика), чтобы пропуск точек под нагрузкой не давал ложный dead.
- `timeout` на все `ip`-вызовы в циклах flush.
Проверка verdict'а шлюза в обход зависшего route-health: дёрнуть точные запросы из строк 48-78 route-health.sh к InfluxDB `10.20.30.130:8086` db=gateways. Все egress-шлюзы реально здоровы (SA up, vpn=1, iperf3=success, ping rc=0). См. [[reference_egress_tunnels]], [[gre.hetzner2.egw]].
## ✅ ИСПРАВЛЕНО 01.08.2026
Оба изменения залиты в репо (коммиты `5f8a5bc` route-health + `8144abe` vici-dropin, gpush в pub.gitlab) и развёрнуты:
1. **`flush_gw_routes()` → bulk flush:** `timeout 60 $ipcmd route flush table "$t" via "$dead_gw"` вместо per-route цикла. Идиома та же, что route-update.sh:827-830. На igw: цикл 18s вместо 20+ мин, table202 v6 via vdska 127259→0 за секунды.
2. **Окно ping/vpn 1m → 3m** (запросы через `last()` — реакцию на реальное падение не задерживает, только убирает пустой результат → ложный dead).
3. **`route-health.timer` включён обратно** — циклы быстрые, зависаний нет.
**Важный дочерний корень ikev2 churn:** даже после фикса flush'а ikev2.vdska продолжал churn'ить dead↔healthy, потому что `check-vpn-status.sh` (под telegraf) не мог прочитать vici-сокет → ложный vpn_status=0. Починено отдельно — см. [[lesson_vici_socket_acl_for_telegraf]]. Развёрнуто на .131/.120/.139/.140.
Реплицировано на egw (91.232.225.14): checkout `/root/etersoft-admin-essential` был позади на 10 коммитов с локальной правкой (== upstream 08365b4) — отбросил дубликат, `git pull --ff-only``8144abe`. На egw route-health.service не запущен (нет unit), скрипт обновлён для консистентности.
---
name: lesson_vici_socket_acl_for_telegraf
description: check-vpn-status.sh под telegraf не может в swanctl (vici 770 root:root) → ложный vpn_status=0 → route-health churn ikev2.*; фикс — ACL на vici-сокет (sudo не работает: /bin/sudo 4710 root:wheel)
metadata:
node_type: memory
type: lesson
originSessionId: c2840474-3dab-4156-9d46-6f9a238fcfe2
modified: 2026-08-01T19:29:21.855Z
---
`router/check-vpn-status.sh` крутится telegraf-exec-плагином (каждые 10s) под юзером **telegraf** на egress-контейнерах ikev2.* (.120/.131/.139/.140). IKEv2-ветка сначала зовёт `swanctl --list-sas`, при неудаче падает в fallback «ipsec0 rx_bytes изменился за 10s».
**Симптом:** ikev2.vdska churn'ил в route-health dead↔healthy (12 flush+11 recover за 3 мин, load ~17, v6-маршруты table202 via vdska упорно 0) — даже после фикса flush-шторма ([[lesson_route_health_flush_hang]]).
**Корень:** swanctl под telegraf падает с *Permission denied* на vici-сокете `/run/charon.vici` (`srwxrwx--- root:root`, 770 — telegraf не в группе). → `established=0` → fallback по rx_bytes → при затишье трафика (10s без пакетов) rx_bytes не меняется → `connected=0` → route-health: `vpn=0 → dead`. Прямой прогон скрипта root'ом давал connected=1 всегда.
**Грабли с sudo (выбранный изначально путь «sudoers + sudo swanctl» НЕ сработал):** на ALT `/bin/sudo` = **4710 root:wheel** — execute только для root и группы wheel. telegraf даже НЕ МОГ запустить sudo (`/bin/sudo: Отказано в доступе`, exit 126). Добавление telegraf в wheel давало бы латентный риск (`%wheel ALL=(ALL) ALL` когда-нибудь добавят), ACL на setuid-бинарник `/bin/sudo` сбрасывается при `epm upgrade sudo`. `/etc/sudoers` на этих CT: `@includedir /etc/sudoers.d` (НЕ /etc/sudo.d — память про /etc/sudo.d на этих хостах неверна, проверять `grep @includedir`), и НЕТ правила `%wheel` (алиас WHEEL_USERS объявлен, но не использован). sudo хочет mode **0400** (не 0440) для файлов в sudoers.d.
**Фикс (чистый, без sudo):** дать telegraf чтение vici-сокета через systemd drop-in на strongSwan (Type=notify → сокет уже есть на ExecStartPost):
```
# /etc/systemd/system/strongswan.service.d/telegraf-vici.conf
[Service]
ExecStartPost=/bin/sh -c "setfacl -m u:telegraf:rwx /run/charon.vici || true"
```
Артефакт в репо: `router/strongswan-vici-telegraf.conf`. `|| true` — чтобы сбой ACL никогда не уронил IPsec-туннель (uptime туннеля важнее мониторинга). Скрипт check-vpn-status.sh править НЕ нужно — `swanctl` и так зовётся первым, просто теперь telegraf до него дотягивается (PATH telegraf включает /sbin → bare `swanctl` резолвится).
**Проверка:** `su -s /bin/sh telegraf -c "swanctl --list-sas | grep -c ESTABLISHED"` = 1. В InfluxDB `SELECT connected FROM vpn_status WHERE gateway='ikev2.vdska'` — стабильно 1, churn прекратился, маршруты table202 via vdska восстановились до 127259.
**⚠️ Перезапуск strongSwan рвёт SA:** при `systemctl restart strongswan` (чтобы применить drop-in) SA кратко падает. На .131/.120 (site-to-site, оба конца наши) переподнялся сам. На .139/.140 (ikev2.gr/fr — длинные туннели к удалённым NAT-пирам rpi/Греция) SA тоже переподнялся сам за ~35-39s (initiator реконнектится, см. [[reference_ikev2_fr_tunnel]], [[reference_ikev2_gr_tunnel]]), но проверять надо через ~40s, не сразу (в первые секунды ESTABLISHED=0 — это нормально, не признак поломки). Альтернатива без рыва SA: применить ACL вручную `setfacl -m u:telegraf:rwx /run/charon.vici` сразу + drop-in для персистентности (сработает при следующем рестарте/буте).
Развёрнуто на .131 (ikev2.vdska, чинил churn), .120 (ikev2.hetzner, латентно), .139 (ikev2.gr, pingonly — косметика health.json), .140 (ikev2.fr, pingonly). См. [[lesson_route_health_flush_hang]], [[reference_egress_tunnels]], [[gre.hetzner2.egw]].
---
name: igw
description: igw (CT на border, 91.232.225.13) — gateway health/failover (route-health.sh, route-update.sh); changelog
metadata:
node_type: memory
type: objects
originSessionId: c2840474-3dab-4156-9d46-6f9a238fcfe2
---
igw = CT на border, 91.232.225.13. Хостит `route-health.sh` (oneshot, timer `route-health.timer` OnUnitActiveSec=20s) + `route-update.sh` (timer 30min). InfluxDB `10.20.30.130:8086` db=gateways. Checkout репо `/root/etersoft-admin-essential` (origin gitlab.eterfund). См. [[reference_egress_tunnels]], [[reference_igw_route_api]].
## 2026-08-01 — починен route-health flush-шторм + ikev2 churn
**route-health.sh** зависал 20+ мин (load 14+), замораживая мониторинг ВСЕХ шлюзов и ложе помечая здоровые gre.hetzner2/ikev2.vdska dead → разрушительный flush 127k v6-маршрутов. Два корня, оба починены (коммиты `5f8a5bc`, `8144abe`, gpush):
1. `flush_gw_routes()` делал per-route `ip route del` ×127k → O(n) netlink-шторм → вис oneshot. → bulk `timeout 60 ip route flush table T via GW`.
2. Окно ping/vpn InfluxDB 1m → пустой результат под нагрузкой → ложный dead. → 3m (запросы через `last()`, реакцию не задерживает).
Дочерний корень ikev2.vdska churn: `check-vpn-status.sh` под telegraf не читал vici-сокет → vpn_status=0. Фикс — ACL на vici-сокет (drop-in на strongSwan), см. [[lesson_vici_socket_acl_for_telegraf]]. Drop-in развёрнут на egress-CT .131/.120/.139/.140 (на border, не на самом igw).
**Что сделано на igw:**
- Деплой фикса route-health.sh + `bash -n` + тест одного цикла (18s, exit 0).
- `route-health.timer` включён обратно (был stop+disable 01.08 днём). Циклы быстрые, churn прекратился, table202 v6 via vdska = 127259 (восстановлено).
- checkout `/root/etersoft-admin-essential` обновлён (там был dirty с моим фиксом в working tree; коммит ушёл через builder64 → pub.gitlab).
**egw (91.232.225.14):** checkout был позади на 10 коммитов + локальная правка route-health.sh (== upstream 08365b4 pingonly). Отбросил дубликат, `git pull --ff-only``8144abe`. На egw route-health.service НЕ запущен (нет unit) — скрипт обновлён для консистентности.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment