kubectl get pods answers the easy questions. The moment you need something it has no column for, which containers have no memory limit, which images the cluster actually runs, which pods restarted most, you reach for -o json and a filter.
These recipes ran against a live two-node k3s cluster (Kubernetes 1.35) with jq 1.8.2, including a deliberately broken deployment so the failure output is real. Swap the namespaces for yours.
The Shape You’re Filtering
Every kubectl get <resource> -o json returns one object with an items array, so most recipes start the same way:
kubectl get pods -A -o json | jq -r '.items[] | ...'-A means all namespaces, -r prints raw strings instead of quoted JSON, and @tsv lines things up for column -t. The jq cookbook covers the filter syntax itself; this guide is about what to ask a cluster.
Find What’s Broken
Pods that aren’t running
kubectl get pods -A -o json \
| jq -r '.items[] | select(.status.phase != "Running")
| [.metadata.namespace, .metadata.name, .status.phase] | @tsv'default broken-7b58495bf7-n24xh Pending
default oneshot Failed
kube-system helm-install-traefik-crd-8pr9q SucceededRead that output carefully, because it shows the trap: the pod stuck on a bad image reports phase Pending, not Failed. Phase describes the pod’s lifecycle, not whether the container is healthy. Completed jobs show as Succeeded and are usually noise here.
Why a container isn’t ready
The useful detail lives in containerStatuses, not in the phase:
kubectl get pods -A -o json \
| jq -r '.items[] | select(.status.containerStatuses != null)
| .status.containerStatuses[] | select(.ready == false)
| [.name,
(.state.waiting.reason // .state.terminated.reason // "-"),
(.state.waiting.message // "-")] | @tsv'nginx ImagePullBackOff Back-off pulling image "nginx:doesnotexist": ErrImagePull
oneshot Error -
traefik ContainerCreating -// supplies a fallback when a key is absent, which matters here because a waiting container has state.waiting and a crashed one has state.terminated.
Recent warnings, without scrolling
kubectl get events -A -o json \
| jq -r '.items[] | select(.type == "Warning")
| [.involvedObject.name, .reason, .message] | @tsv'broken-7b58495bf7-n24xh Failed Failed to pull image "nginx:doesnotexist": rpc error
broken-7b58495bf7-n24xh Failed Error: ImagePullBackOff
k3d-jqdemo-agent-0 InvalidDiskCapacity invalid capacity 0 on image filesystemRestart counts, worst first
kubectl get pods -A -o json \
| jq -r '[.items[] | select(.status.containerStatuses != null)
| {ns: .metadata.namespace, pod: .metadata.name,
restarts: ([.status.containerStatuses[].restartCount] | add)}]
| sort_by(-.restarts)[] | [.ns, .pod, .restarts] | @tsv'kube-system helm-install-traefik-nh92k 2
default broken-7b58495bf7-n24xh 0
default web-6cc9857b4b-hbxwz 0A pod with several containers needs the add: restart counts are per container.
Audit the Cluster
Every image actually running
kubectl get pods -A -o json | jq -r '[.items[].spec.containers[].image] | unique | .[]'busybox:1.37
nginx:1.29-alpine
nginx:doesnotexist
rancher/mirrored-coredns-coredns:1.14.3This is the fastest answer to “are we still running that old tag anywhere?”. Add .spec.initContainers[]? when init containers matter; the ? keeps jq quiet for pods that have none.
Containers with no resource limits
kubectl get pods -A -o json \
| jq -r '.items[] | . as $p | .spec.containers[]
| select(.resources.limits == null)
| [$p.metadata.namespace, $p.metadata.name, .name] | @tsv'default broken-7b58495bf7-n24xh nginx
default oneshot oneshot
kube-system metrics-server-786d997795-bj7fd metrics-server. as $p saves the pod before descending into its containers, so each row can still name the pod it came from. That pattern shows up in most cluster-wide audits.
Requests, per container
kubectl get pods -n default -o json \
| jq -r '.items[] | . as $p | .spec.containers[]
| [$p.metadata.name, .name,
(.resources.requests.cpu // "-"),
(.resources.requests.memory // "-")] | @tsv'broken-7b58495bf7-n24xh nginx - -
web-6cc9857b4b-hbxwz nginx 50m 64Mi
web-6cc9857b4b-n28qb nginx 50m 64MiWhere pods are scheduled
kubectl get pods -A -o json \
| jq -r '.items | group_by(.spec.nodeName)[]
| "\(.[0].spec.nodeName // "unscheduled"): \(length) pods"'k3d-jqdemo-agent-0: 6 pods
k3d-jqdemo-server-0: 8 podsA count of pods by phase
kubectl get pods -A -o json \
| jq -r '[.items[].status.phase] | group_by(.)
| map({phase: .[0], count: length}) | sort_by(-.count)[]
| "\(.count)\t\(.phase)"'5 Pending
5 Running
2 Succeeded
1 FailedNode capacity
kubectl get nodes -o json \
| jq -r '.items[] | [.metadata.name, .status.capacity.cpu,
.status.capacity.memory, .status.allocatable.pods] | @tsv'k3d-jqdemo-agent-0 16 13519688Ki 110
k3d-jqdemo-server-0 16 13519688Ki 110ConfigMap keys without dumping values
kubectl get configmaps -A -o json \
| jq -r '.items[] | select(.data != null)
| [.metadata.namespace, .metadata.name, (.data | keys | join(","))] | @tsv'kube-system cluster-dns clusterDNS,clusterDomain
kube-system coredns Corefile,NodeHostsListing keys instead of values keeps secrets and certificates out of your scrollback. The same filter works on secrets, where it matters more.
Build a label selector from a deployment
kubectl get deployment web -o json \
| jq -r '.spec.selector.matchLabels | to_entries
| map("\(.key)=\(.value)") | join(",")'app=webFeed that straight back in: kubectl get pods -l "$(…)".
jq or jsonpath?
kubectl has a built-in filter, and for simple field extraction it saves a pipe:
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'default broken-7b58495bf7-n24xh
default oneshot
default web-6cc9857b4b-hbxwzUse jsonpath when you want a couple of fields and no dependency. Use jq when you need anything it can’t do: filtering on a comparison, grouping, sorting, counting, defaults for missing keys, or reshaping into CSV. --sort-by and -o custom-columns cover a little more ground, but the moment the question contains “which ones”, jq is shorter and clearer.
One habit worth keeping: kubectl get ... -o json sends the full object over the wire. On a large cluster, narrow it first with -n, -l, or --field-selector rather than filtering everything client-side.
Related Content
- jq Cookbook covers the filter syntax these recipes use:
select,//,group_by,@tsv, and the rest. - Learn Kubernetes covers the objects being queried here.
- Learn systemd is the equivalent for the machines underneath the cluster.
References
- kubectl JSONPath support, for the built-in alternative.
- Kubernetes Pod lifecycle, for why phase and container state differ.
- jq manual, for every filter used above.

Comments #