Skip to content

Instantly share code, notes, and snippets.

@guilbep
Last active April 28, 2025 09:44
Show Gist options
  • Save guilbep/b953fb8faf234089510a5d7339732406 to your computer and use it in GitHub Desktop.
Save guilbep/b953fb8faf234089510a5d7339732406 to your computer and use it in GitHub Desktop.

Below is a “menu” of tactics you can mix-and-match depending on whether your 6-node, 48-vCPU cluster lives on-prem or in the cloud. I’ve ordered them roughly from quickest wins to bigger-picture moves.


1. Tighten your requests & limits

  • Set realistic CPU requests (what the scheduler reserves) and leave limits unset or > requests so Pods can “burst” when you hit a spike.
  • Over-estimating requests leads to 90 % of your cluster sitting idle, yet the scheduler still thinks the nodes are “full.”
  • Use the kubectl top plugin or Grafana dashboards to discover the p95 CPU demand per Pod and calibrate.
  • Once tuned, keep ±30 % head-room; that alone may absorb many spikes.

2. Horizontal Pod Autoscaler (HPA) – but with the new container-level metric

  • HPA is still the simplest lever: scale replicas based on CPU or custom metrics.
  • In Kubernetes 1.30 the “ContainerResource” metric graduated to stable; you can autoscale on the busiest container inside a Pod instead of an average over the whole Pod, avoiding false alarms when one sidecar is quiet. (A Peek at Kubernetes v1.30)
    apiVersion: autoscaling/v2
    metrics:
    - type: ContainerResource
      containerResource:
        container: app
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  • If your API Deployment is the bottleneck, an HPA with min=2, max≈nodes×2 is cheap insurance.

3. Vertical Pod Autoscaler (VPA) for steady services

  • VPA watches historical usage and rewrites a Pod’s requests/limits on reschedule.
  • Great for always-on components such as databases or queues whose demand drifts slowly, freeing CPUs for bursty workloads.
  • VPA reached stable a while back; most managed distributions enable it out-of-the-box now. (Kubernetes VPA: Pros and Cons & Best Practices - ScaleOps)
  • Tip: never run VPA and HPA simultaneously on the same Deployment (HPA hates its targets moving).

4. Cluster-level autoscaling (cloud) or bigger node pool (on-prem)

Platform What to use Notes
Cloud (EKS/GKE/AKS/etc.) Cluster-Autoscaler or the newer Karpenter Adds/removes VMs in ~1 min. Put bursty workloads in a “scale-to-0” node group.
Bare metal / on-prem Keep 2 spare nodes powered on or virtualise your worker nodes so you can spin up extra VMs quickly If physical HW is fixed, you must keep head-room or shift spikes to another queue (see §5).

Autoscaling gives you the luxury of running 10 % today but adding nodes automatically when CPUs stay >70 % for N minutes. The cost trade-off is obvious in cloud; on-prem it’s an electricity vs. risk discussion.


5. Queue big, one-off jobs with Kueue (batch system)

  • If the “sometimes” spikes are batch jobs (ML training, large ETLs, video transcoding), put them behind a queue.
  • Kueue (incubating in SIG-Scheduling) lets you enqueue Jobs and dispatch them only when your API Pods have spare capacity or when autoscaler adds fresh nodes. (Run A Kubernetes Job | Kueue, Workload | Kueue - Kubernetes)
  • You can assign WorkloadPriority so customer-facing traffic always wins; Kueue will pre-empt lower-priority jobs instead of denying API traffic.

6. Use priority classes + Pod disruption budgets

  • Mark API Deployments priorityClassName: high-priority.
  • Batch Pods get a lower class; if the cluster saturates, Kubernetes evicts or refuses the low-priority Pods first.
  • Combine with a PodDisruptionBudget so at least N replicas of the API always stay up.

7. Rate-limit at the edge

Even with autoscaling, a hard requests-per-second ceiling (Envoy, Nginx, API-Gateway) shields the cluster from a runaway client and buys your autoscaler time to wake up new nodes.


8. Forecast & rehearse

  • Run a k6 or Locust load test that ramps to 2× your expected surge.
  • Observe how long it takes the HPA → cluster-autoscaler chain to settle.
  • Use the results to tune HPA cooldowns (behavior), cluster-autoscaler --scale-down-delay-after-add, and node pool min/max sizes.

Putting it together – a sane starting recipe

  1. Right-size resources (requests/limits) & add HPA at 70 % target.
  2. Enable VPA for any long-running services that don’t already HPA.
  3. Cloud? Install the vendor’s cluster-autoscaler (or Karpenter) with node-group max = 2× current.
    On-prem? Keep two workers idle or migrate bursty jobs into a Kueue queue.
  4. Tag Deployments with priority classes so the API never loses CPUs.
  5. Add an edge rate-limiter and keep Grafana alerts on node_cpu_utilisation > 80 % for 10 m.

Follow that ladder and you’ll move from “6 nodes that sometimes melt” to a cluster that breathes with demand while still costing the minimum during those long idle stretches.


Okay, this is a very common scenario in Kubernetes! You have a cluster that's mostly idle but needs to handle significant bursts. Wasting resources during idle time is inefficient, and hitting 100% CPU during peaks can lead to performance degradation (throttling, latency, failed jobs).

Here's a breakdown of strategies you should consider, often used in combination:

1. Implement Autoscaling (Most Recommended):

  • Horizontal Pod Autoscaler (HPA):

    • What it does: Automatically increases or decreases the number of replicas (pods) for a deployment or statefulset based on observed metrics like CPU utilization or memory usage (or custom metrics).
    • How it helps: When API calls surge or jobs start, the HPA sees the CPU usage per pod increase. It will then launch more pods to distribute the load across them, using the available capacity on your existing nodes first. When the load decreases, it scales the pods back down.
    • Setup: Define an HPA resource targeting your deployment(s). Set a target CPU utilization (e.g., 60-70%) and min/max replica counts. You need the Kubernetes Metrics Server installed.
    • Benefit: Reacts quickly to load changes within the existing cluster capacity.
  • Cluster Autoscaler (CA):

    • What it does: Automatically adds or removes nodes from your cluster. It watches for pods that are stuck in a Pending state because there aren't enough resources (CPU, memory) on existing nodes to schedule them. It also removes nodes that have been underutilized for a period.
    • How it helps: If your HPA scales up pods to the point where your 6 nodes are full, the CA will notice the pending pods and provision new nodes (e.g., nodes 7, 8...). When the load decreases, HPA scales pods down, nodes become underutilized, and the CA can eventually terminate the extra nodes, scaling back down to your baseline (or a configured minimum).
    • Setup: Requires configuration specific to your cloud provider (AWS, GCP, Azure) or infrastructure (if on-prem). You define node pools that can be scaled.
    • Benefit: Ensures you have enough overall cluster capacity during peaks, and saves cost/resources by removing nodes during idle times.
  • KEDA (Kubernetes Event-Driven Autoscaling):

    • What it does: Scales pods based on external event sources (e.g., queue length in RabbitMQ/Kafka/SQS, database query results, Prometheus metrics). Can scale pods down to zero.
    • How it helps: Especially useful if your jobs or API surges are triggered by specific events (like messages arriving in a queue). It can proactively scale before CPU usage climbs high, based on the actual work backlog.
    • Benefit: More sophisticated scaling triggers beyond simple CPU/memory. Can be very efficient for event-driven workloads.

2. Optimize Resource Requests and Limits:

  • Requests: This is what Kubernetes uses for scheduling. It guarantees the pod gets at least this much CPU/memory. If the sum of requests on a node exceeds its capacity, new pods won't schedule there.

    • Problem: If requests are set too low, you might overschedule nodes, leading to intense CPU pressure during peaks when pods try to use more than requested (up to their limits). If set too high, you waste resources when idle.
  • Limits: This is the maximum CPU/memory a pod is allowed to use. If a pod tries to exceed its CPU limit, it gets throttled. If it exceeds its memory limit, it might get OOMKilled.

    • Problem: If limits are too close to requests or too low, your application might get throttled during legitimate peaks, causing slowdowns. If set too high (or not set), runaway processes can impact other workloads or the node itself.
  • Action:

    • Monitor actual usage: Use monitoring tools (like Prometheus/Grafana) to see the actual CPU/memory usage of your pods during idle and peak times.
    • Right-size requests: Set requests based on typical baseline usage, perhaps slightly higher, ensuring your critical apps always have what they generally need.
    • Right-size limits: Set limits based on acceptable peak usage. Allow headroom for bursts, but prevent runaway consumption. For CPU, it's often okay to set limits significantly higher than requests (e.g., request 100m CPU, limit 1000m CPU) creating "Burstable" QoS pods. This allows pods to use spare CPU on the node when available (like during your idle periods) but still guarantees the requested amount.
    • Consider VPA (Vertical Pod Autoscaler): It can observe pod usage and automatically recommend or even apply updated requests/limits. Use with caution, especially the Auto mode.

3. Improve Scheduling and Prioritization:

  • Pod Priority and Preemption: Define PriorityClasses to ensure your critical API servers or essential jobs can kick out less important pods (e.g., batch jobs running during low-priority times) if resources become scarce during a surge.
  • Node Taints and Tolerations / Node Affinity/Anti-Affinity:
    • You could potentially taint some nodes to only run specific types of workloads (e.g., dedicate nodes for critical APIs vs. batch jobs).
    • Use anti-affinity to spread replicas of critical deployments across multiple nodes for high availability and better load distribution during peaks.

4. Monitoring and Alerting:

  • Crucial: You need visibility into what's happening.
    • Implement robust monitoring (Prometheus, Grafana, Datadog, etc.).
    • Track cluster-wide CPU/Memory usage, node-level usage, pod-level usage, and importantly, CPU throttling metrics.
    • Set up alerts for:
      • High sustained cluster/node CPU usage.
      • Significant CPU throttling on critical pods.
      • Pods stuck in Pending state.
      • Autoscaling events (HPA scaling up/down, CA adding/removing nodes).

Recommended Approach:

  1. Monitor: Get good visibility into your current usage patterns (idle vs. peak) for key workloads. Understand which deployments/jobs cause the surges.
  2. Right-Size Requests/Limits: Based on monitoring, adjust requests and limits for your main applications. Aim for Burstable QoS where appropriate.
  3. Implement HPA: Start with HPA for deployments that experience variable load (like your API servers). Target a reasonable CPU utilization (e.g., 70%).
  4. Implement CA: If HPA frequently hits its max replicas and pods still struggle or can't be scheduled during peaks, add the Cluster Autoscaler. Configure it with a minimum node count (e.g., 3-4 if that covers idle + baseline) and a maximum (e.g., 10-12, giving headroom). This allows the cluster to shrink during idle times (saving cost if in the cloud) and grow during peaks.
  5. Consider KEDA: If surges are clearly event-driven (queue lengths etc.), KEDA might be more efficient than CPU-based HPA.
  6. Review & Iterate: Continuously monitor performance, costs, and autoscaling behaviour. Adjust HPA targets, CA settings, and resource requests/limits as needed.

By combining these strategies, you can build a cluster that runs efficiently during idle periods while automatically and reliably scaling to handle peak loads without performance degradation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment