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
- Kubernetes1.36.4
- kubectlv1.35.2
- TimeAbout 14 min
- Reviewed22 August 2026
Written against the versions above. The API model here has been stable since 1.0. Verbosity flag output format changes between kubectl releases.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA1001 | 192.168.0.175 | Ubuntu 26.04 LTS | Control Plane Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE01 | 192.168.0.176 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
Before you start
- A working kubectl. No cluster changes are made in this guide.
-
What kubectl actually does
-v=6prints the HTTP requests. Onekubectl get podsbecomes: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=11Expected resultA GET to the API server over HTTPS on port 6443.
Success conditionYou can see the HTTP request behind a kubectl command.
-
Ask for the JSON directly
--rawskips 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 ofwatch: a client reads a list, notes the list's resourceVersion, then asks for changes *since* it. No polling, no missed events.?limit=1is also doing something real - the API paginates, and a client that ignorescontinuetokens 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
PodListkind and two different resourceVersions.Success conditionYou have read the API response without kubectl formatting it.
-
Every write bumps the version
Read the Deployment's resourceVersion, add an annotation, read it again:
171451 -> 174954Any 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 editsometimes 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 annotatedExpected resultA resourceVersion, an annotation applied, then a higher resourceVersion.
Success conditionThe value changed after the write.
Troubleshooting
-v=6shows nothing useful.Why: The output format differs between kubectl versions, and the interesting lines are on stderr.
Fix:Redirect with
2>&1before grepping.-v=8adds request and response bodies, which is verbose but definitive.--rawreturns 404 for a path that looks right.Why: Namespaced resources need the namespace segment, and API groups are not under
/api/v1.Fix:Core group is
/api/v1/...; everything else is/apis/<group>/<version>/....kubectl get --raw=/apislists the groups.resourceVersion too oldfrom a watch.Why: The requested version has fallen out of etcd's history window.
Fix:Expected over long disconnections. The client must re-list and restart the watch - which is exactly what informers do for you.