Kubectl JSON output handling and JQ
WHY
Kubernetes可以看作”Object Store”, Object格式: yaml
or json
, 两者等价.
Kubectl的get
命令读取一系列Objects, 并以JSON格式返回. 这些JSON格式往往很大,有复杂的嵌套,常见的需求:
- 获取Output的subset, 往往deep nested: 某些fields, 而不是全部JSON
- Filter out irrelevant objects by fields: e.g.我们只想返回失败的job
- Sort: sort by creation date, status, etc
相比每次将JSON当做文本,grep
; 我们需要更贴合JSON的方式.
本质上JSON output是我们的数据源, 我们需要query language, 就像query relational database一样.
本文介绍用JQ
, Jid
处理kubectl
输出, 应对常见的情景.
示例代码在github下载
Introduction to JQ
jq
首先是binary tool, 用来query json; 由于强大的query功能,可视为独立的language, 类似awk
.
先看jq
提供的基本功能, Demo JSON
1 | { "store": { |
1 | # prettify JSON |
JQ tutorial也提供了一些例子, 值得一读。
进一步了解 JQ
以下主要来自JQ Manual
以一个具体jq
cmd为例: query k8s jobs, 以创建时间排序, filter, 只留下failed jobs, 并reconstruct object.
1 | k get jobs -o json --sort-by=.metadata.creationTimestamp | jq -r '.items | .[] | select(.metadata.name | contains("openworld")) | select(.status.active != 1) | select(.status.conditions[0].type == "Failed") | {name: .metadata.name, time: .metadata.creationTimestamp, status: .status}' |
可见jq
的query由一系列|
组成,类似UNIX的pipe, 连接独立component的input, output.
每个component, 称为filter, 接收输入,完成输出. 每个filter包含不同的jq
built-in operator, 实行不同的转换.
有些filter会产生multiple output(filter A), 当 pipe filter A
的输出到filter B
时,会runs the second filter for each element of the array
; 即不用explicitly写for-loop, 直接pipe两者即可.
Every filter has input and output, 即使常数”123”也是filter: 不管输入是什么,输出”123”. JQ里一切皆filter.
Filters
1 | . # 最简单的filter, echo input |
Types && Values
jq supports the same set of datatypes as JSON - numbers, strings, booleans, arrays, objects (which in JSON-speak are hashes with only string keys), and “null”.
针对Type转换的主要有[]
: array construction及{}
: object construction.
[]
将multiple input转化为JSON array, 即single object, 如之前提到的
1 | cat example.json | jq '[.store.book | .[]]' |
.[]
是将array打散为multiple output, 因此[]
和.[]
为逆过程.
{}
reconstruct object: 原始object不能满足需要,我们根据输出,重新构造object, 之前例子:
1 | | {name: .metadata.name, time: .metadata.creationTimestamp, status: .status} |
有一个common case: 我们只想获得input object的some fields as new Object, 可以:
1 | {title: .title | author: .author} |
可以简写为: {title, author}
当我们想递归遍下降历所有的sub objects of input “Big” object: 用: ..
:
1 | cat example.json | jq '.. | .price?' |
此例中,bicycle及book都有price, 我们都列出来; .bar?
的?
在没有bar
index时不至于报错.
jq
的基本元素介绍完了, 但实际使用,除了基本原理,了解built-in非常关键,毕竟advance场景, 需要自己实现operator时候不多。
Built-in operators and functions
列举一些常用的operator和function, 感觉在做functional programming.
select(boolean_expression)
重要的filter: 一般用来过滤array的elements:
如果input满足boolean_expression
, 原样保留; 否则丢弃
boolean_expression可以调用input, 结合其他filter:
1 | jq `[1,2,3] | map(select(. >= 2))` |
another example:
1 | jq '.[] | select(.id == "second")' |
四则运算
+ -
初看起来很简单, 但功能丰富:
a + b
连接两个filter, 将输入传递给both a
和b
; 再将两者输出做”add”:
- Numbers: 直接add
- Arrays: concat 2 array into 1 bigger array
- Strings: concat, similar as array
- Objects: merge 2 objects, 右边覆盖左边
同理有-
, 但仅支持两边都是numbers
或array
* / %
一般仅对两个numbers
, 也支持array
, string
和objects
, 但效果比较诡异,应该不常用,略过。
keys
keys, keys_unsorted
要求输入为object
, 输出keys
为array.
has(key)
:
- input: object; output: boolean, 是否含有此key.
- input: array: array是否有此element
1 | jq 'map(has("foo"))' |
in
: if input key in given object, or array; inversed version of has
1 | jq 'map(in([0,1]))' |
map(expr) map_values(expr)
- input: array;
-
expr
: expr or function, 对每个elements或fields应用expr; - output: 应用
expr
后的新array
1 | jq 'map_values(.+1)' |
可以传入其他built-in, 例如获取field type.
1 | jq 'map(type)' |
filter by data type
arrays, objects, iterables, booleans, numbers, normals, finites, strings, nulls, values, scalars
These built-ins select only inputs that are arrays, objects, iterables (arrays or objects), booleans, numbers, normal numbers, finite numbers, strings, null, non-null values, and non-iterables, respectively.
只留下match的type
1 | jq '[].[]|numbers, nulls]' |
any all
input: array of boolean values
output: any
任意一个true为true; all
需要所有为true.
1 | jq 'any' |
array operation
-
sort
,sort_by(path_expression)
1
2
3jq 'sort_by(.foo)'
Input [{"foo":4, "bar":10}, {"foo":3, "bar":100}, {"foo":2, "bar":1}]
Output [{"foo":2, "bar":1}, {"foo":3, "bar":100}, {"foo":4, "bar":10}] -
unique
,unique_by
-
min
,max
,min_by
,max_by
-
reverse
string operation
一系列的操作:
-
contains(s)
-
index(s)
,rindex(s)
-
inside
-
startWith(str)
,endWith(str)
-
split
,join
String interpolation - \(foo)
1 | jq '"The input was \(.), which is one less than \(.+1)"' |
length
“length” of values: 支持string
, array
, object
逻辑判断 operator
作用一目了然, 返回true or false:
-
==, !=
1
2
3
4
5
6jq '.[] == 1'
Input [1, 1.0, "1", "banana"]
Output true
true
false
false -
>, >=, <=, <
-
and
or
not
1
2
3jq '[true, false | not]'
Input null
Output [false, true]
Alternative operator: a // b
: 若a
不是false
或null
, outputa
; 否则输出b
; 等于是给定”默认值”
1 | jq '.foo // 42' |
这些可在jq play上直接看结果.
在local可以直接jq -n 'expr'
来测试query
Regular expressions (PCRE)
jq
支持完整的re
search: 采用与php, ruby, sublime
等相同的re
library: Oniguruma
RE操作:
1 | STRING | FILTER( REGEX ) |
- STRING为待match string, 作为input
- FILTER is one of:
- match: 找到match, 输出object
- test: Like
match
, but does not return match objects, onlytrue
orfalse
- capture: 保留capture name, 结果存到新object
FLAGS is a string consisting of one of more of the supported flags:
1 | g - Global search (find all matches, not just the first) |
测试一系列的match:
1 | jq '.[] | test("a b c # spaces are ignored"; "ix")' |
可以结合select
来正则匹配: select(.metadata.name | test("test-"))
JQ Output format
特殊的filter
, 一般在最终输出前做必要的formatting和encoding/decoding, 语法为@foo
, 有如下几种:
-
@text
: 实际调用tostring
-
@json
: serialize to json -
@html
: HTML escaping:<>&'"
map to<
,>
,&
,'
,"
. -
@uri
: percent-encoding -
@csv
,@tsv
: input必须为array
, 转为csv
,tsv
格式 -
@sh
: escape for POSIX shell -
@base64
: base64 encode;@base64d
: base64 decode
JQ, JSON path, Kubectl
JSON Path standard 提供了不同于jq
的query syntax, 两者的比较见JQ doc: For JSONPath users
Kubectl本身支持JSONPath
, 见官方doc, 但正如doc所说:
不支持 JSONPath 正则表达式。如需使用正则表达式进行匹配操作,您可以使用如 jq 之类的工具
1 | kubectl get pods -o json | jq -r '.items[] | select(.metadata.name | test("test-")).spec.containers[].image' |
用JID visualize field selection
Jid
使用场景: 生成JSON query statement: 一步步的从一个大json中选取deep nested field, 并能autocomplete.
安装jid
tool:
1 | go get -u github.com/simeji/jid/cmd/jid |
使用jid
非常简单:
1 | cat example.json | jid -q | xclip -selection c |
依次指定field来filter, tab
自动补全, 最后pipe到clipboard, ctrl+v
或xclip -selection c -o
即可得到生成的query: .store.book[1].price
结论
我们的主要目标是用query kubectl
的json
output, 一些建议:
- kubectl使用
--sort-by
指定排序规则(只能为integer
或string
) - 如果JSON结果大,复杂,先用
jid
获取jq
能直接使用的query path - 将
kubectl
的输出pipe到jq
中,filter, 转换,得到想要的结果