🚀 Executive Summary

TL;DR: Kubernetes CPU limits, enforced by the Linux Kernel’s Completely Fair Scheduler (CFS), often cause unexpected throttling and latency spikes, even when average CPU usage appears low, due to micro-throttles. The solutions involve either removing CPU limits for latency-sensitive applications, setting CPU requests equal to limits for critical workloads to achieve Guaranteed QoS, or configuring Static CPU Management for ultra-low latency requirements.

🎯 Key Takeaways

  • Kubernetes CPU limits are enforced by the Linux Kernel’s Completely Fair Scheduler (CFS), which throttles applications by pausing them for milliseconds if they exceed their CPU quota within a 100ms period, leading to ‘micro-throttles’ and latency.
  • Standard CPU usage metrics (e.g., from Prometheus) are often averaged over 15-30 seconds, obscuring millisecond-level throttling; specific kernel-level metrics like `container_cpu_cfs_throttled_periods_total` are crucial for accurate diagnosis.
  • Three primary strategies exist to manage CPU resources and avoid throttling: removing CPU limits (Burstable QoS) for burstable applications, setting CPU requests equal to limits (Guaranteed QoS) for predictable performance, and using Static CPU Management Policy for extreme low-latency workloads.

Is Kubernetes resource management really meant to work like this? Am I missing something fundamental?

Summary: Feeling betrayed by Kubernetes CPU limits causing unexpected throttling and latency? You’re not alone. I’ll break down why this happens and give you three real-world solutions, from the quick fix to the architecturally sound approach for taming your cluster’s resource management.

Is Kubernetes Resource Management Broken? No, But Your Frustration is Valid.

I’ll never forget the 3 AM PagerDuty alert. A critical service, our main `payment-processor`, was throwing latency spikes all over the place. Dashboards were a sea of red. The weird part? The pods weren’t crashing, and overall CPU usage on the nodes was well below 50%. The pod’s own CPU metrics looked fine, hovering around 400m out of a 1000m (1 core) limit we had set. It made no sense. We spent hours chasing ghosts in the application code before a junior engineer tentatively asked, “Could it be the CPU limits?” I almost dismissed it. How could a pod be CPU throttled when it was using less than half its limit? Turns out, he was right. And that night taught me a fundamental, non-obvious truth about Kubernetes that trips up almost everyone.

The “Why”: CPU Limits Aren’t a Speedometer, They’re a Handbrake

Most of us start out thinking of CPU requests and limits like this:

  • requests: The minimum CPU the pod is guaranteed. Kubernetes uses this for scheduling.
  • limits: The maximum CPU the pod can ever use. A hard cap.

That seems logical, but it’s dangerously oversimplified. The “hard cap” isn’t a simple ceiling. Under the hood, Kubernetes uses the Linux Kernel’s Completely Fair Scheduler (CFS). The CFS enforces CPU limits by throttling your application’s processes. It gives your container a certain amount of CPU time within a given period (usually 100ms). If your application needs a sudden burst of CPU—even for a few milliseconds—it can exhaust its quota for that period and get throttled. It’s forcefully put to sleep until the next period begins.

So while your average CPU usage over one second might look low, your application might be experiencing thousands of tiny “micro-throttles” that introduce latency and kill performance, especially for services sensitive to response times like an API gateway or a database query proxy.

A Word of Warning: The metrics you see in your dashboard (like from Prometheus) are often averaged over 15-30 seconds. This completely hides the millisecond-level throttling that is actually killing your performance. You have to look for specific kernel-level metrics like container_cpu_cfs_throttled_periods_total to see the real picture.

Three Ways to Fix This Mess

Okay, enough theory. You’ve got a production fire to put out. Here are the three approaches we use at TechResolve, ranging from the immediate band-aid to the long-term architectural fix.

1. The Quick & Dirty Fix: Just Remove the CPU Limits

I can hear the gasps already. Yes, I’m telling you to consider removing the very thing that’s causing the problem. If your application is latency-sensitive, an aggressive CPU limit is often more harmful than no limit at all.

By removing the limit, you stop the CFS throttling completely. The pod can now burst and use any available, unused CPU on the node. Its scheduling is still governed by its requests, which is the most important value for cluster stability.

When to use this: For critical, latency-sensitive applications where occasional CPU bursts are better than consistent throttling-induced latency.

The Catch: This puts your pod in the Burstable QoS class. If you don’t set your CPU requests properly, this pod could become a “noisy neighbor” and starve other pods on the same node. It’s also one of the first pods to get evicted if the node comes under pressure. It’s a trade-off.


# pod-spec-no-limits.yaml
# This pod is in the 'Burstable' QoS class.
apiVersion: v1
kind: Pod
metadata:
  name: prod-api-gateway-burstable
spec:
  containers:
  - name: api-gateway
    image: my-company/api-gateway:1.2.3
    resources:
      requests:
        cpu: "500m" # We guarantee it gets half a core
        memory: "1Gi"
      # NO CPU LIMIT! Let it burst.
      limits:
        memory: "1Gi"

2. The “Right” Way: Set Requests Equal to Limits

This is the grown-up solution. By setting the CPU request and limit to the same value, you are telling Kubernetes exactly what you expect and need. This places your pod into the Guaranteed Quality of Service (QoS) class.

A Guaranteed pod is exactly what it sounds like. It gets the CPU it asked for, no more, no less. It won’t be throttled (because it can’t exceed its request), and it won’t be evicted unless a system daemon needs resources. This provides the most stable, predictable performance for your most critical workloads.

When to use this: For your most important stateful or stateless applications (e.g., databases, critical APIs, message queues) where predictable performance is non-negotiable.

The Catch: You lose the ability to burst, which can mean you have to set your requests higher to accommodate peaks. This can lead to lower pod density on your nodes and potentially higher cloud costs. You must have good performance monitoring to know what value to set.


# pod-spec-guaranteed.yaml
# This pod is in the 'Guaranteed' QoS class.
apiVersion: v1
kind: Pod
metadata:
  name: prod-db-proxy-guaranteed
spec:
  containers:
  - name: db-proxy
    image: my-company/db-proxy:4.5.1
    resources:
      requests:
        cpu: "1" # Request 1 full core
        memory: "2Gi"
      limits:
        cpu: "1" # Limit to the same 1 full core
        memory: "2Gi"

3. The Power User’s Play: Static CPU Management Policy

Sometimes, even the Guaranteed QoS isn’t enough. For high-performance computing, real-time data processing, or ultra-low-latency financial applications, you need to eliminate every possible source of jitter. This is where you go beyond pod specs and configure the kubelet on the node itself.

The Static CPU Management Policy allows Guaranteed pods to be granted exclusive access to specific CPU cores. This means no other processes—not even system daemons—will run on those cores. It completely avoids the CFS scheduler and eliminates CPU context switching, providing the lowest possible latency.

When to use this: Only for the most extreme performance-critical workloads. This is not for your average web app.

The Catch: This is a node-level configuration, not just a pod spec. It reduces the number of general-purpose CPUs available on the node and can be complex to manage. It’s a sledgehammer, not a screwdriver.

Summary: Which Hammer for Which Nail?

Let’s boil it down. Here’s how I decide which strategy to use.

Solution Best For Pros Cons
Remove Limits Latency-sensitive apps that need to burst (e.g., web frontends). Eliminates throttling, simple to implement. Risk of “noisy neighbor” issues, lower priority for K8s.
Requests == Limits Critical workloads needing stability (e.g., databases, APIs). Predictable performance, highest K8s priority. Requires accurate capacity planning, potentially higher cost.
Static CPU Policy Ultra-low latency, high-performance computing. Absolute best performance, no CPU jitter. Complex, requires node-level changes, inflexible.

So, is Kubernetes resource management meant to work like this? Yes, but it’s based on the Linux kernel’s behavior, which is optimized for fairness, not necessarily for every application’s performance profile. You’re not missing something fundamental; you’ve just hit one of the steepest parts of the learning curve. Don’t just set limits because you think you should. Understand the trade-offs, profile your applications, and choose the strategy that fits the workload. Your PagerDuty will thank you.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ Why do my Kubernetes pods experience latency spikes even when CPU usage appears low?

Your pods likely experience latency spikes due to ‘micro-throttles’ caused by Kubernetes CPU limits. The Linux Kernel’s Completely Fair Scheduler (CFS) enforces these limits by pausing your application for milliseconds if it exceeds its CPU quota within a 100ms period, even if its average CPU usage over a longer duration is low.

âť“ What are the main strategies to manage CPU resources in Kubernetes and their trade-offs?

The article outlines three strategies: 1) Removing CPU limits for latency-sensitive apps (Burstable QoS), which eliminates throttling but risks ‘noisy neighbor’ issues. 2) Setting CPU requests equal to limits (Guaranteed QoS) for critical workloads, providing predictable performance but losing burst capability and potentially increasing costs. 3) Using Static CPU Management Policy for ultra-low latency, which offers exclusive core access but is complex and node-level.

âť“ What is a common implementation pitfall when setting CPU limits in Kubernetes and how can it be avoided?

A common pitfall is setting aggressive CPU limits without understanding the Linux Kernel’s CFS behavior, leading to unexpected throttling and latency, especially for bursty applications. This can be avoided by carefully profiling applications and choosing the appropriate strategy: removing limits for burstable apps, setting requests equal to limits for critical workloads, or using Static CPU Management for extreme performance needs.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading