Skip to content

HPA on k3s (CPU Autoscaling Lab)

First PublishedByAtif Alam

This runbook exercises pod-level Horizontal Pod Autoscaler (HPA) on a k3s cluster using CPU utilization. It does not cover node autoscaling (Cluster Autoscaler / Karpenter) — single-node labs have no node pool to grow.

Conceptual background: Autoscaling on EKS. For custom metrics, continue to Prometheus Adapter HPA on k3s after Prometheus + Grafana on k3s.

Terminal window
kubectl config current-context
kubectl get nodes

Expected: context points at k3s; nodes Ready.

k3s often ships metrics-server already:

Terminal window
kubectl top nodes
kubectl get apiservice v1beta1.metrics.k8s.io

Expected: node CPU/memory columns; APIService Available.

If metrics not available, install metrics-server for your k3s version (follow k3s or upstream metrics-server docs), then retry kubectl top nodes.

Terminal window
kubectl create namespace hpa-demo --dry-run=client -o yaml | kubectl apply -f -
kubectl config set-context --current --namespace=hpa-demo

Create hpa-demo.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-demo
labels:
app: hpa-demo
spec:
replicas: 1
selector:
matchLabels:
app: hpa-demo
template:
metadata:
labels:
app: hpa-demo
spec:
containers:
- name: stress
image: registry.k8s.io/hpa-example
ports:
- containerPort: 80
resources:
requests:
cpu: 200m
limits:
cpu: 500m
---
apiVersion: v1
kind: Service
metadata:
name: hpa-demo
labels:
app: hpa-demo
spec:
ports:
- port: 80
selector:
app: hpa-demo

CPU requests are required — without them HPA shows current metrics as unknown.

Apply:

Terminal window
kubectl apply -f hpa-demo.yaml
kubectl rollout status deployment/hpa-demo --timeout=120s

Create hpa-demo-hpa.yaml:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-demo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-demo
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
Terminal window
kubectl apply -f hpa-demo-hpa.yaml
kubectl get hpa
kubectl describe hpa hpa-demo

Expected: TARGETS eventually show a percentage (not <unknown>/50%). REPLICAS may still be 1 until load arrives.

Terminal window
kubectl top pods -l app=hpa-demo
watch -n2 'kubectl get hpa,pods -l app=hpa-demo'

In a separate terminal, run a load generator pod:

Terminal window
kubectl run -it load-generator --rm --restart=Never --image=busybox:1.36 -- /bin/sh

Inside the shell:

Terminal window
while true; do wget -q -O- http://hpa-demo; done

Alternatively from your workstation with port-forward:

Terminal window
kubectl port-forward svc/hpa-demo 8080:80
# another terminal: hey -z 3m -c 20 http://127.0.0.1:8080/

Expected within a few minutes:

  • kubectl get hpa shows rising CURRENT CPU vs TARGET.
  • REPLICAS increases toward maxReplicas (5).

If replicas oscillate, patch stabilization (see Autoscaling on EKS — Scale behavior):

Terminal window
kubectl patch hpa hpa-demo --type=merge -p '
spec:
behavior:
scaleDown:
stabilizationWindowSeconds: 120
'
SymptomWhat to check
current: <unknown>Container CPU requests missing; wait 1–2 min after apply
unable to get metricsmetrics-server pods, APIService v1beta1.metrics.k8s.io
No scale-up under loadTarget too low already met, or load not reaching pods; verify Service endpoints
kubectl top failsmetrics-server not running on k3s
HPA unknown but pods runNetworkPolicy blocking metrics-server scrape path — Network policies
Terminal window
kubectl describe hpa hpa-demo
kubectl get events --sort-by='.lastTimestamp'
kubectl logs -l app=hpa-demo --tail=50
Terminal window
kubectl delete hpa hpa-demo
kubectl delete deployment,svc hpa-demo
kubectl delete pod load-generator --ignore-not-found
# optional: kubectl delete namespace hpa-demo