Skip to main content
Your progress
0 of 7 complete0%
T3 / M3.3 / L2 OF 6 / Engineer LEVEL / 9 min

HPA: the signal it gives you

Outcome

By the end of this lesson, you will be able to read HPA signals to identify right-sizing opportunities, diagnose the four common HPA patterns (at-min, at-max, oscillating, balanced), and tune HPA settings based on observed behavior.


PrerequisitesM3.3.L1 (requests/limits)
Time9 minutes

1. Concept

The Horizontal Pod Autoscaler adds and removes copies of a workload as load changes, watching processor use, memory or a metric of your own.

What it does is useful. What it remembers is more useful. Its history is a record of how much demand this workload has really seen, which is exactly the thing you would otherwise be guessing at when deciding how big it should be.

Terminal window
HPA STATUS shows:
Current replicas (how many running now)
Desired replicas (what HPA wants)
Min / max settings (bounds you set)
Current resource usage vs target
ScalingLimited condition (if any: important!)
HPA HISTORY (over 30 days) shows:
How often at min (over-provisioned at min if always there)
How often at max (under-provisioned at max if always there)
Oscillation pattern (bursty workload signal)
ScalingLimited events (capacity constraint signal)

The history is the goldmine. The current state is a snapshot; the history is a diagnosis.

The “ScalingLimited” condition: the strongest signal

Terminal window
ScalingLimited = the replica count the HPA wanted was clamped by a bound
reason TooManyReplicas: it wanted more than max
reason TooFewReplicas: it wanted fewer than min
EXAMPLE (at max):
HPA target: 70% CPU
Workload at: 95% CPU (clearly above target)
HPA wants to scale OUT to handle the load
But: pod count at max (e.g., 10)
HPA stuck at 10 replicas; ScalingLimited fires
This is a strong signal:
Workload is UNDER-provisioned at max
Either increase max replicas
Or right-size pods up (bigger pods = fewer needed)
At min (TooFewReplicas) the signal reverses:
Load would fit in fewer pods than the min allows
Workload is OVER-provisioned at the min
Reduce min replicas

Always read the reason. Frequent TooManyReplicas = under-provisioning at the top; frequent TooFewReplicas = a min set higher than the load needs.

Four common HPA patterns

Terminal window
PATTERN 1: STUCK AT MIN
HPA at minimum replicas for >50% of the time
CPU below target most hours
ScalingLimited with reason TooFewReplicas
DIAGNOSIS: Min too high
WORKLOAD: Over-provisioned at the low end
ACTION: Reduce min replicas
COST IMPACT: 20-40% savings on low-traffic hours
PATTERN 2: STUCK AT MAX
HPA at maximum replicas for >20% of the time
ScalingLimited (reason TooManyReplicas) frequent
CPU at or above target
DIAGNOSIS: Max too low OR pods too small
WORKLOAD: Under-provisioned at the high end
ACTION: Increase max OR right-size pods up
COST IMPACT: maybe higher cost; lower latency / better UX
PATTERN 3: OSCILLATING (rapid scale up/down)
HPA replica count changing >5×/hour
Constant scale up → scale down → scale up cycle
DIAGNOSIS: Bursty workload; HPA reacting to peaks
WORKLOAD: Spiky but autoscaler can keep up
ACTION: Increase scaling stabilization window
OR set target higher (less sensitive)
OR add KEDA for event-driven scaling (predictive
scaling needs an add-on such as the PredictKube scaler)
COST IMPACT: thrash creates churn; smoothing reduces wasted scaling
PATTERN 4: BALANCED (healthy)
HPA at desired ≈ current always
Replica count varies smoothly with load
Rarely at min or max
DIAGNOSIS: Workload matches target well
ACTION: No action needed; maintain current settings
COST IMPACT: Optimal

Most workloads in unmaintained clusters: 30% Pattern 1, 10% Pattern 2, 10% Pattern 3, 50% Pattern 4. Tuning the first three categories captures the savings.

Investigation pattern

Two sources of evidence: the HPA’s current state, and its history.

Terminal window
CURRENT STATE (ZopNight Kubernetes recommendations, EKS IDs shown;
GKE uses 18xx, AKS 19xx):
RC-1717 HPA At Max Capacity
→ raise max OR right-size pods up; risk of latency during spikes
RC-1770 / RC-1771 CPU Over-Provisioned
→ the HPA reports ScalingLimited / TooFewReplicas (it wants to go
below min); lower min or the CPU request
RC-1743..RC-1746 HPA posture: max = 1, min = max, min = 0,
no metrics
→ fix the HPA spec itself
HISTORY (your own metrics: Prometheus, kube-state-metrics):
Hours per day at min → candidate to reduce min replicas
ScalingLimited events per week → candidate to raise max
Replica changes per day → oscillation; lengthen the
stabilization window, or add KEDA
for event-driven scaling
(predictive scaling needs an
add-on such as the PredictKube
scaler)

ZopNight reads the HPA’s state at each sync, not its history. Time-at-min and oscillation come from your own metrics. Verify either source before changing a production HPA.

HPA + scheduled scaling

Terminal window
WHEN HPA AND A SCALING SCHEDULE ARE BOTH ACTIVE:
A scheduled job (CronJob patching the HPA, or KEDA's cron
scaler) sets the replica RANGE (min, max)
HPA scales WITHIN that range based on metrics
EXAMPLE:
BUSINESS HOURS: min=4, max=12
OFF-HOURS : min=1, max=4
HPA: targets 70% CPU
During business hours: HPA scales 4-12 based on CPU
During off-hours: HPA scales 1-4 based on CPU
COMBINED EFFECT:
The scheduled range provides the floor (cost control)
HPA provides the dynamic adjustment (responsiveness)
Together: low cost during off-hours, full capacity during peaks

The combo is the cost+performance sweet spot for most workloads. A ZopNight schedule is a different tool: stopping a namespace scales its workloads to zero and removes the HPA until the start, when both are restored.

HPA’s limitations

Terminal window
HPA SCALES POD COUNT, NOT POD SIZE
If pods need more memory: HPA can't help
Need to adjust resource requests/limits manually
Or: VPA (Vertical Pod Autoscaler): separate tool
HPA SCALES BASED ON METRICS YOU PROVIDE
CPU: standard, always available
Memory: built-in resource metric (autoscaling/v2, metrics-server)
Custom metrics (queue depth, request rate): need configuration
Tools: Prometheus Adapter, KEDA
HPA HAS LATENCY
Detection: ~30 seconds
Scale-up: 30-60 seconds typically
For sub-second spike response: HPA isn't fast enough
Use over-provisioning + queue-based load shedding for ultra-low-latency

HPA is the workhorse for replica scaling, but it’s not a panacea. Pair with right-sizing (L1), schedules (M3.2), and custom metrics where needed.

Common HPA mistakes

Terminal window
MISTAKE FIX
──────────────────────────────────────────────────────────────────
Setting min replicas too high Measure off-peak utilization
"Just in case" thinking Reduce min until off-peak
is healthy (CPU 30-60%)
Setting max replicas too low Watch for ScalingLimited
Limits scale-out during real spikes events; increase max
User impact during peaks OR right-size pods up
Wrong target value (too high) Target at 80% → scaling near
e.g., 80% saturation; latency suffers
Set 65-70% for most workloads
Wrong target value (too low) Target at 30% → constantly
e.g., 30% over-provisioned
Set higher; only go low for
extremely latency-sensitive
Custom metric mis-configured Verify metric in Prometheus
("queue depth" reports wrong) BEFORE relying on HPA action
HPA + autoscaler not coordinated Cluster autoscaler scales nodes
HPA scales pods but no nodes Both must be configured
See your cluster setup

The fixes are mostly observational + iterative. Set, measure, tune.

Tuning HPA: concrete process

Terminal window
QUARTERLY HPA TUNING (per workload):
WEEK 1: MEASURE
Pull 30 days of HPA history
Categorize: stuck-at-min / stuck-at-max / oscillating / balanced
Quantify: % time at extremes, scaling event count
WEEK 2: DIAGNOSE
For each non-balanced workload:
Identify the issue (too high min, too low max, etc.)
Estimate cost impact of fixing
WEEK 3: APPLY
Pick top 3 highest-impact tunings
Update HPA spec (kubectl apply)
Monitor for 1 week
WEEK 4: VERIFY
Confirm pattern moved toward balanced
Confirm no degradation (latency, error rate, OOM)
Document changes

A workload running smoothly for 6 months still benefits from this review: traffic patterns change.


2. Demo

An illustrative HPA tuning case:

Terminal window
WORKLOAD: order-processor (e-commerce, payment-team)
HPA SETTINGS: min=4, max=12, target=70% CPU
OBSERVATIONS OVER 30 DAYS:
Replica count distribution:
At 4 replicas (min): 62% of time (overnight + low-traffic)
At 5-7 replicas: 28% of time (normal business hours)
At 8-10 replicas: 9% of time (peak times)
At 11-12 replicas: <1% of time
ScalingLimited events: 0
CPU utilization:
Daily avg: 38%
Daily p95: 62%
Daily p99: 78%
DIAGNOSIS:
PATTERN 1: STUCK AT MIN (62% of time)
Min too high: 4 replicas pinned during off-peak
Each replica at ~25% CPU during off-peak
Waste: 4 replicas × 75% unused capacity = 3 replica-equivalents wasted
Max also too high: never reached 12
Could reduce max to 10 without losing capacity
RECOMMENDATIONS:
Reduce min from 4 to 2
Off-peak: 2 replicas handle the light load
HPA scales up to 4-7 during business hours as needed
Reduce max from 12 to 10
Never used 12; 10 is comfortable peak
Keep target at 70%
p95 CPU 62% within target; healthy
COST IMPACT:
Each replica: ~$0.04/hr
Off-peak window: ~15 hrs/day (62% of 24)
Savings per day: 2 replicas saved × 15 hrs × $0.04 = $1.20
Monthly savings: $1.20 × 30 = $36/mo per workload
Doesn't sound huge BUT scales across:
18 workloads with similar pattern: ~$648/mo = $7,776/yr
DEPLOYMENT:
Update HPA spec:
minReplicas: 2
maxReplicas: 10
target: 70% (unchanged)
Monitor: 2 weeks
Verify off-peak CPU stays healthy (<80%)
Verify business hours scaling reaches 4-7 as expected
Watch for any latency degradation
VERIFICATION (after 2 weeks):
Off-peak CPU at 2 replicas: 65% (healthy)
Business hours scaling: 4-6 replicas typical (matches old)
No latency change
Saved: $36/mo as projected
DECISION: roll out pattern to other 17 similar workloads
(similar low-utilization-at-min pattern)

The pattern compounds. Quarterly review across cluster: $5K-15K/year savings typical.


3. Hands-on (5 min)

Audit one of your HPA-managed workloads:

Terminal window
□ STEP 1: Pick a workload
Workload: __________
Current min: ___ max: ___ target: ___%
□ STEP 2: Pull HPA history (30 days)
Prometheus: kube-state-metrics series for the HPA's current
replicas and conditions (30-day range)
$ kubectl describe hpa <workload> (current state, recent events)
ZopNight: the cluster's HPA list shows current min/max/replicas
Time at min: _____ % of period
Time at max: _____ % of period
ScalingLimited events: _____
□ STEP 3: Classify pattern
□ Stuck at min (>50% time)
□ Stuck at max (>20% time)
□ Oscillating (>5 events/hr)
□ Balanced
□ STEP 4: Plan tuning
Action: __________
Estimated savings: $_____/mo
□ STEP 5: Apply + observe
Apply: __________
Monitor for: ___ days
Verification metric: __________

A 15-minute audit per workload reveals patterns. Address top 3 each quarter.


4. Knowledge check

Q1

HPA “ScalingLimited” condition firing frequently:

A. Proof the HPA is scaling correctly
B. A metrics-server outage somewhere on the cluster itself now
C. Successful scaling
D. Signal that a min or max bound clamped the scaling

Show answer

Correct: D. Read the reason before acting. TooManyReplicas (stuck at max) points to under-provisioning: increase max or right-size pods up, because it correlates with user-visible latency. TooFewReplicas (stuck at min) points the other way: the min is higher than the load needs.

Q2

A workload at min replicas for 62% of time (~15 hrs/day):

A. Optimal: that’s autoscaling working
B. Likely over-provisioned at the min
C. Increase min
D. Evidence the HPA target is set too low

Show answer

Correct: B. Reduce min to lower the off-peak baseline, which saves 20-40% of pod cost during off-peak. Verify off-peak CPU stays healthy after the reduction.

Q3

HPA target value of 80% CPU:

A. Aggressive: scaling triggers near saturation
B. Conservative; safe
C. Too low: it scales the workload out far too early
D. Optimal

Show answer

Correct: A. Latency suffers because pods are already near their limit when HPA finally decides to scale. Set target 65-70% for most workloads (more headroom during scaling delay). Lower target for scaling headroom.


5. Apply

Review HPA settings + history quarterly. ZopNight’s Kubernetes recommendations flag HPAs at max, over-provisioned CPU and broken HPA specs; your metrics history shows time-at-min and oscillation. Tune top 3 workloads each quarter; pattern compounds.

For your team: monthly HPA review on the top 5 highest-cost workloads. Address Patterns 1, 2, 3 in priority order.


Glossary terms touched

HPA (Horizontal Pod Autoscaler)


Start with the bill.

Foundations takes about five hours. The first lesson is nine minutes.

Free curriculum. No paywall. 290 lessons across 7 courses, plus an optional 24-lesson beginner course. Browse every course without an account; a free account opens the lessons, saves your progress and opens the exams for three publicly verifiable credentials. Read it on the train, take the exam on a Saturday, list the credential on your résumé Monday.

5h median time to finish Foundations
free account, every course, no paywall
3 certifications, each with a public verifier
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·