HPA on k3s (CPU Autoscaling Lab)
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.
Purpose and prerequisites
Section titled “Purpose and prerequisites”Confirm cluster access
Section titled “Confirm cluster access”kubectl config current-contextkubectl get nodesExpected: context points at k3s; nodes Ready.
Verify metrics-server
Section titled “Verify metrics-server”k3s often ships metrics-server already:
kubectl top nodeskubectl get apiservice v1beta1.metrics.k8s.ioExpected: 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.
Namespace
Section titled “Namespace”kubectl create namespace hpa-demo --dry-run=client -o yaml | kubectl apply -f -kubectl config set-context --current --namespace=hpa-demoStep 1: Deploy the workload
Section titled “Step 1: Deploy the workload”Create hpa-demo.yaml:
apiVersion: apps/v1kind: Deploymentmetadata: name: hpa-demo labels: app: hpa-demospec: 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: v1kind: Servicemetadata: name: hpa-demo labels: app: hpa-demospec: ports: - port: 80 selector: app: hpa-demoCPU requests are required — without them HPA shows current metrics as unknown.
Apply:
kubectl apply -f hpa-demo.yamlkubectl rollout status deployment/hpa-demo --timeout=120sStep 2: Apply HPA
Section titled “Step 2: Apply HPA”Create hpa-demo-hpa.yaml:
apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: hpa-demospec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: hpa-demo minReplicas: 1 maxReplicas: 5 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50kubectl apply -f hpa-demo-hpa.yamlkubectl get hpakubectl describe hpa hpa-demoExpected: TARGETS eventually show a percentage (not <unknown>/50%). REPLICAS may still be 1 until load arrives.
Step 3: Baseline metrics
Section titled “Step 3: Baseline metrics”kubectl top pods -l app=hpa-demowatch -n2 'kubectl get hpa,pods -l app=hpa-demo'Step 4: Generate load
Section titled “Step 4: Generate load”In a separate terminal, run a load generator pod:
kubectl run -it load-generator --rm --restart=Never --image=busybox:1.36 -- /bin/shInside the shell:
while true; do wget -q -O- http://hpa-demo; doneAlternatively from your workstation with port-forward:
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 hpashows rising CURRENT CPU vs TARGET.- REPLICAS increases toward
maxReplicas(5).
Step 5: Optional — scale behavior
Section titled “Step 5: Optional — scale behavior”If replicas oscillate, patch stabilization (see Autoscaling on EKS — Scale behavior):
kubectl patch hpa hpa-demo --type=merge -p 'spec: behavior: scaleDown: stabilizationWindowSeconds: 120'Troubleshooting
Section titled “Troubleshooting”| Symptom | What to check |
|---|---|
current: <unknown> | Container CPU requests missing; wait 1–2 min after apply |
unable to get metrics | metrics-server pods, APIService v1beta1.metrics.k8s.io |
| No scale-up under load | Target too low already met, or load not reaching pods; verify Service endpoints |
kubectl top fails | metrics-server not running on k3s |
| HPA unknown but pods run | NetworkPolicy blocking metrics-server scrape path — Network policies |
kubectl describe hpa hpa-demokubectl get events --sort-by='.lastTimestamp'kubectl logs -l app=hpa-demo --tail=50Cleanup
Section titled “Cleanup”kubectl delete hpa hpa-demokubectl delete deployment,svc hpa-demokubectl delete pod load-generator --ignore-not-found# optional: kubectl delete namespace hpa-demo