← К программе курса

Модуль 10 / 19

Requests, limits, QoS, cgroups и OOM

KUBERNETES

Содержание
01

1. Цели и результат модуля

Вы научитесь задавать вычислительные ресурсы, прогнозировать scheduling и runtime enforcement, читать QoS и доказывать OOMKilled через status и cgroup.

  • request участвует в scheduling
  • limit ограничивает runtime
  • CPU измеряется в cores/millicores
  • memory — в bytes с binary suffix
  • QoS влияет на поведение при resource pressure
02

2. Теория: requests и scheduling

Scheduler сравнивает сумму requests уже размещённых Pods и нового Pod с allocatable Node. Request — основание планирования и доли CPU, а не заранее выделенный отдельный кусок железа. Фактическое потребление может быть ниже или выше request до limit.

Если request больше доступного allocatable, Pod остаётся Pending с FailedScheduling. Scheduler не ориентируется на текущую низкую загрузку вместо requests.

03

3. Теория: limits и enforcement

CPU limit обычно реализуется cgroup quota: процесс throttled, но не убивается. Memory limit — жёсткая граница учтённой памяти; превышение может завершить процесс через cgroup OOM. Exit code 137 сам по себе недостаточен: проверяйте terminated.reason=OOMKilled.

Неограниченный memory workload способен участвовать в node-wide pressure. Limit тоже не заменяет нагрузочное тестирование и корректное управление памятью приложения.

04

4. Единицы и корректные значения

100m CPU — одна десятая core; 1 CPU — один logical core/vCPU. Для memory используйте Mi/Gi. Суффикс m для памяти означает milli-byte: 400m — не 400 MiB. Quantity сравниваются как точные Kubernetes values, а не строки интерфейса.

05

5. QoS: Guaranteed, Burstable, BestEffort

Guaranteed требует CPU и memory request=limit для каждого контейнера Pod. BestEffort не имеет CPU/memory requests и limits. Остальные Pods — Burstable. Init containers и дополнительные containers учитываются, поэтому класс проверяют в status.qosClass.

QoS — один из факторов eviction/OOM при pressure, а не SLA и не гарантия производительности.

06

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.

07

7. Методические указания

  • Начинайте с профиля реальной нагрузки
  • Сравнивайте request с allocatable и другими requests
  • Разделяйте Pending scheduling и runtime throttling/OOM
  • Проверяйте все containers, включая init
  • Не диагностируйте OOM только по 137
  • Смотрите status, Events, node pressure и cgroup
  • Не ставьте limits вслепую ради заполненного YAML
08

8. Лабораторная работа: три QoS-класса

Создайте BestEffort, Burstable и Guaranteed Pods и получите класс из status, а не вычисляйте его по имени.

terminal
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.memory
09

9. Лабораторная работа: cgroup v2

Внутри Burstable container ожидаются memory.max=67108864 и cpu.max примерно 10000 100000. kubectl top на чистом стенде ожидаемо сообщает об отсутствии Metrics API.

terminal
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 || true
10

10. Лабораторная работа: LimitRange и ResourceQuota

LimitRange добавляет defaults новым containers, а ResourceQuota ограничивает совокупные requests/limits namespace. Они применяются admission-time и не переписывают уже существующие Pods.

terminal
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 compute
11

11. Диагностика: OOMKilled и FailedScheduling

Allocator превышает 32Mi и завершается OOMKilled с code 137. Другой Pod просит 100 CPU и остаётся Pending. Одинаковое «не работает» возникает на разных стадиях: scheduler против runtime.

terminal
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

12. Контроль, критерии освоения и cleanup

  • Request резервирует физическое ядро?
  • Почему scheduler не смотрит только на текущую загрузку?
  • CPU limit убивает процесс?
  • Чем Mi отличается от m?
  • Условия Guaranteed?
  • Почему 137 не доказывает OOM?
  • Что показывает memory.max?
  • Почему kubectl top может отсутствовать?
  • Когда применяется LimitRange?
  • Чем quota отличается от limit контейнера?
terminal
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.