← Все статьи

«Ошибки диска» оказались OOM: как тома Longhorn потеряли бэкенд

EIO, ext4 в read-only, повреждённый datadir MySQL и запечатанный Vault выглядели как умирающий диск. dmesg хоста показал OOM на всю ноду, который первым убил instance-manager Longhorn.

В Optimacros одна нода self-managed кластера Kubernetes «поморгала». Поды на ней получили EIO, ext4 перемонтировалась в read-only, datadir MySQL повредился, один пир Vault запечатался, а kubelet ненадолго ушёл в NotReady. Потом всё снова выглядело нормально: нода Ready, Longhorn показывал все тома здоровыми.

Соблазнительный вывод

Ошибки ввода-вывода на блочных устройствах, файловая система в read-only, база с повреждённым datadir — все симптомы указывают на хранилище. Естественные следующие шаги — «заменить диск» или «пропатчить ядро». И то и другое отняло бы время и ничего бы не исправило.

Остановило меня то, что kubectl не мог объяснить событие. «Сейчас здорово» — не объяснение того, что случилось в 03:00, поэтому я пошёл на хост.

Что сказал dmesg

Привилегированный debug-под на ноде (kubectl debug node/... --profile=sysadmin, chroot в хост) и grep по dmesg дали реальный порядок событий:

  1. global_oom, constraint=CONSTRAINT_NONE. У всей ноды закончилась память. OOM killer перебил много процессов, среди них longhorn-instance-manager и CSI-драйвер ноды.
  2. Примерно через две минуты: EXT4-fs error ... unable to read itable block и JBD2: I/O error ... journal superblock на блочных устройствах Longhorn.

Порядок важен. Instance-manager — это блочный бэкенд всех томов Longhorn на этой ноде. Убейте его, и незавершённый ввод-вывод на всех этих томах вернёт EIO; ext4 перемонтируется в read-only; база, которая писала в этот момент, повредится. Ошибки диска были симптомом OOM, а не отказом диска.

Ключевая деталь — CONSTRAINT_NONE. Это значит, что OOM был глобальным на ноду, а не одним контейнером, упёршимся в свой лимит. То есть проблема ёмкости и QoS.

Почему жертвой стал бэкенд хранилища

Совпали две вещи.

  • Много подов работало как BestEffort, вообще без запросов памяти. Планировщик считает только requests, поэтому спокойно упаковал ноду сверх её реальной RAM.
  • Instance-manager работал как Burstable без запроса памяти. У него была PriorityClass longhorn-critical, но PriorityClass влияет на планирование и вытеснение, а не на OOM score ядра. Снизить oom_score_adj может только запрос памяти.

Поэтому, когда память кончилась, ядро увидело большой процесс, выглядящий малоприоритетным, и убило ровно то, от чего зависели все тома на ноде.

Решение

  • Дать instance-manager запрос памяти, в идеале равный лимиту, чтобы он стал Guaranteed, через настройки guaranteed instance-manager в Longhorn.
  • Убрать BestEffort у реальных нагрузок: выставить запросы памяти по фактическому потреблению, чтобы планировщик перестал переподписывать ноды.

Изменение ресурсов instance-manager перезапускает его и ненадолго отключает тома этой ноды, поэтому я делал это по одной ноде в окно. Сама диагностика была read-only — только не забудьте потом удалить под node-debugger.

Выводы

  • EIO на устройствах Longhorn сразу после смерти instance-manager — подпись именно этого отказа. Прочитайте dmesg хоста, прежде чем заказывать диск.
  • constraint=CONSTRAINT_NONE в строке OOM означает, что память кончилась у ноды, а не у пода.
  • PriorityClass защищает планирование, а не выживание при OOM. Для этого нужны запросы памяти.
  • Поды BestEffort не бесплатны: они тратят запас, которого планировщик не видит, а счёт приходит тому, кого ядро выберет убить.