Asterrr's Handbook

Resources, QoS and quotas

Container requests and limits, CPU throttling and OOM kills, QoS classes and eviction order, LimitRange defaults and bounds, ResourceQuota on compute and object counts, and how admission rejects Pods.

Exam tasks: 2.5 (configure Pod admission and scheduling: limits)

The decision: how much CPU and memory should each container reserve and be capped at, and which namespace guardrail (LimitRange or ResourceQuota) enforces that for everyone?

Requests, limits and where each one acts

RequestLimit
Used byThe scheduler: a node must have that much unreserved allocatable capacityThe kubelet and runtime, through cgroups
CPUGuaranteed share under contentionHard cap: the container is throttled, never killed
MemoryCounts toward node fit and eviction rankingHard cap: the container is OOMKilled (exit code 137)
MissingDefaults to the limit if a limit is set, otherwise 0No cap
resources:
  requests:
    cpu: 250m          # 0.25 core
    memory: 192Mi      # Mi = 2^20 bytes; M = 10^6
  limits:
    cpu: "1"
    memory: 384Mi
k set resources deploy/orbit -n space -c orbit --requests=cpu=250m,memory=192Mi --limits=cpu=1,memory=384Mi
k describe node worker-2 | grep -A8 'Allocated resources'   # requests and limits already committed
  • A Pod that fits no node by requests stays Pending with Insufficient cpu or Insufficient memory in its events, even if the nodes are idle in practice. Scheduling never looks at actual usage.
  • cpu: 1 and cpu: 1000m are equal. memory: 1G is about 7% less than 1Gi.

QoS classes

ClassRuleUnder node memory pressure
GuaranteedEvery container has CPU and memory requests equal to limitsEvicted last
BurstableAt least one container has a CPU or memory request or limit, but not GuaranteedEvicted by how far usage exceeds requests
BestEffortNo container has any request or limitEvicted first
k get pod orbit-7d9c -n space -o jsonpath='{.status.qosClass}{"\n"}'
  • QoS is computed from the spec at creation and can't change afterwards, not even by in-place resize.
  • Setting only limits gives requests equal to limits, so a Pod whose every container sets only CPU and memory limits is Guaranteed.

Exam signal

"This Pod must be the last to be evicted" or "must get Guaranteed QoS" means requests equal limits for both CPU and memory, in every container including init and sidecar containers.

LimitRange: per-object defaults and bounds

apiVersion: v1
kind: LimitRange
metadata:
  name: container-bounds
  namespace: space
spec:
  limits:
  - type: Container
    defaultRequest:         # injected when a container sets no request
      cpu: 100m
      memory: 128Mi
    default:                # injected when a container sets no limit
      cpu: 500m
      memory: 256Mi
    min:
      cpu: 50m
      memory: 64Mi
    max:
      cpu: "2"
      memory: 1Gi
  - type: PersistentVolumeClaim
    max:
      storage: 20Gi
  • Applied by the LimitRanger admission plugin when a Pod is created. Existing Pods are untouched, so restart the workload to see the defaults.
  • A container that asks for more than max (or less than min) is rejected with a Forbidden error.
  • type: Pod bounds the sum across containers; maxLimitRequestRatio caps limit divided by request.

Default limit below the request

If a LimitRange sets default.memory: 256Mi and a container sets only requests.memory: 512Mi, the injected limit is smaller than the request and the Pod is rejected. Either set an explicit limit on the container, or make the LimitRange default at least as large as any request you expect.

ResourceQuota: namespace totals

k create quota space-quota -n space \
  --hard=requests.cpu=4,requests.memory=8Gi,limits.cpu=8,limits.memory=16Gi,pods=30,services.loadbalancers=0
k describe quota space-quota -n space      # Used vs Hard
Quota keyCaps
requests.cpu, requests.memory, limits.cpu, limits.memorySums across all non-terminal Pods
pods, services, configmaps, secrets, persistentvolumeclaimsObject counts
count/deployments.apps, count/jobs.batchCount of any namespaced resource, count/<resource>.<group>
requests.storage, <class>.storageclass.storage.k8s.io/requests.storageTotal PVC storage, overall or per StorageClass
services.loadbalancers, services.nodeportsService types
  • With a quota on requests.cpu, every new Pod must set a CPU request (on limits.memory, a memory limit, and so on), or admission rejects it. A LimitRange with defaults is the usual companion.
  • scopeSelector limits a quota to some Pods: BestEffort, NotBestEffort, Terminating, NotTerminating, or a PriorityClass (for example "at most 2 Pods with priority critical-batch in this namespace").
  • Quotas are checked at admission only. Lowering a quota below current use doesn't evict anything; it blocks new objects until usage drops.

Looking for the quota error on the Deployment

When a Deployment's Pods exceed the quota, kubectl get deploy just shows fewer Ready replicas. The exceeded quota error is an event on the ReplicaSet (FailedCreate). Run k describe rs -l app=orbit -n space or k get events -n space to see it.

137
Exit code of a container killed for exceeding its memory limit (OOMKilled).
Requests
What the scheduler uses to fit Pods; actual usage is ignored.
Throttle
What happens to CPU over the limit. Memory over the limit is a kill.
Admission
When LimitRange defaults and quota checks apply. Running Pods are never changed or evicted by them.

The admission chain in short

  • Requests pass authentication, authorization, then mutating admission (LimitRanger defaults, ServiceAccount, mutating webhooks), schema validation, then validating admission (ResourceQuota, Pod Security Admission, ValidatingAdmissionPolicy, validating webhooks). Any one can reject the object.
  • Plugins are toggled on kube-apiserver with --enable-admission-plugins / --disable-admission-plugins. LimitRanger and ResourceQuota are on by default.
  • Pod Security Admission is driven by namespace labels such as pod-security.kubernetes.io/enforce=restricted; a Pod that breaks the level is rejected at creation.

Scenarios

Scenario
Namespace `lab` has a ResourceQuota with requests.cpu=2 and requests.memory=4Gi and no LimitRange. A developer applies a Deployment whose container has no resources block. `kubectl get deploy` shows 0/3 ready and no Pods exist. What is the best fix that keeps the quota?
Scenario
A Pod has two containers. Container `web` has requests and limits of cpu 500m and memory 256Mi. Container `agent` has only a memory limit of 64Mi. What QoS class does the Pod get?
Scenario
A container in Pod `crunch` keeps restarting. `kubectl describe pod` shows Last State: Terminated, Reason: OOMKilled, Exit Code: 137. The container's limits are cpu 2, memory 300Mi. What should you change?

Drill

kubectl config use-context drill-w5. In namespace quarry:

  1. Make sure any container created without resources gets a request of 100m CPU and 128Mi memory and a limit of 300m CPU and 256Mi memory.
  2. Limit the namespace to 10 Pods and a total of 2 CPU in requests.
  3. Create Pod probe (image nginx:1.27-alpine, no resources in the spec) and confirm what it received.

Further reading

On this page