First: which cluster, which namespace
Most “it isn’t there” moments are one of these two.
kubectl config current-context # what am I about to change?
kubectl config get-contexts
kubectl config use-context prod
kubectl config set-context --current --namespace=my-ns # stop typing -nMake current-context a reflex before anything destructive. The command that wrecks prod is usually the one you meant for staging. -A / --all-namespaces works on any get.
Looking around
kubectl get pods -o wide # adds node and pod IP
kubectl get pods -A
kubectl get pods --watch
kubectl get pods --field-selector status.phase=Failed
kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'
kubectl api-resources # what kinds exist here, short names, namespaced?
kubectl explain pod.spec.containers.resources # schema docs, offlinekubectl get all is a trap: it does not include ConfigMaps, Secrets, Ingresses or PVCs.
kubectl explain is the most underused command on this page — it beats searching the docs for field names.
Describe and events
kubectl describe pod POD # spec + recent events for this object
kubectl get events --sort-by=.lastTimestamp
kubectl get events --field-selector involvedObject.name=PODTwo traps. get events is not chronological by default, so always sort. And events are garbage-collected after about an hour, so this morning’s crash is already gone — describe is usually faster anyway because it filters to the object for you.
Logs
kubectl logs POD
kubectl logs POD -f --tail=100
kubectl logs POD -c sidecar # pick a container
kubectl logs POD --all-containers
kubectl logs POD --previous # the container that died
kubectl logs POD --since=15m
kubectl logs -l app=my-app --prefix --max-log-requests=20 # every pod of a deployment--previous is the only way to see why a crashed container died, and it disappears once the pod is rescheduled onto another node. Grab it before anything else.
Getting inside
kubectl exec -it POD -- sh
kubectl exec POD -- env
# distroless or no shell: attach a debug container sharing its process namespace
kubectl debug -it POD --image=busybox --target=app
# node level: kernel log, disk, the OOM killer's report
kubectl debug node/NODE -it --image=busybox
# a throwaway pod inside the cluster network
kubectl run tmp --rm -it --image=nicolaka/netshoot --restart=Never -- shCopying a file out — kubectl cp needs tar inside the container, which distroless images do not have:
kubectl cp POD:/path/file ./file
kubectl exec POD -- cat /path/file > ./file # fallback that always worksResources
kubectl top pod
kubectl top node
kubectl describe node NODE | grep -A5 Allocated
kubectl get pod POD -o jsonpath='{.spec.containers[*].resources}'kubectl top is a sampled gauge over roughly 15–60 seconds. Fine for “what is big right now”, useless as a post-mortem — it will not catch the spike that killed something. Use the metrics stack for that.
Rollouts
kubectl rollout status deploy/NAME
kubectl rollout restart deploy/NAME # clean restart of every pod
kubectl rollout history deploy/NAME
kubectl rollout undo deploy/NAME
kubectl rollout undo deploy/NAME --to-revision=3
kubectl scale deploy/NAME --replicas=0 # actually stop it
kubectl set image deploy/NAME app=repo/image:tagDeleting a pod owned by a Deployment stops nothing — the ReplicaSet recreates it immediately. Scale to 0 instead. And rollout restart beats deleting pods in a loop, because it respects maxUnavailable.
Applying changes safely
kubectl diff -f manifest.yaml # what would actually change
kubectl apply -f manifest.yaml
kubectl apply -f manifest.yaml --dry-run=server -o yaml # validated by the API server
kubectl wait --for=condition=Ready pod/NAME --timeout=120skubectl diff before apply is the cheapest good habit here. --dry-run=server catches what client-side validation misses, because admission controllers and defaulting actually run.
Output worth having
kubectl get pod POD -o yaml
kubectl get pod POD -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}'
kubectl get pods -o custom-columns=\
NAME:.metadata.name,NODE:.spec.nodeName,RESTARTS:.status.containerStatuses[0].restartCount
kubectl get pods -o json | jq '.items[] | {name: .metadata.name, phase: .status.phase}'Config and secrets
kubectl get configmap NAME -o yaml
kubectl get secret NAME -o jsonpath='{.data.password}' | base64 -d
kubectl create secret generic NAME --from-literal=key=value --dry-run=client -o yamlSecrets are base64-encoded, not encrypted. Anyone who can get them can read them.
Networking
kubectl port-forward POD 8080:8080
kubectl port-forward svc/NAME 8080:80
kubectl get endpoints NAME # is the Service selecting any pods?
kubectl get svc,ingressEmpty endpoints means the Service’s selector matches nothing. That is the answer to most “the service returns 503”.
To pull one pod out of a Service without killing it, so you can debug it in peace:
kubectl label pod POD app- # drop the label the selector usesPermissions
kubectl auth can-i delete pods
kubectl auth can-i '*' '*' --all-namespaces
kubectl auth can-i get secrets --as=system:serviceaccount:my-ns:my-saThe --as form is how you check what a workload’s ServiceAccount can do, without deploying anything.
Symptom to first command
| Status | Usually | Check |
|---|---|---|
Pending | no node fits | describe pod |
ContainerCreating | image or volume | describe pod |
ImagePullBackOff | bad tag / no secret | describe pod |
CrashLoopBackOff | exits on startup | logs --previous |
OOMKilled, 137 | memory | below |
Error, 137 | SIGTERM ignored | logs --previous |
Evicted | node pressure | describe node |
Completed | normal for a Job | — |
For 137 and memory in general: OOMKilled without Java OOM.
The danger zone
kubectl delete pod POD --grace-period=0 --force
kubectl drain NODE --ignore-daemonsets --delete-emptydir-data
kubectl cordon NODE
kubectl uncordon NODE--force --grace-period=0 drops the object from the API without waiting for the kubelet to confirm the process is dead. For a StatefulSet that can mean two pods with the same identity writing to the same volume. Use it when you know the node is gone — not to make things go faster.
Worth installing
| Tool | What for |
|---|---|
| k9s | terminal UI: browse, logs, exec, delete |
| kubectx / kubens | switch context and namespace quickly |
| stern | tail logs across many pods, coloured |
| kubecolor | colourised kubectl output |
alias k=kubectl
source <(kubectl completion zsh) # or bash
compdef __start_kubectl k # bash: complete -o default -F __start_kubectl k