«Ошибки диска» оказались 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 дали реальный порядок событий:
global_oom, constraint=CONSTRAINT_NONE. У всей ноды закончилась память. OOM killer перебил много процессов, среди нихlonghorn-instance-managerи CSI-драйвер ноды.- Примерно через две минуты:
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без запроса памяти. У него была PriorityClasslonghorn-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не бесплатны: они тратят запас, которого планировщик не видит, а счёт приходит тому, кого ядро выберет убить.