Shin x Blog

PHPをメインにWebシステムを開発してます。Webシステム開発チームの技術サポートも行っています。

Go で containerd を操作するチュートリアル

containerd のサイトに Go で containerd を操作するチュートリアルがあったのでやってみました。

containerd.io

チュートリアル

このチュートリアルでは、Docker Hub にある redis イメージを取得し、コンテナ(タスク)として実行するまでを実装します。

containerd はデーモンとして実行されており、そのクライアントを Go で実装するイメージです。containerd とクライアントは gRPC で通信しますが、このあたりの処理は containerd/containerd パッケージ内で行われるので実装する必要はありません。

サンプルコード

Vagrant で Debian 環境を構築して実装しました。Vagrantfile 含め、コードを下記にアップしています。

github.com

チュートリアルを試す上では下記が必要となります。

  • containerd
  • runc
  • Go

上記コードでは、vagrant up 時に自動でインストール、セットアップするようになっています。

利用方法

リポジトリからコードを取得して vagrant up するだけです。

$ git clone git@github.com:shin1x1/containerd-getting-started-sample.git
$ cd containerd-getting-started-sample
$ vagrant up

起動が完了すれば、containerd が起動している状態です。

あとは vagrant ssh でログインして、チュートリアルにあるコードを試していきます。

https://containerd.io/docs/getting-started/#connecting-to-containerd

Go コードもリポジトリに含めているので、下記の make コマンドでビルド、実行できます。

$ vagrant ssh
vagrant@contrib-buster:~$ cd /vagrant
vagrant@contrib-buster:/vagrant$ make
go build main.go
(snip)
sudo ./main
2020/06/09 10:34:23 Successfully pulled docker.io/library/redis:alpine image
2020/06/09 10:34:23 Successfully created container with ID redis-server and snapshot with ID redis-server-snapshot
1:C 09 Jun 2020 10:34:23.643 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 09 Jun 2020 10:34:23.647 # Redis version=6.0.4, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 09 Jun 2020 10:34:23.648 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 09 Jun 2020 10:34:23.671 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
1:M 09 Jun 2020 10:34:23.671 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
1:M 09 Jun 2020 10:34:23.671 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
1:M 09 Jun 2020 10:34:23.672 * Running mode=standalone, port=6379.
1:M 09 Jun 2020 10:34:23.673 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1:M 09 Jun 2020 10:34:23.673 # Server initialized
1:M 09 Jun 2020 10:34:23.673 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 09 Jun 2020 10:34:23.674 * Ready to accept connections
1:signal-handler (1591698866) Received SIGTERM scheduling shutdown...
1:M 09 Jun 2020 10:34:26.688 # User requested shutdown...
1:M 09 Jun 2020 10:34:26.688 * Saving the final RDB snapshot before exiting.
1:M 09 Jun 2020 10:34:26.689 * DB saved on disk
1:M 09 Jun 2020 10:34:26.689 # Redis is now ready to exit, bye bye...
redis-server exited with status: 0

注意点

  • runc

環境構築については上記サンプルコードにあるとおりなのですが、runc は自分で入れる必要があります。Debian buster ではパッケージがあるので、apt-get でインストールしました。

# github.com/containerd/containerd/images/archive
/home/vagrant/go/pkg/mod/github.com/containerd/containerd@v1.3.4/images/archive/reference.go:73:21: undefined: "github.com/docker/distribution/reference".ParseDockerRef
  • go mod

また、go mod で依存ライブラリを管理するとビルド時に下記エラーが発生しました。

containerd の issue にもこのエラーの報告があります。 https://github.com/containerd/containerd/issues/3031

コメントにあるとおり、go.mod ファイルで下記のように commit hash を指定することで解決しました。

 //github.com/docker/distribution v2.7.1+incompatible // indirect
    github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible // indirect

crt コマンド

containerd には crt というクライアントコマンドが同梱されています。Go コードを実装しなくても、このコマンドで containerd の機能が利用できます。 -n オプションで namespace を指定します。kubernetes のように namespace を分けることでコンテナやタスク等を分離できます。

# example ネームスペースのコンテナを取得
vagrant@contrib-buster:~$ sudo /opt/containerd/bin/ctr -n example c ls
CONTAINER    IMAGE    RUNTIME

# example ネームスペースのコンテナを削除
vagrant@contrib-buster:~$ sudo /opt/containerd/bin/ctr -n example c rm CONTAINER_NAME

# example ネームスペースのタスクを取得
vagrant@contrib-buster:~$ sudo /opt/containerd/bin/ctr -n example t ls
CONTAINER    IMAGE    RUNTIME

# example ネームスペースのタスクを停止
vagrant@contrib-buster:~$ sudo /opt/containerd/bin/ctr -n example t kill TASK_ID

実行ステップ

これまで漠然とイメージを取得してコンテナとして実行するという考えだったのですが、containerd をコードで操作することで実は下記のようなステップになっていることが分かりました。

Image を取得
↓
Image から OCI runtime spec と Snapshot を生成
↓
OCI runtime spec と Snapshot から Container を生成
↓
Container が Task を生成
↓
Task を開始

コンテナのイメージとして持っていた実行プロセスは、Task という用語で呼ばれています。コンテナ関連の記事で見かける時はあったのですが、いわるゆタスクだと思っていました。

さいごに

最近、Docker の仕組むを見たりしているので、その過程で containerd を触ってみました。操作してみると Docker のコアな機能は containerd(+ runtime) で実現されていることが実感できますね。