KubernetesVI · kubectl for Administratorskubectl for administrators
kubectl output formatting — jsonpath, custom-columns, go-templates
What you'll learn
- Write jsonpath expressions to extract single fields and slices
- Build custom-columns views for ad-hoc tabular reporting
- Use go-templates for nested transformations
- Compose kubectl with jq for shell pipelines that drive automation
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
The default kubectl get output is human-readable columns. That
is the right format for a human reading a terminal — and the
wrong format for everything else. This lesson covers the
production-grade output options: jsonpath for extracting one
field, custom-columns for tabular views, go-templates for
nested transformations, and jq for shell composition.
jsonpath — extract a single field
-o jsonpath='<expression>' is the workhorse for “give me one
field.” The expression is a small DSL that walks the JSON tree
the API server returned.
# Pod names, one per line
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
# Pod names + their node, tab-separated
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}'
# Container images
kubectl get pod web -o jsonpath='{.spec.containers[*].image}'
# The count of Running pods
kubectl get pods -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | wc -w
The grammar:
.— root.foo— descend into fieldfoo[*]— iterate over an array[?(@.field=="value")]— filter (predicate){...}— output expressionrange .items[*]}{...}{end— explicit loop with separator{"\n"}— newline (escapes inside the expression)
The range form is needed for separators and newlines. Without
it, jsonpath concatenates items with no whitespace, which makes
output unreadable for humans but perfect for xargs.
custom-columns — tabular view
-o custom-columns=<NAME>:<JSONPATH>,... builds a table with
named columns. The column values are extracted by jsonpath.
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
Output:
NAME STATUS NODE
web-7c8 Running node-3
web-9d2 Pending <none>
api-4f1 Running node-1
For wider use, store the column list in a file and reuse it:
cat > /tmp/podview.txt <<EOF
NAME .metadata.name
STATUS .status.phase
NODE .spec.nodeName
IP .status.podIP
READY .status.containerStatuses[0].ready
RESTARTS .status.containerStatuses[0].restartCount
AGE .metadata.creationTimestamp
EOF
kubectl get pods -o custom-columns-file=/tmp/podview.txt
This is the right format for ad-hoc audits: “show me every Pod with more than 5 restarts” or “show me every Node with less than 20% free CPU.” It is also the format that exports cleanly to a CSV.
go-templates — nested transformations
-o go-template='...' / -o go-template-file=path is the full
text/template language. Use it when jsonpath is too limited
(arithmetic, conditionals, custom formatting).
kubectl get pods -o go-template='{{range .items}}{{.metadata.name}} {{.status.phase}} {{.spec.nodeName}}{{"\n"}}{{end}}'
# Multi-section output
kubectl get pods -o go-template='{{range .items}}---
name: {{.metadata.name}}
status: {{if eq .status.phase "Running"}}OK{{else}}{{.status.phase}}{{end}}
image: {{.spec.containers[0].image}}
{{end}}'
The most common go-template patterns:
| Pattern | Use |
|---|---|
{{range .items}}...{{end}} | Loop over items |
{{if eq .x "y"}}...{{end}} | Equality check |
{{if .x}}...{{else}}...{{end}} | Boolean check |
{{.x | default "N/A"}} | Default value |
{{printf "%-30s" .x}} | Formatted output |
{{.x | quote}} | Quoted string |
go-templates are powerful but verbose. Prefer jsonpath when the output is one field per item; reach for go-templates when you need branching or formatting.
jq — the shell-composition tool
kubectl returns JSON with -o json. jq is a stream JSON
processor that does what jsonpath and go-templates cannot:
multi-stage pipelines, conditionals on the full document, and
filtering across arrays.
# All Running pod names
kubectl get pods -o json | jq -r '.items[] | select(.status.phase=="Running") | .metadata.name'
# All pod names + their restart count, sorted descending
kubectl get pods -o json | jq -r '.items[] | [.metadata.name, (.status.containerStatuses[0].restartCount // 0)] | @tsv' | sort -k2 -n -r
# All pods that are not ready, with a count
kubectl get pods -o json | jq '.items[] | select(.status.containerStatuses[].ready==false) | .metadata.name'
# All pods with a particular annotation
kubectl get pods -o json | jq -r '.items[] | select(.metadata.annotations["prometheus.io/scrape"]=="true") | .metadata.name'
# Generate kubectl delete commands
kubectl get pods -l app=web -o json | jq -r '.items[] | .metadata.name' | xargs -I {} kubectl delete pod {}
jq is the right tool for any non-trivial pipeline: complex
filters, aggregations (count, sum), joins across resources
(get pods, get nodes, join on nodeName), and the kind of
shell-driven automation that makes up incident response and
audits.
Sort and select
Two flags that pair naturally with output formatting:
# Sort by a jsonpath
kubectl get pods --sort-by=.status.containerStatuses[0].restartCount
# Filter by labels
kubectl get pods -l app=web,tier=frontend
# Filter by labels (set-based)
kubectl get pods -l 'env in (prod,staging)'
# Filter by namespace
kubectl get pods -A --field-selector=status.phase=Pending
# Combine sort + select + custom-columns
kubectl get pods -A --sort-by=.status.containerStatuses[0].restartCount \
-o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount | tail -20
The --sort-by flag takes a jsonpath and sorts ascending.
For descending sort, pipe through sort -r or sort -k N -r.
Choosing the right format
A practical decision tree:
flowchart TD
A[What do I need?] --> B{One field per item?}
B -- yes --> C[jsonpath]
B -- no --> D{Need conditional formatting?}
D -- no --> E[custom-columns]
D -- yes --> F{Complex multi-stage?}
F -- no --> G[go-template]
F -- yes --> H[jq]
- jsonpath:
kubectl get pods -o jsonpath='{.items[*].metadata.name}'— when the answer is “give me one field per object.” - custom-columns: when the answer is “give me a table with these specific columns.”
- go-template: when the answer requires branching or formatting inside the expression.
- jq: when the answer requires multiple stages, joins across resources, or aggregation.
Cross-course references
- The Linux course part
XXII-Linux-NetTroubleshootcovers text pipelines (grep,awk,sed); the kubectl output options are the cluster-level equivalent but with structured input. - The Observability course part
XII-Observability-PromQLFoundationscovers PromQL aggregation; jq pipelines are similar in spirit (filter, group, aggregate) but operate on cluster data instead of time series. - The Ansible course part
XXXV-Ansible-Scriptingcovers shell scripting discipline; production kubectl usage is built on the same discipline.
Quiz
Knowledge check · 4 questions
Q1. Which jsonpath expression prints the names of all Pods, one per line?
Q2. `kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase` produces output that is safe to redirect directly to a CSV file with `> pods.csv` and load into a spreadsheet.
Q3. An on-call engineer is asked to produce a list of all Pods across all namespaces whose containers have restarted more than 5 times, sorted by restart count descending. Build the command.
Cluster has ~2000 pods across 30 namespaces. The on-call engineer needs a one-shot audit. The output should show: namespace, pod name, container name, restart count. They have kubectl and jq installed.
Q4. When is `jq` the right output tool instead of jsonpath or custom-columns?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Use
jqfor everything that requires more than one filter. jsonpath and custom-columns are single-stage; the moment you need a join, an aggregation, or a multi-step transformation, reach forkubectl get -o json | jq. - Ship
jqandyqon every operator workstation and CI runner. Production shell pipelines against the API server are not possible without them. - Quote jsonpath expressions with single quotes outside, double quotes inside. That keeps the expression readable and avoids shell-escape bugs.
- Sort before formatting.
--sort-byruns at the API server; piping throughsortruns at the client. For very large lists, sort server-side. - Save audit output to a file with a timestamp. Every ad-hoc audit is a future postmortem input.