Pod в Pending из-за CPU
Что означает FailedScheduling, чем requests отличаются от limits и почему нода с 4 ядрами не даст 4 ядра.
Pod создан, но не запускается. Минуту, пять, час - статус не меняется.
NAME READY STATUS RESTARTS AGE
heavy 0/1 Pending 0 2m
Pending означает «объект в API есть, контейнера ещё нет». Между этими
двумя точками стоит scheduler - компонент, который выбирает ноду для
Pod. Если подходящей ноды нет, Pod будет ждать сколько угодно долго,
без ошибок и без рестартов.
Из-за чего Pod застревает в Pending
- Scheduler не нашёл ноду. Не хватает ресурсов, мешают taints, не сошлись affinity или nodeSelector.
- Образ ещё тянется. Долгий pull большого образа.
- Том не нашёлся. Pod просит PVC, который ещё не привязан к PersistentVolume.
- Init-контейнер работает. Pod ждёт его завершения.
Первый пункт - самый частый, и он же самый непонятный на вид: с Pod всё в порядке, просто его некуда положить.
Что показывает диагностика
kubectl describe pod heavy
Главная секция Events. При проблеме со scheduling там видно:
Events:
Type Reason Message
Warning FailedScheduling 0/1 nodes are available: 1 Insufficient cpu.
Формат сообщения всегда одинаковый:
0/X nodes are availableозначает, что ни одна из X нод не подошла.- Дальше идёт причина: что именно не сошлось, и на скольких нодах.
Типичные причины:
| Текст | Что значит |
|---|---|
Insufficient cpu | Сумма requests.cpu существующих Pod плюс новый больше, чем свободно на ноде |
Insufficient memory | То же самое про память |
node(s) had untolerated taint | На нодах есть taint, у Pod нет соответствующего toleration |
node(s) didn't match Pod's node affinity | nodeSelector или affinity не сошёлся |
node(s) didn't have free ports | hostPort уже занят |
pod has unbound immediate PersistentVolumeClaims | PVC не привязан |
Requests против limits
Различие тут решающее.
requests - сколько Pod гарантированно получит. Scheduler смотрит
только на requests, когда выбирает ноду. Сумма requests.cpu всех Pod
на ноде не может превысить allocatable.
limits - потолок, выше которого контейнер не поднимется во время работы. На выбор ноды limits не влияют вообще.
Если Pod просит requests.cpu: 64, scheduler ищет ноду, где свободно
минимум 64 vCPU. В кластере из нод по 4 ядра такой ноды не будет
никогда, и Pod останется в Pending навсегда.
Значение requests больше, чем allocatable самой большой ноды, не
запланируется никогда. Ошибки при этом нет: Pod просто висит в Pending,
пока его не удалят.
Единицы CPU и памяти
CPU:
1или1000mозначает 1 vCPU (одно ядро).100mозначает 0.1 vCPU (десятую часть ядра).2.5означает 2.5 vCPU.
m это millicore, тысячная доля ядра. К мегагерцам отношения не имеет.
Память:
Miэто мебибайт (1024 KiB),Giэто гибибайт (1024 MiB).MиGэто мегабайт и гигабайт по 1000. Использовать можно, но считать в уме сложнее.
Без суффикса для CPU подразумеваются целые ядра, для памяти байты.
Поэтому cpu: 64 читается как «дай мне 64 ядра целиком», а cpu: 64m
это шестьдесят четыре тысячных ядра. Одна буква меняет запрос в тысячу
раз.
Capacity против allocatable
kubectl describe node показывает два разных числа:
Capacity:
cpu: 4
memory: 8Gi
Allocatable:
cpu: 3800m
memory: 7Gi
Capacity - сколько у ноды физически. Allocatable меньше: часть зарезервирована под kubelet, kube-proxy и системные демоны. Scheduler считает по allocatable.
Ниже в том же выводе идёт то, что уже занято:
Allocated resources:
Resource Requests Limits
cpu 500m (13%) 1 (26%)
memory 256Mi (3%) 512Mi (7%)
Свободное место считается как Allocatable - Allocated (Requests).
Если новый Pod просит больше, scheduler отвечает Insufficient.
allocatable 3800m ████████████████████
занято 500m ███
свободно 3300m █████████████████
Pod просит 64000m не влезает
Как чинят
Путей два.
Первый: уменьшить requests до реального потребления. Ориентиры грубые,
но рабочие: 100m для лёгкого сервиса, 500m для среднего, 2 для
тяжёлого. Точные цифры берут из метрик, например из kubectl top pod
на работающей копии.
Второй: добавить ноду. В облаке это делает cluster autoscaler, который как раз реагирует на Pod в состоянии Pending.
Отдельная тонкость: resources у существующего Pod изменить нельзя,
поле иммутабельное. Самостоятельный Pod приходится удалять и применять
заново:
kubectl delete pod heavy
kubectl apply -f heavy.yaml # тот же манифест, но с адекватным requests.cpu
Если Pod создан Deployment, правят шаблон Deployment, и Pod пересоздаются сами.
Полезные команды при разборе:
# Причина в конце describe
kubectl describe pod heavy | tail -10
# Сколько на ноде занято
kubectl describe node | grep -A5 "Allocated resources"
# Сколько просит Pod
kubectl get pod heavy -o jsonpath='{.spec.containers[0].resources}'
Как не допустить
Requests ставят по фактическому потреблению, а не «с запасом на всякий случай». Завышенные requests съедают место на нодах, и кластер упирается в лимиты, будучи загруженным на 20 процентов.
В namespace ставят LimitRange с дефолтными и максимальными значениями:
тогда Pod без requests получит разумные числа, а Pod с cpu: 64
отвалится сразу на admission, с внятной ошибкой вместо тихого Pending.
Коротко
Pendingэто «scheduler ещё не выбрал ноду», ошибка лежит в Events какFailedScheduling.- Формат
0/X nodes are available: <причина>читается буквально. - Scheduler считает только requests, limits на размещение не влияют.
- Allocatable меньше capacity: часть ресурсов ноды забрана под системные компоненты.
resourcesу Pod иммутабельны, самостоятельный Pod пересоздают, Deployment правят через шаблон.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс