Kubernetes で高度な開発を進めていると、以下のようなシナリオに遭遇することがあります。
- 特定の Namespace 内にある全てのリソースを表示したい
- クラスタ内の Pod にどういったラベルが付いているのか知りたい
このような場合に、jq と yq の reduce
と from/to/with_entries
が使えます。
基本的な使い方
reduce
reduce
を使うと、配列を縮約して一つの値を計算できます。 reduce
の最初の引数には入力となる配列を指定します。
$ # jq
$ echo '[1, 2, 3]' | jq 'reduce .[] as $i (0; . + $i)'
6
yq
の場合は ireduce
を使います。また、キーワードの並びが jq
と異なります。
$ # yq
$ echo '[1, 2, 3]' | yq '.[] as $i ireduce(0; . + $i)'
6
from/to/with_entries
to_entries
はオブジェクトを key
と value
の配列に分解します。
$ # jq
$ # -c は compact
$ echo '{"a": 1, "b": 2}' | jq -c 'to_entries'
[{"key":"a","value":1},{"key":"b","value":2}]
$ # yq
$ # -P は pretty print
$ echo '{"a": 1, "b": 2}' | yq -P 'to_entries'
- key: a
value: 1
- key: b
value: 2
逆に、from_entries
は key
と value
の配列からオブジェクトを組み立てます。
$ # jq
$ echo '{"a": 1, "b": 2}' | jq -c 'to_entries | from_entries'
{"a":1,"b":2}
$ # yq
$ echo '{"a": 1, "b": 2}' | yq -P 'to_entries | from_entries'
a: 1
b: 2
with_entries
は、オブジェクトを key
と value
の配列に分解し、各要素を加工してオブジェクトを組み立て直します。 yq
ではフィールド名 .value
と演算子 *
の間にスペースが必要です。
$ # jq
$ echo '{"a": 1, "b": 2}' | jq -c 'with_entries(.value=.value*10)'
{"a":10,"b":20}
$ # yq
$ echo '{"a": 1, "b": 2}' | yq -P 'with_entries(.value = .value * 10)'
a: 10
b: 20
Namespace 内のリソースを全て表示する
kubectl get
では、リソースの種類を複数指定してオブジェクトを取得できます。
$ kubectl get deploy,daemonset -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/coredns 2/2 2 2 79s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/kindnet 4 4 4 4 4 <none> 76s
daemonset.apps/kube-proxy 4 4 4 4 4 kubernetes.io/os=linux 79s
そこで、kubectl api-resources --namespaced -o name
で Namespaced リソースの種類の一覧を取得し、paste
コマンドで連結すると全ての種類のリソースを取得できます。 macOS をお使いの場合は paste -sd, -
としてください。
$ # Ubuntu
$ kubectl api-resources --namespaced -o name | paste -sd,
bindings,componentstatuses,configmaps,endpoints,events,...
GET できないリソースに関するエラーを無視するため、末尾に 2>/dev/null
を追加します。
$ NAMESPACE=kube-system
$ kubectl get -n ${NAMESPACE} $(kubectl api-resources --namespaced -o name | paste -sd,) -o json 2>/dev/null
これで Namespace 内の全てのリソースのマニフェストが手に入りました。 kubectl get
のデフォルトの出力(上記参照)だと情報量が多いため、ここでは jq
か yq
で加工します。 reduce
を使って map に出力をマージします。
jq
の場合、{<kind>:{<name>:1}}
という形のオブジェクトを合成し、最後に name の辞書を with_entries(.value=(.value | keys))
でキーの配列に変換します。jq
で map のキーを計算する際は式を ()
に入れる必要があります。
yq
は *+
で配列のマージができるので、{<kind>:[<name>]}
という形のオブジェクトを *+
で合成します。
$ # jq
$ # -S は sort
$ NAMESPACE=kube-system
$ kubectl get -n ${NAMESPACE} $(kubectl api-resources --namespaced -o name | paste -sd,) -o json 2>/dev/null | jq -S 'reduce .items[] as $i ({}; . * {($i.kind): {($i.metadata.name): 1}}) | with_entries(.value=(.value | keys))'
$ # yq
$ NAMESPACE=kube-system
$ kubectl get -n ${NAMESPACE} $(kubectl api-resources --namespaced -o name | paste -sd,) -o yaml 2>/dev/null | yq '.items[] as $i ireduce({}; . *+ {$i.kind: [$i.metadata.name]}) | sortKeys(.)'
出力例
{
"ConfigMap": [
"coredns",
"extension-apiserver-authentication",
"kube-proxy",
"kube-root-ca.crt",
"kubeadm-config",
"kubelet-config"
],
"ControllerRevision": [
"kindnet-6f5f87b9f5",
"kube-proxy-b9c5d5dc4"
],
"DaemonSet": [
"kindnet",
"kube-proxy"
],
...
}
Pod に付けられたラベルの統計情報を取得する
同じような方法で、クラスタ内の Pod に付いているラベルの統計情報を取得できます。 まず、クラスタ内の全ての Pod のマニフェストを取得し、ラベルの key と value の配列を取得します。
jq
, yq
いずれの場合も、パイプライン全体を []
で囲うと出力を一つの配列にまとめられます。 こうすると後段の処理が書きやすくなります。
jq
の to_entries
は入力が null
の場合(ここでは、Pod にラベルが付いていない場合)にエラーとなるので、エラーを無視するため ?
を付けています。yq
の to_entries
は null
を自動的に無視します。
$ # jq
$ kubectl get po -A -o json | jq '[.items[].metadata.labels | to_entries? | .[]]'
$ # yq
$ kubectl get po -A -o yaml | yq '[.items[].metadata.labels | to_entries | .[]]'
出力例
[
{
"key": "k8s-app",
"value": "kube-dns"
},
{
"key": "pod-template-hash",
"value": "565d847f94"
},
...
]
ラベルのキーと値が全て取得できたので、reduce
で結果を整形します。
$ # jq
$ kubectl get po -A -o json | jq -S '[.items[].metadata.labels | to_entries? | .[]] | reduce .[] as $i ({}; . * {($i.key): {($i.value): 1}}) | with_entries(.value=(.value | keys))'
$ # yq
$ kubectl get po -A -o yaml | yq '[.items[].metadata.labels | to_entries | .[]] | .[] as $i ireduce({}; . * {$i.key: {$i.value: 1}}) | with_entries(.value = (.value | keys)) | sortKeys(.)'
出力例
{
"app": [
"kindnet",
"local-path-provisioner"
],
"component": [
"etcd",
"kube-apiserver",
"kube-controller-manager",
"kube-scheduler"
],
...
}
クエリを少し変えると、各ラベルに何種類の値が存在するかを調べられます。
$ # jq
$ kubectl get po -A -o json | jq -S '[.items[].metadata.labels | to_entries? | .[]] | reduce .[] as $i ({}; . * {($i.key): {($i.value): 1}}) | with_entries(.value=(.value | length))'
$ # yq
$ kubectl get po -A -o yaml | yq '[.items[].metadata.labels | to_entries | .[]] | .[] as $i ireduce({}; . * {$i.key: {$i.value: 1}}) | with_entries(.value=(.value | length))'
出力例
{
"app": 2,
"component": 4,
"controller-revision-hash": 2,
"k8s-app": 3,
"pod-template-generation": 1,
"pod-template-hash": 2,
"tier": 2
}
@tsv
を使うと、結果をテーブル形式で出力できます。
$ # jq
$ kubectl get po -A -o json | jq -r '[.items[].metadata.labels | to_entries? | .[]] | reduce .[] as $i ({}; . * {($i.key): {($i.value): 1}}) | with_entries(.value=(.value | keys | join(","))) | to_entries | .[] | [.key,.value] | @tsv' | column -t | sort
$ # yq
$ kubectl get po -A -o json | yq '[.items[].metadata.labels | to_entries | .[]] | .[] as $i ireduce({}; . * {$i.key: {$i.value: 1}}) | with_entries(.value=(.value | keys | join(","))) | to_entries | .[] | [.key,.value] | @tsv' | column -t | sort
出力例
app kindnet,local-path-provisioner
component etcd,kube-apiserver,kube-controller-manager,kube-scheduler
controller-revision-hash 5b547684d9,6bc6858f58
k8s-app kindnet,kube-dns,kube-proxy
pod-template-generation 1
pod-template-hash 547f784dff,558bd4d5db
tier control-plane,node
まとめ
ここでは reduce
, from/to/with_entries
の基本的な使い方と、応用例を 2 つ紹介しました。 この組み合わせは、リソースの種類やインスタンスを横断してクラスタの内容を掴みたいときに絶大な威力を発揮します! ぜひぜひ日々の運用にご活用ください。
参考資料
-
yq コマンドで Kubernetes の YAML を操作する
https://yokaze.github.io/2021/04/17/ -
jq Manual
https://stedolan.github.io/jq/manual/