Skip to main content
RunBook Academy

KubernetesIII · Kubernetes APIKubernetes API

kubectl as a Kubernetes REST client

Intermediate⏱ ~16 minkubectl

What you'll learn

  • Trace a kubectl command from the kubeconfig to the API server response
  • Identify the kubectl verbs and the REST endpoints they map to
  • Explain kubeconfig structure (clusters, contexts, users) and how to manage multiple clusters
  • Use kubectl flags to produce useful output for production workflows

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

kubectl is the standard command-line client for Kubernetes. It is a thin REST client over HTTPS: every command maps to one or more HTTP requests to the API server. This lesson covers what kubectl does under the hood, how it discovers the API server, and how to use it confidently in production.

kubectl in one sentence

kubectl is an HTTP client that speaks the Kubernetes API on behalf of an authenticated identity, configured by a kubeconfig file that names the cluster, the user, and the context.

flowchart LR
    U[User shell] --> K[kubectl]
    K -->|reads| KC[kubeconfig<br/>~/.kube/config or KUBECONFIG]
    K -->|HTTPS + auth| API[API server]
    API --> ETCD[etcd]

The three artefacts kubectl needs:

  1. Server — the API server’s URL and CA certificate
  2. Identity — credentials (client cert, bearer token, OIDC)
  3. Context — which cluster + user + namespace to use

kubeconfig

The kubeconfig is a YAML file (typically ~/.kube/config, or set via KUBECONFIG env var) with three top-level keys:

apiVersion: v1
kind: Config
clusters:
- name: prod-eu-west-1
  cluster:
    server: https://api.prod.example.com:6443
    certificate-authority-data: BASE64-CA-BUNDLE
- name: staging-eu-west-1
  cluster:
    server: https://api.staging.example.com:6443
    certificate-authority-data: BASE64-CA-BUNDLE

users:
- name: admin-prod
  user:
    client-certificate-data: BASE64-CLIENT-CERT
    client-key-data: BASE64-CLIENT-KEY
- name: admin-staging
  user:
    token: REDACTED-TOKEN

contexts:
- name: prod
  context:
    cluster: prod-eu-west-1
    user: admin-prod
    namespace: default
- name: staging
  context:
    cluster: staging-eu-west-1
    user: admin-staging
    namespace: default

current-context: prod

The current-context is what kubectl uses by default. You can override per-call:

kubectl --context=staging get pods
kubectl --cluster=prod-eu-west-1 --user=admin-prod get pods
kubectl -n team-a-prod get pods

Production operators carry multiple kubeconfigs (prod, staging, dev, DR) and select the right one deliberately. A common operational mistake is to run a destructive command against the wrong context.

How kubectl speaks to the API

Every kubectl command is one or more HTTPS requests. The verb and the resource determine the endpoint:

kubectl commandHTTP verbEndpoint
kubectl get podsGET/api/v1/namespaces/<ns>/pods
kubectl get pod webGET/api/v1/namespaces/<ns>/pods/web
kubectl apply -f pod.yamlPOST/PUT/api/v1/namespaces/<ns>/pods
kubectl delete pod webDELETE/api/v1/namespaces/<ns>/pods/web
kubectl edit pod webGET + PUTthe same as get+apply
kubectl describe pod webGET/api/v1/namespaces/<ns>/pods/web
kubectl logs pod web(separate service)API server connects to kubelet
kubectl exec -it pod web -- sh(separate service)API server connects to kubelet
kubectl get pods -wGET (with watch=true)streaming watch

You can see the exact request kubectl makes with -v=8 (or higher) for verbose logging:

kubectl get pod web -v=8 2>kubectl.log
I0815 12:01:01 round_trippers.go:...] GET https://api.prod.example.com:6443/api/v1/namespaces/prod/pods/web
I0815 12:01:01 round_trippers.go:...] Request Headers:
I0815 12:01:01 round_trippers.go:...]     Accept: application/json; ...
I0815 12:01:01 round_trippers.go:...]     Authorization: Bearer <REDACTED>
I0815 12:01:01 round_trippers.go:...] Response Headers:
I0815 12:01:01 round_trippers.go:...]     Content-Type: application/json
I0815 12:01:01 round_trippers.go:...] Response Status: 200 OK in 12 milliseconds

You can also bypass kubectl and use the API directly with kubectl get --raw:

kubectl get --raw /healthz
kubectl get --raw /readyz
kubectl get --raw /api/v1/namespaces/prod/pods/web

For curl, you need the bearer token and CA bundle:

TOKEN=$(kubectl config view --raw -o jsonpath='{.users[?(@.name=="admin-prod")].user.token}')
curl --cacert /etc/kubernetes/pki/ca.crt \
  -H "Authorization: Bearer $TOKEN" \
  https://api.prod.example.com:6443/api/v1/namespaces/prod/pods/web

kubectl verbs (the high-frequency commands)

Production operators live in a small subset of kubectl verbs. The rest are occasional:

VerbPurpose
getList or read resources
describeGet + format events and conditions
applyDeclarative create or update
deleteRemove a resource (cascades via owner refs)
editOpen the resource in $EDITOR, apply on save
patchUpdate specific fields
label / annotateUpdate metadata
logsContainer stdout/stderr
execRun a command in a container
explainInline API documentation
topResource usage (via metrics-server)
auth can-iRBAC check
configManage kubeconfig
rolloutDeployment/StatefulSet rollout management
scaleReplica count adjustment
drain / cordon / uncordonNode maintenance

Output formats

kubectl supports several output formats; production operators use them deliberately:

kubectl get pods -o yaml                # full YAML
kubectl get pods -o json                # full JSON
kubectl get pods -o jsonpath='{.items[*].metadata.name}'  # one field
kubectl get pods -o wide                # add node, IP, image
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName
kubectl get pods -o name               # resource names, one per line

jsonpath is the standard way to extract one field for scripting:

kubectl get pod web -o jsonpath='{.status.podIP}'
# 10.244.1.5

--template is the older flag; jsonpath is preferred.

Common production flags

kubectl get pods -A                     # all namespaces
kubectl get pods -n team-a-prod         # one namespace
kubectl get pods -l app=web             # label selector
kubectl get pods --field-selector=status.phase!=Running
kubectl get pods --sort-by=.metadata.creationTimestamp
kubectl get pods --chunk-size=500       # large list pagination
kubectl get pods -o yaml --show-managed-fields  # show all fields
kubectl get pods -w                     # watch (streaming)
kubectl get pods --server-print=false   # no "NAME READY ..." header

--show-managed-fields is critical for understanding what server-side apply has changed. Production changes should show their managed fields before applying.

Resources kubectl discovers

kubectl api-resources lists every API resource the cluster exposes:

kubectl api-resources --namespaced=true | head -20
NAME                              SHORTNAMES   APIVERSION                     NAMESPACED   KIND
bindings                                       v1                           true         Binding
configmaps                        cm           v1                           true         ConfigMap
endpoints                         ep           v1                           true         Endpoints
events                            ev           v1                           true         Event
limitranges                       limits       v1                           true         LimitRange
persistentvolumeclaims            pvc          v1                           true         PersistentVolumeClaim
pods                              po           v1                           true         Pod
...
kubectl api-resources --namespaced=false
NAME                              SHORTNAMES   APIVERSION                     NAMESPACED   KIND
componentstatuses                 cs           v1                           false        ComponentStatus
namespaces                        ns           v1                           false        Namespace
nodes                             no           v1                           false        Node
persistentvolumes                 pv           v1                           false        PersistentVolume
...

kubectl explain <resource> shows the schema:

kubectl explain pods.spec.containers
RESOURCE: containers <[]Object>
DESCRIPTION:
    List of containers belonging to the pod. Containers cannot currently
    be added or removed. There must be at least one container in a Pod.

FIELDS:
  args     <[]string>
  command  <[]string>
  env      <[]Object>
    name      <string> -required-
    value     <string>
    valueFrom <Object>
  ...

Namespaces and contexts

A context includes a default namespace. Operators can override per-call:

kubectl -n team-a-prod get pods
kubectl get pods --all-namespaces        # equivalent to -A
kubectl get pods -A

kubens and kubectx are third-party tools that switch namespace and context rapidly. Production teams sometimes adopt them; the built-in kubectl is sufficient for most.

Authentication: what kubectl sends

kubectl authenticates with the configured credentials:

  • Client certificateclient-certificate-data in kubeconfig; sent as the TLS client cert.
  • Bearer tokentoken field; sent as Authorization: Bearer <token>.
  • OIDC — kubectl runs an OIDC flow with your IdP; the resulting token is sent.
  • ServiceAccount token — the projected token in the Pod’s mount path (/var/run/secrets/kubernetes.io/serviceaccount/ token).

kubectl auth can-i lets you test what your identity is allowed to do:

kubectl auth can-i delete pods -n team-a-prod
# yes
kubectl auth can-i delete pods -n kube-system
# no
kubectl auth whoami
# User: admin-prod
# Groups: [system:masters]

How to reproduce kubectl with curl

When kubectl is unavailable (e.g., the API server is reachable but kubectl’s kubeconfig is broken), curl is the fallback:

# Get the CA bundle
kubectl config view --raw -o jsonpath='{.clusters[?(@.name=="prod-eu-west-1")].cluster.certificate-authority-data}' | base64 -d > /tmp/ca.crt

# Get the token (if using bearer)
TOKEN=$(kubectl config view --raw -o jsonpath='{.users[?(@.name=="admin-prod")].user.token}')

# Make a request
curl --cacert /tmp/ca.crt -H "Authorization: Bearer $TOKEN" \
  https://api.prod.example.com:6443/api/v1/namespaces/prod/pods

This works for any kubectl command. The endpoint format follows the OpenAPI schema:

GET    /api/v1/pods
GET    /api/v1/namespaces/{namespace}/pods
GET    /api/v1/namespaces/{namespace}/pods/{name}
POST   /api/v1/namespaces/{namespace}/pods
PUT    /api/v1/namespaces/{namespace}/pods/{name}
DELETE /api/v1/namespaces/{namespace}/pods/{name}
PATCH  /api/v1/namespaces/{namespace}/pods/{name}

For more, the OpenAPI schema is at /openapi/v2 (Swagger 2.0) or /openapi/v3 (OpenAPI v3).

Cross-course references

  • The Docker course part XXIX-Docker-Build covers image and registry interactions; kubectl uses the same TLS and authentication primitives.
  • The Linux course part XXVI-Linux-SSH covers credential management; kubectl kubeconfigs are a similar pattern applied to a different protocol.
  • The Observability course part IX-Observability-Exporters covers kubectl top and how it gets data from the metrics stack.
  • The Linux course part II-Linux-Shell covers shell composition; production kubectl usage composes with shell pipelines (jq, xargs, sort).

Quiz

Knowledge check · 4 questions

  1. Q1. Which HTTP request does `kubectl get pod web -n prod` make to the API server?

  2. Q2. kubectl stores bearer tokens in the kubeconfig in plain text by default.

  3. Q3. An on-call engineer runs `kubectl delete pod web-7c8 -n team-a-prod` intending to delete a Pod in the production cluster. The command succeeds. They later realise they meant to delete the same-named Pod in the staging cluster. Walk through what happened, how to detect it, and how to recover.

    Engineer's kubeconfig: ```yaml contexts: - name: prod context: {cluster: prod-eu-west-1, user: admin-prod, namespace: default} - name: staging context: {cluster: staging-eu-west-1, user: admin-staging, namespace: default} current-context: prod ``` Commands run: ```bash # (intended) kubectl --context=staging delete pod web-7c8 -n team-a-prod # (actually) kubectl delete pod web-7c8 -n team-a-prod kubectl delete pod web-7c8 -n team-a-prod # pod "web-7c8" deleted from production ``` After: ```bash $ kubectl get pod web-7c8 -n team-a-prod # (gone in production; still running in staging if it existed there) ```

  4. Q4. Describe the three top-level keys of a kubeconfig and how they combine to form a context. What is `current-context`?

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Verify context and namespace before every destructive operation: kubectl config view --minify | grep -E "context| namespace".
  • Restrict filesystem permissions on ~/.kube/config (chmod 600); the file contains credentials.
  • Prefer short-lived credentials (OIDC, projected ServiceAccount tokens) over long-lived bearer tokens or client certs in kubeconfigs.
  • Use kubectl get --show-managed-fields before any server-side apply to see what changed.
  • For incident debugging, fall back to curl when kubectl cannot reach the cluster but the API server is reachable.