コンテナ内でcontainerdを動かす

この記事は[Docker Advent Calendar 2019](https://qiita.com/advent-calendar/2019/docker)の21日目の記事です。

Dockerが内部で使用しているHigh-levelランタイムの`containerd`をコンテナ内で起動して動かしてみた話です。

コンテナ内で動かす方法に関しては[containerdのリポジトリ](https://github.com/containerd/containerd/blob/bb9616ba206c0d39f551e2bb54ec34ca21aea652/BUILDING.md)にそのやり方が記載されていますが、その通りにやっても私の環境では一部上手くいかなかったのでその解決策も含めて紹介できればと思います。

# 環境
- OS: ubuntu 16.04
- golang: go1.13
- Docker: 19.03.5
- containerd: v1.3.0-203-g97712c8a 97712c8ad73dd302d5d226e0384b2d86e5de2989

# ソースの取得
`containerd`を動かすに当たってソースからバイナリを作成するのでローカルにソースを取得しておきます。
`contaierd`の他にlow-levelランタイムである`runc`も今回は一緒にビルドするので合わせて取得します。

取得方法は`go get -u github.com/containerd/containerd`と`go get -u github.com/opencontainers/runc`を実行すればOKです。

# Dockerイメージの作成
ソースの取得が終わったら次に`containerd`を動かすコンテナのDockerイメージを作成します。
`containerd`はGo言語を使用して作られているので、Dockerイメージのgolangをベースに、ビルドするのに必要なモジュールをインストールしていきます。
必要なモジュールはB-Tree FileSystem用の`btrfs-tools`とSeccomp用の`libseccomp-dev`の二つです。

```dockerfile:Dockerfile
FROM golang:latest

RUN apt-get update \
    && apt-get install -y btrfs-tools libseccomp-dev
```
あとは`docker build -t containerd/build .`でDockerイメージを作成すればコンテナ用のDockerイメージの準備は完了です。

# コンテナの起動
上記で作成したDockerイメージを使ってコンテナを起動します。

```shell
docker run -it --privileged \
    -v /var/lib/containerd \
    -v ${GOPATH}/src/github.com/opencontainers/runc:/go/src/github.com/opencontainers/runc \
    -v ${GOPATH}/src/github.com/containerd/containerd:/go/src/github.com/containerd/containerd \
    -e GOPATH=/go \
    -w /go/src/github.com/containerd/containerd containerd/build bash
```
`containerd`の起動には特権が必要になるので`--privileged`オプションをつけ、起動に必要なフォルダと上記で取得したソースをマウントした上で起動しています。
そのほかのオプションは`GOPATH`の設定とコンテナ起動時のディレクトリを指定しています。
公式のやり方では`bash`ではなく`sh`を起動しているのですが、個人的に`bash`の方が慣れているので変えています。

# ソースのビルド
次はコンテナ内で`containerd`と`runc`をビルドしていきます。
それぞれ`Makefile`が用意されているのでそれを利用します。

```shell
// containerdのフォルダに移動。
// コンテナ起動時にこのフォルダがカレントディレクトリになっているはずなので実行しなくても大丈夫
$ cd /go/src/github.com/containerd/containerd 
$ make && make install

// エラー!
vendor/github.com/containerd/btrfs/btrfs.go:38:25: fatal error: btrfs/ioctl.h: No such file or directory
#include <btrfs/ioctl.h>
^
compilation terminated.
make: *** [bin/containerd] Erreur 2
```
実はこの状態でビルドするとエラーとなります。`containerd`が依存している`contaienrd/btrfs`というモジュールがあり、そのモジュールから`btrfs`がないと言われるのが原因です。

Dockerイメージを作る際に`btrfs-tools`をインストールしましたが、どうもこれでは駄目なようで`btrfs-tools`の代わりに`libbtrfs-dev`をインストールすればビルドが通りました。

```Dockerfile:Dockerfile
FROM golang:latest

RUN apt-get update \
    && apt-get install -y libbtrfs-dev libseccomp-dev
```
公式に記載されているやり方と異なるので問題がある可能性もありますが、一旦これでDockerイメージを作成しなおしてコンテナを起動すればビルドは通るようになります。

あとは`containerd`と同様に`runc`もビルドしてやれば準備完了です。

```shell
cd /go/src/github.com/opencontainers/runc
make BUILDTAGS='seccomp apparmor' && make install
```

# containerdの起動
`make install`した段階で`containerd`のバイナリが`PATH`の通っているディレクトリに配置されますのでそれを起動すれば`containerd`が動きます。

```shell
$ containerd // バックグラウンドで起動したい場合は containerd &

$ INFO[2019-12-21T13:56:32.716026618Z] starting containerd ...
$ INFO[2019-12-21T13:56:32.760340925Z] loading plugin ...
// 以下ログが出力される
```
これでやっと`containerd`が動かせました!
あとは好きなようにいじりたおせばOKですね。

# まとめ
簡単ですがコンテナ内で`containerd`を動かすやり方を紹介させてもらいました。

そもそも何故動かそうと思ったかというと、個人的にコンテナランタイムの開発に挑戦していて`Docker`で動かしてみたら上手く動かず原因を探っていくと`containerd`とのやりとりに問題がありそうだという事が分かったので、`containerd`を動かして見ようと思ったのがきっかけでした。

ただ起動するだけでなく構成を記述した設定ファイルを渡す事もできるようなので、その辺りも公式に情報があるのでよければそちらも見てみて下さい。

# 参考
[containerd - Build](https://github.com/containerd/containerd/blob/bb9616ba206c0d39f551e2bb54ec34ca21aea652/BUILDING.md)
[Getting started with containerd](https://containerd.io/docs/getting-started/)