cri-apiをGoで使ってみる

この記事は[Kubernetes Advent Calendar 2019 その2](https://qiita.com/advent-calendar/2019/kubernetes2)の6日目の記事です。

[kubernets/cri-api](https://github.com/kubernetes/cri-api)をGoで使ってみたので、そのやり方を紹介したいと思います。

#cri-apiとは
`cri-api`はCRI（Container Runtime Interface）で規定されているI/Fの定義です。

KubernetesにはkubeletとHigh-LevelコンテナランタイムのI/Fを規定したCRIというものが存在します。

これはKubernetesに他のコンテナランタイムを組み込みやすくする事を目的に規定されているもので、CRIを満たしたコンテナランタイムであればKubernetsで動かす事ができます。

この`cri-api`はprotocol bufferとgRPCで構成されており、
今回はprotocol bufferを使用してGoで`cri-api`を使ってみてます。

# 試した環境
- macOS Mojave 10.14
- Go 1.13
- protoc 3.11.1

# protocol bufferのインストール
protocol bufferをインストールします。
macであればhomebrewでインストール可能です。

```
# インストール
brew update
brew install protobuf

# 確認
protoc --version #libprotc 3.11.1
```

# Goで実装
今回やってみたのはclientとserverのプログラムを実装して、
client->serverに`cri-api`で定義されている構造体を送って確認する、といったものになります。
(実装に当たっては[protocol buffers をGolangで使う](https://qiita.com/subaruf/items/deb6fbd53f63ca6a36b4)を参考にさせて頂いています。)

以下実装していきますが事前に
`go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`
しておきます。

まずはclientから実装していきます。
## clientの実装
```main.go
package main

import (
    "net"

    "github.com/golang/protobuf/proto"
    "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
 
   
)

func main() {
    // cri-apiに定義されているPodSandbox構造体
    pod := &v1alpha2.PodSandbox{
        Id: "1000",
        Metadata: &v1alpha2.PodSandboxMetadata{
            Name:      "FakePod",
            Uid:       "123456789",
            Namespace: "System",
            Attempt:   0,
        },
        State:     v1alpha2.PodSandboxState_SANDBOX_READY,
        CreatedAt: 1,
    }

    // シリアライズ化
    out, err := proto.Marshal(pod)
    if err != nil {
        panic(err)
    }
    conn, err := net.Dial("tcp", "localhost:9000")
    if err != nil {
        panic(err)
    }

    // serverにデータを送信
    conn.Write(out)
}
```

`cri-api`で定義されている`PodSandbox`構造体を作成。
構造体に適当な値を入れてシリアライズ化した後に`localhost:9000`宛に送信しています。
（`cri-api`は`PodSandbox`以外にも多くの定義がありますが今回は`PodSandbox`を使っています）

次にserverを実装します。

## serverの実装
```main.go
package main

import (
    "bytes"
    "io"
    "net"
    "os"

    "github.com/golang/protobuf/jsonpb"
    "github.com/golang/protobuf/proto"
    "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)

func main() {
    // localhost:9000で待ち受け
    ln, err := net.Listen("tcp", ":9000")
    if err != nil {
        panic(err)
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            panic(err)
        }
        go func() {
            // 受け取ったデータをbufにcopy
            var buf bytes.Buffer
            io.Copy(&buf, conn)

            // 受け取ったデータをPodSandbox型の変数に格納
            pod := &v1alpha2.PodSandbox{}
            if err := proto.Unmarshal(buf.Bytes(), pod); err != nil {
                panic(err)
            }

            // jsonに変換して標準出力に出力
            versionMarshal := jsonpb.Marshaler{Indent: "  "}
            versionMarshal.Marshal(os.Stdout, pod)
        }()
    }
}
```
clientから受け取ったデータを分かりやすいようにjsonに変換した上で標準出力に出力しています。

# 動作確認
事前に作成したserverのプログラムを起動して`localhost:9000`宛に通信ができるようにしておきます。
その上でclientプログラムを起動させると、以下のように表示されて`cri-api`で定義されている内容を利用してデータを受け取れている事が確認できます。

```
{
  "id": "1000",
  "metadata": {
    "name": "FakePod",
    "uid": "123456789",
    "namespace": "System"
  },
  "createdAt": "1"
}
```

# まとめ
`cri-api`で定義されている構造体を簡単ではありますが、Goで使ってみる事ができました。
何かの参考になれば幸いです。