ftops.space
83 subscribers
661 photos
98 videos
33 files
420 links
ftops.space
Сервера на linux, FreeBSD, сети на микротик и сиськах.
Download Telegram
ZFS не тормозил. Его просто не измеряли

Мониторинг кричал о задержках диска на FreeBSD-шлюзе. Железо было исправно, ошибок в пуле не было. Настоящая причина сидела выше: ARC разросся под рабочей нагрузкой, полезные метаданные начали вытесняться, а jails синхронно получили паузы на повторном чтении с накопителей.

Криминалистика начинается не с покупки NVMe. Сначала смотрят zpool status -v, zpool iostat -v 1 и sysctl kstat.zfs.misc.arcstats. Если размер ARC, промахи и состав кэша не сопоставлены с реальным профилем I/O, любой график latency рассказывает только половину истории.

FreeBSD остается эталоном для сетевых шлюзов и надежного хранения не из-за UNIX-ностальгии. ZFS, jails и прозрачные системные счетчики дают оператору главное: предсказуемость отказа. Но стабильность появляется только там, где ARC ограничен осознанно, пул регулярно проверяется, а деградацию замечают до того, как она становится аварией.

Облако продает лишнюю память как лекарство от незнания. На своем железе диагноз дешевле: измерить кэш, проверить пул, отделить задержку ZFS от задержки устройства. Неизмеренная память не является запасом надежности.

https://ftops.space

#ftops #devops #linux #freebsd #sre #инфраструктура
Мониторинг кричал: apiserver недоступен, S3 отвечает медленно. Дежурный проверил не легенду, а железо: cat /proc/pressure/io, iostat -xz 1, dmesg -T \| tail -100, ss -lntp \| egrep ':2379\|:2380'. I/O pressure не рос, TCP не терялся. Сломался не транспорт — нарушили процедуру membership. На одной server-ноде K3s остановили и выполнили: k3s server --cluster-reset --cluster-reset-restore-path\=<snapshot-name> --etcd-s3 --etcd-s3-bucket\=<bucket> .... Для нового хоста нужен исходный --token: им расшифровываются bootstrap-данные. S3 Secret из Kubernetes при restore недоступен — API ещё мёртв, поэтому параметры S3 подаются через CLI или конфиг на диске. После сообщения о завершённом reset запустили только первый server и проверили curl -sf http://127.0.0.1:2381/metrics \| egrep 'etcdserverhasleader\|etcddiskwalfsyncdurationseconds'. На остальных server-нодах при остановленном K3s архивировали и удалили из рабочего пути /var/lib/rancher/k3s/server/db, затем присоединили их заново. Ст...
Мониторинг кричал: apiserver недоступен, S3 отвечает медленно. Дежурный проверил не легенду, а железо: cat /proc/pressure/io, iostat -xz 1, dmesg -T \| tail -100, ss -lntp \| egrep ':2379\|:2380'. I/O pressure не рос, TCP не терялся. Сломался не транспорт — нарушили процедуру membership. На одной server-ноде K3s остановили и выполнили: k3s server --cluster-reset --cluster-reset-restore-path\=<snapshot-name> --etcd-s3 --etcd-s3-bucket\=<bucket> .... Для нового хоста нужен исходный --token: им расшифровываются bootstrap-данные. S3 Secret из Kubernetes при restore недоступен — API ещё мёртв, поэтому параметры S3 подаются через CLI или конфиг на диске. После сообщения о завершённом reset запустили только первый server и проверили curl -sf http://127.0.0.1:2381/metrics \| egrep 'etcdserverhasleader\|etcddiskwalfsyncdurationseconds'. На остальных server-нодах при остановленном K3s архивировали и удалили из рабочего пути /var/lib/rancher/k3s/server/db, затем присоединили их заново. Старые member ID нельзя пускать в новый quorum. Снепшот в бакете — лишь сырьё. DR существует только после регулярного restore-test, отдельного хранения token и записанного порядка запуска узлов. Практика bare-metal эксплуатации: https://ftops.space
Разбор аварии: active, но мертв

В 14:07 мониторинг увидел живой frr.service и четыре BGP-сессии в состоянии Established. Пользователи в это время получали таймауты. Дежурный перезапустил FRR, затем сетевой стек. Ничего не изменилось: процесс был исправен, а маршрут — нет.

Один транзитный провайдер принял утечку более специфичного префикса, второй предпочел этот маршрут и вернул трафик первому. Получилась межоператорская петля. Пакеты ходили между двумя AS до обнуления TTL, пока systemctl is-active продолжал печатать active. Он не врал: он отвечал на бесполезный вопрос о состоянии PID.

Криминалистика началась с mtr и tcpdump, а не с рестарта. Повторяющиеся адреса транзитов показали петлю, ICMP Time Exceeded подтвердил смерть по TTL, а сравнение received-routes и advertised-routes нашло более специфичный префикс. Счетчики ip -s link были чистыми: ядро передавало пакеты ровно туда, куда велела таблица маршрутизации.

После инцидента проверку сервиса заменили проверкой функции: внешний ...
Разбор аварии: active, но мертв

В 14:07 мониторинг увидел живой frr.service и четыре BGP-сессии в состоянии Established. Пользователи в это время получали таймауты. Дежурный перезапустил FRR, затем сетевой стек. Ничего не изменилось: процесс был исправен, а маршрут — нет.

Один транзитный провайдер принял утечку более специфичного префикса, второй предпочел этот маршрут и вернул трафик первому. Получилась межоператорская петля. Пакеты ходили между двумя AS до обнуления TTL, пока systemctl is-active продолжал печатать active. Он не врал: он отвечал на бесполезный вопрос о состоянии PID.

Криминалистика началась с mtr и tcpdump, а не с рестарта. Повторяющиеся адреса транзитов показали петлю, ICMP Time Exceeded подтвердил смерть по TTL, а сравнение received-routes и advertised-routes нашло более специфичный префикс. Счетчики ip -s link были чистыми: ядро передавало пакеты ровно туда, куда велела таблица маршрутизации.

После инцидента проверку сервиса заменили проверкой функции: внешний probe до контрольных адресов, контроль AS_PATH и префиксов, лимиты max-prefix, RPKI-фильтрация и аварийное отключение дефолтного экспорта. systemctl проверяет процесс. Эксплуатация обязана проверять функцию.

https://ftops.space

#ftops #devops #linux #freebsd #sre #инфраструктура
В 03:00 мониторинг показал DiskPressure, pod eviction и провал rollout. Поверхностный диагноз — «маленький VPS». Реальность: /var/lib/rancher/k3s/agent/containerd разросся слоями образов, а ImageGC стартовал слишком поздно и не успел освободить место до eviction threshold. Проверяем не красивый процент в панели, а ноду: kubectl describe node NODE; df -hT; df -ih; sudo du -xhd2 /var/lib/rancher/k3s/agent/containerd \| sort -h; sudo k3s crictl images. Если df показывает дефицит, а du нет — ищем удалённые, но открытые файлы: sudo lsof \+L1. Смотрим, что происходило внизу: cat /proc/pressure/io; awk '\{print $3,$4,$6,$8,$10,$13\}' /proc/diskstats; journalctl -u k3s -u k3s-agent --since '-2h' \| grep -Ei 'imagegc\|diskpressure\|evict\|filesystem'. Настраиваем kubelet заранее: imageGCHighThresholdPercent и imageGCLowThresholdPercent должны оставлять реальный резерв до evictionHard, а не делить последние гигабайты. На малой ноде свободное место — эксплуатационный бюджет. Ограничивайте размер ...
В 03:00 мониторинг показал DiskPressure, pod eviction и провал rollout. Поверхностный диагноз — «маленький VPS». Реальность: /var/lib/rancher/k3s/agent/containerd разросся слоями образов, а ImageGC стартовал слишком поздно и не успел освободить место до eviction threshold. Проверяем не красивый процент в панели, а ноду: kubectl describe node NODE; df -hT; df -ih; sudo du -xhd2 /var/lib/rancher/k3s/agent/containerd \| sort -h; sudo k3s crictl images. Если df показывает дефицит, а du нет — ищем удалённые, но открытые файлы: sudo lsof \+L1. Смотрим, что происходило внизу: cat /proc/pressure/io; awk '\{print $3,$4,$6,$8,$10,$13\}' /proc/diskstats; journalctl -u k3s -u k3s-agent --since '-2h' \| grep -Ei 'imagegc\|diskpressure\|evict\|filesystem'. Настраиваем kubelet заранее: imageGCHighThresholdPercent и imageGCLowThresholdPercent должны оставлять реальный резерв до evictionHard, а не делить последние гигабайты. На малой ноде свободное место — эксплуатационный бюджет. Ограничивайте размер образов, удаляйте неиспользуемые теги, контролируйте inode и проверяйте GC после каждого обновления K3s. Практика bare-metal эксплуатации: https://ftops.space
В 03:00 S3 честно отдал snapshot, а K3s после восстановления получил зависшие контроллеры, старые объекты в informer cache и попытки прежних server-нод вернуться в quorum. Поверхностный мониторинг написал: «etcd backup corrupted». Нет. Оператор восстановил байты, но не восстановил согласованное состояние кластера. Начинать надо не с перезапуска всего подряд: k3s etcd-snapshot ls --s3, затем проверить объект и выполнить на единственном выбранном server: k3s server --cluster-reset --cluster-reset-restore-path\=<SNAPSHOT>. После успешного reset убрать этот флаг, запустить узел штатно и только потом последовательно присоединять остальные server-ноды с актуальным token. Старые каталоги /var/lib/rancher/k3s/server/db на бывших участниках нельзя считать безобидными. Во время восстановления смотреть не только journal: journalctl -u k3s -b, ss -lntp \| grep 2379, ss -s, nstat -az \| grep -E 'TcpRetransSegs\|TcpExtTCPTimeouts', cat /proc/sys/net/netfilter/nfconntrackcount и cat /proc/sys/net/n...
В 03:00 S3 честно отдал snapshot, а K3s после восстановления получил зависшие контроллеры, старые объекты в informer cache и попытки прежних server-нод вернуться в quorum. Поверхностный мониторинг написал: «etcd backup corrupted». Нет. Оператор восстановил байты, но не восстановил согласованное состояние кластера. Начинать надо не с перезапуска всего подряд: k3s etcd-snapshot ls --s3, затем проверить объект и выполнить на единственном выбранном server: k3s server --cluster-reset --cluster-reset-restore-path\=<SNAPSHOT>. После успешного reset убрать этот флаг, запустить узел штатно и только потом последовательно присоединять остальные server-ноды с актуальным token. Старые каталоги /var/lib/rancher/k3s/server/db на бывших участниках нельзя считать безобидными. Во время восстановления смотреть не только journal: journalctl -u k3s -b, ss -lntp \| grep 2379, ss -s, nstat -az \| grep -E 'TcpRetransSegs\|TcpExtTCPTimeouts', cat /proc/sys/net/netfilter/nfconntrackcount и cat /proc/sys/net/netfilter/nfconntrackmax. Для диска: iostat -xz 1, grep -E 'Dirty\|Writeback' /proc/meminfo. Иногда «etcd timeout» — это забитый conntrack, потери TCP или fsync, утонувший в writeback. DR считается рабочим только после отдельной проверки API, quorum, системных контроллеров, секретов и повторного подключения нод. Хранить snapshot в S3 умеет даже ленивый managed-сервис; доказать восстановление умеет только команда с регулярно исполняемым runbook. Практика bare-metal эксплуатации: https://ftops.space
Мониторинг кричал: S3 недоступен, restore упирается в timeout. Проверили ядро: ss -s, nstat -az | egrep 'TcpRetransSegs|IpFragFails', conntrack -C, conntrack -S. Retransmit, фрагментация и conntrack были в норме. Сеть оправдана. Настоящий отказ был выше: настройки S3 хранились в Secret внутри самого кластера. Во время аварийного восстановления kube-apiserver ещё не работает, поэтому --etcd-s3-config-secret бесполезен. Получился классический замок с ключом внутри сейфа. Восстановление запускается на одном остановленном server с явными S3-параметрами и исходным токеном: k3s server --cluster-reset --cluster-reset-restore-path=SNAPSHOT --etcd-s3 --etcd-s3-bucket=BUCKET --etcd-s3-access-key=KEY --etcd-s3-secret-key=SECRET --token=ORIGINAL_TOKEN. Затем обычный запуск K3s; на остальных etcd-server старую базу убирают только после отдельной копии и присоединяют узлы заново. В DR-комплект входят снепшот, /var/lib/rancher/k3s/server/token, CA для S3, автономные credentials и проверенный runbook....
Мониторинг кричал: S3 недоступен, restore упирается в timeout. Проверили ядро: ss -s, nstat -az | egrep 'TcpRetransSegs|IpFragFails', conntrack -C, conntrack -S. Retransmit, фрагментация и conntrack были в норме. Сеть оправдана. Настоящий отказ был выше: настройки S3 хранились в Secret внутри самого кластера. Во время аварийного восстановления kube-apiserver ещё не работает, поэтому --etcd-s3-config-secret бесполезен. Получился классический замок с ключом внутри сейфа. Восстановление запускается на одном остановленном server с явными S3-параметрами и исходным токеном: k3s server --cluster-reset --cluster-reset-restore-path=SNAPSHOT --etcd-s3 --etcd-s3-bucket=BUCKET --etcd-s3-access-key=KEY --etcd-s3-secret-key=SECRET --token=ORIGINAL_TOKEN. Затем обычный запуск K3s; на остальных etcd-server старую базу убирают только после отдельной копии и присоединяют узлы заново. В DR-комплект входят снепшот, /var/lib/rancher/k3s/server/token, CA для S3, автономные credentials и проверенный runbook. Если restore никогда не прогоняли на пустом железе, резервной копии у вас нет. Есть надежда с HTTP API. https://ftops.space
03:00. Учебный постмортем: прямой WireGuard блокируется, после переноса через wstunnel в WSS соединение устанавливается. Проверка TCP/443 зелёная, handshake обновлялся недавно, но SSH зависает под нагрузкой. Дежурный объявляет: «DPI научился резать и это». Проверка порта такого диагноза не даёт. В выбранном режиме UDP WireGuard едет внутри WebSocket поверх TLS/TCP. Потеря внешнего TCP-сегмента задерживает выдачу следующих байтов приложению, даже если они уже пришли. Внутренние TCP-сессии ждут, а при достаточной задержке запускают собственные повторные передачи. Проблемы производительности такой связки отмечены в трекере wstunnel. Это механизм возможного отказа, ещё не доказательство причины конкретной аварии. На узле wstunnel: ss -tinp '( dport \= :443 )'. Находим нужный сокет по адресу и процессу, наблюдаем rtt, rto, retrans и Send-Q во время зависания. nstat -az TcpRetransSegs TcpExtTCPTimeouts снимаем дважды: важен прирост, счётчики от...
03:00. Учебный постмортем: прямой WireGuard блокируется, после переноса через wstunnel в WSS соединение устанавливается. Проверка TCP/443 зелёная, handshake обновлялся недавно, но SSH зависает под нагрузкой. Дежурный объявляет: «DPI научился резать и это». Проверка порта такого диагноза не даёт. В выбранном режиме UDP WireGuard едет внутри WebSocket поверх TLS/TCP. Потеря внешнего TCP-сегмента задерживает выдачу следующих байтов приложению, даже если они уже пришли. Внутренние TCP-сессии ждут, а при достаточной задержке запускают собственные повторные передачи. Проблемы производительности такой связки отмечены в [трекере wstunnel](https://github.com/erebe/wstunnel/issues/439). Это механизм возможного отказа, ещё не доказательство причины конкретной аварии. На узле wstunnel: ss -tinp '( dport = :443 )'. Находим нужный сокет по адресу и процессу, наблюдаем rtt, rto, retrans и Send-Q во время зависания. nstat -az TcpRetransSegs TcpExtTCPTimeouts снимаем дважды: важен прирост, счётчики относятся ко всему сетевому namespace. Сопоставляем его с захватом tcpdump -ni eth0 'host SERVER_IP and tcp port 443', подставив внешний интерфейс и IP сервера. Повторные передачи подтверждают проблему доставки, но сами по себе не отличают DPI от обычных потерь. Критерий восстановления — полезный трафик и задержка под нагрузкой, включая проверку на потерях. Отдельно проверяем, что маршрут к WSS-серверу не завернулся в сам VPN. WSS не гарантирует обход любого DPI, а уменьшение MTU не отменяет упорядоченную доставку TCP. Ещё один ingress не исправит свойства транспорта. Эксплуатация bare metal: https://ftops.space.
ПОСТМОРТЕМ: Systemd drop-in и молчаливый сброс лимитов Мониторинг кричал: nginx возвращает 502, worker процессы падают массово. On-call видит CPU в норме, RAM свободна, диски живые. Классический ложный диагноз — «приложение». На деле ядро честно логировало EMFILE в /proc/$(pidof nginx)/fd, а systemd ещё раньше тихо применил конкурирующий параметр из system.conf. Как воспроизвести диагностику: cat /proc/$(pidof nginx)/limits \| grep 'open files' systemctl show nginx \| grep -i limit journalctl -u nginx --since '2h ago' \| grep 'Too many open' ls /proc/$(pidof nginx)/fd \| wc -l Что произошло в иерархии systemd: пакетный апдейт дистрибутива доставил новый /lib/systemd/system/nginx.service с ExecStartPre и секцией Service без LimitNOFILE. Drop-in /etc/systemd/system/nginx.service.d/limits.conf исправно содержал LimitNOFILE\=1048576 и пережил обновление. Но параллельно мейнтейнер системы добавил в /etc/systemd/system.conf строку DefaultLimitNOFILE\=65536. Согласно иерархии systemd: Defau...
ПОСТМОРТЕМ: Systemd drop-in и молчаливый сброс лимитов Мониторинг кричал: nginx возвращает 502, worker процессы падают массово. On-call видит CPU в норме, RAM свободна, диски живые. Классический ложный диагноз — «приложение». На деле ядро честно логировало EMFILE в /proc/$(pidof nginx)/fd, а systemd ещё раньше тихо применил конкурирующий параметр из system.conf. Как воспроизвести диагностику: cat /proc/$(pidof nginx)/limits \| grep 'open files' systemctl show nginx \| grep -i limit journalctl -u nginx --since '2h ago' \| grep 'Too many open' ls /proc/$(pidof nginx)/fd \| wc -l Что произошло в иерархии systemd: пакетный апдейт дистрибутива доставил новый /lib/systemd/system/nginx.service с ExecStartPre и секцией Service без LimitNOFILE. Drop-in /etc/systemd/system/nginx.service.d/limits.conf исправно содержал LimitNOFILE\=1048576 и пережил обновление. Но параллельно мейнтейнер системы добавил в /etc/systemd/system.conf строку DefaultLimitNOFILE\=65536. Согласно иерархии systemd: DefaultLimit в system.conf задаёт глобальный дефолт; drop-in перекрывает конкретный юнит-файл, но только если в юнит-файле нет явного значения; когда юнит-файл умалчивает LimitNOFILE — применяется DefaultLimit из system.conf, и drop-in с LimitNOFILE\=1048576 его перекрывает корректно. Ловушка: если drop-in написан как LimitNOFILE\= (пустое значение для сброса в дефолт) — он явно сбрасывает к DefaultLimitNOFILE. Именно это случилось: junior-инженер «почистил» drop-in полгода назад. Правильная фиксация: # /etc/systemd/system/nginx.service.d/limits.conf Service LimitNOFILE\=1048576 LimitNPROC\=65536 systemctl daemon-reload systemctl restart nginx systemctl show nginx \| grep -E 'LimitNOFILE\|LimitNPROC' Проверяй system.conf после каждого dist-upgrade: grep -E 'DefaultLimit' /etc/systemd/system.conf /etc/systemd/system.conf.d/.conf 2>/dev/null Дополнительно: Pedit COW (CVE, раскрытая Anti-Malware на этой неделе) — ещё одно напоминание, что ядро исполняет то, что написано, а не то, что ты имел в виду. systemd работает так же. Архитектурный вывод: drop-in — это патч к юнит-файлу, а не абсолютный оверрайд системного дефолта. Если значение в drop-in не явное и не финальное — system.conf выиграет без предупреждения. https://ftops.space
Плановый вывод узла в Longhorn: allowScheduling\=false не делает того, что вы думаете. Мониторинг кричал: PVC в состоянии Terminating, pod зависает на ContainerCreating. На самом деле в ядре и планировщике Longhorn произошло следующее: флаг allowScheduling\=false запрещает размещение новых реплик на узле, но не триггерит eviction существующих. Реплики на выводимом узле продолжают входить в quorum volume до тех пор, пока узел физически доступен. Как только нода ушла из кластера без предварительной эвакуации реплик, Longhorn потерял кворум — volume перешёл в degraded, а затем в faulted. CSI-драйвер вернул I/O error монтирующему поду. Время обнаружения: 8-12 минут после отключения ноды, когда node controller сменил статус на NotReady. Диагностика до начала вывода узла: longhornctl get replica --node\=<node-name> longhornctl get volume \| grep -v healthy kubectl get nodes -o wide kubectl describe node <node-name> \| grep Taint Правильная процедура эвакуации реплик перед drain: kubectl -n l...
Плановый вывод узла в Longhorn: allowScheduling\=false не делает того, что вы думаете. Мониторинг кричал: PVC в состоянии Terminating, pod зависает на ContainerCreating. На самом деле в ядре и планировщике Longhorn произошло следующее: флаг allowScheduling\=false запрещает размещение новых реплик на узле, но не триггерит eviction существующих. Реплики на выводимом узле продолжают входить в quorum volume до тех пор, пока узел физически доступен. Как только нода ушла из кластера без предварительной эвакуации реплик, Longhorn потерял кворум — volume перешёл в degraded, а затем в faulted. CSI-драйвер вернул I/O error монтирующему поду. Время обнаружения: 8-12 минут после отключения ноды, когда node controller сменил статус на NotReady. Диагностика до начала вывода узла: longhornctl get replica --node\=<node-name> longhornctl get volume \| grep -v healthy kubectl get nodes -o wide kubectl describe node <node-name> \| grep Taint Правильная процедура эвакуации реплик перед drain: kubectl -n longhorn-system patch node.longhorn.io/<node-name> \ --type\=merge \ -p '\{"spec":\{"allowScheduling":false,"evictionRequested":true\}\}' Ждать полной эвакуации: watch -n5 'kubectl -n longhorn-system get replica \ -l longhornnode\=<node-name> -o wide' Только после того, как все реплики перенесены и volume вернулся в статус healthy, выполнять kubectl drain. Метрика ядра, которую нужно смотреть во время эвакуации: iostat -xz 2 на узле-источнике — активный replication flush даёт устойчивый write throughput до нуля. Пока есть I/O — реплика не эвакуирована. Longhorn не Ceph. Он не абстрагирует вас от топологии узлов. Каждая реплика — это живое состояние, привязанное к ноде. Полная процедура планового обслуживания узлов Longhorn и разбор смежных отказов — на ftops.space.
Мониторинг кричал: OOM, приложение упало, нужно больше RAM. Дежурный заказал upgrade инстанса. Петля продолжалась. Постмортем. Ночь, BGP-сессия между двумя edge-узлами начала анонсировать маршрут через самого себя. traceroute показал TTL-expiry в цикле между двумя хопами. tcpdump на обоих концах — пакеты идут туда-обратно, TTL декрементируется до нуля, ICMP time-exceeded генерируется тысячами в секунду. Каждый новый поток через петлю открывает conntrack-запись. Диагностика, которая вскрыла правду: ip route show table all | grep -E 'nexthop|via' — немедленно показывает рекурсию cat /proc/sys/net/netfilter/nf_conntrack_count vs nf_conntrack_max — счётчик у потолка tcpdump -i eth0 -nn 'icmp and icmp[0] == 11' — ICMP time-exceeded сыпался сотнями в секунду dmesg | grep 'nf_conntrack: table full' — было в логах, но никто не смотрел ss -s — показал 80k+ ESTABLISHED при реальной нагрузке в 200 соединений Когда conntrack-таблица переполнилась, ядро начало дропать новые соединения. Приложение з...
Мониторинг кричал: OOM, приложение упало, нужно больше RAM. Дежурный заказал upgrade инстанса. Петля продолжалась. Постмортем. Ночь, BGP-сессия между двумя edge-узлами начала анонсировать маршрут через самого себя. traceroute показал TTL-expiry в цикле между двумя хопами. tcpdump на обоих концах — пакеты идут туда-обратно, TTL декрементируется до нуля, ICMP time-exceeded генерируется тысячами в секунду. Каждый новый поток через петлю открывает conntrack-запись. Диагностика, которая вскрыла правду: ip route show table all | grep -E 'nexthop|via' — немедленно показывает рекурсию cat /proc/sys/net/netfilter/nf_conntrack_count vs nf_conntrack_max — счётчик у потолка tcpdump -i eth0 -nn 'icmp and icmp[0] == 11' — ICMP time-exceeded сыпался сотнями в секунду dmesg | grep 'nf_conntrack: table full' — было в логах, но никто не смотрел ss -s — показал 80k+ ESTABLISHED при реальной нагрузке в 200 соединений Когда conntrack-таблица переполнилась, ядро начало дропать новые соединения. Приложение зависло в ожидании ответа. OOM-killer увидел рост RSS из-за накопленных буферов сокетов и расстрелял процесс. Дежурный прочитал это как нехватку памяти. Фикс занял 40 секунд: birdc show route — петля видна, изменение local_pref, bgp soft-in на пире, conntrack flush через conntrack -F. Сервер поднялся без апгрейда железа. Правило одно: двусторонний tcpdump раньше OOM-killer. Всегда. https://ftops.space
Мониторинг кричал: zero-trust-политики применены, nftables-правила на месте, WireGuard-туннели подняты. На самом деле: весь east-west трафик между нодами кластера шёл мимо политик уже четыре месяца. Диагностика вскрыла проблему в три шага. Первый: смотришь на conntrack-таблицу и видишь один поток UDP/51820 на каждую пару нод. conntrackcount не растёт пропорционально числу сервисных сессий — это флаг. Второй: nft trace показывает, что правила срабатывают на внешний WireGuard-пакет ДО расшифровки. Политика видит src\=wg-peer-ip, а не src\=pod-ip. Третий: смотришь в /proc/net/nfconntrack и убеждаешься — внутренних записей для pod-to-pod трафика нет вообще. Netfilter не разбирает payload UDP-туннеля. Корень: WireGuard в Linux реализован как виртуальный сетевой интерфейс. Расшифровка происходит после прохождения netfilter PREROUTING. Это означает, что любые nftables-политики на физическом интерфейсе применяются к зашифрованному пакету. Чтобы политика работала на расшифрованный трафик, пра...
Мониторинг кричал: zero-trust-политики применены, nftables-правила на месте, WireGuard-туннели подняты. На самом деле: весь east-west трафик между нодами кластера шёл мимо политик уже четыре месяца. Диагностика вскрыла проблему в три шага. Первый: смотришь на conntrack-таблицу и видишь один поток UDP/51820 на каждую пару нод. conntrack_count не растёт пропорционально числу сервисных сессий — это флаг. Второй: nft trace показывает, что правила срабатывают на внешний WireGuard-пакет ДО расшифровки. Политика видит src=wg-peer-ip, а не src=pod-ip. Третий: смотришь в /proc/net/nf_conntrack и убеждаешься — внутренних записей для pod-to-pod трафика нет вообще. Netfilter не разбирает payload UDP-туннеля. Корень: WireGuard в Linux реализован как виртуальный сетевой интерфейс. Расшифровка происходит после прохождения netfilter PREROUTING. Это означает, что любые nftables-политики на физическом интерфейсе применяются к зашифрованному пакету. Чтобы политика работала на расшифрованный трафик, правила обязаны висеть на wg0-интерфейсе или в отдельном network namespace с корректным routing. eBPF/TC-хук на wg0 после decap — единственный способ получить per-flow visibility без userspace-прокси. Либо Cilium с native WireGuard encryption, где policy enforcement происходит после расшифровки на уровне BPF map. Конкретные команды для диагностики: nft monitor trace ip xfrm policy list cat /proc/net/nf_conntrack | grep -v udp | wc -l sysctl net.netfilter.nf_conntrack_count bpftool net show dev wg0 Архитектурная цена zero-trust на WireGuard без eBPF: вы платите за иллюзию сегментации, пока ядро честно пишет всё в /proc. Разбор на ftops.space.