KubernetesIII · Kubernetes APIKubernetes API
kubectl as a Kubernetes REST client
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
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:
- Server — the API server’s URL and CA certificate
- Identity — credentials (client cert, bearer token, OIDC)
- 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 command | HTTP verb | Endpoint |
|---|---|---|
kubectl get pods | GET | /api/v1/namespaces/<ns>/pods |
kubectl get pod web | GET | /api/v1/namespaces/<ns>/pods/web |
kubectl apply -f pod.yaml | POST/PUT | /api/v1/namespaces/<ns>/pods |
kubectl delete pod web | DELETE | /api/v1/namespaces/<ns>/pods/web |
kubectl edit pod web | GET + PUT | the same as get+apply |
kubectl describe pod web | GET | /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 -w | GET (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:
| Verb | Purpose |
|---|---|
get | List or read resources |
describe | Get + format events and conditions |
apply | Declarative create or update |
delete | Remove a resource (cascades via owner refs) |
edit | Open the resource in $EDITOR, apply on save |
patch | Update specific fields |
label / annotate | Update metadata |
logs | Container stdout/stderr |
exec | Run a command in a container |
explain | Inline API documentation |
top | Resource usage (via metrics-server) |
auth can-i | RBAC check |
config | Manage kubeconfig |
rollout | Deployment/StatefulSet rollout management |
scale | Replica count adjustment |
drain / cordon / uncordon | Node 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 certificate —
client-certificate-datain kubeconfig; sent as the TLS client cert. - Bearer token —
tokenfield; sent asAuthorization: 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-Buildcovers image and registry interactions; kubectl uses the same TLS and authentication primitives. - The Linux course part
XXVI-Linux-SSHcovers credential management; kubectl kubeconfigs are a similar pattern applied to a different protocol. - The Observability course part
IX-Observability-Exporterscoverskubectl topand how it gets data from the metrics stack. - The Linux course part
II-Linux-Shellcovers shell composition; production kubectl usage composes with shell pipelines (jq, xargs, sort).
Quiz
Knowledge check · 4 questions
Q1. Which HTTP request does `kubectl get pod web -n prod` make to the API server?
Q2. kubectl stores bearer tokens in the kubeconfig in plain text by default.
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) ```
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-fieldsbefore 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.