get lists resources and answers "what exists". describe shows one resource in full, including the events attached to it, and answers "why is this not working".
Most Kubernetes debugging is describe followed by reading the bottom of the output.
kubectl get pods
kubectl get pods,services,deployments # several types at once
kubectl get all # common types in this namespace
kubectl get pods -A # every namespace
kubectl get pods -n paymentskubectl get all is a convenience rather than a promise. It covers Pods, Services, Deployments, ReplicaSets and StatefulSets, but not ConfigMaps, Secrets, PVCs, Ingresses or anything custom.
kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE
payments-api-7d4f-x8k2 1/1 Running 0 12m 10.244.1.17 node-1
payments-api-7d4f-m3p9 0/1 Running 4 (30s ago) 12m 10.244.2.31 node-2
Three columns carry most of the signal:
READY is containers passing readiness over total. 0/1 while Running means the process started but the readiness probe is failing, so the Pod receives no traffic.RESTARTS with a timestamp means it restarted recently, which is very different from four restarts two days ago.NODE matters when a failure correlates with one machine.kubectl get pods -wLive updates as things change. The clearest way to see a rolling update actually happen.
kubectl get pods -l app=payments-api
kubectl get pods -l 'env in (staging,prod)'
kubectl get pods -l 'app=payments-api,version!=1.4.2'
kubectl get pods --show-labelsLabel selectors are how Kubernetes objects find each other, and --show-labels is how you check why a Service selector is not matching.
Field selectors filter on object fields rather than labels:
kubectl get pods --field-selector status.phase=Running
kubectl get pods --field-selector spec.nodeName=node-1
kubectl get events --field-selector type=Warningkubectl get pods --sort-by=.status.containerStatuses[0].restartCount
kubectl get events --sort-by=.lastTimestampSorting events by timestamp is worth memorising. The default order is not chronological, which is confusing exactly when you are in a hurry.
kubectl get pod payments-api-7d4f-x8k2 -o yaml
kubectl get pod payments-api-7d4f-x8k2 -o json-o yaml shows the object as stored, including status and defaults you never wrote. It is the definitive answer to "what is actually configured".
Extracting one value, for scripts or just to avoid scrolling:
kubectl get pod <name> -o jsonpath='{.status.podIP}'
# Why did the container die?
kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'
# Every image in the namespace
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'A readable table of exactly what you want:
kubectl get pods -o custom-columns=\
NAME:.metadata.name,\
STATUS:.status.phase,\
READY:.status.containerStatuses[0].ready,\
RESTARTS:.status.containerStatuses[0].restartCount,\
NODE:.spec.nodeNamekubectl describe pod payments-api-7d4f-m3p9describe assembles the object, its defaults, related objects, and, the part that matters, the events for it.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m default-scheduler Successfully assigned payments/payments-api-7d4f-m3p9 to node-2
Normal Pulled 2m kubelet Container image "ghcr.io/acme/payments-api:1.5.0" already present
Warning Unhealthy 30s (x6 over 90s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404
That last line is the whole diagnosis: the readiness path returns 404, so the Pod never becomes Ready and gets no traffic. No log reading required.
Events you will meet constantly:
| Reason | Meaning |
|---|---|
FailedScheduling | No node fits (the message lists why per node) |
Failed / ErrImagePull | Image cannot be pulled; the registry's own error is quoted |
Unhealthy | A probe failed, with the status code or error |
OOMKilled | Exceeded the memory limit |
FailedMount | Volume could not be attached or mounted |
BackOff | Restarting with increasing delay |
describe works on everything, and is just as useful elsewhere:
kubectl describe node node-2 # conditions, capacity, allocated resources
kubectl describe service payments-api # selector and endpoints
kubectl describe pvc payments-data # why a claim is Pending
kubectl describe deployment payments-apidescribe only shows events for one object. Cluster-wide:
kubectl get events -A --sort-by=.lastTimestamp
kubectl get events --field-selector type=Warning
kubectl get events --field-selector involvedObject.name=payments-api-7d4f-m3p9Events expire after about an hour by default. If an incident is more than an hour old, the event that explains it is already gone, which is a good argument for shipping events into your logging system.
| Use | For |
|---|---|
get | What exists, current status, many objects at once, scripting |
get -o yaml | Exactly what is configured, including defaults |
describe | Why one object is unhealthy (events are the payoff) |
The habit worth building: get to find the broken thing, describe to find out why, logs only if describe did not say.
| Task | Command |
|---|---|
| List with detail | kubectl get pods -o wide |
| Everything common | kubectl get all |
| Across namespaces | kubectl get pods -A |
| Watch live | kubectl get pods -w |
| By label | kubectl get pods -l app=<label> |
| Show labels | kubectl get pods --show-labels |
| Full object | kubectl get pod <n> -o yaml |
| One field | kubectl get pod <n> -o jsonpath='{.status.podIP}' |
| Diagnose | kubectl describe pod <n> |
| Recent events | kubectl get events -A --sort-by=.lastTimestamp |
| Warnings only | kubectl get events --field-selector type=Warning |
describe is the debugging command, and the Events section at the bottom is why.READY 0/1 with status Running means a failing readiness probe: the Pod gets no traffic.get -o yaml shows the object as stored, including defaults you never wrote.--show-labels resolves most Service selector mismatches in one command..lastTimestamp: the default order is not chronological.kubectl get all does not include ConfigMaps, Secrets, PVCs or Ingresses.<MCQExercise exercise={{ id: 'k8s-getdesc-ready-column', title: 'Running but not serving', description: 'kubectl get pods shows a Pod as Running with READY 0/1 and RESTARTS 0. What does this tell you?', options: [ { id: 'a', text: 'The container started successfully but is failing its readiness probe, so it is excluded from Service endpoints' }, { id: 'b', text: 'The container is still being created and has not started yet' }, { id: 'c', text: 'The image failed to pull and the Pod is waiting to retry' }, { id: 'd', text: 'The Pod has been scheduled but no node has accepted it' }, ], correctId: 'a', explanation: 'Running means the container process is up, and a restart count of zero means it has not crashed. READY counts containers passing readiness, so 0/1 points squarely at the probe. kubectl describe pod will quote the failure. Most often a 404 from the wrong path, a connection refused on the wrong port, or a delay shorter than the application's real startup time.', wrongExplanations: { b: 'A container still being created shows status ContainerCreating with the Pod in Pending, not Running.', c: 'An image pull failure shows ImagePullBackOff or ErrImagePull in the STATUS column, which would be visible instead of Running.', d: 'An unscheduled Pod is Pending with no node assigned. This one is running on a node already.', }, }} />
<MCQExercise exercise={{ id: 'k8s-getdesc-events-gone', title: 'An incident with no evidence', description: 'You investigate a crash that happened three hours ago. kubectl describe pod shows no events at all, though the Pod clearly restarted. Why?', options: [ { id: 'a', text: 'Events have a roughly one-hour TTL by default, so the ones explaining the crash have already been deleted' }, { id: 'b', text: 'describe only shows events for Pods that are currently unhealthy' }, { id: 'c', text: 'Events are only recorded when a Pod is first scheduled' }, { id: 'd', text: 'The Pod was recreated, and events do not transfer between Pods' }, ], correctId: 'a', explanation: 'Events are stored in etcd with a short TTL, one hour by default, because they are high volume and considered transient. That window is routinely shorter than the delay between something breaking and someone looking at it. Shipping events to your logging system with a tool such as the Kubernetes Event Exporter is what makes post-incident analysis possible.', wrongExplanations: { b: 'describe shows whatever events still exist for the object, regardless of its current health. A healthy Pod shows its recent scheduling and pull events too.', c: 'Events are emitted throughout a Pod's life. Probe failures, restarts, evictions, mount problems. Scheduling is only the first of many.', d: 'Events are associated with an object by UID, so a recreated Pod genuinely has its own. But here the restart count shows the same Pod persisted; the events simply aged out.', }, }} />
kubectl describe pod <name> and read the Events section at the bottom. It usually states the cause outright: FailedScheduling with the per-node reasons, an image pull error quoting the registry's own message, Unhealthy with the probe's status code, or a FailedMount for a volume problem. Only if the events do not explain it do you need kubectl logs <pod> --previous, which shows output from the container run that actually crashed.
Zero of one containers are passing their readiness probe. If the Pod's status is also Running, the process started fine and the probe itself is failing. A wrong path or port, an endpoint behind authentication returning 401, or an initialDelaySeconds shorter than real startup time. A Pod in this state is deliberately excluded from its Service's endpoints, so it receives no traffic, which is exactly what readiness is for.
Use jsonpath: kubectl get pod <name> -o jsonpath='{.status.podIP}'. It is particularly useful for values buried in status, such as kubectl get pod <name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}' which returns OOMKilled for a container killed on memory. For tabular output across many objects, -o custom-columns lets you build exactly the table you want rather than scrolling through YAML.
They expire. Kubernetes keeps events for about an hour by default, set by the API server's --event-ttl, because they are high volume and treated as transient. Anything older than that is gone, which routinely means the event explaining an overnight failure has vanished before anyone looks. Exporting events into your log store, the Kubernetes Event Exporter is the usual tool, preserves them, and it is not something the common monitoring stacks do for you.
No, and the name is misleading. It covers the common workload and networking types. Pods, Services, Deployments, ReplicaSets, StatefulSets, DaemonSets and Jobs, but omits ConfigMaps, Secrets, PersistentVolumeClaims, Ingresses, ServiceAccounts and every custom resource. To see genuinely everything in a namespace you need to enumerate the API resources and query each, which kubectl api-resources supports but is rarely worth doing by hand.
Learn kubectl apply vs create vs replace, preview changes with kubectl diff, use server-side apply, delete resources safely and fix objects stuck deleting.
Debug pods with kubectl logs (including --previous), kubectl exec, port-forward and kubectl cp. Handle distroless images with kubectl debug.
Learn how kubeconfig works, switch clusters with kubectl config use-context, set a default namespace, merge kubeconfig files and avoid the wrong cluster.