ftops.space
84 subscribers
555 photos
98 videos
33 files
278 links
ftops.space
Сервера на linux, FreeBSD, сети на микротик и сиськах.
Download Telegram
FreeBSD не умер. Он просто отказался деградировать.

Пока Linux-мир тащит systemd, overlayfs-костыли и eBPF-хаки для того, чтобы хоть как-то обеспечить изоляцию процессов, во FreeBSD это решено архитектурно. Jails появились в 2000 году — за 13 лет до Docker. Не как эксперимент, а как production-примитив с чёткой семантикой: отдельный filesystem namespace, отдельный сетевой стек, полная изоляция PID. Никаких cgroup-иерархий, никакого overlayfs. Просто работает.

ZFS на FreeBSD — это не feature, это фундамент. ARC кэш адаптивно занимает свободную RAM и освобождает её под давлением без участия оператора. Copy-on-write гарантирует, что partial write при отказе питания физически невозможен. Checksumming на уровне блоков ловит bit rot, который ext4 и xfs молча пропускают годами. Для сетевого шлюза, где данные — это конфиги, сертификаты и состояние туннелей, это не опция, это требование.

Пул ZFS на 4 дисках с mirror vdev даёт read IOPS, которые линейно масштабируются с числом дисков. zfs send/receive — атомарная репликация снапшотов по сети без rsync и без риска рассинхрона. Один скрипт в cron, и у тебя полная DR-копия шлюза на удалённом узле, актуальная с точностью до часа.

Для сетевых шлюзов и хранилищ FreeBSD выигрывает не маркетингом, а архитектурным консерватизмом: стек проверен, ABI стабилен, обновления предсказуемы. В 2026 году это редкость.

https://ftops.space

#ftops #devops #linux #freebsd #sre #инфраструктура
Разбор аварии: systemctl is-active врёт

Происходит чаще, чем хочется признавать. Мониторинг зелёный, алерты молчат, дашборд показывает healthy. Пользователи не могут подключиться уже 40 минут.

Корневая причина: systemd оценивает состояние юнита по exit-коду ExecStart, а не по тому, работает ли сам сервис. Если процесс запустился, инициализировался и завершился с кодом 0 — юнит помечается active (exited). Это нормальное поведение для oneshot-сервисов. Но если так же ведёт себя ваш nginx или postgres — у вас тихая авария.

Конкретный сценарий: база данных поднялась, провела миграции, лог написала, завершилась с кодом 0. Systemd: active. В реальности — сокет не слушает, соединения не принимаются, приложение падает при первом запросе.

Что делать правильно:

1. Проверять функцию, а не юнит. После деплоя: nc -z localhost 5432, psql -c '\l', curl -sf http://localhost/health — не systemctl is-active.
2. Настроить ExecStartPost с реальной проверкой. Если ExecStartPost падает — systemd откати...
Разбор аварии: systemctl is-active врёт

Происходит чаще, чем хочется признавать. Мониторинг зелёный, алерты молчат, дашборд показывает healthy. Пользователи не могут подключиться уже 40 минут.

Корневая причина: systemd оценивает состояние юнита по exit-коду ExecStart, а не по тому, работает ли сам сервис. Если процесс запустился, инициализировался и завершился с кодом 0 — юнит помечается active (exited). Это нормальное поведение для oneshot-сервисов. Но если так же ведёт себя ваш nginx или postgres — у вас тихая авария.

Конкретный сценарий: база данных поднялась, провела миграции, лог написала, завершилась с кодом 0. Systemd: active. В реальности — сокет не слушает, соединения не принимаются, приложение падает при первом запросе.

Что делать правильно:

1. Проверять функцию, а не юнит. После деплоя: nc -z localhost 5432, psql -c '\l', curl -sf http://localhost/health — не systemctl is-active.
2. Настроить ExecStartPost с реальной проверкой. Если ExecStartPost падает — systemd откатит юнит в failed и вы получите алерт.
3. В мониторинге использовать blackbox-экспортер (Prometheus) или аналогичный инструмент, который проверяет порт или HTTP-эндпоинт снаружи, а не статус юнита.
4. Для критичных сервисов добавить Restart=on-failure + StartLimitIntervalSec — но это страховка, не диагностика.

BGP-постмортем по аналогии: провайдер объявляет маршрут, IGP его принимает, is-active говорит ОК. А трафик уходит в петлю, потому что AS-path не проверялся, а next-hop недостижим. Мониторинг видит сессию established — и молчит. Реальная проверка — это synthetic probe через этот маршрут, а не состояние BGP-сессии.

Вывод один: статус — это мнение системы о себе. Функция — это факт.

https://ftops.space

#ftops #devops #linux #sre #инфраструктура
PII нельзя отправлять в API по умолчанию

Если приложение передает во внешний AI-шлюз объект запроса целиком, вместе с полезным текстом наружу могут уйти ФИО, телефоны, cookies, внутренние URL и секреты конфигурации. HTTPS защищает канал, но не меняет получателя данных.

Защита начинается до отправки: локальный шлюз классифицирует поля, удаляет персональные данные и секреты, заменяет идентификаторы на псевдонимы, фиксирует аудит и только затем выбирает внешний или локальный inference. Маскирование после API-вызова — это уже не защита.

Для 152-ФЗ важен доказуемый контроль потока данных: какие поля разрешены, куда они уходят, кто это подтвердил и можно ли восстановить историю обращения.

Проверьте интеграции: что произойдет, если разработчик случайно передаст в prompt объект запроса целиком? Практические схемы суверенного шлюза: https://ftops.space

#ftops #devops #linux #freebsd #sre #инфраструктура
Безопасность и суверенитет данных: что остаётся за вами

152-ФЗ — это не про формальность. Это про физическое место хранения персональных данных резидентов. Если ваша инфраструктура отдаёт PII в сторонний API за пределами РФ, вы уже в зоне риска, даже если контракт подписан. Локализация баз — первый рубеж, но не последний.

Суверенный AI-шлюз решает проблему на уровне архитектуры: перед отправкой запроса во внешнюю модель шлюз вычищает ФИО, паспорта, телефоны, email, адреса и токены. Модель получает обезличенный контекст и не может сохранить то, чего не видела. Это не обфускация, а полное удаление полей до выхода пакета за периметр.

Отдельный пласт — утечка ключей и секретов. Скан репозиториев, git-истории, переменных окружения, docker-образов и логов. Практика: gitleaks или trufflehog в CI, ротация каждые 90 дней, vault вместо env-файлов, pre-commit хуки против коммита секретов. Один утёкший токен способен перечеркнуть годы работы над репутацией.

Суверенитет — это не лозунг, а инжен...
Безопасность и суверенитет данных: что остаётся за вами

152-ФЗ — это не про формальность. Это про физическое место хранения персональных данных резидентов. Если ваша инфраструктура отдаёт PII в сторонний API за пределами РФ, вы уже в зоне риска, даже если контракт подписан. Локализация баз — первый рубеж, но не последний.

Суверенный AI-шлюз решает проблему на уровне архитектуры: перед отправкой запроса во внешнюю модель шлюз вычищает ФИО, паспорта, телефоны, email, адреса и токены. Модель получает обезличенный контекст и не может сохранить то, чего не видела. Это не обфускация, а полное удаление полей до выхода пакета за периметр.

Отдельный пласт — утечка ключей и секретов. Скан репозиториев, git-истории, переменных окружения, docker-образов и логов. Практика: gitleaks или trufflehog в CI, ротация каждые 90 дней, vault вместо env-файлов, pre-commit хуки против коммита секретов. Один утёкший токен способен перечеркнуть годы работы над репутацией.

Суверенитет — это не лозунг, а инженерная дисциплина. Контроль над данными означает контроль над тем, что видит каждая внешняя система. Если вы не можете ответить, куда уходит PII и где лежат ваши ключи, значит ответ уже есть у кого-то другого.

#ftops #devops #linux #freebsd #sre #инфраструктура
Сценарий ночного отказа: 03:00, API сыплет 502. На хосте свободная RAM, CPU скучает. Поверхностный диагноз — «мало воркеров, добавим сервер». Сервис работает в chroot с cgroups v2 и seccomp. После роста нагрузки пул пытается создавать потоки, но clone() возвращает EAGAIN. Запросы обслуживать некому. Начинаем с cat /proc/$PID/cgroup: в cgroups v2 строка 0:: укажет путь группы. Для найденной группы читаем pids.current, pids.max и pids.events; проверяем также предков. Рост счётчика max вместе с отказами создания потоков выводит на PID-лимит. Он учитывает и потоки. strace -f -e trace\=clone,clone3 -p "$PID" позволяет увидеть отказ при воспроизведении. Один EAGAIN ещё не доказывает причину: есть и другие ограничения. Семантика счётчиков — в документации ядра. Исправление — ограничить рост пула, согласовать его размер с pids.max и оставить запас под служебные потоки. Алертить нужно на рост pids.events:max и приближение pids...
Сценарий ночного отказа: 03:00, API сыплет 502. На хосте свободная RAM, CPU скучает. Поверхностный диагноз — «мало воркеров, добавим сервер». Сервис работает в chroot с cgroups v2 и seccomp. После роста нагрузки пул пытается создавать потоки, но clone() возвращает EAGAIN. Запросы обслуживать некому. Начинаем с cat /proc/$PID/cgroup: в cgroups v2 строка 0:: укажет путь группы. Для найденной группы читаем pids.current, pids.max и pids.events; проверяем также предков. Рост счётчика max вместе с отказами создания потоков выводит на PID-лимит. Он учитывает и потоки. strace -f -e trace\=clone,clone3 -p "$PID" позволяет увидеть отказ при воспроизведении. Один EAGAIN ещё не доказывает причину: есть и другие ограничения. Семантика счётчиков — в документации ядра. Исправление — ограничить рост пула, согласовать его размер с pids.max и оставить запас под служебные потоки. Алертить нужно на рост pids.events:max и приближение pids.current к действующему лимиту. Просто поднять потолок — перенести аварию. Покупка managed K8s не исправляет бесконтрольное размножение потоков. Chroot меняет корень разрешения путей и сам по себе не создаёт защитную песочницу; cgroups ограничивают ресурсы; seccomp фильтрует системные вызовы. Это разные механизмы, а ядро остаётся общим. Гостевой ОС нет, но нулевых накладных расходов никто не обещал. Основания: chroot(2), seccomp(2). Эксплуатация начинается с понимания границ: https://ftops.space.
Разбор сценария отказа: 03:00, распределённый K3s поверх WireGuard. Скомпрометированный pod изолируют политикой, закрывающей исходящий доступ. Мониторинг сообщает: новое подключение к БД не проходит, карантин работает. Но ранее открытый TCP-сеанс продолжает передавать данные. Дежурный подозревает обходной маршрут. Проверка измеряла только новые подключения. В варианте с netfilter улику ищем на узле обработки трафика: conntrack -L -p tcp --dport 5432 и iptables-save -c. Если ACCEPT для ESTABLISHED срабатывает раньше проверки ограничения, пакеты старого сеанса проходят. Рост счётчика этого правила сопоставляем с захватом конкретного потока. sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax показывает заполнение таблицы, но само наличие записи не доказывает обход политики. Это не универсальное поведение Kubernetes: применение изменённой NetworkPolicy к существующим соединениям зависит от реализации. Это прямо закреплено в документации Kubernetes
Разбор сценария отказа: 03:00, распределённый K3s поверх WireGuard. Скомпрометированный pod изолируют политикой, закрывающей исходящий доступ. Мониторинг сообщает: новое подключение к БД не проходит, карантин работает. Но ранее открытый TCP-сеанс продолжает передавать данные. Дежурный подозревает обходной маршрут. Проверка измеряла только новые подключения. В варианте с netfilter улику ищем на узле обработки трафика: conntrack -L -p tcp --dport 5432 и iptables-save -c. Если ACCEPT для ESTABLISHED срабатывает раньше проверки ограничения, пакеты старого сеанса проходят. Рост счётчика этого правила сопоставляем с захватом конкретного потока. sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax показывает заполнение таблицы, но само наличие записи не доказывает обход политики. Это не универсальное поведение Kubernetes: применение изменённой NetworkPolicy к существующим соединениям зависит от реализации. Это прямо закреплено в документации Kubernetes. WireGuard шифрует межузловой канал; судьбу старого сеанса определяет механизм фильтрации внутри него. Исправление: включить в процедуру карантина проверенный для своего dataplane механизм отзыва действующих потоков. Приёмочный тест держит соединение открытым, применяет изоляцию и проверяет прекращение передачи на каждой площадке, затем отдельно проверяет новый сеанс. Покупка ещё одного контроллера эту проверку не заменяет. Для https://ftops.space критерий простой: карантин завершён, когда запрещённый обмен действительно остановлен.
Разбор типового отказа в 03:00. Балансировщик сыплет таймаутами, CPU скучает. Дежурный обвиняет сеть и поднимает net.core.somaxconn. Клиенты теперь дольше стоят перед закрытой дверью: accept4() возвращает EMFILE, процесс исчерпал свой лимит дескрипторов. Это отличается от ENFILE — исчерпания общесистемного лимита открытых файлов. Семантика ошибок: accept(2). Сначала улики: prlimit --pid "$PID" --nofile показывает лимит работающего процесса; ls -U /proc/$PID/fd \| wc -l — приблизительное число занятых FD. PID задаём для нужного worker. ss -lnt показывает очередь слушающего сокета. Два замера nstat -az TcpExtListenOverflows TcpExtListenDrops под нагрузкой выявляют прирост счётчиков. В K3s сетевые замеры выполняем в network namespace приложения. Переполнение очереди само по себе ещё не доказывает нехватку FD: нужны ошибки accept и заполнение лимита. net.core.somaxconn ограничивает backlog, запрошенный приложением через listen(); подня...
Разбор типового отказа в 03:00. Балансировщик сыплет таймаутами, CPU скучает. Дежурный обвиняет сеть и поднимает net.core.somaxconn. Клиенты теперь дольше стоят перед закрытой дверью: accept4() возвращает EMFILE, процесс исчерпал свой лимит дескрипторов. Это отличается от ENFILE — исчерпания общесистемного лимита открытых файлов. Семантика ошибок: accept(2). Сначала улики: prlimit --pid "$PID" --nofile показывает лимит работающего процесса; ls -U /proc/$PID/fd \| wc -l — приблизительное число занятых FD. PID задаём для нужного worker. ss -lnt показывает очередь слушающего сокета. Два замера nstat -az TcpExtListenOverflows TcpExtListenDrops под нагрузкой выявляют прирост счётчиков. В K3s сетевые замеры выполняем в network namespace приложения. Переполнение очереди само по себе ещё не доказывает нехватку FD: нужны ошибки accept и заполнение лимита. net.core.somaxconn ограничивает backlog, запрошенный приложением через listen(); поднять только sysctl недостаточно. Увеличение очереди помогает пережить короткий всплеск, если обработчик затем её разгребает. listen(2). tcptwreuse разрешает повторное использование TIMEWAIT-сокетов для новых соединений при безопасных условиях протокола; лимит FD и отказ accept он не исправляет. [Документация ядра](https://www.kernel.org/doc/html/latest/networking/ip-sysctl.html#tcp-tw-reuse). Исправление начинаем с причины накопления FD: утечка, зависшие соединения или честно выросшая конкуренция. Затем согласуем лимит процесса, бюджет соединений приложения и backlog. После применения проверяем фактический RLIMITNOFILE у worker, ошибки accept, прирост ListenOverflows и задержку под нагрузкой. Kubernetes не рассчитывает этот бюджет за инженера. Эксплуатация начинается с измеренного предела: https://ftops.space.
Разбор типового отказа в 03:00: маленький VPS, K3s, поды уходят в Evicted. Мониторинг доступности требует ещё одну ноду, график свободных гигабайтов зелёный. Ложный диагноз — «кластеру мало мощности». Реальная причина: кеш приложения на общей файловой системе съел inode. DiskPressure возникает и по нехватке inode, до полного исчерпания байтов. Механизм описан в [документации Kubernetes](https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/). Улики: df -h /var/lib/rancher/k3s /var/lib/kubelet и df -i /var/lib/rancher/k3s /var/lib/kubelet для стандартных путей. Затем kubectl describe node NODE: сверяем Conditions и Events. В node_exporter смотрим node_filesystem_avail_bytes и node_filesystem_files_free по нужному mountpoint. Это два разных ресурса файловой системы. Для поиска россыпи файлов — sudo du --inodes -x -d 2 /var/lib/kubelet; обход большого дерева создаёт дополнительную нагрузку. ImageGC удаляет неиспользуемые образы. Его пороги заполнения в байтах не о...
Разбор типового отказа в 03:00: маленький VPS, K3s, поды уходят в Evicted. Мониторинг доступности требует ещё одну ноду, график свободных гигабайтов зелёный. Ложный диагноз — «кластеру мало мощности». Реальная причина: кеш приложения на общей файловой системе съел inode. DiskPressure возникает и по нехватке inode, до полного исчерпания байтов. Механизм описан в [документации Kubernetes](https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/). Улики: df -h /var/lib/rancher/k3s /var/lib/kubelet и df -i /var/lib/rancher/k3s /var/lib/kubelet для стандартных путей. Затем kubectl describe node NODE: сверяем Conditions и Events. В node_exporter смотрим node_filesystem_avail_bytes и node_filesystem_files_free по нужному mountpoint. Это два разных ресурса файловой системы. Для поиска россыпи файлов — sudo du --inodes -x -d 2 /var/lib/kubelet; обход большого дерева создаёт дополнительную нагрузку. ImageGC удаляет неиспользуемые образы. Его пороги заполнения в байтах не ограничивают число файлов приложения; даже успешная уборка образов не устраняет источник роста. Для байтового запаса настраиваем imageGCHighThresholdPercent и imageGCLowThresholdPercent с учётом eviction-порогов и размера очередной распаковки. Low должен быть ниже High. Поведение — в [документации ImageGC](https://kubernetes.io/docs/concepts/architecture/garbage-collection/#containers-images). Профилактика: ограничить число и срок жизни файлов кеша, настроить ротацию логов, алертить отдельно свободные байты и inode. Запас проверять на деплое, когда старый и новый образы сосуществуют. Покупать VPS вместо ограничения бесконечного кеша — платить налог на лень. https://ftops.space
Учебный постмортем, 03:00. Мониторинг кричит: рестарты, exit 137, «добавьте памяти». В K3s падает liveness-проверка, завязанная на удалённую зависимость. После истечения срока завершения kubelet добивает контейнер. Облачный рецепт — купить RAM. Но маршрут от этого короче не станет. Проверяем версию OOM: journalctl -k --since '-15 min' и cat /sys/fs/cgroup/<путь-контейнера>/memory.events. Нужна дельта oom_kill за аварию, пока cgroup ещё существует. В этом сценарии прироста нет, журнал ядра без OOM, события kubelet подтверждают провал liveness. Один пустой журнал ничего не доказывает. Семантика счётчика: [документация ядра](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). На клиенте и сервере одновременно запускаем tcpdump -ni any -nn -vv 'tcp port 443 or icmp', сопоставляем адреса с учётом NAT. Клиент повторяет SYN, сервер их не видит: локализовали потерю между точками захвата, но ещё не доказали петлю. Выполняем traceroute -n -T -p 443 <IP-сервера>, затем проверяем о...
Учебный постмортем, 03:00. Мониторинг кричит: рестарты, exit 137, «добавьте памяти». В K3s падает liveness-проверка, завязанная на удалённую зависимость. После истечения срока завершения kubelet добивает контейнер. Облачный рецепт — купить RAM. Но маршрут от этого короче не станет. Проверяем версию OOM: journalctl -k --since '-15 min' и cat /sys/fs/cgroup/<путь-контейнера>/memory.events. Нужна дельта oom_kill за аварию, пока cgroup ещё существует. В этом сценарии прироста нет, журнал ядра без OOM, события kubelet подтверждают провал liveness. Один пустой журнал ничего не доказывает. Семантика счётчика: [документация ядра](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html). На клиенте и сервере одновременно запускаем tcpdump -ni any -nn -vv 'tcp port 443 or icmp', сопоставляем адреса с учётом NAT. Клиент повторяет SYN, сервер их не видит: локализовали потерю между точками захвата, но ещё не доказали петлю. Выполняем traceroute -n -T -p 443 <IP-сервера>, затем проверяем обратное направление. Повторяющиеся хопы — зацепка; подтверждение — FIB соседних маршрутизаторов отправляет адрес назначения друг через друга. Параметры TCP-трассировки: [traceroute](https://www.man7.org/linux/man-pages/man8/traceroute.8.html). Причина сценария — изменение BGP-политики создало цикл пересылки. Проверка выбранных next-hop и установленных маршрутов связала петлю с изменением; один traceroute этого не умеет. Откатываем политику, проверяем оба направления и восстановление handshake. Убираем удалённую зависимость из liveness, её доступность учитываем в readiness по смыслу сервиса. BGP Established не гарантирует доставку. Разбор эксплуатации: https://ftops.space.
03:00. Типовой постмортем: приложение ловит DNS-таймауты, дашборд обвиняет CoreDNS. Его CPU свободен, время обработки нормальное. Добавление реплик ничего не меняет. В кластере с kube-proxy в режиме iptables подозреваем участок Pod → Service IP: UDP-запрос может потеряться при гонке conntrack/DNAT ещё до DNS-сервера. Зелёная серверная метрика такой запрос вообще не видела. На проблемной ноде снимаем conntrack -S несколько раз: смотрим прирост insertfailed, drop, earlydrop. Команда sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax помогает проверить заполнение таблицы. Гонка возможна и без её насыщения; один счётчик причину не доказывает. Сопоставляем захваты tcpdump -ni any -nn 'udp port 53' у клиента и сервера: где виден запрос, где ответ, где начинается повтор. Исправление для этого пути — NodeLocal DNSCache на нодах: локальный DNS-трафик обходит DNAT и conntrack, промахи кэша для кластерных имён можно отправлять в CoreDNS по TCP. Проверяем фактический адрес ре...
03:00. Типовой постмортем: приложение ловит DNS-таймауты, дашборд обвиняет CoreDNS. Его CPU свободен, время обработки нормальное. Добавление реплик ничего не меняет. В кластере с kube-proxy в режиме iptables подозреваем участок Pod → Service IP: UDP-запрос может потеряться при гонке conntrack/DNAT ещё до DNS-сервера. Зелёная серверная метрика такой запрос вообще не видела. На проблемной ноде снимаем conntrack -S несколько раз: смотрим прирост insertfailed, drop, earlydrop. Команда sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax помогает проверить заполнение таблицы. Гонка возможна и без её насыщения; один счётчик причину не доказывает. Сопоставляем захваты tcpdump -ni any -nn 'udp port 53' у клиента и сервера: где виден запрос, где ответ, где начинается повтор. Исправление для этого пути — NodeLocal DNSCache на нодах: локальный DNS-трафик обходит DNAT и conntrack, промахи кэша для кластерных имён можно отправлять в CoreDNS по TCP. Проверяем фактический адрес резолвера и правила обхода: один DaemonSet ещё не доказывает изменение маршрута. Механизм описан в документации Kubernetes. После изменения проверяем DNS p99 из Pod на каждой ноде, таймауты и прирост потерь под прежней нагрузкой. Отдельно проверяем перезапуск локального кэша: теперь это зависимость всей ноды. Покупать дополнительные реплики до локализации потерь — платить налог на отсутствие диагностики. Эксплуатация начинается с пути пакета: https://ftops.space.
03:00, сценарий планового вывода узла K3s. Дежурный выставил Longhorn allowScheduling\=false и выключил сервер. Тома стали degraded, восстановление реплик нагрузило соседние диски. Поверхностный диагноз: «хранилище тормозит». Настоящий: запрет новых реплик приняли за перенос существующих. Kubernetes не отменяет физику дисков, зато прекрасно размножает зелёные статусы. До отключения смотрим размещение: kubectl -n longhorn-system get replicas.longhorn.io -o custom-columns\=NAME:.metadata.name,NODE:.spec.nodeID,VOLUME:.spec.volumeName. На оставшихся узлах: cat /proc/pressure/io и iostat -xz 1. Рост PSI io some/full показывает время задержек задач из-за I/O; await — задержки блочных запросов. Это следы нагрузки, а не доказательство поломки диска. Сопоставляем их с началом rebuild. Для эвакуации нужны allowScheduling\=false и evictionRequested\=true в Longhorn Node CR. Longhorn переносит реплику после успешного восстановления замены — это описано в документации
03:00, сценарий планового вывода узла K3s. Дежурный выставил Longhorn allowScheduling\=false и выключил сервер. Тома стали degraded, восстановление реплик нагрузило соседние диски. Поверхностный диагноз: «хранилище тормозит». Настоящий: запрет новых реплик приняли за перенос существующих. Kubernetes не отменяет физику дисков, зато прекрасно размножает зелёные статусы. До отключения смотрим размещение: kubectl -n longhorn-system get replicas.longhorn.io -o custom-columns\=NAME:.metadata.name,NODE:.spec.nodeID,VOLUME:.spec.volumeName. На оставшихся узлах: cat /proc/pressure/io и iostat -xz 1. Рост PSI io some/full показывает время задержек задач из-за I/O; await — задержки блочных запросов. Это следы нагрузки, а не доказательство поломки диска. Сопоставляем их с началом rebuild. Для эвакуации нужны allowScheduling\=false и evictionRequested\=true в Longhorn Node CR. Longhorn переносит реплику после успешного восстановления замены — это описано в документации. Заранее проверяем ёмкость и ограничения размещения на принимающих узлах. Если замене негде жить, флаг не создаст ей диск. Перед выводом проверяем завершение эвакуации, отсутствие реплик на узле и восстановление требуемой избыточности томов. Затем — штатный drain с учётом политики Longhorn и отключение. Для краткого обслуживания стратегия может отличаться, но сам allowScheduling\=false разрешением на shutdown не становится. Эксплуатация начинается с проверки результата: https://ftops.space.
03:17. Учебный постмортем: потерян quorum etcd, начинаем восстановление K3s. Последняя выгрузка в S3 зелёная, API недоступен, restore с прежним S3 Secret не работает. Первый диагноз — отвалился маршрут до бакета. Покупка объектного хранилища уже состоялась. Покупка работоспособного DR — только в презентации. Проверяем журнал: journalctl -u k3s -b -n 200 --no-pager. Снимаем nstat -az TcpRetransSegs до и после попытки, сравниваем прирост; при включённом conntrack смотрим sysctl net.netfilter.nfconntrackcount net.netfilter.nfconntrackmax. Эти показатели проверяют сетевую гипотезу, но не доказывают доступность S3. Улика здесь в конфигурации: доступ к бакету задан через etcd-s3-config-secret. Во время restore API недоступен и выдать Secret не может. До скачивания дело не дошло. Выход: заранее вынести S3 credentials и server token соответствующего снепшота в защищённый аварийный комплект вне кластера. Остановить K3s на всех server-узлах. Восстанавливать один узел, передав S3-параметры яв...