Модуль 10 / 19
Requests, limits, QoS, cgroups та OOM
Зміст
1. Цілі та результат модуля
Ви навчитеся задавати обчислювальні ресурси, прогнозувати scheduling і runtime enforcement, читати QoS та доводити OOMKilled через status і cgroup.
- request бере участь у scheduling
- limit обмежує runtime
- CPU вимірюється в cores/millicores
- memory — у bytes із binary suffix
- QoS впливає на поведінку під час resource pressure
2. Теорія: requests і scheduling
Scheduler порівнює суму requests уже розміщених Pods і нового Pod з allocatable Node. Request — основа планування та частки CPU, а не заздалегідь виділений шматок заліза. Фактичне споживання може бути нижчим або вищим за request до limit.
Якщо request більший за доступний allocatable, Pod лишається Pending із FailedScheduling. Scheduler не підміняє requests поточним низьким навантаженням.
3. Теорія: limits і enforcement
CPU limit зазвичай реалізується cgroup quota: процес throttled, але не вбивається. Memory limit — жорстка межа врахованої пам’яті; перевищення може завершити процес через cgroup OOM. Exit code 137 недостатній: перевіряйте terminated.reason=OOMKilled.
Необмежений memory workload може брати участь у node-wide pressure. Limit не замінює навантажувальне тестування та правильне керування пам’яттю.
4. Одиниці та коректні значення
100m CPU — одна десята core; 1 CPU — один logical core/vCPU. Для memory використовуйте Mi/Gi. Суфікс m для пам’яті означає milli-byte: 400m — не 400 MiB. Quantity порівнюються як точні Kubernetes values, а не рядки UI.
5. QoS: Guaranteed, Burstable, BestEffort
Guaranteed вимагає CPU і memory request=limit для кожного контейнера Pod. BestEffort не має CPU/memory requests і limits. Решта Pods — Burstable. Init та додаткові containers враховуються, тому клас перевіряють у status.qosClass.
QoS — один із чинників eviction/OOM під час pressure, а не SLA чи гарантія продуктивності.
6. cgroups v2 і спостереження
Kubelet і runtime переводять limits у cgroups. На стенді використано cgroup v2 та systemd driver: memory.max показує memory limit, cpu.max — quota і period. Requests також впливають на scheduler і CPU weight.
Metrics API — окремий компонент. Якщо metrics-server не встановлено, kubectl top не працює; це не означає відсутність cgroup accounting.
7. Методичні вказівки
- Починайте з профілю реального навантаження
- Порівнюйте request з allocatable та іншими requests
- Розділяйте Pending scheduling і runtime throttling/OOM
- Перевіряйте всі containers, включно з init
- Не діагностуйте OOM лише за 137
- Дивіться status, Events, node pressure та cgroup
- Не ставте limits навмання заради YAML
8. Лабораторна робота: три QoS-класи
Створіть BestEffort, Burstable і Guaranteed Pods та отримайте клас зі status, а не обчислюйте його за ім’ям.
mkdir -p /tmp/k8s-m10
cat > /tmp/k8s-m10/qos.yaml <<'EOF'
apiVersion: v1
kind: Namespace
metadata: {name: k8s-m10}
---
apiVersion: v1
kind: Pod
metadata: {name: besteffort, namespace: k8s-m10}
spec:
containers: [{name: nginx, image: nginx:1.29-alpine}]
---
apiVersion: v1
kind: Pod
metadata: {name: burstable, namespace: k8s-m10}
spec:
containers:
- name: nginx
image: nginx:1.29-alpine
resources:
requests: {cpu: 50m, memory: 32Mi}
limits: {cpu: 100m, memory: 64Mi}
---
apiVersion: v1
kind: Pod
metadata: {name: guaranteed, namespace: k8s-m10}
spec:
containers:
- name: nginx
image: nginx:1.29-alpine
resources:
requests: {cpu: 50m, memory: 32Mi}
limits: {cpu: 50m, memory: 32Mi}
EOF
kubectl apply -f /tmp/k8s-m10/qos.yaml || true
kubectl apply --dry-run=server -f /tmp/k8s-m10/qos.yaml
kubectl apply -f /tmp/k8s-m10/qos.yaml
kubectl -n k8s-m10 wait --for=condition=Ready pod/besteffort pod/burstable pod/guaranteed --timeout=180s
kubectl -n k8s-m10 get pod -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass,CPU-REQUEST:.spec.containers[0].resources.requests.cpu,MEMORY-LIMIT:.spec.containers[0].resources.limits.memory9. Лабораторна робота: cgroup v2
Усередині Burstable container очікуються memory.max=67108864 і cpu.max приблизно 10000 100000. kubectl top на чистому стенді очікувано повідомляє про відсутність Metrics API.
stat -fc %T /sys/fs/cgroup
sudo grep -E '^cgroupDriver:' /var/lib/kubelet/config.yaml
kubectl -n k8s-m10 exec burstable -- sh -c '
printf "memory.max="; cat /sys/fs/cgroup/memory.max
printf "memory.current="; cat /sys/fs/cgroup/memory.current
printf "cpu.max="; cat /sys/fs/cgroup/cpu.max
'
kubectl describe node | sed -n '/Allocated resources:/,/Events:/p'
kubectl top pod -n k8s-m10 2>&1 || true10. Лабораторна робота: LimitRange і ResourceQuota
LimitRange додає defaults новим containers, а ResourceQuota обмежує сукупні requests/limits namespace. Вони застосовуються admission-time і не переписують наявні Pods.
cat > /tmp/k8s-m10/policy.yaml <<'EOF'
apiVersion: v1
kind: LimitRange
metadata: {name: containers, namespace: k8s-m10}
spec:
limits:
- type: Container
defaultRequest: {cpu: 25m, memory: 16Mi}
default: {cpu: 100m, memory: 64Mi}
---
apiVersion: v1
kind: ResourceQuota
metadata: {name: compute, namespace: k8s-m10}
spec:
hard:
requests.cpu: 500m
requests.memory: 256Mi
limits.cpu: "1"
limits.memory: 512Mi
pods: "10"
EOF
kubectl apply --dry-run=server -f /tmp/k8s-m10/policy.yaml
kubectl apply -f /tmp/k8s-m10/policy.yaml
kubectl -n k8s-m10 run defaulted --image=nginx:1.29-alpine
kubectl -n k8s-m10 get pod defaulted -o jsonpath='{.spec.containers[0].resources}{"\n"}'
kubectl -n k8s-m10 describe limitrange containers
kubectl -n k8s-m10 describe resourcequota compute11. Діагностика: OOMKilled і FailedScheduling
Allocator перевищує 32Mi та завершується OOMKilled із code 137. Інший Pod просить 100 CPU й лишається Pending. Однакове «не працює» виникає на різних стадіях: scheduler проти runtime.
kubectl delete resourcequota compute -n k8s-m10
cat > /tmp/k8s-m10/failures.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata: {name: memory-failure, namespace: k8s-m10}
spec:
restartPolicy: Never
containers:
- name: allocator
image: python:3.13-alpine
command: ["python", "-c", "x=bytearray(96*1024*1024); print(len(x)); __import__('time').sleep(300)"]
resources:
requests: {memory: 16Mi}
limits: {memory: 32Mi}
---
apiVersion: v1
kind: Pod
metadata: {name: unschedulable, namespace: k8s-m10}
spec:
containers:
- name: pause
image: registry.k8s.io/pause:3.10.1
resources:
requests: {cpu: "100", memory: 1Gi}
limits: {cpu: "100", memory: 1Gi}
EOF
kubectl apply -f /tmp/k8s-m10/failures.yaml
kubectl -n k8s-m10 wait --for=jsonpath='{.status.containerStatuses[0].state.terminated.reason}'=OOMKilled pod/memory-failure --timeout=180s
kubectl -n k8s-m10 get pod memory-failure -o jsonpath='phase={.status.phase}{" reason="}{.status.containerStatuses[0].state.terminated.reason}{" exit="}{.status.containerStatuses[0].state.terminated.exitCode}{" qos="}{.status.qosClass}{"\n"}'
kubectl -n k8s-m10 describe pod memory-failure
kubectl -n k8s-m10 describe pod unschedulable
kubectl -n k8s-m10 get events --sort-by=.metadata.creationTimestampПорядок: spec resources → Pod conditions/status → Events → Node allocatable/allocated → terminated state/restarts → cgroup values → application profile.
12. Контроль, критерії засвоєння та cleanup
- Request резервує фізичне ядро?
- Чому scheduler не дивиться лише на поточне навантаження?
- CPU limit убиває процес?
- Чим Mi відрізняється від m?
- Умови Guaranteed?
- Чому 137 не доводить OOM?
- Що показує memory.max?
- Чому kubectl top може бути відсутнім?
- Коли застосовується LimitRange?
- Чим quota відрізняється від limit контейнера?
kubectl delete namespace k8s-m10 --wait=true
rm -rf /tmp/k8s-m10
kubectl get namespace k8s-m10 2>&1 || trueМодуль засвоєно, якщо ви пояснюєте placement і enforcement окремо, отримуєте три QoS-класи та розрізняєте FailedScheduling від підтвердженого OOMKilled.