kubectl Essentials
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages. |
kubectl is the primary way a human drives a Kubernetes cluster, and this page collects the commands and flags used
on almost every other page in this section: how kubectl structures a command, the three ways to manage objects,
the read commands (get, describe, explain), the everyday operational commands (logs, exec, cp,
port-forward, top, events, wait), the write commands that generate and reconcile manifests (--dry-run,
diff, server-side apply, patch, label, annotate), and the discovery and extension commands
(api-resources, auth can-i/auth whoami, plugins and krew). It is written against kubectl 1.37 talking to a
Kubernetes 1.37 API server (kubectl supports servers one minor version above or below itself); every command works
unmodified against the Bookshelf cluster built on Getting Started
with Kubernetes. Where a command corrects something either book gets wrong for the current release, that
correction is called out in prose next to the command it replaces.
Command structure and the three ways to manage objects
Every kubectl invocation has the same shape:
kubectl [command] [TYPE] [NAME] [flags]
command is a verb (get, apply, delete, …), TYPE is a resource type (pods, deploy, svc, or a plural,
singular or short form — kubectl api-resources lists every valid form), NAME is optional and, when omitted,
means "all objects of that type in scope". Global flags such as -n/--namespace, -A/--all-namespaces,
--context and --kubeconfig are accepted by almost every command, because they belong to the client-side
connection, not the verb.
Kubernetes recognizes three distinct techniques for getting objects onto the cluster, and mixing them on the same object is the single most common source of confusion:
| Technique | How it works | Example | Trade-offs |
|---|---|---|---|
Imperative commands |
A single |
|
Fastest for one-off changes and demos; nothing in version control describes the object, so the cluster’s actual
state and its intended state can drift silently. Best for throwaway objects and generating a manifest to edit
(see |
Imperative object configuration |
A manifest file is required, but the command still names the specific action ( |
|
The file can live in version control, but |
Declarative configuration ( |
|
|
The technique every Bookshelf example in this section uses from Kustomize
onward. It composes safely with other actors changing the same object (see server-side apply below), but the
first |
Client-side apply (the default, without --server-side) stores the last-applied configuration as the
kubectl.kubernetes.io/last-applied-configuration annotation on the object and diffs against it locally; it is what
every kubectl apply -f in the earlier pages of this section has been doing. Server-side apply, covered later on
this page, replaces that local diff with a request the API server itself resolves, and is what apply should move
to for anything with more than one writer.
Reading objects: get, describe and explain
kubectl get lists objects, kubectl describe renders a detailed, human-formatted view including recent Events,
and kubectl explain prints an object type’s schema straight from the API server’s OpenAPI/discovery document, so
it is always in sync with the exact cluster version in front of you — more reliable for "what field goes where"
than searching the web docs for one API version.
kubectl get deployments -n bookshelf
kubectl describe deployment catalog-api -n bookshelf
kubectl explain deployment.spec.strategy
kubectl explain pod.spec.containers.livenessProbe --recursive
NAME READY UP-TO-DATE AVAILABLE AGE
catalog-api 3/3 3 3 4d2h
reviews-api 3/3 3 3 4d2h
web 2/2 2 2 4d2h
--recursive expands every nested field of the requested path instead of stopping one level down, which is the
fastest way to see an entire embedded object’s shape (a probe, a volume, a full container spec) in one screen.
Source: kubectl get,
kubectl describe and
kubectl explain
Output formats
-o/--output reshapes what get prints. Each format solves a different problem:
| Format | Use it for |
|---|---|
|
A few extra columns on top of the default table — node name, Pod IP, container images — without switching to a
structured format. |
|
The full object, including server-populated fields ( |
|
Just |
|
A JSONPath expression evaluated against the object(s), for extracting one or two fields into a script. |
|
A table with columns you name explicitly, when |
|
A Go |
kubectl get pods -n bookshelf -o wide
kubectl get deploy catalog-api -o jsonpath='{.spec.template.spec.containers[0].image}'
kubectl get pods -n bookshelf \
-o custom-columns=NAME:.metadata.name,STATUS:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount
kubectl get pods -n bookshelf -o go-template='{{range .items}}{{.metadata.name}}{{"\t"}}{{.status.phase}}{{"\n"}}{{end}}'
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
catalog-api-7b8f9c9d6f-2n7kq 1/1 Running 0 4d2h 10.244.1.12 bookshelf-worker <none> <none>
catalog-api-7b8f9c9d6f-8qr2m 1/1 Running 0 4d2h 10.244.2.9 bookshelf-worker2 <none> <none>
Book 2’s kubectl get … -a (--show-all) has no replacement to reach for: the flag was removed once completed and
failed Pods stopped being hidden from the default get pods output, so today’s plain kubectl get pods already
shows everything a Job left behind.
Selecting, watching and sorting
| Flag | Meaning |
|---|---|
|
Label selector: equality-based ( |
|
Field selector: matches on a fixed, resource-specific set of fields (not arbitrary JSONPath), combined with commas
as AND. Fewer fields are selectable than labels, but it reaches into |
|
Keep the connection open and stream further changes as additional table rows, instead of exiting after one
snapshot. |
|
A JSONPath expression to sort the listed objects by, most useful for finding the oldest or the most-restarted Pod in a long list. |
|
Ignore the current namespace and list across every namespace; adds a |
kubectl get pods -n bookshelf -l app.kubernetes.io/name=catalog-api
kubectl get pods -A --field-selector status.phase=Running
kubectl get pods -n bookshelf -w
kubectl get pods -n bookshelf --sort-by='{.status.containerStatuses[0].restartCount}'
Source: Labels and Selectors and Field Selectors
Working with running Pods
| Command | Purpose |
|---|---|
|
Stream a container’s stdout/stderr. |
|
|
|
Run a process inside a container’s namespaces and attach a TTY. Book 2’s |
|
Copy files between the local machine and a container, using |
|
Forward a local port to a Service, Pod or Deployment, for reaching something with no external entry point yet.
Every earlier page’s |
|
Run a local HTTP proxy that authenticates to the API server once and re-exposes it (with CORS handling) on
|
|
Current CPU/memory usage from the Metrics API (needs metrics-server installed; see Autoscaling) — a live snapshot, not a history, unlike Prometheus. |
|
List Events for one object, or the whole namespace, newest last by default; the same data |
|
Block until a condition is met (or a timeout elapses) — the scriptable alternative to polling |
kubectl logs -f --tail=50 -l app.kubernetes.io/name=catalog-api -n bookshelf
kubectl exec -it -n bookshelf redis-0 -c redis -- redis-cli ping
kubectl wait --for=condition=Available=true deployment/web -n bookshelf --timeout=90s
deployment.apps/web condition met
Generating and reconciling manifests
--dry-run=client runs every validation kubectl can do locally and, combined with -o yaml, turns an imperative
command into a starting manifest instead of an object — the fastest way to get a syntactically correct skeleton to
then hand-edit and commit:
kubectl create deployment catalog-api --image=ghcr.io/example/bookshelf-catalog-api:1.0.0 \
--dry-run=client -o yaml > k8s/base/deployment-catalog-api.yaml
kubectl create configmap catalog-api-config --from-literal=LOG_LEVEL=info \
--dry-run=client -o yaml
--dry-run=server instead sends the request to the API server with the persist step skipped, so admission
webhooks, ValidatingAdmissionPolicy (see
Admission Control and Policy) and defaulting all run for
real — a stronger check than client, at the cost of one round trip to a live cluster.
kubectl diff -f k8s/base/ shows what kubectl apply -f k8s/base/ would change, without changing anything; it
shells out to diff (or KUBECTL_EXTERNAL_DIFF) over the same three-way comparison apply itself computes, and
belongs in front of every apply run against something that matters.
Server-side apply, field managers and conflicts
Client-side apply computes its three-way diff on the machine running kubectl, from a locally cached
last-applied-configuration annotation — fragile once more than one actor (a person, a CI pipeline, an operator, an
HPA) touches the same object. Server-side apply (kubectl apply --server-side) moves that merge into the API
server itself: the client sends only the fields it actually cares about, and the server tracks, per field, which
field manager set it, recorded in metadata.managedFields.
kubectl apply --server-side -f k8s/base/deployment-catalog-api.yaml
kubectl get deployment catalog-api -n bookshelf -o jsonpath='{.metadata.managedFields}' | jq
managedFields:
- manager: kubectl
operation: Apply
apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:spec:
f:replicas: {}
f:template:
f:spec:
f:containers: {}
- manager: horizontal-pod-autoscaler
operation: Update
apiVersion: apps/v1
fieldsV1:
f:spec:
f:replicas: {}
When two managers try to own the same field with different values, the server returns a conflict instead of
silently picking one — this is what stops kubectl apply from fighting the HPA over spec.replicas, or a Helm
release from fighting a hand-run kubectl edit. Resolve a conflict one of three ways: drop the contested field from
your manifest and let the other manager keep it; change the manifest and force the take-over with
--force-conflicts (the new value wins, and the field moves to your manager); or, for legitimate shared fields,
have every manager apply the same value, which is allowed without a conflict.
kubectl apply --server-side --force-conflicts -f k8s/base/deployment-catalog-api.yaml
Client-side apply (the plan’s and every earlier page’s default) remains the simpler choice for a single pipeline
that is the only writer of an object; move to --server-side as soon as something else — an autoscaler, an
operator, another team’s pipeline — writes to the same object too.
ApplySet pruning
Plain kubectl apply -f <dir> --prune -l <selector> deletes objects that match the selector but are no longer in
the directory — but it identifies which objects belong to the set purely by that label selector, so a typo, or
another tool’s object carrying the same label, can be deleted by mistake. ApplySet-based pruning replaces the
selector with an explicit, server-tracked set: kubectl stamps a parent object with an
applyset.kubernetes.io/id and every member object it applies gets a matching label, so --prune
--applyset=<parent> deletes exactly, and only, what that specific apply run put there. The feature is still
alpha in kubectl, so it must be enabled with the KUBECTL_APPLYSET=true environment variable:
KUBECTL_APPLYSET=true kubectl apply --server-side --prune --applyset=configmap/bookshelf-applyset \
-n bookshelf -f k8s/base/
Because ApplySet pruning is still maturing feature by feature across recent minors, re-verify its exact status
(alpha/beta and required feature gates) against
the server-side apply reference before relying
on it outside a throwaway cluster; the legacy label-selector --prune remains available but keeps the "read the
current disclaimer before use" caveat it has always carried. Kustomize and
GitOps and CI/CD show the alternative most teams reach for instead:
let Argo CD or Flux own pruning declaratively, rather than passing --prune by hand.
edit, patch, label and annotate
kubectl edit deployment catalog-api -n bookshelf # opens the live object in $EDITOR, applies on save
kubectl patch deployment catalog-api -n bookshelf \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"catalog-api","env":[{"name":"LOG_LEVEL","value":"debug"}]}]}}}}'
# strategic merge patch (default): understands lists that
# merge by key, e.g. containers by name, instead of by index
kubectl patch configmap catalog-api-config -n bookshelf --type merge \
-p '{"data":{"LOG_LEVEL":"debug"}}' # RFC 7386 JSON merge patch: plain object merge, no
# Kubernetes-aware list semantics
kubectl patch deployment catalog-api -n bookshelf --type json \
-p '[{"op":"replace","path":"/spec/replicas","value":5}]'
# RFC 6902 JSON patch: an explicit list of operations
# (add/remove/replace/test) against exact paths
kubectl label pods -n bookshelf -l app.kubernetes.io/name=reviews-api tier=backend --overwrite
kubectl annotate deployment catalog-api -n bookshelf kubernetes.io/change-cause="bump log level to debug"
edit is imperative (whatever you save wins, with no reconciliation); patch is the scriptable equivalent, and
its --type choice matters most for arrays — a strategic merge patch on a container list finds the entry by
name and merges into it, where a JSON merge patch would replace the whole list. label/annotate are shorthand
patches for exactly those two maps, and both refuse to overwrite an existing key without --overwrite.
Discovery, authorization and extending kubectl
kubectl api-resources # every resource type the server serves, with its API
# group, whether it is namespaced, and its short names
kubectl api-resources --namespaced=true --verbs=list,get
kubectl api-versions # every served <group>/<version>, e.g. apps/v1, batch/v1
kubectl auth can-i create deployments -n bookshelf
kubectl auth can-i delete pods -n bookshelf --as=system:serviceaccount:bookshelf:notifier
kubectl auth whoami # the identity kubectl itself is currently authenticated as
yes
no
ATTRIBUTE VALUE
Username kubernetes-admin
Groups [system:masters system:authenticated]
auth can-i answers one RBAC question at a time and, with --as, answers it as another identity — indispensable
when preparing a ServiceAccount’s Role before ever running a Pod with it (see
Authentication, Authorization and RBAC).
auth whoami answers a different question — not "can I", but "who does the server think I am right now" — useful
after switching kubeconfig contexts or assuming a cloud identity, to confirm which user or group a subsequent
can-i check is actually being evaluated against.
Plugins and krew
A kubectl plugin is any executable on $PATH named kubectl-<name>; running kubectl <name> finds and execs it,
passing through global flags such as --namespace and --context automatically. krew is the plugin manager for
this ecosystem — itself installed as a plugin (kubectl krew) — that indexes, installs and upgrades community
plugins from a public manifest index:
kubectl krew install neat tree who-can
kubectl neat get deployment catalog-api -n bookshelf -o yaml # strips managedFields, status and other server noise
kubectl tree deployment catalog-api -n bookshelf # ReplicaSet + Pod ownership tree, one command instead
# of following owner references by hand
kubectl who-can delete pods -n bookshelf # every RoleBinding/ClusterRoleBinding granting a verb
kubectl krew upgrade
Source: Extend kubectl with Plugins and krew
kubectl for Docker users
| Docker CLI | kubectl equivalent |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a Service plus, for isolation, a NetworkPolicy — there is no per-command network flag |
|
a Volume or
a PersistentVolumeClaim referenced from
|
|
|
Source: kubectl for Docker Users
Book 2 corrections
Book 2’s terminal sessions predate several kubectl changes that would otherwise mislead a reader following along
on 1.37:
-
kubectl run --restart=OnFailurecreating a Job. In the book’s era,kubectl runhad generators that could produce a Job, a Deployment or a bare Pod depending on--restartand other flags. The generators were removed in 1.18:kubectl runnow always creates a single, bare Pod, whatever--restartis set to (onlyAlways,OnFailureandNeverremain, and they set the Pod’s ownrestartPolicy, not a controller choice). Usekubectl create job --image=… notifier-once — <command>for the one-shot behaviour the book was reaching for, orkubectl create deploymentfor a durable, replicated workload — Jobs and CronJobs rebuilds the book’s exact Job patterns this way. Along with it, prefer the recommendedapp.kubernetes.io/name/app.kubernetes.io/part-oflabels over therun: <name>labelkubectl runstill applies by default, since a Service or NetworkPolicy selectingrun: …reads as legacy the moment a reader sees it next to any other object in this section. Separately,kubectl run --filename/-f(reading a Pod override document from a file) is deprecated in 1.37; pass--overrideswith inline JSON, or an actual manifest applied withkubectl apply -f, instead. -
kubectl get … -a(--show-all). Already covered above: the flag is gone, and today’s plaingetalready shows completed and failed Pods. -
kubectl exec pod cmdwithout--. Already covered above: always writekubectl exec pod — cmd, and add-c <container>in a multi-container Pod (the sampler’s Redis StatefulSet, rebuilt on Stateful Applications in Practice, needs exactly this to reach therediscontainer rather than its sidecar).
Job objects also carry two auto-generated labels worth knowing before you write a selector against one: job-name
(the owning Job’s name) and controller-uid (a value unique to that particular Job object, distinguishing two Jobs
that happen to share a name over time). kubectl create job sets manualSelector: false by default, meaning the
Job controller manages these labels itself; hand-writing spec.selector and setting manualSelector: true is
possible but easy to get wrong, and is not needed for anything in Bookshelf.