containerd コンテナランタイムで情報収集

# はじめに
先日 [An experiment of how to detect OOMKilled events on Container-Optimized OS with containerd](https://medium.com/zeals-tech-blog/an-experiment-of-how-to-detect-oomkilled-events-on-container-optimized-os-with-containerd-a7e036a1b2f9) というタイトルでテックブログを執筆しました。ここでは必要最低限度の情報を取得するための方法だけ簡単に紹介しましたが、このブログを書くにあたって試行錯誤していたことを備忘録的に残しておきます。

# 環境
この実験に使用した環境は以下の通りです。

| 項目名 | 値 |
| --- | --- |
| GKE node version | v1.18.20-gke.901 |
| OS Image | Container-Optimized OS from Google |
| Kernel version | 5.4.109+ |
| Container runtime | containerd://1.4.3 |

# 準備
テックブログと同じように `default` ネームスペースに `nginx` をデプロイしておきます。この nginx にはノード上のソケットファイル `/run/containerd/containerd.sock` をマウントしております。

```bash
kubectl apply -f k8s/nginx.yaml
kubectl get pods
```
```:コマンドの結果
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7d87f6d94b-4d7bm   1/1     Running   0          3h54m
```

なおマニフェストファイルは[こちら](https://github.com/takkeybook/testkit_for_detecting_oomkill)から入手可能です。

# 情報収集
## コンテナに設定されているラベル
コンテナオブジェクトにはラベル情報を返す関数 `Labels` が用意されています（関数定義は[こちら](https://github.com/containerd/containerd/blob/45e0e5a77ef71d1e8afe7670d31d11b72269b9a6/container.go#L131-L137)を参照）。これを使ってラベル情報を取得してみましょう。

```go:label.go
package main
  
import (
    "context"
    "log"
    "fmt"
    "encoding/json"

    "github.com/containerd/containerd"
)

const (
    address = "/run/containerd/containerd.sock"
    namespace = "k8s.io"
)

func main() {
    client, err := containerd.New(
        address,
        containerd.WithDefaultNamespace(namespace),
    )

    if err != nil {
        log.Fatal(err)
    }

    defer client.Close()

    ctx := context.Background()

    containers, _ := client.Containers(ctx)
    for _, c := range containers {
        c_label, _ := c.Labels(ctx)
        label, _ := json.Marshal(c_label)

        fmt.Printf("%s\n", string(label))
    }
}
```

```bash
GOOS=linux GOARCH=amd64 go build label.go
kubectl cp label $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}'):/root/label
kubectl exec $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}') -- /root/label > label.json
jq '. | if ."io.kubernetes.pod.name" == "nginx-deployment-7d87f6d94b-4d7bm" then . else empty end' label.json
```

nginx に関連のあるものだけ表示すると以下のような結果を得ます。

```json
{
  "io.cri-containerd.kind": "container",
  "io.kubernetes.container.name": "nginx",
  "io.kubernetes.pod.name": "nginx-deployment-7d87f6d94b-4d7bm",
  "io.kubernetes.pod.namespace": "default",
  "io.kubernetes.pod.uid": "1edc1c49-ab0a-4b99-b935-21a912624c2d"
}
{
  "app": "nginx",
  "io.cri-containerd.kind": "sandbox",
  "io.kubernetes.pod.name": "nginx-deployment-7d87f6d94b-4d7bm",
  "io.kubernetes.pod.namespace": "default",
  "io.kubernetes.pod.uid": "1edc1c49-ab0a-4b99-b935-21a912624c2d",
  "pod-template-hash": "7d87f6d94b"
}
```

## ランタイム仕様情報
こちらもランタイム仕様情報を返す関数 `Spec` が用意されています(関数定義は[こちら](https://github.com/containerd/containerd/blob/45e0e5a77ef71d1e8afe7670d31d11b72269b9a6/container.go#L160-L170))。これを使ってランタイム仕様情報を取得してみましょう。呼ぶ関数が異なるだけで、基本的にやってることは同じです。

```go:spec.go
package main
  
import (
    "context"
    "log"
    "fmt"
    "encoding/json"

    "github.com/containerd/containerd"
)

const (
    address = "/run/containerd/containerd.sock"
    namespace = "k8s.io"
)

func main() {
    client, err := containerd.New(
        address,
        containerd.WithDefaultNamespace(namespace),
    )

    if err != nil {
        log.Fatal(err)
    }

    defer client.Close()

    ctx := context.Background()

    containers, _ := client.Containers(ctx)
    for _, c := range containers {
        c_spec, _ := c.Spec(ctx)
        spec, _ := json.Marshal(c_spec)

        fmt.Printf("%s\n", string(spec))
    }
}
```

```bash
GOOS=linux GOARCH=amd64 go build spec.go
kubectl cp spec $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}'):/root/spec
kubectl exec $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}') -- /root/spec > spec.json
jq '. | if .annotations."io.kubernetes.cri.container-name" == "nginx" then . else empty end' spec.json
```

```json
{
  "ociVersion": "1.0.2-dev",
  "process": {
    "user": {
      "uid": 0,
      "gid": 0
    },
    "args": [
      "/docker-entrypoint.sh",
      "nginx",
      "-g",
      "daemon off;"
    ],
    "env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "HOSTNAME=nginx-deployment-7d87f6d94b-4d7bm",
      "NGINX_VERSION=1.21.1",
      "NJS_VERSION=0.6.1",
      "PKG_RELEASE=1~buster",
      "KUBERNETES_PORT_443_TCP=tcp://10.4.0.1:443",
      "KUBERNETES_PORT_443_TCP_PROTO=tcp",
      "KUBERNETES_PORT_443_TCP_PORT=443",
      "KUBERNETES_PORT_443_TCP_ADDR=10.4.0.1",
      "KUBERNETES_SERVICE_HOST=10.4.0.1",
      "KUBERNETES_SERVICE_PORT=443",
      "KUBERNETES_SERVICE_PORT_HTTPS=443",
      "KUBERNETES_PORT=tcp://10.4.0.1:443"
    ],
    "cwd": "/",
    "capabilities": {
      "bounding": [
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FSETID",
        "CAP_FOWNER",
        "CAP_MKNOD",
        "CAP_NET_RAW",
        "CAP_SETGID",
        "CAP_SETUID",
        "CAP_SETFCAP",
        "CAP_SETPCAP",
        "CAP_NET_BIND_SERVICE",
        "CAP_SYS_CHROOT",
        "CAP_KILL",
        "CAP_AUDIT_WRITE"
      ],
      "effective": [
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FSETID",
        "CAP_FOWNER",
        "CAP_MKNOD",
        "CAP_NET_RAW",
        "CAP_SETGID",
        "CAP_SETUID",
        "CAP_SETFCAP",
        "CAP_SETPCAP",
        "CAP_NET_BIND_SERVICE",
        "CAP_SYS_CHROOT",
        "CAP_KILL",
        "CAP_AUDIT_WRITE"
      ],
      "inheritable": [
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FSETID",
        "CAP_FOWNER",
        "CAP_MKNOD",
        "CAP_NET_RAW",
        "CAP_SETGID",
        "CAP_SETUID",
        "CAP_SETFCAP",
        "CAP_SETPCAP",
        "CAP_NET_BIND_SERVICE",
        "CAP_SYS_CHROOT",
        "CAP_KILL",
        "CAP_AUDIT_WRITE"
      ],
      "permitted": [
        "CAP_CHOWN",
        "CAP_DAC_OVERRIDE",
        "CAP_FSETID",
        "CAP_FOWNER",
        "CAP_MKNOD",
        "CAP_NET_RAW",
        "CAP_SETGID",
        "CAP_SETUID",
        "CAP_SETFCAP",
        "CAP_SETPCAP",
        "CAP_NET_BIND_SERVICE",
        "CAP_SYS_CHROOT",
        "CAP_KILL",
        "CAP_AUDIT_WRITE"
      ]
    },
    "apparmorProfile": "cri-containerd.apparmor.d",
    "oomScoreAdj": 1000
  },
  "root": {
    "path": "rootfs"
  },
  "mounts": [
    {
      "destination": "/proc",
      "type": "proc",
      "source": "proc",
      "options": [
        "nosuid",
        "noexec",
        "nodev"
      ]
    },
    {
      "destination": "/dev",
      "type": "tmpfs",
      "source": "tmpfs",
      "options": [
        "nosuid",
        "strictatime",
        "mode=755",
        "size=65536k"
      ]
    },
    {
      "destination": "/dev/pts",
      "type": "devpts",
      "source": "devpts",
      "options": [
        "nosuid",
        "noexec",
        "newinstance",
        "ptmxmode=0666",
        "mode=0620",
        "gid=5"
      ]
    },
    {
      "destination": "/dev/mqueue",
      "type": "mqueue",
      "source": "mqueue",
      "options": [
        "nosuid",
        "noexec",
        "nodev"
      ]
    },
    {
      "destination": "/sys",
      "type": "sysfs",
      "source": "sysfs",
      "options": [
        "nosuid",
        "noexec",
        "nodev",
        "ro"
      ]
    },
    {
      "destination": "/sys/fs/cgroup",
      "type": "cgroup",
      "source": "cgroup",
      "options": [
        "nosuid",
        "noexec",
        "nodev",
        "relatime",
        "ro"
      ]
    },
    {
      "destination": "/dev/shm",
      "type": "bind",
      "source": "/run/containerd/io.containerd.grpc.v1.cri/sandboxes/51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5/shm",
      "options": [
        "rbind",
        "rprivate",
        "rw"
      ]
    },
    {
      "destination": "/etc/hosts",
      "type": "bind",
      "source": "/var/lib/kubelet/pods/1edc1c49-ab0a-4b99-b935-21a912624c2d/etc-hosts",
      "options": [
        "rbind",
        "rprivate",
        "rw"
      ]
    },
    {
      "destination": "/dev/termination-log",
      "type": "bind",
      "source": "/var/lib/kubelet/pods/1edc1c49-ab0a-4b99-b935-21a912624c2d/containers/nginx/020ba2a4",
      "options": [
        "rbind",
        "rprivate",
        "rw"
      ]
    },
    {
      "destination": "/etc/hostname",
      "type": "bind",
      "source": "/var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5/hostname",
      "options": [
        "rbind",
        "rprivate",
        "rw"
      ]
    },
    {
      "destination": "/etc/resolv.conf",
      "type": "bind",
      "source": "/var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5/resolv.conf",
      "options": [
        "rbind",
        "rprivate",
        "rw"
      ]
    },
    {
      "destination": "/run/containerd/containerd.sock",
      "type": "bind",
      "source": "/run/containerd/containerd.sock",
      "options": [
        "rbind",
        "rprivate",
        "rw"
      ]
    },
    {
      "destination": "/var/run/secrets/kubernetes.io/serviceaccount",
      "type": "bind",
      "source": "/var/lib/kubelet/pods/1edc1c49-ab0a-4b99-b935-21a912624c2d/volumes/kubernetes.io~secret/default-token-snxmc",
      "options": [
        "rbind",
        "rprivate",
        "ro"
      ]
    }
  ],
  "annotations": {
    "io.kubernetes.cri.container-name": "nginx",
    "io.kubernetes.cri.container-type": "container",
    "io.kubernetes.cri.sandbox-id": "51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5"
  },
  "linux": {
    "resources": {
      "devices": [
        {
          "allow": false,
          "access": "rwm"
        }
      ],
      "memory": {},
      "cpu": {
        "shares": 2,
        "period": 100000
      }
    },
    "cgroupsPath": "/kubepods/besteffort/pod1edc1c49-ab0a-4b99-b935-21a912624c2d/287fc9c90bd187dee5a1628ac032f828fb99cc7e24c99eef72dcef27ce782b48",
    "namespaces": [
      {
        "type": "pid"
      },
      {
        "type": "ipc",
        "path": "/proc/15625/ns/ipc"
      },
      {
        "type": "uts",
        "path": "/proc/15625/ns/uts"
      },
      {
        "type": "mount"
      },
      {
        "type": "network",
        "path": "/proc/15625/ns/net"
      }
    ],
    "maskedPaths": [
      "/proc/acpi",
      "/proc/kcore",
      "/proc/keys",
      "/proc/latency_stats",
      "/proc/timer_list",
      "/proc/timer_stats",
      "/proc/sched_debug",
      "/proc/scsi",
      "/sys/firmware"
    ],
    "readonlyPaths": [
      "/proc/asound",
      "/proc/bus",
      "/proc/fs",
      "/proc/irq",
      "/proc/sys",
      "/proc/sysrq-trigger"
    ]
  }
}
```

## メタデータ情報
コンテナ型構造体には `Extensions` というメンバーが存在し、ここにはクライアントによって明示された情報が格納されております。これを取得するための関数として `Extensions` が用意されているので、これを使ってみましょう。ただしコンテナの情報がたくさん出てくると面倒なので、nginx の ID だけに絞っておきます（IDの値は予め調べておきます）。

```go:extensions1.go
package main
  
import (
    "context"
    "log"
    "fmt"
    "encoding/json"
    
    "github.com/containerd/containerd"
)

const (
    address = "/run/containerd/containerd.sock"
    namespace = "k8s.io"
)

func main() {
    client, err := containerd.New(
        address,
        containerd.WithDefaultNamespace(namespace),
    )

    if err != nil {
        log.Fatal(err)
    }

    defer client.Close()

    ctx := context.Background()

    containers, _ := client.Containers(ctx)
    for _, c := range containers {
        if c.ID() == "51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5" {
            ext, _ := c.Extensions(ctx)
            ext_metadata_json, _ := json.Marshal(ext)
            fmt.Printf("%s\n", string(ext_metadata_json))
        }
     }
}
```

```bash
GOOS=linux GOARCH=amd64 go build extensions1.go
kubectl cp extensions1 $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}'):/root/extensions1
kubectl exec $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}') -- /root/extensions1
```

```json
{
  "io.cri-containerd.sandbox.metadata": {
    "type_url": "github.com/containerd/cri/pkg/store/sandbox/Metadata",
    "value": "eyJWZXJzaW9uIjoidjEiLCJNZXRhZGF0YSI6eyJJRCI6IjUxYmQxYjNlMGU3YjM4OTUzNTE0ODJmZTkzZDE4MmNlNzFiYjdiN2YyNDViNTM2YmI4MGQ3NmZmMDliMzA0YjUiLCJOYW1lIjoibmdpbngtZGVwbG95bWVudC03ZDg3ZjZkOTRiLTRkN2JtX2RlZmF1bHRfMWVkYzFjNDktYWIwYS00Yjk5LWI5MzUtMjFhOTEyNjI0YzJkXzAiLCJDb25maWciOnsibWV0YWRhdGEiOnsibmFtZSI6Im5naW54LWRlcGxveW1lbnQtN2Q4N2Y2ZDk0Yi00ZDdibSIsInVpZCI6IjFlZGMxYzQ5LWFiMGEtNGI5OS1iOTM1LTIxYTkxMjYyNGMyZCIsIm5hbWVzcGFjZSI6ImRlZmF1bHQifSwiaG9zdG5hbWUiOiJuZ2lueC1kZXBsb3ltZW50LTdkODdmNmQ5NGItNGQ3Ym0iLCJsb2dfZGlyZWN0b3J5IjoiL3Zhci9sb2cvcG9kcy9kZWZhdWx0X25naW54LWRlcGxveW1lbnQtN2Q4N2Y2ZDk0Yi00ZDdibV8xZWRjMWM0OS1hYjBhLTRiOTktYjkzNS0yMWE5MTI2MjRjMmQiLCJkbnNfY29uZmlnIjp7InNlcnZlcnMiOlsiMTAuNC4wLjEwIl0sInNlYXJjaGVzIjpbImRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJzdmMuY2x1c3Rlci5sb2NhbCIsImNsdXN0ZXIubG9jYWwiLCJjLnplYWxzLWFuYWx5dGljcy1kZXYuaW50ZXJuYWwiLCJnb29nbGUuaW50ZXJuYWwiXSwib3B0aW9ucyI6WyJuZG90czo1Il19LCJwb3J0X21hcHBpbmdzIjpbeyJjb250YWluZXJfcG9ydCI6ODB9XSwibGFiZWxzIjp7ImFwcCI6Im5naW54IiwiaW8ua3ViZXJuZXRlcy5wb2QubmFtZSI6Im5naW54LWRlcGxveW1lbnQtN2Q4N2Y2ZDk0Yi00ZDdibSIsImlvLmt1YmVybmV0ZXMucG9kLm5hbWVzcGFjZSI6ImRlZmF1bHQiLCJpby5rdWJlcm5ldGVzLnBvZC51aWQiOiIxZWRjMWM0OS1hYjBhLTRiOTktYjkzNS0yMWE5MTI2MjRjMmQiLCJwb2QtdGVtcGxhdGUtaGFzaCI6IjdkODdmNmQ5NGIifSwiYW5ub3RhdGlvbnMiOnsia3ViZXJuZXRlcy5pby9jb25maWcuc2VlbiI6IjIwMjEtMDktMjRUMDQ6Mzg6MjEuMTk5OTU1ODM5WiIsImt1YmVybmV0ZXMuaW8vY29uZmlnLnNvdXJjZSI6ImFwaSJ9LCJsaW51eCI6eyJjZ3JvdXBfcGFyZW50IjoiL2t1YmVwb2RzL2Jlc3RlZmZvcnQvcG9kMWVkYzFjNDktYWIwYS00Yjk5LWI5MzUtMjFhOTEyNjI0YzJkIiwic2VjdXJpdHlfY29udGV4dCI6eyJuYW1lc3BhY2Vfb3B0aW9ucyI6eyJwaWQiOjF9LCJzZWNjb21wX3Byb2ZpbGVfcGF0aCI6InJ1bnRpbWUvZGVmYXVsdCJ9LCJzeXNjdGxzIjp7Im5ldC5jb3JlLnNvbWF4Y29ubiI6IjEwMjQiLCJuZXQuaXB2NC5jb25mLmFsbC5hY2NlcHRfcmVkaXJlY3RzIjoiMCIsIm5ldC5pcHY0LmNvbmYuYWxsLmZvcndhcmRpbmciOiIxIiwibmV0LmlwdjQuY29uZi5hbGwucm91dGVfbG9jYWxuZXQiOiIxIiwibmV0LmlwdjQuY29uZi5kZWZhdWx0LmZvcndhcmRpbmciOiIxIiwibmV0LmlwdjQuaXBfZm9yd2FyZCI6IjEiLCJuZXQuaXB2NC50Y3BfZmluX3RpbWVvdXQiOiI2MCIsIm5ldC5pcHY0LnRjcF9rZWVwYWxpdmVfaW50dmwiOiI2MCIsIm5ldC5pcHY0LnRjcF9rZWVwYWxpdmVfcHJvYmVzIjoiNSIsIm5ldC5pcHY0LnRjcF9rZWVwYWxpdmVfdGltZSI6IjMwMCIsIm5ldC5pcHY0LnRjcF9ybWVtIjoiNDA5NiA4NzM4MCA2MjkxNDU2IiwibmV0LmlwdjQudGNwX3N5bl9yZXRyaWVzIjoiNiIsIm5ldC5pcHY0LnRjcF90d19yZXVzZSI6IjAiLCJuZXQuaXB2NC50Y3Bfd21lbSI6IjQwOTYgMTYzODQgNDE5NDMwNCIsIm5ldC5pcHY0LnVkcF9ybWVtX21pbiI6IjQwOTYiLCJuZXQuaXB2NC51ZHBfd21lbV9taW4iOiI0MDk2IiwibmV0LmlwdjYuY29uZi5kZWZhdWx0LmFjY2VwdF9yYSI6IjAiLCJuZXQubmV0ZmlsdGVyLm5mX2Nvbm50cmFja19nZW5lcmljX3RpbWVvdXQiOiI2MDAiLCJuZXQubmV0ZmlsdGVyLm5mX2Nvbm50cmFja190Y3BfYmVfbGliZXJhbCI6IjEiLCJuZXQubmV0ZmlsdGVyLm5mX2Nvbm50cmFja190Y3BfdGltZW91dF9jbG9zZV93YWl0IjoiMzYwMCIsIm5ldC5uZXRmaWx0ZXIubmZfY29ubnRyYWNrX3RjcF90aW1lb3V0X2VzdGFibGlzaGVkIjoiODY0MDAifX19LCJOZXROU1BhdGgiOiIvdmFyL3J1bi9uZXRucy9jbmktODY0ZjllNTktYjAzYS0zMTRhLTUzNmUtNGQ0OTIwODU1YjVlIiwiSVAiOiIxMC4wLjEuMTQiLCJBZGRpdGlvbmFsSVBzIjpudWxsLCJSdW50aW1lSGFuZGxlciI6IiIsIkNOSVJlc3VsdCI6eyJJbnRlcmZhY2VzIjp7ImV0aDAiOnsiSVBDb25maWdzIjpbeyJJUCI6IjEwLjAuMS4xNCIsIkdhdGV3YXkiOiIxMC4wLjEuMSJ9XSwiTWFjIjoiYjI6MTc6OWI6ZWI6MDY6MGEiLCJTYW5kYm94IjoiL3Zhci9ydW4vbmV0bnMvY25pLTg2NGY5ZTU5LWIwM2EtMzE0YS01MzZlLTRkNDkyMDg1NWI1ZSJ9LCJsbyI6eyJJUENvbmZpZ3MiOlt7IklQIjoiMTI3LjAuMC4xIiwiR2F0ZXdheSI6IiJ9LHsiSVAiOiI6OjEiLCJHYXRld2F5IjoiIn1dLCJNYWMiOiIwMDowMDowMDowMDowMDowMCIsIlNhbmRib3giOiIvdmFyL3J1bi9uZXRucy9jbmktODY0ZjllNTktYjAzYS0zMTRhLTUzNmUtNGQ0OTIwODU1YjVlIn0sInZldGg1MDViMjE2NiI6eyJJUENvbmZpZ3MiOm51bGwsIk1hYyI6IjkyOjY3OjQxOjBlOjY3OjdjIiwiU2FuZGJveCI6IiJ9fSwiRE5TIjpbe30se31dLCJSb3V0ZXMiOlt7ImRzdCI6IjAuMC4wLjAvMCJ9XX0sIlByb2Nlc3NMYWJlbCI6IiJ9fQ=="
  }
}
```

Marshalize されているようなので、`typeurl.UnmarshalAny()` で Unmarshal できそうに思うのですが、ビルド時に以下のエラーメッセージが出て使えなさそうです。なぜかわかりません。

```
cannot use ext["io.cri-containerd.sandbox.metadata"] (type "github.com/gogo/protobuf/types".Any) as type *"github.com/gogo/protobuf/types".Any in argument to typeurl.UnmarshalAny
```

そこで以下のようにコードを修正してみます。

```go:extensions2.go
package main
  
import (
    "context"
    "log"
    "fmt"
    "encoding/json"

    "github.com/containerd/containerd"
    "github.com/containerd/cri/pkg/store/container"
)

const (
    address = "/run/containerd/containerd.sock"
    namespace = "k8s.io"
)

type Metadata struct {
    TypeUrl string `json:"type_url"`
    Value   []byte `json:"value"`
}

func main() {
    client, err := containerd.New(
        address,
        containerd.WithDefaultNamespace(namespace),
    )

    if err != nil {
        log.Fatal(err)
    }

    defer client.Close()

    ctx := context.Background()

    containers, _ := client.Containers(ctx)
    for _, c := range containers {
        if c.ID() == "51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5" {
            ext, _ := c.Extensions(ctx)
            ext_metadata := ext["io.cri-containerd.sandbox.metadata"]
            ext_metadata_json, _ := json.Marshal(ext_metadata)

            var m Metadata
            err := json.Unmarshal(ext_metadata_json, &m)
            if err != nil {
                log.Fatal(err)
            }
            if m.TypeUrl == "" {
                continue
            }

            var cri_metadata container.Metadata
            err = cri_metadata.UnmarshalJSON(m.Value)
            if err != nil {
                log.Fatal(err)
            }
            cri_md, _ := json.Marshal(cri_metadata)

            fmt.Printf("%s\n", string(cri_md))
        }
    }
}
```

```bash
GOOS=linux GOARCH=amd64 go build extensions2.go
kubectl cp extensions2 $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}'):/root/extensions2
kubectl exec $(kubectl get pods -l app=nginx -o jsonpath='{.items[*].metadata.name}') -- /root/extensions2 | jq '.'
```

```:コマンドの結果
{
  "ID": "51bd1b3e0e7b3895351482fe93d182ce71bb7b7f245b536bb80d76ff09b304b5",
  "Name": "nginx-deployment-7d87f6d94b-4d7bm_default_1edc1c49-ab0a-4b99-b935-21a912624c2d_0",
  "SandboxID": "",
  "Config": {
    "metadata": {
      "name": "nginx-deployment-7d87f6d94b-4d7bm"
    },
    "labels": {
      "app": "nginx",
      "io.kubernetes.pod.name": "nginx-deployment-7d87f6d94b-4d7bm",
      "io.kubernetes.pod.namespace": "default",
      "io.kubernetes.pod.uid": "1edc1c49-ab0a-4b99-b935-21a912624c2d",
      "pod-template-hash": "7d87f6d94b"
    },
    "annotations": {
      "kubernetes.io/config.seen": "2021-09-24T04:38:21.199955839Z",
      "kubernetes.io/config.source": "api"
    },
    "linux": {
      "security_context": {
        "namespace_options": {
          "pid": 1
        },
        "seccomp_profile_path": "runtime/default"
      }
    }
  },
  "ImageRef": "",
  "LogPath": "",
  "StopSignal": "",
  "ProcessLabel": ""
}
```