Skip to main content
RunBook Academy

KubernetesVI · kubectl for Administratorskubectl for administrators

kubectl output formatting — jsonpath, custom-columns, go-templates

Intermediate⏱ ~18 minkubectl

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

Not yet marked complete on this device.

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 field foo
  • [*] — iterate over an array
  • [?(@.field=="value")] — filter (predicate)
  • {...} — output expression
  • range .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:

PatternUse
{{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-NetTroubleshoot covers text pipelines (grep, awk, sed); the kubectl output options are the cluster-level equivalent but with structured input.
  • The Observability course part XII-Observability-PromQLFoundations covers 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-Scripting covers shell scripting discipline; production kubectl usage is built on the same discipline.

Quiz

Knowledge check · 4 questions

  1. Q1. Which jsonpath expression prints the names of all Pods, one per line?

  2. 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.

  3. 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.

  4. 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 jq for 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 for kubectl get -o json | jq.
  • Ship jq and yq on 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-by runs at the API server; piping through sort runs 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.