Seeking Wisdom · AI/ML Engineering · Investing · Books · Projects · About ·

Kubernetes by Example · Part 13

Scaling and Permissions

An autoscaler that will never scale, and a pod quietly forbidden from doing its job. Both report perfect health.

assumes Parts 1–12~19 minutestwo silent failures

The symptom

Two problems, both of which look like nothing happening.

The first: somebody queues two thousand hours of archive for re-transcription. The backlog climbs. The rewind worker sits at one replica, serenely.

$ kubectl get hpa rewind
NAME     REFERENCE           TARGETS         MINPODS   MAXPODS   REPLICAS
rewind   Deployment/rewind   /70%   1         10        1

The second: the sweeper is meant to clean up shared memory for departed stations. It isn't. Its logs are clean. The pod is healthy. The rings pile up until the node runs out.

$ kubectl logs deploy/sweeper | tail -2
sweeper: starting
sweeper: listing pods on node airwaves-worker

It says it's listing pods, and then says nothing ever again.

One of these is an autoscaler that cannot see its metric. The other is a pod that is not allowed to ask the question it's asking — and neither failure is reported anywhere you'd think to look.

Growing, shrinking, and being allowed

These two topics sit together because they're both about a pod's relationship with the cluster rather than with its own work. One changes how many of you there are. The other decides what you may ask.

And both fail silently in the same irritating way: the system reports perfect health while doing nothing at all.

HorizontalPodAutoscaler reads: CPU at 70% of requested writes: spec.replicas it is a controller. nothing more. needs metrics-server, and needs requests set queue-depth scaling · KEDA reads: messages waiting on the bus writes: spec.replicas — and can reach 0 scale to zero costs a cold start the first message waits for a pod to boot every pod runs as somebody — whether you chose or not Pod serviceAccountName: sweeper ServiceAccount an identity, and nothing else by itself it can do nothing RoleBinding joins an identity to a set of permissions Role verbs on resources get, list, watch… Role is namespaced · ClusterRole is cluster-wide · RoleBinding can grant a ClusterRole inside one namespace nothing is granted by default. the default ServiceAccount can do essentially nothing, and that is correct. read a number, write a number, repeat no requests set → target is a percentage of nothing → <unknown> no binding → 403, and only your pod's logs say so

Two silent failures to recognise

TARGETS: <unknown> — no metrics-server, or no CPU request on the pod. The HPA exists, is healthy, and will never act.

A 403 in your pod's logs and nowhere else. No event, no status, no describe output. kubectl auth can-i answers it instantly.

The commands

Autoscaling, and why it says unknown

Click any piece of the command.

Permissions, and proving you have them

Click any piece of the command.

Scaling on things Kubernetes cannot see

Click any piece of the command.

Build it

The web front end scaling on CPU, the rewind worker scaling on backlog down to zero, and the sweeper finally allowed to do its job.

CPU scaling

kubectl apply -f manifests/metrics-server.yaml   # kind needs this
kubectl top nodes                                # confirm it works
kubectl apply -f manifests/web-hpa.yaml
kubectl get hpa web

If TARGETS shows a percentage, you're fine. If it shows <unknown>, stop here — nothing below will work, and the cause is one of exactly two things.

Queue scaling, down to zero

kubectl apply -f manifests/keda.yaml
kubectl apply -f manifests/rewind-scaledobject.yaml
kubectl get scaledobject,hpa,pods -l app=rewind

No rewind pods at all, and that's correct — the queue is empty. KEDA created an HPA underneath; the mechanism is the same, only the metric is better.

Permission for the sweeper

kubectl apply -f manifests/sweeper-rbac.yaml
kubectl auth can-i list pods \
  --as=system:serviceaccount:airwaves:sweeper -n airwaves

yes. Three objects — ServiceAccount, Role, RoleBinding — and the pod can finally ask its question. Open sweeper-rbac.yaml and notice how narrow the Role is: list and watch on pods, nothing else. It needs nothing else.

Break it

1 · Reproduce the unknown target

kubectl apply -f manifests/no-requests.yaml
kubectl autoscale deploy/no-requests --min=1 --max=5 --cpu-percent=70
sleep 60
kubectl get hpa no-requests
kubectl describe hpa no-requests | tail -5

<unknown>, and the events say missing request for cpu. The HPA is perfectly healthy and structurally incapable of acting, because 70% of an unspecified request is not a number.

Add a request and it starts working within a minute, with no other change.

2 · Take the sweeper's permission away

kubectl delete rolebinding sweeper
kubectl logs -f deploy/sweeper

If you're lucky the client logs the 403. If it swallows errors — and plenty do — you get silence, which is the symptom from the top of this page.

Confirm it properly instead of guessing:

kubectl auth can-i list pods \
  --as=system:serviceaccount:airwaves:sweeper -n airwaves    # no

kubectl auth can-i --list \
  --as=system:serviceaccount:airwaves:sweeper -n airwaves

Two seconds, and unambiguous. The ServiceAccount still exists, the Role still exists, and without the binding between them the pod can do nothing — which is precisely the shape of the most common RBAC mistake.

3 · Find out what the default account can reach

kubectl auth can-i --list \
  --as=system:serviceaccount:airwaves:default -n airwaves

On a stock cluster this is a short and reassuring list. On one where somebody once bound cluster-admin to the default account to make something work, it is not — and every pod in the namespace inherits it, including anything that ever gets compromised.

Put it back: reapply sweeper-rbac.yaml.

What follows from this

Because……this is what happens
An HPA just writes replicasYour apply fights it. Remove replicas from manifests an HPA owns.
CPU targets are % of requestNo request means no scaling, ever. TARGETS: <unknown>.
Scale-down is stabilisedUp is fast, down waits five minutes. Deliberate, and tunable via behavior.
HPAs stop at 1Scale-to-zero needs KEDA or similar — and CPU was the wrong metric for a queue worker anyway.
Zero has a cold startThe first message waits for a pod to boot. Keep one warm if a human is waiting.
A ServiceAccount grants nothingIt is identity only. Role + RoleBinding + ServiceAccount, and the binding is the one forgotten.
403s are returned, not recordedNo event, no status. kubectl auth can-i --as=… is the diagnostic.

Cheat sheet

Part 13 · scaling and permissions

• An HPA is a controller that writes replicas. Everything downstream is Part 1's loop.

• CPU targets are a percentage of the request. No request → <unknown> → never scales.

• Needs metrics-server. Not installed by default on kind.

• Up is quick; down waits ~5 minutes. Tune with behavior.

• Remove replicas from any manifest an HPA owns, or they will fight.

• HPAs can't reach zero. KEDA can — and queue depth beats CPU for workers anyway.

• Scale-to-zero costs a cold start. minReplicaCount: 1 buys it back.

• Every pod runs as a ServiceAccount; by itself it can do nothing, which is correct.

• Three objects: ServiceAccount + Role + RoleBinding. The binding is the one people forget.

• kubectl auth can-i --as=system:serviceaccount:ns:name before debugging any application code.