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 #инфраструктура
Мониторинг кричал о задержках диска на 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 были чистыми: ядро передавало пакеты ровно туда, куда велела таблица маршрутизации.
После инцидента проверку сервиса заменили проверкой функции: внешний ...
В 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 #инфраструктура
В 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.
GitHub
Raw tun device support, and tcp fast ack · Issue #439 · erebe/wstunnel
Describe the feature Allow directly forwarding raw traffic over websocket stream between existing tun devices on local and remote. Sending direct ack to incoming tcp so it doesn't wait for remo...
ПОСТМОРТЕМ: 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.