jqで入れ子の配列を展開して複数のオブジェクトにする


jqで以下のことをやりたくて、ちょっと悩んだ末にできたのでメモ。

やりたかったこと

以下のような配列を含む1つのオブジェクトを、

sample.json(配列を含む1つのオブジェクト)
{
  "pod": {
    "name": "mypod"
  },
  "containers": [
    {
      "name": "mycontainer1",
      "usage": {
            "cpu": "50"
          }
    },
    {
      "name": "mycontainer2",
      "usage": {
            "cpu": "100"
          }
    }
  ]
}

以下のような複数のオブジェクトにしたい。

(複数のオブジェクト)
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "100"
}

やり方

jq ManualのObject Constructionのところに解説があるが、新たなオブジェクトを作り、このときにcontainers配列の皮むき([])を行うことで、別々のオブジェクトに展開できる。

入れ子の配列の展開

コマンド
cat sample.json | \
  jq '{
        pod,
        container: .containers[]
      }'
実行結果(複数のオブジェクト)
{
  "pod": {
    "name": "mypod"
  },
  "container": {
    "name": "mycontainer1",
    "usage": {
      "cpu": "50"
    }
  }
}
{
  "pod": {
    "name": "mypod"
  },
  "container": {
    "name": "mycontainer2",
    "usage": {
      "cpu": "100"
    }
  }
}

(補足)podのところは省略した形式で、元のフィールドをそのまま渡しているが、ちゃんと書くこともできる。

cat sample.json | \
  jq '{
        pod: { name: .pod.name },
        container: .containers[]
      }'

オブジェクトの生成

展開した結果から欲しい情報をピックアップして新たなオブジェクトにする。

コマンド
cat sample.json | \
  jq '{
        pod,
        container: .containers[]
      } |
      {
        pod_name: .pod.name,
        container_name: .container.name,
        container_cpu: .container.usage.cpu
      }'
実行結果(複数のオブジェクト)
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "100"
}

注意点

中間オブジェクトを経由せずにオブジェクトをつくると結果がかけ算になりオブジェクトの数が多くなってしまう。

コマンド
cat sample.json | \
  jq '{
        pod_name: .pod.name,
        container_name: .containers[].name,
        container_cpu: .containers[].usage.cpu
       }'
実行結果
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "100"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "100"
}

中間オブジェクトを経由せずに配列をつくると元々の配列の要素がそのまま並んでしまう。

コマンド
cat sample.json | \
  jq '[
        .pod.name,
        .containers[].name,
        .containers[].usage.cpu
       ]'
実行結果
[
  "mypod",
  "mycontainer1",
  "mycontainer2",
  "50",
  "100"
]

おまけ

もともとやりたかったことは、KubernetesのMetric APIから以下のようなjsonが返ってくるので、これをcsvにすること。以下はIBM Cloud Private v3.1.2の場合。

{
  "kind": "PodMetricsList",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/metrics.k8s.io/v1beta1/pods"
  },
  "items": [
    {
      "metadata": {
        "name": "audit-logging-fluentd-ds-pgp88",
        "namespace": "kube-system",
        "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/audit-logging-fluentd-ds-pgp88",
        "creationTimestamp": "2019-04-10T01:22:42Z"
      },
      "timestamp": "2019-04-10T01:22:17Z",
      "window": "30s",
      "containers": [
        {
          "name": "fluentd",
          "usage": {
            "cpu": "2513099n",
            "memory": "62276Ki"
          }
        }
      ]
    },

(以下略)

トークンの取得

cloudctl loginした後、cloudclt tokensでトークンを取得する。

ID_TOKEN=$(LANG=C cloudctl tokens | grep "ID token:" | awk '{print $3}')

Metric APIからのデータの取得

curlでjsonデータを取得して変数に入れる。

CLUSTER="mycluster.icp"
JSON=$(curl -s -k -H "Authorization: Bearer ${ID_TOKEN}" \
  "https://${CLUSTER}:8001/apis/metrics.k8s.io/v1beta1/pods" \
  | jq -c '.')

jqを使ってjsonをcsvに変換

jqを使って配列を複数のオブジェクトに展開した中間データを作成し、そこから欲しいデータをピックアップしてそれぞれを配列にし、@csvを使ってcsvにする。ついでにrtrimstr(str)関数を使って単位を除く。

コマンド
echo $JSON | \
  jq -r '.items[] |
          {
            metadata,
            timestamp,
            container: .containers[]
          } |
          [
            .timestamp,
            .metadata.namespace,
            .metadata.name,
            .container.name,
            ( .container.usage.cpu | rtrimstr("n") ),
            ( .container.usage.memory | rtrimstr("Ki") )
          ] | @csv'
実行結果
"2019-04-10T02:35:25Z","kube-system","monitoring-prometheus-collectdexporter-57b8c6ff5c-dclw4","collectd-exporter","1488183","11416"
"2019-04-10T02:35:25Z","kube-system","monitoring-prometheus-collectdexporter-57b8c6ff5c-dclw4","router","54112","4532"
"2019-04-10T02:35:09Z","kube-system","logging-elk-filebeat-ds-2x49r","filebeat","4745057","28692"
"2019-04-10T02:35:20Z","kube-system","monitoring-prometheus-elasticsearchexporter-7f5967f56-d68sd","router","12534","8312"
"2019-04-10T02:35:20Z","kube-system","monitoring-prometheus-elasticsearchexporter-7f5967f56-d68sd","elasticsearchexporter","3207930","13132"
"2019-04-10T02:35:10Z","kube-system","k8s-etcd-9.188.124.130","etcd","50751515","261432"

(以下略)