Skip to content

The kind inner loop

Fast iteration on the controller against a real cluster. Relevant once the controller exists and is running in-cluster (Q4/Q8); for host-run development make run against any context is faster still. The goal here is a seconds-not-minutes edit → deploy → observe loop without recreating the cluster.

Reuse the cluster

Creating a kind cluster costs tens of seconds; keep one alive across iterations. make setup-test-e2e creates the dedicated e2e cluster only if it doesn't already exist, so it is safe to re-run. Only delete and recreate when you've changed the cluster's own shape — Kubernetes version, feature gates, node count. A stale controller Deployment is cheap to replace; a fresh cluster is not.

Use the dedicated kind cluster, never a dev or prod kubecontext. Confirm with kubectl config current-context before applying anything.

Defeat the image cache with unique tags

kind caches images by tag. If you rebuild and reuse :latest (or any fixed tag), the kubelet may run the old image because the tag already resolves — the single most common "my fix didn't take" trap. Give every build a unique tag:

TAG=dev-$(git rev-parse --short HEAD)-$(date +%s)
docker build -t headroom:$TAG .
kind load docker-image headroom:$TAG --name <cluster>

A content-addressable tag (commit SHA plus a counter) guarantees the kubelet pulls what you just built.

Roll the new image without redeploying

Once the Deployment exists, don't re-apply manifests for a code change — just point it at the new tag:

kubectl set image deployment/headroom-controller-manager manager=headroom:$TAG -n headroom-system
kubectl rollout status deployment/headroom-controller-manager -n headroom-system

kubectl set image triggers a rollout in place; rollout status blocks until the new pod is serving. This is the hot path of the loop.

Debug with targeted pods

To observe policy behavior, schedule small, disposable workloads with explicit requests rather than reasoning in the abstract:

kubectl run slack-probe --image=registry.k8s.io/pause:3.9 \
  --overrides='{"spec":{"containers":[{"name":"p","image":"registry.k8s.io/pause:3.9","resources":{"requests":{"cpu":"500m"}}}]}}' \
  -n <managed-namespace>

Then watch the controller act:

kubectl get pod slack-probe -n <ns> -o jsonpath='{.spec.containers[0].resources.limits.cpu}{"\n"}'
kubectl describe pod slack-probe -n <ns>          # events + status annotation
kubectl logs deploy/headroom-controller-manager -n headroom-system -f

Scale a probe up or down to change node slack and confirm limits move the way the policy predicts. Delete probes when done — they exist to move slack, not to stay running.

The loop, condensed

  1. Edit Go.
  2. make test (catch it before the cluster — see testing.md).
  3. Build with a unique tag, kind load.
  4. kubectl set image + rollout status.
  5. Poke with a probe pod; read limits, events, logs.
  6. Repeat. Recreate the cluster only when its shape changes.