Lesson  in  Kubernetes 101

Horizontal Pod Autoscaler

Let the cluster scale for you: set up an HPA on a Deployment, generate real load and watch the replicas grow to absorb it. Optional lesson.

In the Deployment and lifecycle lesson you scaled by hand with kubectl scale. It works, but it needs someone watching. The Horizontal Pod Autoscaler (HPA) closes the loop: it compares the real consumption of the Pods with a target you declare and adjusts the replicas on its own.

The Scaling chapter of the book presents the three Kubernetes autoscalers (HPA, VPA and the node one) and why they solve different problems. This module puts the first two to work.

Important

⚠️ This lesson is optional and somewhat more temperamental than the rest: it depends on the metrics cycle of metrics-server (about 15 seconds) and on the HPA's decision window, so between generating load and seeing new replicas, 1 to 3 minutes can go by. That is how autoscaling really behaves, not a fault of the lab.

The HPA needs to know how much each Pod consumes, and that information is served by metrics-server, which k3s ships installed. Check it from the dev-machine tab:

kubectl top nodes
kubectl top pods -A

That kubectl top you just used is also the basic day-to-day tool for resource observability.

Step 1: A Deployment with requests (mandatory) and its Service

The CPU HPA calculates percentages over the Pod's requests. Without requests there is no percentage to calculate, and the HPA is useless: it is the definitive practical reason to always declare them. Create api.yaml with both pieces:

cat << 'EOF' > api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: nginx
        image: ghcr.io/iximiuz/labs/nginx:alpine
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 20m
            memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
  - port: 80
    targetPort: 80
EOF

Everything in this file you have already written in earlier lessons; the only new detail is the intention: that deliberately low cpu: 20m will turn a little load into a high percentage, so the lab scales fast.

kubectl apply -f api.yaml

Step 2: The HPA

Create hpa.yaml:

cat << 'EOF' > hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 1
  maxReplicas: 4
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
EOF

The YAML, explained in questions and answers

What does scaleTargetRef point at?

The object whose replicas the HPA will move. It does not manage Pods directly: it modifies the replicas field of the Deployment and lets the machinery you already know (Deployment, ReplicaSet) do the rest.

What does averageUtilization: 50 mean?

The target: the average CPU of the Pods should hover around 50 percent of their requests. With requests of 20m, that is 10m of average consumption. If the average goes up, the HPA adds replicas to spread the load; if it goes down, it removes them.

What are minReplicas and maxReplicas for?

They are the guardrails. The minimum guarantees service even with no load; the maximum protects the cluster (and your bill) from a spike or a metric gone haywire.

Why autoscaling/v2 and not v1?

v2 is the current API and supports several metrics at once, including memory and custom metrics. v1 only understood CPU.

Apply it and watch its first calculation (it takes a few seconds to stop saying unknown):

kubectl apply -f hpa.yaml
kubectl get hpa api-hpa

Step 3: Load and liftoff

Generate continuous traffic with a Pod that never stops requesting the page (generador means "generator"):

kubectl run generador --image=ghcr.io/iximiuz/labs/nginx:alpine --command -- \
  sh -c 'for i in 1 2 3 4 5; do (while true; do wget -qO- http://api >/dev/null 2>&1; done) & done; wait'

And get comfortable watching the key instrument:

kubectl get hpa api-hpa --watch

You will see the TARGETS column go from a low percentage to above 50 percent, and shortly after, the REPLICAS column go up. Patience: the full cycle (metrics-server measures, the HPA decides, the Deployment rolls out) takes between 1 and 3 minutes.

Once the check passes, run the reverse experiment: delete the generator (kubectl delete pod generador) and leave the watch open. Going back to 1 replica will take quite a bit longer, about 5 minutes: the HPA scales down with a deliberately conservative stabilization window, because running short of replicas hurts more than running with too many.

Summary

  • The HPA adjusts the replicas of a Deployment by comparing real consumption against a declared target.
  • Without requests there is no CPU HPA: the percentage is calculated over them.
  • metrics-server provides the signal, and kubectl top is your window into it.
  • Scaling up is fast; scaling down, cautious. That is design, not slowness.

And with this the course comes to an end. You have walked the whole road: from the first Pod to a cluster that repairs itself, configures itself, protects itself and now also sizes itself.

Previous lesson
Jobs and CronJobs