Задача
На ноде заканчивается место на диске. Kubelet начинает выселять поды со статусом Evicted. Среди выселенных — поды, которые вообще ничего не пишут на диск.
Вопрос: почему выселяют «невиновных» и по какому принципу выбирается жертва?
Что кажется очевидным
«Должны выселять того, кто занял диск». Логично, но kubelet устроен иначе — и понимание этого меняет подход к настройке.
Как это работает на самом деле
Kubelet следит за ресурсами ноды и при пересечении пороговых значений начинает вытеснение (eviction), чтобы спасти саму ноду. Отслеживаются:
memory.available— свободная память;nodefs.available— место на файловой системе ноды;imagefs.available— место под образы контейнеров;nodefs.inodesFree— свободные inode.
Пороги по умолчанию — примерно так:
memory.available < 100Mi
nodefs.available < 10%
imagefs.available < 15%
nodefs.inodesFree < 5%Различают жёсткие пороги (вытеснение немедленно) и мягкие (вытеснение после evictionSoftGracePeriod, с корректным завершением пода).
Как выбирается жертва
Порядок такой:
- Сначала — превысившие свои
requests. Под, потребляющий меньше, чем запросил, считается «в рамках договора» и защищён. - Затем — по классу QoS: сначала
BestEffort, потомBurstable, в последнюю очередьGuaranteed(см. задачу 3). - Внутри группы — по приоритету (
PriorityClass) и величине превышения.
Вот и ответ на «почему невиновные»: если под относится к BestEffort (нет requests), он будет выселен раньше того, кто реально забил диск, но имеет Guaranteed. Kubelet спасает ноду, а не ищет виноватого.
Что считается за под при дисковом давлении
Для дисковых порогов kubelet учитывает:
- логи контейнеров (то, что пишется в stdout/stderr и складывается в файлы на ноде);
emptyDir-тома;- записываемый слой контейнера — всё, что приложение пишет внутрь контейнера мимо томов.
Отсюда и типовые виновники: приложение, пишущее логи в файл внутри контейнера; забытый emptyDir без лимита; скачанные во временный каталог файлы.
Постоянные тома (PV) в этот счёт не входят — они живут отдельно.
Eviction против OOMKilled
Эти два состояния постоянно путают:
Evicted | OOMKilled | |
|---|---|---|
| Кто инициирует | kubelet | ядро (OOM killer) |
| Причина | Давление на ресурсы ноды | Контейнер превысил свой лимит памяти |
| Что происходит | Под удаляется, планируется заново | Контейнер перезапускается на месте |
| Виден в | status.phase: Failed, reason Evicted | lastState.terminated.reason: OOMKilled |
| Лечение | Ресурсы ноды, requests | Лимит контейнера, утечка |
Подробнее про второе — в задаче 5.
Ответ
Kubelet выселяет не «виновного», а того, кого дешевле и безопаснее выселить: сначала поды, превысившие свои requests, затем по классам QoS начиная с BestEffort. Под без requests, ничего не писавший на диск, окажется первым кандидатом — просто потому, что он ничего не запросил и потому ничем не защищён.
Что с этим делать
Диагностика
# Состояние ноды: ищем условия давления
kubectl describe node node-1 | grep -A8 Conditions
# MemoryPressure False
# DiskPressure True ← вот оно
# PIDPressure False
# Выселенные поды по кластеру
kubectl get pods -A --field-selector status.phase=Failed
# Причина по конкретному поду
kubectl describe pod evicted-pod | grep -A5 "Status\|Message"
# Что занимает место на ноде
kubectl debug node/node-1 -it --image=busybox -- df -h /hostЗащита рабочих нагрузок
- Задавайте
requestsвсем подам. Это одновременно и место в очереди на выселение, и корректное планирование.BestEffortв проде — почти всегда ошибка. - Критичным сервисам —
Guaranteed(requests=limits) и высокийPriorityClass. - Логи только в stdout/stderr, и обязательно с ротацией на уровне контейнерного рантайма (
containerLogMaxSize,containerLogMaxFiles). Приложение, пишущее логи в файл внутри контейнера, — самая частая причинаDiskPressure. - Ограничивайте
emptyDir:Без лимита такой том способен занять весь диск ноды. С лимитом будет выселен только сам нарушитель.YAMLvolumes: - name: cache emptyDir: sizeLimit: 1Gi - Используйте
ephemeral-storageв ресурсах, если под работает с временными файлами:YAMLresources: requests: ephemeral-storage: "2Gi" limits: ephemeral-storage: "4Gi"
Настройка ноды
- Резервируйте ресурсы под систему и kubelet (
--system-reserved,--kube-reserved), чтобы давление не убивало саму ноду. - Чистка образов происходит автоматически при достижении
imagefsпорогов, но узлы, где крутится много разных образов, стоит держать с запасом по диску. - Мониторьте предвестники: заполнение диска ноды,
MemoryPressure/DiskPressureв условиях ноды, количествоEvicted-подов. Алерт на 80% заполнения даёт время среагировать до начала выселений. - Прибирайте выселенные поды. Объекты
Evictedостаются в API и засоряют вывод:BASHkubectl delete pods -A --field-selector status.phase=Failed