perf(traffic): optimize production reconciliation
This commit is contained in:
+22
-11
@@ -243,8 +243,8 @@ struct PeerTrafficStatus {
|
||||
`discover_target()`:
|
||||
|
||||
1. Прочитать упорядоченный `amnezia.container_names`.
|
||||
2. Найти первый запущенный контейнер с точным совпадением имени.
|
||||
3. Получить container ID и PID.
|
||||
2. Для каждого имени по порядку выполнить один `docker inspect`, который сразу возвращает running state, container ID и PID.
|
||||
3. Выбрать первый запущенный контейнер с точным совпадением имени; отсутствующий или stopped candidate не меняет порядок fallback.
|
||||
4. Построить `netns_path`.
|
||||
5. Определить интерфейс в этом namespace.
|
||||
6. Вернуть `RuntimeTarget`.
|
||||
@@ -313,7 +313,7 @@ tc class add dev <iface> parent 1: classid 1:1 htb rate <root_rate> ceil <root_r
|
||||
for each peer:
|
||||
tc class add dev <iface> parent 1:1 classid 1:<id> htb rate <peer_limit> ceil <peer_limit>
|
||||
tc qdisc add dev <iface> parent 1:<id> fq_codel
|
||||
tc filter add dev <iface> parent 1: protocol ip u32 match ip dst <peer_ip> flowid 1:<id>
|
||||
tc filter add dev <iface> parent 1: protocol ip pref 10 flower dst_ip <peer_ip>/32 classid 1:<id>
|
||||
```
|
||||
|
||||
Ingress через IFB:
|
||||
@@ -329,9 +329,11 @@ tc class add dev <ifb_name> parent 2: classid 2:1 htb rate <root_rate> ceil <roo
|
||||
for each peer:
|
||||
tc class add dev <ifb_name> parent 2:1 classid 2:<id> htb rate <peer_limit> ceil <peer_limit>
|
||||
tc qdisc add dev <ifb_name> parent 2:<id> fq_codel
|
||||
tc filter add dev <ifb_name> parent 2: protocol ip u32 match ip src <peer_ip> flowid 2:<id>
|
||||
tc filter add dev <ifb_name> parent 2: protocol ip pref 10 flower src_ip <peer_ip>/32 classid 2:<id>
|
||||
```
|
||||
|
||||
Все peer-фильтры на каждом parent используют один `pref 10`: для `flower` это даёт один hash table на priority вместо множества priority structures. Маска всегда `/32`, поэтому один priority не смешивает несовместимые masks. Catch-all redirect в IFB остаётся `u32`, поскольку это одно правило, а не per-peer lookup.
|
||||
|
||||
## 9. Алгоритм reconcile
|
||||
|
||||
Traffic и firewall — два независимых reconcile с отдельными блокировками. Они не объединены в одну транзакцию: сбой одного не должен блокировать другой.
|
||||
@@ -343,10 +345,12 @@ reconcile_traffic():
|
||||
target = discover_target()
|
||||
peers = discover_peers(config, target)
|
||||
если peers пуст:
|
||||
залогировать "no peers found, skip tc apply" и выйти
|
||||
проверить наличие AWG2-owned topology
|
||||
если она осталась от прежних peers: teardown_owned_topology(target)
|
||||
иначе выйти с debug-диагностикой
|
||||
drift = inspect_and_diff(target, peers, config)
|
||||
если drift отсутствует:
|
||||
залогировать "traffic rules up-to-date, skip apply" и выйти
|
||||
выйти с debug-диагностикой
|
||||
teardown_owned_topology(target)
|
||||
apply_full_topology(target, peers, config) # внутри — verify-after-apply
|
||||
```
|
||||
@@ -365,12 +369,15 @@ apply_firewall():
|
||||
Требования к устойчивости парсеров:
|
||||
|
||||
- `tc filter show` разбирается блоками (конечный автомат), а не наивным сканированием токенов по несвязанным строкам;
|
||||
- единый parser понимает production `flower` и legacy `u32`, включая counters; точный healthy-контракт требует `flower`, `pref 10`, правильное direction-field и `/32`, а legacy `u32` peer rules считаются migration drift;
|
||||
- нераспознанный или malformed detailed filter block не игнорируется, а делает инспекцию drifted;
|
||||
- отображение `peer_ip -> class_id` проверяется для обеих сторон: egress (`1:`) и ingress (`2:`);
|
||||
- проверка обязана падать, если классы и фильтры существуют, но отображение отличается от детерминированного присвоения.
|
||||
|
||||
Политика apply/verify:
|
||||
|
||||
- все шаги apply — явные вызовы команд;
|
||||
- `modprobe ifb`, создание IFB и его перевод в UP выполняются строго и до `tc` apply;
|
||||
- после teardown вся `tc` topology подаётся одним fail-fast сценарием в stdin команды `tc -batch -`; `-force` не используется, поэтому первая ошибка останавливает batch и сохраняет номер строки в stderr;
|
||||
- ожидаемые конфликты идемпотентности допускаются выборочно, через отдельную обёртку, и по закрытому списку признаков: `file exists`, `eexist`, `object already exists`, `exclusive flag on`;
|
||||
- ошибки «объект не найден» (`no such`, `not found`, `cannot find`, `does not exist`, `handle of zero`) допускаются только на шагах удаления — в teardown и при отключении владеемых таблиц;
|
||||
- любая другая ошибка apply фатальна;
|
||||
@@ -437,9 +444,9 @@ table inet awg2_harness {
|
||||
- counter `antitorrent_dropped_v4`;
|
||||
- meter/set state, создаваемый правилом.
|
||||
|
||||
Status обязан показывать наличие таблицы/chain/правила, настроенный порог и drop counter. Также он показывает давление на conntrack хоста через `net.netfilter.nf_conntrack_count` и `net.netfilter.nf_conntrack_max`, когда они доступны.
|
||||
Status обязан показывать наличие таблицы/chain/правила, настроенный порог и drop counter. Также он один раз читает `net.netfilter.nf_conntrack_count` и `net.netfilter.nf_conntrack_max` и, когда оба значения доступны, печатает `nf_conntrack_utilization` в процентах.
|
||||
|
||||
При `anti_torrent.enabled = false` демон удаляет только владеемую anti-torrent таблицу. Глобальный ruleset nftables не сбрасывается никогда. Если контейнер в этот момент отсутствует, удаление в его namespace просто пропускается — это не ошибка.
|
||||
При `anti_torrent.enabled = false` демон удаляет только владеемую anti-torrent таблицу. Глобальный ruleset nftables не сбрасывается никогда. Если контейнер в этот момент именно не найден, удаление в его namespace пропускается — это не ошибка. Ошибка Docker API/daemon/permissions не маскируется под «контейнер не найден» и возвращается оператору.
|
||||
|
||||
## 12. Инспекция фактического состояния
|
||||
|
||||
@@ -456,6 +463,8 @@ Status обязан показывать наличие таблицы/chain/п
|
||||
- классы на `<ifb_name>`;
|
||||
- фильтры под parent `2:` на `<ifb_name>`.
|
||||
|
||||
Один вывод `tc qdisc show dev <iface>` используется одновременно для root, ingress и per-peer `fq_codel`; один вывод `tc class show dev <device>` — для root и per-peer classes. Инспекция не делает отдельный child process на каждый parent.
|
||||
|
||||
Дополнительно детектируются дубли: один и тот же peer IP в нескольких фильтрах и один и тот же class id у нескольких IP. Оба случая считаются drift.
|
||||
|
||||
`status` показывает компактную сводку и таблицу по peers.
|
||||
@@ -469,6 +478,8 @@ Status обязан показывать наличие таблицы/chain/п
|
||||
|
||||
Таймер обязателен. События могут быть пропущены при рестарте демона, рестарте Docker или транзиентных ошибках.
|
||||
|
||||
Внутри одного startup/periodic/event-цикла traffic и anti-torrent получают один и тот же результат discovery `RuntimeTarget`; повторные `docker inspect`, PID/interface discovery и transport construction в этом же цикле не выполняются. Наличие host `nsenter` кэшируется на время жизни процесса.
|
||||
|
||||
Проверка готовности не должна хардкодить `awg0`. Она вызывает общий детектор интерфейса с интервалом 2 секунды до истечения `traffic.container_ready_timeout_secs`.
|
||||
|
||||
Порядок на старте демона и разница в критичности ошибок:
|
||||
@@ -487,7 +498,7 @@ Status обязан показывать наличие таблицы/chain/п
|
||||
|
||||
Ошибки отслеживаются по подсистемам (`traffic`, `firewall`, `swap`, `sysctl_tuning`, `docker_events`), а не одним общим полем. Общее поле в `last_action.state` показывает ту ошибку, которая ещё не устранена: успешный reconcile одной подсистемы не гасит незакрытую ошибку другой и не объявляет восстановление того, что не падало. Поток `docker events` при успешном переподключении тоже отмечается как восстановленный — иначе один рестарт Docker оставлял бы ошибку в `status` навсегда при исправно работающем новом потоке.
|
||||
|
||||
В `events.log` пишутся только **переходы состояния**: первое появление ошибки, изменение текста ошибки и восстановление после неё. Рутинный успех не пишется. Раньше каждый успешный цикл добавлял строку — при интервале 15 секунд это порядка 17 тысяч записей в сутки, каждая с полным чтением, дополнением и перезаписью файла, и на их фоне терялись записи, ради которых журнал и читают. Полная поцикловая картина остаётся в journald через tracing.
|
||||
В `events.log` и journald на уровне `info`/`error` пишутся только **переходы состояния**: первое появление ошибки, изменение её текста и восстановление. Рутинный healthy-цикл и повтор той же ошибки остаются на `debug`. Это исключае повторяющийся journal-шум на каждом 15-секундном reconcile, но сохраняет детали для отладки при включённом debug-уровне.
|
||||
|
||||
## 14. Блокировки
|
||||
|
||||
@@ -552,7 +563,7 @@ Swap:
|
||||
desired_active, exclusive, fstab_exclusive, fstab_swap_entries,
|
||||
swappiness_current, swappiness_persisted, drift[, drift_reason]
|
||||
Conntrack:
|
||||
nf_conntrack_count, nf_conntrack_max
|
||||
nf_conntrack_count, nf_conntrack_max, nf_conntrack_utilization
|
||||
Last actions:
|
||||
содержимое /run/awg2-harness/last_action.state
|
||||
Recent events:
|
||||
|
||||
Reference in New Issue
Block a user