Full lesson
Explore the full explanation, examples, and visuals at your own pace.
Part 1
Two pods can share one busy node and get very different treatment. Here, checkout-api has its CPU and memory fenced off, while report-job squeezes into whatever's left. Neither is luckier. The difference comes from what each pod declared in its spec, and that's what we'll unpack.
checkout-api container resources
This is checkout-api's resources block. Requests are the minimum the scheduler reserves on a node: one CPU and one gibibyte of memory. Limits are the hard ceiling it can't pass. Notice both match exactly here. That equality looks small, but it's what decides the pod's QoS class.
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "1"
memory: 1Gireport-job container spec
Now look at report-job. Its container has a name and an image, but no resources block at all. That means no reservation and no ceiling. A namespace LimitRange could inject defaults here, but we're assuming there isn't one, so this job declares nothing.
containers:
- name: report
image: report:1.4
# no resources blockScheduler books requests, not actual usage
The scheduler only books what pods request. checkout-api asks for 1 CPU and 1Gi, so the scheduler subtracts that from the node's 4 CPU and 8Gi. report-job requests nothing, so it looks free and fits anywhere, even a busy node. Once it's running, it can use far more than it reserved.
QoS class comes from your spec
You never set a QoS class yourself. Kubernetes derives it from your spec. checkout-api has requests equal to limits, so it's Guaranteed. report-job declares nothing, so it's BestEffort. Anything in between, with some requests, is Burstable.
- Guaranteed: requests equal limits, every container
- Burstable: some requests, below or without limits
- BestEffort: no requests or limits at all
If report-job sets a memory request of one gigabyte and a limit of four gigabytes, what QoS class does it get?
If report-job sets a memory request of one gigabyte and a limit of four gigabytes, what QoS class does it get?
- Guaranteed
- Burstable
- BestEffort
If report-job sets a memory request of one gigabyte and a limit of four gigabytes, what QoS class does it get?
Burstable. It has a request, so it is not BestEffort. But its request is lower than its limit, so it cannot be Guaranteed, which needs requests equal to limits for CPU and memory in every container. That middle ground is Burstable.
- Guaranteed
- Burstable
- BestEffort
CPU contention: slowed down, not killed
CPU is compressible, so a busy node can squeeze it without killing anyone. When it's saturated, CPU time is split by request weight. checkout-api keeps its share, while report-job gets a tiny one. And if checkout-api tries to go above its 1 CPU limit, it's throttled, not terminated.
Memory pressure: eviction follows QoS
Memory is different, because you can't throttle it. Watch the sequence. report-job grabs 6Gi, the node runs low, and the kubelet steps in. It evicts report-job first, since BestEffort goes first. checkout-api keeps running. After BestEffort, the kubelet targets Burstable pods above their requests, and Guaranteed pods go last.
Same API, different spec, different fate
Kubernetes doesn't know checkout-api is important. Without requests, it competes with the job like any other pod, so latency spikes and eviction become real risks. Declare the resources and the picture changes. Requests reserve a seat, limits cap it, and QoS sets the eviction order.
Practical gotchas
Three gotchas. A tight CPU limit throttles checkout-api even when the node has spare CPU. Going over a memory limit gets the container OOMKilled. And give report-job modest requests, so the scheduler accounts for it honestly.
- CPU limits can throttle even idle nodes
- Exceeding memory limit means OOMKilled
- Give jobs modest requests too
Declare it, or Kubernetes cannot protect it
Requests reserve a seat; limits cap it; QoS sets eviction order. Kubernetes can only protect what a pod declares, so checkout-api was safe and report-job wasn't. Set requests on every production pod, and the busy node stops being a gamble.




