Foundations

The API object model

Why everything is a resource, what the control loop actually does, and how to read any CRD you meet later without waiting for someone to document it.

KCNACKAD 9 min read

Kubernetes has one idea in it, repeated. You write down what you want, a controller compares that to what exists, and it acts to close the gap. Learn the shape of that loop and every new resource you meet — from a Deployment to a CiliumNetworkPolicy to something your colleague invented last week — is recognisable on sight.

Spec is a wish. Status is a measurement.

Nearly every object has the same four parts: apiVersion and kind to say what it is, metadata to name it, spec for what you want, and status for what is true. You write spec. Controllers write status. Confusing the two is the most common beginner mistake, and the reason editing status by hand does nothing useful.

kubectl get deploy my-app -o jsonpath='{.spec.replicas}'       # what you asked for
kubectl get deploy my-app -o jsonpath='{.status.readyReplicas}'  # what you have

The loop, in one paragraph

A controller watches a resource type. When one changes, it is put on a work queue. The controller reads the current world, computes the difference from spec, and takes one step towards closing it — then it does that again, forever. It must be safe to run the same reconcile twice, because it will be. Nothing in the system assumes a message is delivered exactly once.

This is why Kubernetes recovers from almost anything you do to it, and also why it sometimes does nothing at all and gives you no error: the controller may simply not be watching the thing you changed.

Read the API, not the blog post

The cluster documents itself, including every CRD installed on it. Three commands cover most of what you would otherwise go searching for:

kubectl api-resources                    # everything this cluster knows about
kubectl explain deployment.spec.strategy --recursive
kubectl get crd                          # what has been added beyond core Kubernetes
This is the single most useful habit on the list. kubectl explain reads the OpenAPI schema out of the API server, so it is correct for your cluster at your version, which a search result is not. It also works on custom resources the moment someone installs them.

Ownership is how deletion works

A Deployment does not manage pods. It manages a ReplicaSet, which manages pods, and each child carries an ownerReferences entry pointing at its parent. Delete the parent and garbage collection removes the children.

kubectl get rs -l app=my-app -o jsonpath='{.items[*].metadata.ownerReferences[*].kind}'

# orphan the children instead of deleting them - occasionally what you want in an incident
kubectl delete deploy my-app --cascade=orphan

Labels do the wiring

There are no pointers in a Kubernetes manifest. A Service finds pods because its selector matches their labels, and nothing validates that the match succeeds. A Service with a typo in its selector is a perfectly valid object with zero endpoints.

# the real question when a Service returns nothing
kubectl get endpointslices -l kubernetes.io/service-name=my-svc
An empty EndpointSlice means the selector matched nothing, or nothing matched is ready. Those two causes look identical from the Service and are fixed in completely different places — one is a label typo, the other is a failing readiness probe. Check the pods before you touch the Service.

What to actually do with this

  • Pick any resource in your cluster and read its status alongside its spec. Notice which fields you never wrote.
  • Run kubectl explain on something you thought you knew — pod.spec.securityContext is a good one.
  • Break a Service selector on purpose and follow it down to the EndpointSlice.
This article covers one checkpoint on the roadmap. Open The API object model on the roadmap → — it lists what this depends on and everything else written about it.

Something wrong or out of date? Open an issue — corrections are welcome and get credited.