Kubernetes how-to
Kubernetes HPA: Autoscale Pods With autoscaling/v2
The Horizontal Pod Autoscaler changes the replica count of a Deployment, StatefulSet or similar workload based on observed metrics. For CPU and memory it compares live usage with the requests you set on each container. Without metrics-server and with no requests, the autoscaler has nothing to act on, and kubectl shows the target as unknown.
By Muhammad Soliman, Founder at SeaGit
·
Key takeaways
- Use autoscaling/v2. It accepts several metrics and a behavior section that controls how fast the HPA scales up and down.
- Resource utilization is measured against requests, not limits. A container with no CPU request gives the HPA no percentage to compare.
- metrics-server must be running. It serves the metrics.k8s.io API that the HPA reads.
- When TARGETS shows <unknown>, the HPA cannot read the metric. Check metrics-server, the requests, and the pod readiness before you touch the replica count.
What the HPA needs before it can scale
The HPA controller is built into the control plane. It does not measure anything itself. It reads metrics from the aggregated API layer, and for CPU and memory that is the metrics.k8s.io API, which the metrics-server add-on provides. The official task page is explicit that the cluster administrator must enable the aggregation layer and register that API.
- metrics-server installed and Running in kube-system, with kubectl top pods returning numbers.
- CPU or memory requests set on every container in the target pods. Utilization is usage divided by request.
- A scale target that supports the scale subresource, such as a Deployment or StatefulSet.
# metrics-server must answer before the HPA can work
kubectl get deployment metrics-server -n kube-system
kubectl top pods -n prod
# Requests must be present on the containers the HPA will measure
kubectl get deploy my-api -n prod \
-o jsonpath='{.spec.template.spec.containers[*].resources.requests}'An autoscaling/v2 HPA manifest
The autoscaling/v2 API replaces the older v1 form, which only supported a single CPU target. The example below keeps average CPU at 60 percent across the pods, with a floor of 2 and a ceiling of 10 replicas, and a memory target alongside it. A metric that goes above its target wins, so the HPA scales for whichever resource is tightest.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-api
namespace: prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-api
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75Do not set the Deployment replicas field and the HPA target at the same time as two sources of truth. If you apply a Deployment with a fixed replicas value, every apply resets the count the HPA chose. Leave replicas out of the Deployment once the HPA owns the count.
How the replica count is calculated
The controller computes a ratio between the current metric value and the target, then applies a ceiling to the current replica count. The documented formula is desiredReplicas = ceil(currentReplicas times currentMetricValue divided by desiredMetricValue). If the average CPU is 90 percent across 4 pods and the target is 60 percent, the result is ceil(4 times 1.5), which is 6 replicas.
A tolerance of 0.1 by default stops the HPA from acting on small differences. If the ratio is within 10 percent of 1.0, no change is made. When it averages utilization, the controller discards pods that are being deleted or have failed, and it handles pods that are not yet Ready with extra care, so a slow start-up does not trigger an immediate scale-up.
Control the pace with the behavior section
The behavior block tunes scaling in each direction. Its defaults match the historical HPA behavior: scale-down waits for a 300-second stabilization window, and scale-up has no window. The window stops the HPA from removing pods on a short dip in load. Scale-up is immediate so that a spike is answered quickly.
spec:
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # wait 5 min of lower load before removing pods
policies:
- type: Percent
value: 50 # remove at most half the pods per period
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 4 # add at most 4 pods per 15 seconds
periodSeconds: 15
selectPolicy: MaxChoosing a window
A longer scale-down window protects a service with slow warm-up, such as a JVM or a cache that rebuilds on start. A shorter one reclaims cost faster. Start with the defaults, watch the replica history for a week, and only then change the numbers.
Create and inspect an HPA with kubectl
kubectl autoscale creates an HPA for a Deployment without a file. It is quick for a test, but it only sets a CPU target. Use the YAML when you need memory targets or a behavior section.
# Quick CPU-only HPA, as in the Deployment page examples
kubectl autoscale deployment my-api -n prod --min=2 --max=10 --cpu-percent=60
# List HPAs with current and target metrics
kubectl get hpa -n prod
# Conditions and recent scaling decisions
kubectl describe hpa my-api -n prodThe describe output is the best place to look. Its Conditions block reports whether the HPA can compute the metric and whether it is allowed to scale. Its Events list the last scale decisions with the numbers used.
Test the scaling path before you rely on it
An HPA that has never scaled is a guess. Before you rely on it, drive load at the service in a staging namespace and watch three things: the CPU or memory reading, the replica count, and the readiness of the new pods. A healthy result is a steady climb in replicas that stops once the average falls under the target, followed by a slow descent after the scale-down window has passed.
# Watch the HPA decision and the pods together
kubectl get hpa my-api -n staging -w &
kubectl get pods -n staging -l app=my-api -w
# Read the per-pod usage the HPA is using
kubectl top pods -n staging -l app=my-apiWatch for two failure shapes. The first is flapping, where replicas go up and down every minute. That usually means the target is too tight for the real load, or the readiness probe is too slow, so a new pod looks idle while it warms up. The second is no movement at all, which usually means the requests are set too high, so the measured utilization never reaches the target. Fix the requests before you change the target.
Fixing <unknown> in TARGETS
A target that shows <unknown> means the controller could not get a value for the metric. The replica count stays where it is. This is the most common HPA complaint, and it has a short list of causes.
- metrics-server is not installed, is crash-looping, or cannot reach the kubelets. kubectl top fails in the same way.
- The containers have no request for the resource. Utilization needs the request as the denominator, so add cpu or memory requests.
- The pods are not Ready yet. A Deployment that is still starting may show no usable value until its readiness probe passes.
- The HPA points at the wrong object. A scaleTargetRef with a typo in the name or kind leaves the HPA unable to find pods.
- A custom or external metric is named in the spec, but no adapter serves it. The API aggregation layer must expose custom.metrics.k8s.io or external.metrics.k8s.io.
# The condition that explains the failure
kubectl get hpa my-api -n prod -o jsonpath='{.status.conditions[*].message}'
# Confirm metrics are flowing at all
kubectl top pods -n prod -l app=my-api