`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 -o json` returns one object with an `items` array, so most recipes start the same way: ```bash 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][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 ```bash kubectl get pods -A -o json \ | jq -r '.items[] | select(.status.phase != "Running") | [.metadata.namespace, .metadata.name, .status.phase] | @tsv' ``` ```text default broken-7b58495bf7-n24xh Pending default oneshot Failed kube-system helm-install-traefik-crd-8pr9q Succeeded ``` Read 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: ```bash 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' ``` ```text 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 ```bash kubectl get events -A -o json \ | jq -r '.items[] | select(.type == "Warning") | [.involvedObject.name, .reason, .message] | @tsv' ``` ```text 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 filesystem ``` ### Restart counts, worst first ```bash 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' ``` ```text kube-system helm-install-traefik-nh92k 2 default broken-7b58495bf7-n24xh 0 default web-6cc9857b4b-hbxwz 0 ``` A pod with several containers needs the `add`: restart counts are per container. ## Audit the Cluster ### Every image actually running ```bash kubectl get pods -A -o json | jq -r '[.items[].spec.containers[].image] | unique | .[]' ``` ```text busybox:1.37 nginx:1.29-alpine nginx:doesnotexist rancher/mirrored-coredns-coredns:1.14.3 ``` This 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 ```bash 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' ``` ```text 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 ```bash 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' ``` ```text broken-7b58495bf7-n24xh nginx - - web-6cc9857b4b-hbxwz nginx 50m 64Mi web-6cc9857b4b-n28qb nginx 50m 64Mi ``` ### Where pods are scheduled ```bash kubectl get pods -A -o json \ | jq -r '.items | group_by(.spec.nodeName)[] | "\(.[0].spec.nodeName // "unscheduled"): \(length) pods"' ``` ```text k3d-jqdemo-agent-0: 6 pods k3d-jqdemo-server-0: 8 pods ``` ### A count of pods by phase ```bash kubectl get pods -A -o json \ | jq -r '[.items[].status.phase] | group_by(.) | map({phase: .[0], count: length}) | sort_by(-.count)[] | "\(.count)\t\(.phase)"' ``` ```text 5 Pending 5 Running 2 Succeeded 1 Failed ``` ### Node capacity ```bash kubectl get nodes -o json \ | jq -r '.items[] | [.metadata.name, .status.capacity.cpu, .status.capacity.memory, .status.allocatable.pods] | @tsv' ``` ```text k3d-jqdemo-agent-0 16 13519688Ki 110 k3d-jqdemo-server-0 16 13519688Ki 110 ``` ### ConfigMap keys without dumping values ```bash kubectl get configmaps -A -o json \ | jq -r '.items[] | select(.data != null) | [.metadata.namespace, .metadata.name, (.data | keys | join(","))] | @tsv' ``` ```text kube-system cluster-dns clusterDNS,clusterDomain kube-system coredns Corefile,NodeHosts ``` Listing 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 ```bash kubectl get deployment web -o json \ | jq -r '.spec.selector.matchLabels | to_entries | map("\(.key)=\(.value)") | join(",")' ``` ```text app=web ``` Feed 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: ```bash kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}' ``` ```text default broken-7b58495bf7-n24xh default oneshot default web-6cc9857b4b-hbxwz ``` Use 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][jq-cookbook] covers the filter syntax these recipes use: `select`, `//`, `group_by`, `@tsv`, and the rest. * [Learn Kubernetes][learn-kubernetes] covers the objects being queried here. * [Learn systemd][learn-systemd] is the equivalent for the machines underneath the cluster. ## References * [kubectl JSONPath support][jsonpath-docs], for the built-in alternative. * [Kubernetes Pod lifecycle][pod-lifecycle], for why phase and container state differ. * [jq manual][jq-manual], for every filter used above. [jq-cookbook]: https://jeffbailey.us/learn-jq/ [learn-kubernetes]: https://jeffbailey.us/learn-kubernetes/ [learn-systemd]: https://jeffbailey.us/learn-systemd/ [jsonpath-docs]: https://kubernetes.io/docs/reference/kubectl/jsonpath/ [pod-lifecycle]: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/ [jq-manual]: https://jqlang.org/manual/