CertGrid CertGrid
Concepts·Kubernetes and Cloud Native Associate

kubectl and the Kubernetes API

Turn up the verbosity and kubectl stops being magic: it is a GET to an HTTPS endpoint that returns JSON. Everything else in Kubernetes - controllers, the scheduler, the kubelet - is another client of the same API.

Kubernetes Fundamentals Guide 3 of 46 Beginner

Written against the versions above. The API model here has been stable since 1.0. Verbosity flag output format changes between kubectl releases.

Any cluster works for this. Nothing here changes state except one annotation.
Server NameIP AddressOSRolesCPURAMHDD
CKA1001192.168.0.175Ubuntu 26.04 LTSControl Plane Node2 Core4 GB50 GB
CKA1001-NODE01192.168.0.176Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB

Before you start

  1. What kubectl actually does

    -v=6 prints the HTTP requests. One kubectl get pods becomes:

    verb="GET" url="https://192.168.0.175:6443/api/v1/namespaces/..."

    That is the whole trick. kubectl is a REST client with good defaults and a table formatter. There is no privileged channel, no special protocol - anything that can make an authenticated HTTPS request can do everything kubectl can.

    This is worth internalising because it reframes the architecture. The scheduler, the controller manager, the kubelet and Argo CD are all clients of this same API, with no more access than a token grants them. The API server is the only component that talks to etcd, and everything else negotiates through it.

    bash Example session
    kubectl --context cka1001 get pods -n kube-system --request-timeout=10s -v=6 2>&1 | grep -E 'GET|round_trippers' | head -4I0822 03:03:43.698134 1480840 round_trippers.go:632] "Response" verb="GET" url="https://192.168.0.175:6443/api/v1/namespaces/kube-system/pods?limit=500&timeout=10s" status="200 OK" milliseconds=11

    Expected resultA GET to the API server over HTTPS on port 6443.

    Success conditionYou can see the HTTP request behind a kubectl command.

  2. Ask for the JSON directly

    --raw skips kubectl's formatting entirely and hands back the response body. Two fields are worth noticing:

    "kind":"PodList" - a list is itself a typed object, not a bare array. Every response has a kind and an apiVersion, which is how the same endpoint can evolve without breaking clients.

    resourceVersion - appears twice, at different values. The one on the list is the etcd revision the whole response was consistent at; the one on each item is that object's own. That distinction is the foundation of watch: a client reads a list, notes the list's resourceVersion, then asks for changes *since* it. No polling, no missed events.

    ?limit=1 is also doing something real - the API paginates, and a client that ignores continue tokens will quietly see only the first page.

    bash Example session
    kubectl --context cka1001 get --raw='/api/v1/namespaces/kube-system/pods?limit=1' | tr ',' '\n' | grep -E '"kind"|"resourceVersion"' | head -3{"kind":"PodList""metadata":{"resourceVersion":"174912""resourceVersion":"11837"

    Expected resultA PodList kind and two different resourceVersions.

    Success conditionYou have read the API response without kubectl formatting it.

  3. Every write bumps the version

    Read the Deployment's resourceVersion, add an annotation, read it again:

    171451
    ->
    174954

    Any accepted write produces a new resourceVersion, cluster-wide and monotonic. It is not a per-object counter - the jump is large because the number is etcd's global revision and the whole cluster is busy.

    This is what makes optimistic concurrency work. Send an update carrying a stale resourceVersion and the API server rejects it with a conflict rather than silently overwriting - which is why kubectl edit sometimes tells you the object changed underneath you. That is the mechanism protecting you, not a bug.

    It is also why you cannot set resourceVersion yourself, and why comparing them between two different objects tells you nothing except which was written later.

    bash Example session
    kubectl --context cka1001 -n hpa-demo get deploy cpu-demo -o jsonpath='{.metadata.resourceVersion}{"\n"}'171451kubectl --context cka1001 -n hpa-demo annotate deploy cpu-demo cg-touched=yes --overwritedeployment.apps/cpu-demo annotated

    Expected resultA resourceVersion, an annotation applied, then a higher resourceVersion.

    Success conditionThe value changed after the write.

Troubleshooting

Official sources