top of page

How to Configure Auto Scaling for AI Applications on Amazon EKS

Sep 1
11 min read





The Day the Traffic Surged

 

Every machine learning team celebrates the day their AI service goes live. Your model is serving predictions, your FastAPI endpoints respond in milliseconds, and your Amazon EKS cluster runs quietly in the background.

 

Then comes the real-world test.

 

A marketing campaign launches, a major enterprise customer integrates your API, or a downstream batch processing job fires at midnight. Within minutes, request traffic spikes from 10 requests per second to 1,500.

 

What happens next separates production-grade platforms from fragile experiments:

 

In an unconfigured cluster, incoming HTTP requests pile up in queues. CPU utilization hits 100%. Thread pools starve. Memory usage climbs as concurrent tensors are allocated, triggering the Linux Out-Of-Memory (OOM) Killer. Kubernetes abruptly terminates your inference pods. The load balancer returns `502 Bad Gateway` and `504 Gateway Timeout` errors. Panicked on-call engineers scramble to manually edit replica counts and provision larger EC2 instances in the AWS console.

 

By the time the cluster stabilizes, customers have experienced outages, SLAs have been breached, and executive trust has been eroded.

 

Deploying an AI application on Kubernetes is only the beginning. Without automated, multi-tiered scaling, Kubernetes is just a complicated way to run static virtual machines.

 

In this guide, we will examine how to architect and implement an enterprise-grade auto-scaling system for AI applications on Amazon EKS. We will move through the entire scaling stack: from container-level resource boundaries and the Horizontal Pod Autoscaler (HPA) to just-in-time node provisioning with Karpenter and EKS Auto Mode. We will explore how to trigger scaling using both infrastructure metrics and custom AI-specific signals, manage graceful scale-downs, and enforce strict financial governance.

 

 

The Two-Tier Architecture of Kubernetes Auto Scaling

 

Auto-scaling in Kubernetes is not a single mechanism; it is a coordinated, two-tier feedback loop operating at distinct layers of the infrastructure hierarchy:

 

 

Tier 1: Pod-Level Scaling (Horizontal Pod Autoscaler)


When traffic increases, the application needs more process instances (replicas) to distribute the computation. The Horizontal Pod Autoscaler continuously monitors workload metrics—such as CPU utilization, memory pressure, or HTTP request rates—and adjusts the replica count of your Kubernetes Deployment to keep metric averages at your desired target.

 

Tier 2: Node-Level Scaling (Karpenter & EKS Auto Mode)


Adding pods only works as long as your underlying EC2 nodes have available compute capacity. Once your existing nodes are full, new pods enter a `Pending` state. The node-level autoscaler detects these unschedulable pods, calculates their exact CPU, memory, and GPU requirements, and provisions new EC2 worker nodes just in time to host them.

 

If either tier is misconfigured, scaling fails:

- Pod scaling without node scaling results in pods stuck in `Pending` during traffic spikes.

- Node scaling without pod scaling leaves expensive EC2 instances idle while single pods drown under load.

 

 

 


 

Step 1: Defining Resource Requests and Limits — The Scaling Contract

 

Before you can autoscale a single pod, you must establish its resource contract. In Kubernetes, every container in a pod specification can declare `requests` and `limits` for CPU and memory.

 

For AI applications, this configuration is the single most critical factor determining cluster stability and autoscaling accuracy.

 

Resource

Minimum / Base Usage

Request (Guaranteed by K8s)

Limit (Hard Cap)

CPU

Base Idle Usage

500m (0.5 Core) — Guaranteed Baseline

2000m (2 Cores) — Burstable Peaks

Memory

OS & Model Weight

2 GiB — Baseline Working Set

4 GiB — OOM Kill Ceiling

 

The Anatomy of Requests vs. Limits

 

1. `requests` (Scheduling & Autoscaling Baseline):

   - Represents the minimum guaranteed compute resources Kubernetes reserves for your pod on a node.

   - The Kubernetes scheduler will never place a pod on a node that lacks sufficient unallocated requested resources.

   - Crucially: HPA calculates utilization percentages relative to `requests`, NOT `limits`. If your pod requests 500m CPU and consumes 400m, HPA sees 80% utilization.

 

2. `limits` (Hard Enforcement Ceiling):

   - Represents the maximum resource ceiling the container is permitted to consume.

   - CPU Limits: Enforced via Linux Completely Fair Scheduler (CFS) bandwidth quotas. If your container exceeds its CPU limit, it is throttled (slowed down), but not killed.

   - Memory Limits: Enforced via Linux cgroups. If your container allocates memory beyond its limit, the kernel immediately terminates the process with an `OOMKilled` error code.

 

The AI Workload Trap: Memory vs. CPU

 

AI inference workloads have fundamentally different resource profiles than standard CRUD web apps:

 

- Model Loading Footprint: When an AI container boots, loading neural network weights into memory creates an immediate, static memory floor (e.g., 1.5 GB for a language model or 800 MB for an image classification model).

- Dynamic Tensor Buffers: As concurrent requests arrive, tensor allocations and intermediate layer activations consume dynamic memory on top of the model weights.

- CPU Bursts: Matrix operations spike CPU usage sharply during token generation or image decoding, then drop back to idle.

 

Enterprise Best Practice: Set memory `requests` equal to or slightly above the static model footprint plus a baseline buffer, and set memory `limits` with sufficient headroom to accommodate peak batch sizes. For CPU, set realistic `requests` (e.g., 1000m = 1 vCPU) that represent healthy single-pod throughput.

 

 

 


 

Step 2: Configuring the Horizontal Pod Autoscaler (HPA)

 

With resource requests established, we configure the Horizontal Pod Autoscaler (HPA). The HPA controller runs inside the Kubernetes control plane as a continuous control loop with a default evaluation period of 15 seconds.

 

The Scaling Algorithm

 

The HPA computes desired replicas using a deterministic mathematical formula:


Desired Replicas = ceil[ Current Replicas × ( Current Metric Value / Target Metric Value ) ]


For example, if your deployment currently runs 2 replicas, your target CPU utilization is 60%, and incoming load pushes average CPU utilization across the pods to 90%:


Desired Replicas = ceil[ 2 × ( 90 / 60 ) ] = ceil[ 3.0 ] = 3 Replicas

 

The HPA immediately updates the Deployment's replica count to 3, instructing Kubernetes to schedule a third pod.

 

 

Why CPU/Memory Alone Are Not Enough for AI

 

While CPU and memory metrics work well for general web APIs, modern AI platforms often require application-specific custom metrics:

 

1. HTTP Request Queue Depth: If an inference request takes 200ms of pure GPU compute, a queue of 50 requests means 10 seconds of latency. Scaling based on queue depth (via KEDA or Prometheus) responds before CPU spikes saturate.


2. GPU Duty Cycle & GPU Memory: For GPU-accelerated models (running on NVIDIA A10G, T4, or L4 instances), CPU utilization is often idle while the GPU core is pinned at 100%. Autoscaling must query NVIDIA Data Center GPU Manager (DCGM) metrics.


3. Token Generation Rate / Inference Latency: Scaling based on P95 response latency directly aligns infrastructure expansion with user experience SLAs.

 

AWS recommends utilizing Kubernetes Event-driven Autoscaling (KEDA) or the AWS CloudWatch Metrics Adapter to feed these custom signals directly into the HPA.

 


Step 3: Observing the Scale-Up Under Synthetic Load

 

To validate that your autoscaling policies function correctly before deploying to production, execute an automated load test against your cluster.

 

Phase A: Baseline Steady State

 

Under baseline conditions, the AI application runs at its configured `minReplicas` (e.g., 2 pods). Metrics Server reports low resource utilization:

 

- Pod count: 2/2 Running

- Average CPU utilization: ~4% / 50% target

- Average Memory utilization: ~35% / 70% target

 

 

Phase B: Applying Concurrent Synthetic Load

 

Using a lightweight load-testing script (or tools like Locust / k6), we generate sustained concurrent HTTP POST requests against the `/predict` inference endpoint.

 

As concurrent requests hit the FastAPI service:

1. Matrix computations and text tokenization saturate the allocated CPU cores.


2. Within 15 seconds, the Kubernetes Metrics Server records average pod CPU jumping from 4% → 88%.


3. The HPA control loop detects that 88% exceeds the 50% target threshold.


4. The HPA calculates that `2 (88/50) = 3.52 -> 4` pods are required, and as traffic persists, escalates the request up to *8 replicas**.


5. Kubernetes creates the new pod objects and immediately transitions them to `ContainerCreating` and `Running`.

  

 

Step 4: Node-Level Elasticity — Karpenter vs. EKS Auto Mode

 

Scaling pods is only half the battle. What happens when your 8 AI pods require a combined 16 vCPUs and 32 GB of RAM, but your existing cluster only has one 4-vCPU node?

 

Without a node autoscaler, 6 of those pods will remain trapped in `Pending` with the event `0/1 nodes are available: insufficient cpu`.

 

Feature / Metric

Legacy: Cluster Autoscaler (CAS)

Modern: Karpenter / EKS Auto Mode

Infrastructure Integration

Manages EC2 Auto Scaling Groups (ASGs)

Group-less, direct EC2 Fleet API integration

Instance Selection

Homogeneous instance sizes

Heterogeneous, right-sized compute provisioning

Scale-Up Latency

Slow scale-up latency (3 to 6 minutes)

Ultra-fast scale-up latency (30 to 45 seconds)

Resource Orchestration

Rigid GPU/Compute separation

Automatic Spot/On-Demand & GPU orchestration

 

The Limitations of Legacy Cluster Autoscaler

 

Historically, Kubernetes relied on the Kubernetes Cluster Autoscaler (CAS). CAS works by manipulating AWS EC2 Auto Scaling Groups (ASGs). When a pod is pending, CAS increases the `DesiredCapacity` of an ASG.

 

While functional for traditional web apps, CAS introduces significant drawbacks for modern AI workloads:

- High Provisioning Latency: Scaling an ASG involves EC2 launch orchestration, CloudWatch alarms, and node bootstrapping, often taking 3 to 6 minutes before a node can accept pods.

- Inflexible Instance Sizing: ASGs are bound to fixed instance types. If you need a mixture of compute-optimized (`c6i`), memory-optimized (`r6i`), and GPU (`g5`) instances, you must configure and manage dozens of separate ASGs.

 

Modern Solution: Karpenter & EKS Auto Mode

 

AWS now strongly recommends Karpenter (and EKS Auto Mode, which incorporates Karpenter's native capabilities into managed EKS clusters).

 

Karpenter is an open-source, high-performance node autoscaler designed specifically for Kubernetes on AWS:

 

1. Direct Fleet Provisioning: Karpenter bypasses Auto Scaling Groups entirely. It communicates directly with the AWS EC2 Fleet API to launch virtual machines in seconds.

2. Just-In-Time Right-Sizing: Karpenter inspects the exact resource requests, volume constraints, node selectors, and GPU tolerations of pending pods, and launches the single cheapest EC2 instance type that perfectly fits the workload.

3. Automated Node Consolidation: When traffic drops, Karpenter actively consolidates workloads onto fewer instances or replaces expensive nodes with smaller, cheaper alternatives, maximizing cost efficiency. 


 

Step 5: Managing Scale-Down, Stabilization, and Cost Controls

 

Scaling up protects availability; scaling down protects your budget.

 

In enterprise AI deployments, unmanaged compute infrastructure is one of the fastest ways to run up massive cloud bills. However, scaling down AI containers requires careful engineering to avoid service disruptions.

 

The Danger of "Flapping" (Thrashing)

 

Imagine a scenario where traffic fluctuates around your threshold:

- 12:00 PM: Traffic spikes → HPA scales from 2 to 8 pods.

- 12:01 PM: Traffic dips slightly → HPA immediately scales down from 8 to 2 pods.

- 12:02 PM: Traffic spikes again → HPA scales back to 8 pods.

 

This phenomenon is known as flapping (or thrashing). In AI applications, flapping is disastrous because AI containers suffer from cold-start latency (downloading model weights, initializing PyTorch/CUDA runtimes, and warming caches). If you terminate pods too quickly, incoming requests will hit un-warmed containers, causing severe latency spikes.

 

The Solution: HPA Stabilization Windows

 

Kubernetes HPA provides configurable scaling policies with built-in stabilization windows:

 

behavior:
  scaleDown:
    stabilizationWindowSeconds: 300  # Wait 5 minutes of sustained low traffic
    policies:
    - type: Percent
      value: 25                      # Terminate at most 25% of pods per minute
      periodSeconds: 60
  scaleUp:
    stabilizationWindowSeconds: 0    # Scale up immediately without delay
    policies:
    - type: Percent
      value: 100                     # Double capacity in one step if needed
      periodSeconds: 15

 

- `scaleUp` (Instant Response): Configured with zero stabilization delay, allowing the cluster to explode capacity outward in seconds when a surge occurs.


- `scaleDown` (Conservative Dampening): Enforces a 5-minute stabilization window and caps pod terminations at 25% per minute. This ensures that brief traffic troughs do not prematurely destroy healthy, warm containers.

 

Graceful Pod Termination and Pre-Warming

 

When Kubernetes terminates an AI pod during scale-down:

1. The pod is removed from the Kubernetes Service endpoints list (stopping new traffic).

2. Kubernetes sends a `SIGTERM` signal to the container process.

3. The application must finish processing all in-flight inference requests before shutting down.

 

Configure `terminationGracePeriodSeconds: 60` in your deployment to allow long-running inference requests to complete cleanly without dropping connections.

 

 

Enterprise Production Considerations

 

Deploying auto-scaling for enterprise AI workloads requires addressing specialized operational constraints:

 

1. Spot Instance Orchestration for AI


EC2 Spot Instances offer up to 90% discounts compared to On-Demand pricing. For AI inference, Karpenter can be configured to provision Spot instances dynamically for peak bursting while maintaining a baseline of On-Demand instances for guaranteed minimum availability.

 

If AWS issues a 2-minute Spot Interruption Warning, Karpenter automatically intercepts the notification, cordons the node, provisions a replacement instance, and gracefully drains existing pods before termination occurs.

 

2. Multi-Zone Availability and Pod Disruption Budgets (PDB)


Never allow auto-scaling to concentrate all replicas in a single AWS Availability Zone (AZ):

- Topology Spread Constraints: Enforce `topologySpreadConstraints` in your pod spec to mandate that Kubernetes distributes replicas evenly across at least 3 AZs.

- Pod Disruption Budgets (PDB): Define a `PodDisruptionBudget` specifying `minAvailable: 2` to guarantee that voluntary administrative evictions or node consolidation routines never reduce your active replica count below safe operational levels.

 

3. Model Cache Pre-Warming & Shared Storage


If your model weights exceed 5–10 GB, downloading them from Amazon S3 on every pod cold-start creates unacceptable scaling delays.

 

Enterprise solutions utilize Amazon FSx for Lustre or Amazon EFS mounted as persistent volumes across nodes, or pre-cache model weights on custom AMI snapshots. When Karpenter provisions a new node, the model weights are already present on local NVMe storage, reducing container startup time from 4 minutes to 8 seconds.

 


 

Common Auto-Scaling Pitfalls & How to Avoid Them

 


 1. Missing Resource Requests


  • Root Cause: Omitting resources.requests in pod specifications.

  • Impact: HPA cannot calculate percentage utilization; autoscaling fails entirely.

  • Recommended Solution: Mandate resource requests on all production containers via Kyverno or Open Policy Agent (OPA).


2. Requests Equal to Limits


  • Root Cause: Setting CPU requests equal to limits.

  • Impact: Eliminates burst capacity; triggers CPU throttling under minor traffic spikes.

  • Recommended Solution: Set requests to baseline throughput requirements and limits to peak headroom.


3. Flapping on Scale-Down


  • Root Cause: Relying on default or zero scale-down stabilization windows.

  • Impact: Pods are continuously destroyed and recreated, causing latency spikes and thrashing.

  • Recommended Solution: Configure stabilizationWindowSeconds: 300 within the HPA behavior configuration.


4. Untuned Readiness Probes


  • Root Cause: Marking pods as ready before models or dependencies load into VRAM/memory.

  • Impact: Load balancers route live traffic to initializing pods, generating HTTP 500 errors.

  • Recommended Solution: Implement an explicit /health/ready probe that directly verifies model load status.


5. Over-Reliance on Cluster Autoscaler


  • Root Cause: Using legacy ASG-based autoscaling for heterogeneous AI/ML workloads.

  • Impact: 3 to 6 minute node provisioning delays during sudden traffic spikes.

  • Recommended Solution: Migrate to Karpenter or EKS Auto Mode for sub-minute, right-sized node launches.


6. Ignoring GPU Duty Cycles


  • Root Cause: Scaling GPU inference workloads based strictly on host CPU metrics.

  • Impact: Pods remain at 1 replica while the GPU is 100% saturated.

  • Recommended Solution: Use KEDA or DCGM metrics to scale dynamically based on GPU utilization or queue depth.


7. Missing Pod Disruption Budgets


  • Root Cause: Omitting minimum availability constraints during cluster maintenance or node consolidation.

  • Impact: Node drain events trigger temporary cluster-wide service outages.

  • Recommended Solution: Define a PodDisruptionBudget (PDB) with enforced minAvailable thresholds.


 

The Production Auto-Scaling Readiness Checklist

 

Validate every item on this checklist before signing off on automated scaling for production AI applications:

 

Pod Specification & Resource Management

- [ ] Explicit `cpu` and `memory` requests and limits defined for all containers.

- [ ] Memory requests set above static model weight footprint.

- [ ] Memory limits provide adequate headroom to prevent `OOMKilled` crashes during heavy batches.

- [ ] `terminationGracePeriodSeconds` set appropriately for long-running inference requests.

- [ ] Liveness and readiness probes properly configured and decoupled.

 

Horizontal Pod Autoscaler (HPA)

- [ ] `minReplicas` and `maxReplicas` defined based on capacity and budget modeling.

- [ ] Scaling target thresholds set conservatively (e.g., 50–70% average CPU/memory).

- [ ] Scale-down stabilization window configured (minimum 300 seconds) to prevent flapping.

- [ ] Custom metrics (queue depth, request latency, GPU duty cycle) integrated via KEDA where applicable.

 

Node-Level Autoscaler (Karpenter / EKS Auto Mode)

- [ ] Karpenter or EKS Auto Mode configured with diverse, cost-optimized instance type allowances.

- [ ] NodePool configured to support both On-Demand and Spot instances with automated fallback.

- [ ] GPU tolerations and node selectors configured for GPU-accelerated workloads.

- [ ] Node consolidation and expiration policies configured for aggressive cost recovery.

 

Resilience & High Availability

- [ ] `topologySpreadConstraints` configured to distribute pods across multiple AWS Availability Zones.

- [ ] `PodDisruptionBudget` (PDB) active to ensure minimum availability during node draining.

- [ ] CloudWatch alarms configured for scaling anomalies, failed node launches, and HPA max-replica saturation.

 

 

Closing Thoughts: Elasticity Is an Engineering Discipline

 

Auto-scaling is not a switch you flip; it is an architectural contract between your application code, your container runtime, and your cloud infrastructure.

 

When AI applications are deployed onto static compute clusters, organizations inevitably face an unpalatable choice: over-provision compute and burn millions in idle cloud costs, or under-provision and risk catastrophic outages when traffic surges.

 

By implementing the multi-tiered auto-scaling architecture outlined in this guide—anchoring container boundaries with disciplined resource requests, configuring reactive and custom-metric HPAs, and pairing them with the lightning-fast node orchestration of Karpenter on Amazon EKS—you eliminate that false compromise.

 

Your AI platform expands instantly to meet incoming user demand, maintains rock-solid latencies under extreme load, and contracts aggressively the moment traffic subsides.

 

If your team is currently managing Kubernetes scaling manually or struggling with latency spikes and runaway GPU infrastructure bills, establishing these auto-scaling foundations is the single most impactful architectural upgrade you can deliver.

 

Deploying Kubernetes is not enough; the workload demands predictable scaling, cold-start mitigation, and rigorous financial governance. Elasticity transforms Kubernetes from a static hosting environment into a resilient, self-healing, and cost-optimized AI engine.

 


Comments


bottom of page