Cloud Infrastructure Nov 14, 2025· 4 min read

Cutting AWS Compute Bills by 35% with K3s Pod Request Tuning

Shafikul Islam
Shafikul Islam

Backend Infrastructure & Cloud Performance Consultant

Cutting AWS Compute Bills by 35% with K3s Pod Request Tuning

Executive Summary & Background

When high-growth B2B SaaS platforms scale past $1M ARR, monthly AWS cloud compute bills frequently double faster than subscription revenue.

During an infrastructure diagnostic audit for a multi-tenant SaaS client, the monthly AWS EC2 bill had reached $11,000/month. The cluster was running 8 t3.2xlarge EC2 worker nodes (16 vCPU, 32GB RAM each), yet overall node CPU utilization averaged less than 6.5%.

This case study demonstrates how we conducted an empirical 30-day Prometheus metrics audit, right-sized pod resource requests to match actual P95 consumption, and deployed Horizontal Pod Autoscaling (HPA) alongside Cluster Autoscaler — reducing the required worker node count from 8 instances to 4 instances, saving $3,850/month ($46,200/year) with zero downtime.


Financial Impact & Metrics Comparison

Optimization MetricPre-Audit Cluster StatePost-Optimization StateFinancial Impact / Benefit
EC2 Worker Node Count8 x t3.2xlarge Instances4 x t3.2xlarge Instances50% Node Count Reduction
Monthly AWS Compute Bill$11,000 / month$7,150 / month$3,850/mo ($46,200/yr) Saved
Average Node CPU Utilization6.2% (Massive Waste)58.4% (Optimal Efficiency)9x Resource Utilization Increase
Pod OOMKilled Incidents12 per month0 per month100% Application Stability

Root Cause Analysis: The Idle Resource Trap

In Kubernetes, worker node capacity is allocated based on Resource Requests, NOT actual CPU/RAM usage.

Developers had configured default pod requests of cpu: "2000m" (2 vCPU cores) and memory: "4Gi" for every microservice container to prevent Out-Of-Memory (OOM) crashes during initial staging tests.

When scaled across 20+ microservice replicas, Kubernetes reserved 32 vCPU cores on node capacity. The Kubernetes scheduler refused to schedule new pods on existing worker nodes, forcing AWS Autoscaling Groups to spin up 8 expensive EC2 instances even though actual CPU usage across the entire cluster was under 6%.

[ EC2 Worker Node: 16 vCPU / 32GB RAM ]
 ├── Pod A: Requested 4 CPU (Actual Avg: 0.1 CPU) ──▶ Reserved
 ├── Pod B: Requested 4 CPU (Actual Avg: 0.15 CPU) ──▶ Reserved
 ├── Pod C: Requested 4 CPU (Actual Avg: 0.08 CPU) ──▶ Reserved
 └── Node Capacity Full! (Kubernetes demands Node 2 even though CPU is 95% idle!)

3-Step Remediation Engineering

Step 1: Scraping 30-Day P95/P99 Metrics with PromQL

We deployed Prometheus and scraped container-level metrics across peak business hours to identify actual resource usage patterns:

# 1. Scrape P95 CPU consumption per microservice container over 30 days
histogram_quantile(0.95, sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, container, le))

# 2. Scrape P99 Memory working set bytes per container
quantile_over_time(0.99, container_memory_working_set_bytes{container!=""}[30d])

# 3. Detect pods with excessive CPU request-to-usage ratio (> 10x gap)
(sum(kube_pod_container_resource_requests{resource="cpu"}) by (pod)
  /
 sum(rate(container_cpu_usage_seconds_total[5m])) by (pod)) > 10

Step 2: Right-Sizing Pod Requests & Limits

Based on empirical PromQL data, we updated deployment manifests. We aligned Requests to actual P95 consumption and set Limits to P99 plus a 25% safety margin:

# k8s/deployments/backend-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: saas-api-backend
  namespace: production
spec:
  replicas: 4
  template:
    spec:
      containers:
        - name: api-server
          image: registry.company.com/api-server:v2.4.1
          resources:
            requests:
              cpu: "150m"      # Reduced from 2000m (P95 actual: 110m)
              memory: "256Mi"  # Reduced from 4096Mi (P95 actual: 180Mi)
            limits:
              cpu: "500m"      # Allows bursts during request spikes
              memory: "512Mi"  # Prevents runaway memory leaks

Step 3: Configuring Horizontal Pod Autoscaling (HPA)

Instead of running fixed replica counts, we configured HPA to scale dynamically based on custom HTTP request throughput metrics scraped from Traefik Ingress:

# k8s/autoscaling/hpa-backend.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: saas-api-backend-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: saas-api-backend
  minReplicas: 3
  maxReplicas: 16
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: External
      external:
        metric:
          name: traefik_entrypoint_requests_per_second
        target:
          type: AverageValue
          averageValue: "150"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 60

Final Results & Recommendations

  1. ▸
    Immediate Compute Savings: Safely drained and decommissioned 4 EC2 worker nodes, saving $3,850/month ($46,200/year).
  2. ▸
    Zero Performance Loss: Passed synthetic 5x traffic load tests with zero HTTP 5xx errors and sub-200ms P95 API latencies.
  3. ▸
    Automated Scalability: During marketing email blasts, HPA automatically scales backend pods from 3 to 12 replicas in under 20 seconds, scaling back down smoothly after traffic subsides.
Infrastructure Advisory

Scaling Infrastructure or Want to Cut Cloud Spend?

I help B2B SaaS platforms eliminate Kubernetes downtime, harden container security, and cut AWS compute bills by 30%+. Book a 20-minute diagnostic audit call.

Related Technical Articles