Панель Grafana, которая говорит, когда под упадёт по OOM
Как превратить тренд памяти в ETA до OOM и до заполнения диска на PromQL, два бага, из-за которых панель врала, и утечка, которую она поймала в первый же день.
В Gambchamp я отвечаю за мультитенантную продуктовую платформу на AWS EKS: все кластеры пишут метрики в один VictoriaMetrics, перед которым стоит общая Grafana. Дашборды golden signals показывают, что сломано сейчас. Мне был нужен дашборд, который отвечает на другой вопрос: если тренд сохранится, когда у этого пода кончится память и когда заполнится этот том?
Формула
Основа — одна строка:
(limit - current) / clamp_min(deriv(usage[6h]), 1)
deriv строит прямую по точкам окна и возвращает её наклон в байтах в секунду. Оставшийся запас, делённый на этот наклон, — секунды до лимита. Важны два окна: 6 часов ловят острый рост, 24 часа ловят медленные утечки, которые прячет шум короткого окна.
clamp_min(..., 1) делает две вещи. Защищает от деления на ноль и превращает уменьшающийся ряд в огромный ETA вместо отрицательного. Без этого таблица забивается -Inf и отрицательным мусором. Пороги в таблице идут от меньшего к большему: красный меньше 24 часов, оранжевый до 72 часов, зелёный дальше. Единица s сама форматируется в дни и часы.
Та же идея легла и на managed-базы: свободное место, делённое на скорость, с которой оно уходит, даёт ETA до заполнения диска.
Утечка в первый же день
При первой отрисовке панель подсветила один admin-бэкенд с ETA ноль часов. Я проверил: его убивал OOMKill с кодом 137, около 900 Mi при лимите 1 Gi, шесть рестартов. CronJob перезапускал его по расписанию, пряча симптом и ничего не исправляя.
Баг первый: отрицательный ETA у подов без лимитов
Часть бэкендов задавала лимит памяти только сайдкару, а не основному контейнеру. Тогда sum(limits) - sum(used) на уровне пода вычитает потребление всего пода из лимита сайдкара, уходит в минус, и панель красит совершенно стабильный под в красный. Исправление: считать по контейнерам с inner join, чтобы контейнеры без лимита выпадали, и оставлять только ряды с реально положительным наклоном.
Баг второй: двойной счёт в cAdvisor
На этой инсталляции cAdvisor ставит одинаковое значение container на каждый уровень cgroup пода: cgroup самого пода, каждый контейнер и pause-контейнер. sum by(pod) складывает cgroup пода, который уже и есть сумма, с контейнерами, и все панели памяти и CPU удвоились. Один под показывал 156 % лимита при реальных 79 %. Правило, которому я теперь следую: max by(pod) для метрик cAdvisor, sum by(pod) для лимитов и реквестов из kube-state.
Чего прогноз не видит
Тренд ловит только монотонные утечки. Под, который уже ходит по кругу «рост, OOMKill, рестарт», имеет наклон регрессии около нуля, и прогноз правильно его не показывает. Такие поды видны на панелях рестартов и процента памяти. Пустая таблица прогноза означает «сейчас нет монотонной утечки», а не «всё здорово».
Ещё две ловушки той же недели. Для рестартов читай сырой счётчик, а не increase[1h]: пересозданный под начинает новый ряд с нуля, и increase прячет шторм рестартов ровно на том поде, который тебе нужен. И автомасштаб оси Y врёт на больших медленных значениях: том базы выглядел так, будто падает с обрыва, а изменение было 15 MB из 163 GiB, ETA почти семь лет.
Что вынести
- Оставшийся запас, делённый на
deriv, — рабочий ETA, но наклон надо ограничивать снизу и оставлять только растущие ряды. - Метрики cAdvisor и kube-state агрегируются по-разному; проверь один под руками, прежде чем верить любому
sum by(pod). - Трендовый прогноз и счётчик рестартов ловят разные классы отказов. Нужны оба.
- Медленную ёмкость оценивай по абсолютному числу и ETA, а не по крутизне линии.