Evicted: почему kubelet выселяет поды и что значит «The node was low on resource»
Команда KAI Lab · · 8 мин чтения
Статус Evicted значит, что kubelet сам остановил под, чтобы спасти ноду: на ней заканчивалась память, место на диске, inode или идентификаторы процессов. Под переходит в фазу Failed и на этой ноде больше не запускается, а Deployment или StatefulSet создаёт замену; первыми выселяются поды, которые потребляют больше своих requests.
Выселение — защитный механизм, а не сбой Kubernetes. Но частые выселения значат, что на нодах кончаются ресурсы, и следующим шагом может стать нода, которая перестала отвечать. Разберём, как прочитать причину, найти виновника и настроить кластер так, чтобы выселения были редкостью.
Что такое node-pressure eviction
kubelet следит за ресурсами ноды: памятью, местом и inode на файловых системах, числом процессов. Когда ресурс опускается ниже порога, kubelet сначала пытается освободить его сам — удаляет завершённые контейнеры и неиспользуемые образы. Если не помогло, он выбирает поды, переводит их в фазу Failed и останавливает.
Важно отличать это от выселения через API, которое делает kubectl drain:
- при node-pressure eviction kubelet не смотрит на PodDisruptionBudget;
- при жёстком пороге под останавливается сразу, с периодом завершения 0 секунд, без учёта
terminationGracePeriodSeconds; - при мягком пороге kubelet ждёт заданное время и даёт поду не больше
eviction-max-pod-grace-period.
Пороги по умолчанию для жёсткого выселения из документации Kubernetes:
| Сигнал | Порог по умолчанию | Что значит |
|---|---|---|
memory.available | меньше 100Mi (Linux) | свободная память ноды |
nodefs.available | меньше 10% | место на основной файловой системе, где /var/lib/kubelet и логи |
imagefs.available | меньше 15% | место на файловой системе образов, если она отдельная |
nodefs.inodesFree | меньше 5% (Linux) | свободные inode на основной файловой системе |
imagefs.inodesFree | меньше 5% (Linux) | свободные inode на файловой системе образов |
Память kubelet считает по cgroup, а не по free -m, поэтому цифры могут не совпадать с тем, что вы видите в top. Проверка порогов идёт с интервалом housekeeping-interval, по умолчанию раз в 10 секунд.
Как выглядит Evicted
$ kubectl get pods -n shop
NAME READY STATUS RESTARTS AGE
report-worker-6d8f7c9b5-4xk2p 0/1 Evicted 0 3h
report-worker-6d8f7c9b5-9m2wq 1/1 Running 0 2mВыселенный под остаётся в списке как объект в фазе Failed, рядом появляется замена. В новых версиях Kubernetes в колонке STATUS такой под может показываться как Error или ContainerStatusUnknown: kubectl выводит причину завершения контейнера поверх причины пода. Надёжнее смотреть поле .status.reason и сообщение:
$ kubectl describe pod report-worker-6d8f7c9b5-4xk2p -n shop
...
Status: Failed
Reason: Evicted
Message: The node was low on resource: memory. Threshold quantity: 100Mi, available: 87604Ki. Container worker was using 1823952Ki, request is 256Mi, has larger consumption of memory.Все выселенные поды кластера с нодами:
$ kubectl get pods -A --field-selector=status.phase=Failed \
-o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,REASON:.status.reason,NODE:.spec.nodeName
NAMESPACE NAME REASON NODE
shop report-worker-6d8f7c9b5-4xk2p Evicted worker-3Как читать сообщение
Сообщение собирает kubelet, и в нём три части:
The node was low on resource: memory— какой ресурс кончился:memory,ephemeral-storage(место на диске),inodesилиpids;Threshold quantity: 100Mi, available: 87604Ki— порог и сколько было свободно в момент решения;Container worker was using 1823952Ki, request is 256Mi— какой контейнер потреблял больше своего request. Такие контейнеры kubelet выселяет в первую очередь.
Если части про контейнер нет, под сам ничего лишнего не потреблял: его выселили по приоритету, когда более очевидных кандидатов не осталось.
Бывают выселения и без давления на ноду: kubelet выселяет под, который превысил собственный лимит диска. Тогда в сообщении Container worker exceeded its local ephemeral storage limit "2Gi". или Usage of EmptyDir volume "cache" exceeds the limit "500Mi". Нода в порядке, виноват конкретный под.
Пошаговая диагностика
Шаг 1. Состояние ноды
$ kubectl describe node worker-3
...
Conditions:
Type Status Reason Message
---- ------ ------ -------
MemoryPressure False KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure True KubeletHasDiskPressure kubelet has disk pressure
PIDPressure False KubeletHasSufficientPID kubelet has sufficient PID available
Ready True KubeletReady kubelet is posting ready status
Taints: node.kubernetes.io/disk-pressure:NoSchedule
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning EvictionThresholdMet 3m kubelet Attempting to reclaim ephemeral-storageКолонки с временем мы опустили. DiskPressure True и taint node.kubernetes.io/disk-pressure:NoSchedule значат, что новые поды на эту ноду не планируются: вот откуда берутся соседние поды в Pending. Состояние меняется обратно не сразу: kubelet выжидает eviction-pressure-transition-period, по умолчанию 5 минут, чтобы нода не мигала.
Шаг 2. Кто съел память
$ kubectl top pod -A --sort-by=memoryСравните потребление с requests. Под без requests относится к классу BestEffort и первым идёт на выселение, как и Burstable поды, которые потребляют больше запрошенного. QoS-класс пода показывает kubectl get pod ИМЯ -o jsonpath='{.status.qosClass}'.
Если память растёт быстрее, чем kubelet успевает её заметить, раньше выселения сработает OOM killer ядра, и вместо Evicted вы увидите OOMKilled. Документация Kubernetes описывает это как известную проблему. Чем они отличаются, мы разобрали в статье OOMKilled и exit code 137.
Шаг 3. Кто съел диск
На самой ноде:
$ df -h /var/lib/kubelet /var/lib/containerd
$ df -i /var/lib/kubelet
$ sudo du -sh /var/log/pods/* | sort -h | tail -5
$ sudo du -sh /var/lib/kubelet/pods/*/volumes/kubernetes.io~empty-dir/* | sort -h | tail -5
$ sudo crictl imagesТипичные источники: временные файлы, которые приложение пишет в свой слой контейнера или в emptyDir; накопившиеся образы; логи, которые приложение пишет в файлы внутри контейнера. Логи stdout и stderr kubelet ротирует сам: по умолчанию до 10Mi на файл и 5 файлов на контейнер. Образы kubelet начинает чистить, когда диск занят на 85%, и чистит до 80%. Неиспользуемые образы можно убрать и вручную: sudo crictl rmi --prune.
Если нода работает на Docker Engine или вы видите ту же проблему на обычном сервере с Docker, разбор команд — в статье no space left on device в Docker.
Шаг 4. Почему выбрали именно этот под
kubelet сортирует кандидатов по трём признакам: превышает ли потребление requests, приоритет пода и насколько потребление больше requests. Первыми уходят BestEffort и Burstable поды, вышедшие за свои requests. Последними — Guaranteed и Burstable поды, которые укладываются в requests. При нехватке диска kubelet выбирает поды по занятому месту: локальные тома, логи и слой контейнера.
Причины Evicted: таблица
| Причина | Как узнать | Как исправить |
|---|---|---|
| На ноде кончается память, под ест больше requests | low on resource: memory, Container … was using …, request is … | requests по реальному потреблению, limits, больше памяти на нодах |
| Диск ноды забит образами, логами, данными подов | low on resource: ephemeral-storage, DiskPressure True | очистка образов, ротация логов приложения, requests и limits на ephemeral-storage |
| Кончились inode | low on resource: inodes | найти каталог с миллионами мелких файлов и чистить его |
| Под превысил свой лимит диска | exceeded its local ephemeral storage limit | поднять лимит или писать данные в PersistentVolume |
| emptyDir вырос больше sizeLimit | Usage of EmptyDir volume … exceeds the limit … | чистить временные файлы, пересмотреть sizeLimit |
| Кончились идентификаторы процессов | low on resource: pids, PIDPressure True | найти утечку процессов или потоков, задать лимит PID на под |
Как не допускать выселений
- Задавайте requests по реальному потреблению. Под без requests — первый кандидат на выселение.
- Критичным сервисам ставьте requests равными limits по памяти и CPU: класс
Guaranteedвыселяется последним. Для ещё большей защиты — PriorityClass. - Задавайте
requestsиlimitsдляephemeral-storageиsizeLimitдляemptyDir, если под пишет временные файлы. - Не отдавайте подам всю ноду: резервы
system-reservedиkube-reservedоставляют память системе и kubelet. - Мониторьте состояния нод
MemoryPressure,DiskPressure,PIDPressureи заполненность дисков, а не только выселения. - Меняйте пороги осторожно. По документации, если задать в
evictionHardтолько один порог, остальные станут нулевыми, а не останутся по умолчанию. Чтобы они унаследовали значения по умолчанию, в конфигурации kubelet включаютMergeDefaultEvictionSettings.
Выселенные поды остаются объектами, чтобы их можно было изучить. Удалить их в пространстве имён можно одной командой, но она удалит и упавшие поды Job, которые вы, возможно, ещё не разобрали:
$ kubectl delete pods -n shop --field-selector=status.phase=FailedСборщик завершённых подов в kube-controller-manager удаляет их сам, когда их больше порога --terminated-pod-gc-threshold, по умолчанию 12 500.
Частые вопросы
Evicted под перезапустится? Сам этот под — нет, он остаётся в фазе Failed. Если под создан Deployment, StatefulSet или другим контроллером, тот создаст замену, и планировщик разместит её там, где хватает ресурсов. Под, созданный вручную без контроллера, просто исчезнет из работы.
Почему выселили мой под, а не тот, кто съел всю память? kubelet сначала выбирает поды, которые превышают свои requests. Если у «жадного» пода requests большие, а у вашего requests нет вовсе, первым уйдёт ваш. Защита — честные requests и нужный приоритет.
Помогает ли PodDisruptionBudget? Нет. PodDisruptionBudget учитывается при выселении через API, например при kubectl drain. При выселении из-за нехватки ресурсов kubelet его не смотрит, как и terminationGracePeriodSeconds при жёстком пороге.
Чем Evicted отличается от OOMKilled? Evicted — решение kubelet по всему поду из-за нехватки ресурсов на ноде; под не перезапускается на этой ноде. OOMKilled — ядро убило контейнер за превышение его собственного лимита памяти, и kubelet перезапускает контейнер в том же поде.
Как понять, что под выселили, если в STATUS написано Error? Посмотрите kubectl describe pod или поле .status.reason: у выселенного пода там Evicted, а в Message — ресурс и порог. У такого пода также есть условие DisruptionTarget с причиной TerminationByKubelet. Вопросы про QoS-классы и порядок выселения регулярно задают на собеседованиях по Kubernetes.
Потренироваться на практике
В симуляторе DevOps KAI Lab месяц про Kubernetes включает работу с ресурсами подов и обслуживание нод, а консоль в браузере отдаёт реальные выводы kubectl describe. Проверить себя можно в бесплатном тесте навыков DevOps.
Источники
- Kubernetes. Node-pressure Eviction — сигналы, пороги по умолчанию, мягкие и жёсткие пороги, порядок выбора подов, состояния нод,
MergeDefaultEvictionSettings, известные проблемы. - Kubernetes. Pod Quality of Service Classes — Guaranteed, Burstable, BestEffort.
- Kubernetes. Taints and Tolerations — taints по состоянию ноды.
- Kubernetes. Disruptions — условие
DisruptionTargetи причинаTerminationByKubelet. - Kubernetes. Kubelet Configuration (v1beta1) —
containerLogMaxSize,containerLogMaxFiles, пороги очистки образов,evictionPressureTransitionPeriod. - Kubernetes. kube-controller-manager —
--terminated-pod-gc-threshold. - Kubernetes, исходный код kubelet: eviction/helpers.go — текст сообщения о выселении; printers.go — как kubectl выбирает значение STATUS.