Useful JsonPath Queries for Kubernetes
Luke Hebert
- 6 minutes read - 1070 wordsJsonPath in kubectl is a lightweight, shell-native way to print exactly the columns you want in a tab-separated or custom-formatted layout. It’s great for quick lookups and scripts, but it stops short of conditional logic; for that, fall back to jq.
The Building Blocks
Every JsonPath expression in kubectl is composed of a few primitives:
| Syntax | Meaning |
|---|---|
{.field.subfield} | Extract a value from the object |
{.items[0].name} | Index into an array |
{.items[*]} | Iterate over all array elements |
{range ...}{end} | Loop (like a for-loop) |
{"\t"} | Literal tab character |
{"\n"} | Literal newline |
{"literal text"} | Any literal string |
{.a}{.b} | Concatenate two values |
The entire expression is wrapped in single quotes in the shell:
kubectl get nodes -o=jsonpath='{...}'The Core Pattern: range / end
This is the workhorse. It iterates over a list and prints one line per item:
# Basic structure
kubectl get <resource> -o=jsonpath='{range .items[*]} <FIELD1> {"\t"} <FIELD2> {"\n"} {end}'Nodes - name, CPU, architecture
kubectl get nodes -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.capacity.cpu}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}'Output:
master 4 amd64
node01 8 amd64
node02 16 arm64Pods - name, namespace, node, phase, restarts
kubectl get pods -A -o=jsonpath='{range .items[*]} {.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.nodeName}{"\t"}{.status.phase}{"\t"}{.status.containerStatuses[0].restartCount}{"\n"}{end}'Services - name, type, cluster IP, external IP
kubectl get svc -A -o=jsonpath='{range .items[*]} {.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.type}{"\t"}{.spec.clusterIP}{"\t"}{.status.loadBalancer.ingress[0].ip}{"\n"}{end}'Deployments - name, desired, ready, available
kubectl get deployments -A -o=jsonpath='{range .items[*]} {.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.replicas}{"\t"}{.status.readyReplicas}{"\t"}{.status.availableReplicas}{"\n"}{end}'PVCs - name, storage class, capacity, status
kubectl get pvc -A -o=jsonpath='{range .items[*]} {.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.storageClassName}{"\t"}{.status.capacity.storage}{"\t"}{.status.phase}{"\n"}{end}'Ingresses - host, path, backend service
kubectl get ingress -A -o=jsonpath='{range .items[*]} {.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.rules[0].host}{"\t"}{.spec.rules[0].http.paths[0].path}{"\t"}{.spec.rules[0].http.paths[0].backend.service.name}{"\n"}{end}'Indexing Into Arrays
When a field is an array, you index with [n]:
# First container's image
kubectl get pods -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
# Second container's image (if it exists)
kubectl get pods -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.spec.containers[1].image}{"\n"}{end}'
# First node's kernel version
kubectl get nodes -o=jsonpath='{.items[0].status.nodeInfo.kernelVersion}'If the index is out of bounds, you get <no value> in that cell. There’s no built-in “if exists” guard in kubectl’s JsonPath implementation.
Single-Value Extraction (No Loop)
When you only need one value (common in scripts):
# Get the cluster IP of a specific service
kubectl get svc my-svc -n production -o=jsonpath='{.spec.clusterIP}'
# Get the node a pod is scheduled on
kubectl get pod my-pod -n production -o=jsonpath='{.spec.nodeName}'
# Get the first node's name
kubectl get nodes -o=jsonpath='{.items[0].metadata.name}'
# Get a node's kubelet version
kubectl get nodes -o=jsonpath='{.items[0].status.nodeInfo.kubeletVersion}'
# Get a pod's container image
kubectl get pod my-pod -o=jsonpath='{.spec.containers[0].image}'These are ideal for shell scripting:
CLUSTER_IP=$(kubectl get svc my-svc -n prod -o=jsonpath='{.spec.clusterIP}')
echo "Service is at $CLUSTER_IP"Literal Strings and Formatting
You can inject any literal text between fields:
# Add a label prefix
kubectl get nodes -o=jsonpath='{range .items[*]} NODE: {.metadata.name}{"\t"}CPU: {.status.capacity.cpu}{"\n"}{end}'
# Add a header row
kubectl get nodes -o=jsonpath='NAME\tCPU\tARCH\n{range .items[*]} {.metadata.name}{"\t"}{.status.capacity.cpu}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}'
# Add a separator
kubectl get pods -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.phase}{"\n"}---{end}'Output with header:
NAME CPU ARCH
master 4 amd64
node01 8 amd64Conditional / Computed Output
kubectl’s JsonPath does not support if/else or arithmetic. However, you can work around it:
“Ready” vs “Not Ready”
# This won't work (no ternary in kubectl JsonPath):
# {.status.readyReplicas == .spec.replicas ? "Ready" : "Degraded"}
# Workaround: print both values and let the reader (or awk) decide
kubectl get deployments -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.readyReplicas}/{.spec.replicas}{"\n"}{end}'Filtering with ? (limited support)
Some kubectl versions support a basic filter syntax inside JsonPath:
# List only nodes that are Ready (not universally supported)
kubectl get nodes -o=jsonpath='{range .items[?(@.status.conditions[?(@.type=="Ready")].status=="True")]} {.metadata.name}{"\n"}{end}'This ?(@...) syntax is not consistently supported across all kubectl versions. If it fails, fall back to jq:
kubectl get nodes -o json | jq -r '.items[] | select(.status.conditions[] | select(.type=="Ready").status=="True") | .metadata.name'Multi-Line / Nested Output
You can print nested structures by adding extra newlines:
# Print each node with its full capacity block
kubectl get nodes -o=jsonpath='{range .items[*]} === {.metadata.name} ==={"\n"} CPU: {.status.capacity.cpu}{"\n"} Memory: {.status.capacity.memory}{"\n"} Pods: {.status.capacity.pods}{"\n"}{"\n"}{end}'Output:
=== master ===
CPU: 4
Memory: 16Gi
Pods: 110
=== node01 ===
CPU: 8
Memory: 32Gi
Pods: 110Combining with Other Flags
JsonPath works alongside all the standard kubectl flags:
# Filter by label + JsonPath
kubectl get pods -l app=nginx -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
# Filter by namespace + JsonPath
kubectl get pods -n production -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.phase}{"\n"}{end}'
# Sort + JsonPath (sort is applied before output)
kubectl get nodes --sort-by=.metadata.name -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.capacity.cpu}{"\n"}{end}'
# All namespaces + JsonPath
kubectl get pods -A -o=jsonpath='{range .items[*]} {.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.status.phase}{"\n"}{end}'Debugging Tips
1. Inspect the raw JSON first
When you’re unsure of a field path, dump the full JSON and explore:
kubectl get pod my-pod -o json | jq '.spec.containers[0]'
kubectl get node node01 -o json | jq '.status.capacity'2. Test incrementally
Build your expression piece by piece:
# Step 1: just the name
kubectl get nodes -o=jsonpath='{range .items[*]} {.metadata.name}{"\n"}{end}'
# Step 2: add CPU
kubectl get nodes -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.capacity.cpu}{"\n"}{end}'
# Step 3: add arch
kubectl get nodes -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.status.capacity.cpu}{"\t"}{.status.nodeInfo.architecture}{"\n"}{end}'3. <no value> means the path is wrong
# This will show <no value> for pods without a priorityClassName
kubectl get pods -o=jsonpath='{range .items[*]} {.metadata.name}{"\t"}{.spec.priorityClassName}{"\n"}{end}'That’s expected behaviour, not an error.
4. Shell quoting
Always use single quotes around the JsonPath expression. Double quotes will cause the shell to interpret {} and " characters:
# Correct
kubectl get pods -o=jsonpath='{range .items[*]} {.metadata.name}{"\n"}{end}'
# Wrong - shell will mangle the expression
kubectl get pods -o=jsonpath="{range .items[*]} {.metadata.name}{"\n"}{end}"Quick-Reference: Common Field Paths
| Resource | Field | Path |
|---|---|---|
| Pod | Name | .metadata.name |
| Pod | Namespace | .metadata.namespace |
| Pod | Node | .spec.nodeName |
| Pod | Phase | .status.phase |
| Pod | Container image | .spec.containers[0].image |
| Pod | Restarts | .status.containerStatuses[0].restartCount |
| Pod | Priority | .spec.priorityClassName |
| Node | CPU | .status.capacity.cpu |
| Node | Memory | .status.capacity.memory |
| Node | Arch | .status.nodeInfo.architecture |
| Node | Kernel | .status.nodeInfo.kernelVersion |
| Node | Kubelet ver | .status.nodeInfo.kubeletVersion |
| Service | Type | .spec.type |
| Service | Cluster IP | .spec.clusterIP |
| Service | External IP | .status.loadBalancer.ingress[0].ip |
| Deployment | Desired | .spec.replicas |
| Deployment | Ready | .status.readyReplicas |
| PVC | Storage class | .spec.storageClassName |
| PVC | Capacity | .status.capacity.storage |
| Ingress | Host | .spec.rules[0].host |
| Ingress | Backend svc | .spec.rules[0].http.paths[0].backend.service.name |
When to Use JsonPath vs. custom-columns vs. jq
| Need | Best tool |
|---|---|
| Quick one-off field extraction | custom-columns (more readable) |
| Complex formatting, literals, headers | jsonpath |
| Filtering, arithmetic, complex logic | jq (pipe from -o json) |
| Scripting / single value | jsonpath (no loop) |
| Nested / multi-line output | jsonpath |
Conditional logic (if ready > 0) | jq |
They’re not mutually exclusive; you can even combine them:
# Use jq for the filter, then format
kubectl get pods -A -o json | jq -r '.items[] | select(.status.phase=="Running") | "\(.metadata.namespace)\t\(.metadata.name)\t\(.spec.nodeName)"'This gives you the full power of JsonPath for formatting while still having jq available when the logic gets too complex for kubectl’s limited expression language.