Класс приоритета задаёт, какие поды важнее при нехватке ресурсов на узле. Можно назначить класс в манифесте Deployment, проверить вытеснение и понять, почему под застрял в Pending.
Использование класса приоритета в Deployment
В Deckhouse Kubernetes Platform (DKP) уже есть набор классов приоритета. Далее приведён пример использования класса приоритета в шаблоне пода в Deployment.
Создайте файл deployment-with-priority.yaml, чтобы развернуть приложение с предустановленным в DKP классом production-high (значение 9000, в разделе «Доступные классы приоритета»):
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
# Указание имени класса приоритета.
priorityClassName: production-high
containers:
- name: app
image: nginx:latest
ports:
- containerPort: 80
Примените манифест в кластере:
d8 k apply -f deployment-with-priority.yaml
Проверьте приоритеты созданных подов:
d8 k get pods -l app=my-app -o custom-columns=NAME:.metadata.name,CLASS:.spec.priorityClassName,PRIORITY:.spec.priority
Пример вывода:
NAME CLASS PRIORITY
my-app-7d8f9b5c4f-2xq9t production-high 9000
my-app-7d8f9b5c4f-4p7k2 production-high 9000
my-app-7d8f9b5c4f-9w5r8 production-high 9000
Параметр priorityClassName нельзя изменить у работающего пода. Это поле является неизменяемым. Чтобы сменить приоритет, обновите шаблон в Deployment и пересоздайте поды.
Пошаговая демонстрация вытеснения
В этом примере показано, как планировщик вытесняет под с более низким приоритетом, когда на узле не хватает ресурсов для нового пода с более высоким приоритетом. Сначала на worker-узле запускается под с классом develop (1000), который занимает большую часть свободных ресурсов. Затем создаётся под с классом production-high (9000) с такими же requests — места на узле уже нет, и планировщик вытесняет под с низким приоритетом в пользу нового.
В примерах вместо worker-0 укажите имя своего worker-узла.
Пример рассчитан на один worker-узел с 4 CPU и 8 Gi памяти. Если узлов несколько, поды могут разместиться на разных узлах, и вытеснения не будет. Подберите requests так, чтобы под с низким приоритетом занял большую часть свободных ресурсов узла.
Проверьте список узлов в кластере:
d8 k get nodes
Пример вывода:
NAME STATUS ROLES AGE VERSION
master-0 Ready control-plane,master 14d v1.34.9
worker-0 Ready worker 14d v1.34.9
Проверьте ресурсы на worker-узле:
d8 k describe node worker-0 | grep -E "Capacity|Allocatable|Allocated" -A 5
Пример вывода:
Capacity:
cpu: 4
memory: 8174932Ki
Allocatable:
cpu: 3800m
memory: 7174932Ki
Allocated resources:
cpu: 1200m (31%)
memory: 2Gi (28%)
Создайте под с низким приоритетом, который займёт значительную часть свободных ресурсов, а затем под с высоким приоритетом, которому не хватит оставшегося места.
Создайте файл low-priority-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: low-priority-pod
spec:
priorityClassName: develop
containers:
- name: app
image: nginx
resources:
requests:
cpu: "2"
memory: "4Gi"
Примените манифест:
d8 k apply -f low-priority-pod.yaml
Дождитесь, пока под перейдёт в статус Running:
d8 k get pods low-priority-pod
Пример вывода:
NAME READY STATUS RESTARTS AGE
low-priority-pod 1/1 Running 0 10s
Создайте файл high-priority-pod.yaml, чтобы запустить под с высоким приоритетом, который запросит больше ресурсов, чем осталось свободно на узле:
apiVersion: v1
kind: Pod
metadata:
name: high-priority-pod
spec:
priorityClassName: production-high
containers:
- name: app
image: nginx
resources:
requests:
cpu: "2"
memory: "4Gi"
Примените манифест:
d8 k apply -f high-priority-pod.yaml
Проверьте статус подов:
d8 k get pods | grep priority
Пример вывода:
NAME READY STATUS RESTARTS AGE
high-priority-pod 1/1 Running 0 5s
Под high-priority-pod находится в статусе Running. Под low-priority-pod может отсутствовать в выводе, так как планировщик вытеснил его за считанные секунды. В некоторых случаях можно увидеть low-priority-pod в статусе Pending.
Проверьте события вытеснения:
d8 k get events -A --field-selector reason=Preempted
Пример вывода:
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
default 68s Normal Preempted pod/low-priority-pod Preempted by pod d9d25b95-4a7d-4214-8a30-8ce1fd616f67 on node worker-0
Разделение окружений по приоритетам
Неймспейс сам по себе не влияет на вытеснение: планировщик сравнивает только классы приоритета подов, независимо от того, в каких неймспейсах они находятся. Чтобы защитить production-нагрузку, в тестовых и develop-окружениях указывайте более низкий класс приоритета (develop, staging), а в production — более высокий (production-low, production-medium, production-high).
Защита Stateful-приложений
Stateful-приложения (с сохранением состояния, например базы данных, очереди сообщений) хранят данные в памяти или в постоянных томах (PVC). Их защита требует особого подхода, так как внезапное уничтожение пода без корректного завершения работы может повредить данные, а массовое вытеснение реплик приводит к потере доступности сервиса.
Подробнее о механизмах защиты от вытеснения смотрите в разделе «Защита пода от вытеснения». В подразделе «Демонстрация защиты Stateful-приложения» показан практический сценарий, в котором эти механизмы работают вместе.
Для защиты Stateful-приложений в данном примере используется комбинация трёх механизмов:
- Высокий
priorityClassName— в примере у StatefulSet указанproduction-medium(6000). Для реальных критичных Stateful-приложений обычно берутproduction-high(9000) и выше, см. раздел «Доступные классы приоритета». Так поды реже становятся кандидатами на вытеснение. - PodDisruptionBudget (PDB) гарантирует минимальное количество работающих реплик (например,
minAvailable: 5). - Параметр
terminationGracePeriodSecondsзадаёт время на запись данных на диск и закрытие транзакций перед завершением пода (рекомендуется 30–60 секунд).
Демонстрация защиты Stateful-приложения
В этом примере разворачивается учебное Stateful-приложение: ему назначается класс production-medium (6000) вместе с PodDisruptionBudget и terminationGracePeriodSeconds, чтобы ограничить масштаб вытеснения и дать подам время корректно завершить работу. Затем создаётся под emergency-task с более высоким классом production-high (9000) и большим запросом памяти — чтобы смоделировать критическую нехватку ресурсов на узле и проверить, как срабатывают механизмы защиты.
Шаг 1. Создание защищённого StatefulSet с PDB
Создайте файл stateful-protect.yaml:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mock-stateful
spec:
serviceName: mock-stateful
replicas: 7
selector:
matchLabels:
app: mock-stateful
template:
metadata:
labels:
app: mock-stateful
spec:
priorityClassName: production-medium
terminationGracePeriodSeconds: 30
containers:
- name: app
image: busybox
command:
- sh
- -c
- |
trap 'echo ">>> НАЧАЛО: Сохранение данных на диск..."; sleep 10; echo ">>> КОНЕЦ: Данные сохранены, выход."' TERM
echo "Приложение запущено и работает..."
while true; do sleep 1; done
resources:
requests:
cpu: "100m"
memory: "256Mi"
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: mock-stateful-pdb
spec:
minAvailable: 5
selector:
matchLabels:
app: mock-stateful
Примените конфигурацию:
d8 k apply -f stateful-protect.yaml
Дождитесь статуса Running для всех 7 подов:
d8 k get pods -l app=mock-stateful -w
Шаг 2. Симуляция нехватки ресурсов
Создайте файл emergency-task.yaml, чтобы запустить под с предустановленным классом production-high (значение 9000), который выше, чем у Stateful-приложения (production-medium, значение 6000):
apiVersion: v1
kind: Pod
metadata:
name: emergency-task
spec:
priorityClassName: production-high
containers:
- name: task
image: busybox
command: ["sleep", "infinity"]
resources:
requests:
cpu: "1"
memory: "5Gi"
Примените манифест:
d8 k apply -f emergency-task.yaml
Шаг 3. Наблюдение за работой механизмов защиты
Проверьте статус подов:
d8 k get pods | grep -E 'mock-stateful|emergency-task'
Пример вывода:
NAME READY STATUS RESTARTS AGE
emergency-task 0/1 Pending 0 5s
mock-stateful-0 1/1 Terminating 0 55s
mock-stateful-1 1/1 Terminating 0 53s
mock-stateful-2 1/1 Running 0 51s
mock-stateful-3 1/1 Running 0 49s
mock-stateful-4 1/1 Running 0 47s
mock-stateful-5 1/1 Running 0 45s
mock-stateful-6 1/1 Terminating 0 43s
Логи вытесняемых подов доступны только пока под находится в статусе Terminating. Чтобы увидеть процесс корректного завершения работы, необходимо успеть выполнить команду d8 k logs до полного удаления пода.
Проверьте логи завершающегося пода:
d8 k logs mock-stateful-0 --tail=20
Пример вывода:
Приложение запущено и работает...
>>> НАЧАЛО: Сохранение данных на диск...
>>> КОНЕЦ: Данные сохранены, выход.
Как работают механизмы защиты в критической ситуации
Поскольку приоритет созданного в «Демонстрации защиты Stateful-приложения» пода emergency-task (9000, класс production-high) выше, чем у Stateful-приложения (6000, класс production-medium), и других кандидатов на вытеснение нет, планировщик вынужден выбрать Stateful-приложение для вытеснения. При этом срабатывают защитные механизмы:
- В данном случае PodDisruptionBudget пытается ограничить масштаб ущерба, но поскольку запрос экстремально велик, планировщик нарушает PDB, хоть и пытается минимизировать количество удаляемых подов.
- Параметр
terminationGracePeriodSecondsгарантирует сохранность данных, даже в случае полного вытеснения.
Эксплуатация и диагностика
В этом разделе приведены практические команды для проверки состояния подов и событий планировщика. Описание причин проблем и возможных действий на уровне кластера приведено в административном разделе «Эксплуатация и диагностика».
В «Пошаговой демонстрации вытеснения» под high-priority-pod переходит в Running. В следующих подразделах тот же под разбирается в двух случаях, когда он остаётся в Pending: не хватает CPU или памяти, и планировщик не находит поды для вытеснения.
Под не запускается из-за нехватки ресурсов
Если в кластере нет свободных ресурсов и вытеснение невозможно, под останется в состоянии Pending.
Проверьте статус пода:
d8 k get pod high-priority-pod
Пример вывода:
NAME READY STATUS RESTARTS AGE
high-priority-pod 0/1 Pending 0 2m
Проверьте события пода:
d8 k describe pod high-priority-pod | grep -A10 "Events:"
Ищите в разделе Events сообщения вида:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m default-scheduler 0/3 nodes are available: 2 Insufficient cpu, 1 Insufficient memory. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
Что означают эти сообщения:
Insufficient cpuилиInsufficient memory— на узлах не хватает запрошенных ресурсов, соответственно CPU и память.Preemption is not helpful for scheduling— вытеснение существующих подов не освободит достаточно ресурсов (например, все поды имеют равный или более высокий приоритет).
Проверьте доступные ресурсы на узлах:
d8 k describe nodes | grep -A 5 "Allocated resources"
Пример вывода:
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 3800m (95%) 4200m (105%)
memory 7Gi (87%) 8Gi (100%)
Возможные решения:
- Уменьшите
requestsв манифесте пода. - Добавьте ресурсы в кластер (новые узлы или увеличение существующих).
- Удалите неиспользуемые поды.
Практическая диагностика невозможности вытеснения
Если вытеснение подов с низким приоритетом не происходит, хотя ресурсы свободны, проверьте события с причиной FailedPreemption:
d8 k get events -A --field-selector reason=FailedPreemption --sort-by='.metadata.creationTimestamp'
Пример вывода:
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
default 30s Warning FailedPreemption pod/high-priority-pod no preemption victims found for pod
Сообщение no preemption victims found for pod обычно значит, что на узле нет подов с более низким приоритетом — их нельзя вытеснить ради нового пода. Разбор такого случая — в разделе «Практическая проверка отсутствия подходящих подов».
Под может не запуститься и по причинам, не связанным с классом приоритета: например, нет подходящих узлов из-за taint и tolerations. Тогда в событиях будет FailedScheduling с сообщениями вроде untolerated taint(s), а не FailedPreemption.
Возможные действия на уровне кластера смотрите в разделе «Эксплуатация и диагностика».
Практическая проверка отсутствия подходящих подов
В этом примере используется состояние кластера после «Демонстрации защиты Stateful-приложения». Под mock-stateful-0 имеет приоритет 6000. На целевом узле остальные поды mock-stateful имеют такой же приоритет, emergency-task — более высокий (9000), а системные поды — ещё более высокий. Поэтому планировщик не может вытеснить ни один из них ради запуска mock-stateful-0: подходящих подов с более низким приоритетом на узле нет.
Если в событиях есть сообщение No preemption victims found for incoming pod, проверьте приоритеты подов на узле:
d8 k get pods --all-namespaces -o wide --field-selector spec.nodeName=worker-0 -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,PRIORITY:.spec.priority --sort-by=.spec.priority
Пример вывода:
NAME NODE PRIORITY
mock-stateful-5 worker-0 6000
mock-stateful-2 worker-0 6000
mock-stateful-3 worker-0 6000
mock-stateful-4 worker-0 6000
emergency-task worker-0 9000
multitenancy-manager-5968799d76-ktjgl worker-0 2000000000
csi-node-s82x4 worker-0 2000001000
agent-wqzxq worker-0 2000001000
early-oom-6tkzg worker-0 2000001000
safe-agent-updater-ntrzq worker-0 2000001000
kubernetes-api-proxy-worker-0 worker-0 2000001000
node-exporter-cddjm worker-0 2000001000
oom-kills-exporter-pplfk worker-0 2000001000
Проверьте точную причину, почему mock-stateful-0 не смог вытеснить другие поды:
d8 k describe pod mock-stateful-0 | grep -A10 "Events:"
Пример сообщения из событий пода:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 13s default-scheduler 0/2 nodes are available: preemption: 0/2 nodes are available: 1 No preemption victims found for incoming pod
| Сообщение | Значение |
|---|---|
0/2 nodes are available |
В кластере есть 2 узла, но ни один не подходит для размещения пода. |
1 No preemption victims found for incoming pod |
На узле с нехваткой памяти нет подов с более низким приоритетом, которые можно было бы вытеснить. |
Что делать на уровне кластера, смотрите в разделе «Эксплуатация и диагностика».
Практический пример «Лимит подов на узле»
Даже если освободились CPU и память, лимит на максимальное количество подов на узле может помешать запуску пода с высоким приоритетом.
Проверьте лимит подов на узле:
d8 k describe node worker-0 | grep pods -A2
Пример вывода:
Capacity:
pods: 120
Проверьте текущее количество подов на узле:
d8 k get pods --all-namespaces -o wide | grep worker-0 | wc -l
Пример вывода:
64
Сейчас на узле 64 пода из 120. Свободное место ещё есть.
Создайте файл pod-filler.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-filler
spec:
replicas: 110
selector:
matchLabels:
app: filler
template:
metadata:
labels:
app: filler
spec:
priorityClassName: develop
containers:
- name: filler
image: busybox
command: ["sleep", "infinity"]
resources:
requests:
cpu: "1m"
memory: "5Mi"
Примените манифест:
d8 k apply -f pod-filler.yaml
Дождитесь, пока Deployment заполнит узел:
d8 k get pods -l app=filler -o wide | grep worker-0 | wc -l
Создайте файл high-priority-limit-pod.yaml, чтобы запустить под с высоким приоритетом:
apiVersion: v1
kind: Pod
metadata:
name: high-priority-limit-pod
spec:
priorityClassName: production-high
containers:
- name: app
image: nginx
resources:
requests:
cpu: "100m"
memory: "256Mi"
Примените манифест:
d8 k apply -f high-priority-limit-pod.yaml
Проверьте статус пода с высоким приоритетом:
d8 k get pod high-priority-limit-pod
Пример вывода:
NAME READY STATUS RESTARTS AGE
high-priority-limit-pod 0/1 Pending 0 11s
Посмотрите причину в событиях:
d8 k describe pod high-priority-limit-pod | grep -A10 "Events:"
Пример вывода:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 11s default-scheduler 0/2 nodes are available: 1 Too many pods, 1 node(s) had untolerated taint(s).
Проверьте, что вытеснения не произошло:
d8 k get events -A --field-selector reason=Preempted
Пример вывода:
No resources found
Суть проблемы: лимит подов на узле уже достигнут, поэтому даже под с высоким приоритетом не может запуститься. Вытеснение существующих подов не помогает, так как количество подов останется прежним (вытесненный под будет заменён новым).
Полезные команды для мониторинга приоритетов
Подсчёт количества подов по классам приоритета:
d8 k get pods -A -o jsonpath='{range .items[*]}{.spec.priorityClassName}{"\n"}{end}' | sort | uniq -c | sort -rn
Пример вывода:
34 cluster-medium
30 cluster-low
26 system-cluster-critical
18 system-node-critical
6 production-high
Просмотр событий вытеснения во всех неймспейсах:
d8 k get events -A --field-selector reason=Preempted -o custom-columns=NAMESPACE:.metadata.namespace,POD:.involvedObject.name,MESSAGE:.message
Пример вывода:
NAMESPACE POD MESSAGE
d8-chrony chrony-master-9wzbl Preempted by pod ac651aed-... on node master-0
d8-console backend-58f9989c9d-4svjw Preempted by pod ac651aed-... on node master-0
d8-monitoring prometheus-main-0 Preempted by pod 91f6e071-... on node worker-0
default log-collector-dlxpv Preempted by pod 91f6e071-... on node worker-0
События хранятся ограниченное время (обычно около часа). Если вытеснения давно не было, эти команды могут ничего не вернуть — повторите демонстрацию вытеснения и выполните команды сразу после неё.
Поды, которые вытеснялись чаще всего:
d8 k get events -A --field-selector reason=Preempted -o jsonpath='{range .items[*]}{.involvedObject.name}{"\n"}{end}' | sort | uniq -c | sort -rn | head -10
Пример вывода:
2 prometheus-main-0
2 memcached-0
1 user-api-77494dc777-jzp7p
1 upmeter-dex-authenticator-7f54c8dfb4-wwv22
1 upmeter-dex-authenticator-7f54c8dfb4-wqfn5
1 upmeter-dex-authenticator-7f54c8dfb4-h6784
1 upmeter-dex-authenticator-7f54c8dfb4-bxsvk
1 upmeter-dex-authenticator-7f54c8dfb4-28fgt
1 upmeter-agent-4chrw
1 status-dex-authenticator-786c6cc554-mfsdw