Docker+Unikernelを触ってみた

# はじめに

先日Docker+UnikernelのオンラインMeetupを見たので([視聴メモ](https://github.com/Unikernel-Systems/DockerConEU2015-demo))、Docker+Unikernelを試してみました。
現状簡単に試せるものではないようなので、まずは上記のMeetupのデモ(元はDockerConEU2015で行われたデモみたいです)を動かしてみようと思います。本当はもう少し応用させようと思ったんですが、めちゃくちゃ苦戦してしまったのでデモを動かすだけのポストですm(_ _)m

# 動かしてみた

物理マシン上で実行するべきなのかと思いますが、手元のマシンはキレイにしておきたいのでDigitalOcean上のVMで行います。DockerとKVMが動くLinuxマシンであればどこでも動くのでその他の環境でも問題ないかと思います。これを機にDigitalOceanを始めますって人がいたら[こちら](https://www.digitalocean.com/?refcode=7be3336a0400)から申し込んでもらえるとお互いハッピーなはずです(多分ｗ)。

## ホストの用意

まずはホスト用のマシンを準備します。[docker-machine](https://github.com/docker/machine)で立ち上げると最初からdockerも入るので楽だと思います。docker-machineは上記のレポジトリのリリースからダウンロードしたものにPATHを通すだけでOKです。

```bash:docker-machineでVMを作成
docker-machine create -d digitalocean --digitalocean-image "ubuntu-15-10-x64" --digitalocean-region "sfo1" --digitalocean-size "32gb" --digitalocean-access-token ${DIGITALOCEAN_API_TOKEN} unikernel-demo
```

DigitalOceanのAPI Token(上記の${DIGITALOCEAN_API_TOKEN})とvmの名前(上記のunikernel-demo)は適宜変更してください。

上記はメモリ32GBのインスタンスを使っていますが、もっと安いインスタンスで大丈夫です。今回はあとでカーネルのビルド(一応省略可)を行うので、早く終わるよう大きめにしました(でも多分大きすぎたｗ)。

また、ubuntu以外のディストリビューションでもOKですが、debian系じゃないとあとで必要となる`makefs`を手動ビルドする必要が出てきます。

次に今作ったVMにsshでアクセスします。

```bash:作ったVMにsshでアクセス
docker-machine ssh unikernel-demo
```

適当に作業ディレクトリを作って、デモのコードをGitHubからcloneします。

```bash:作業ディレクトリの作成と必要なコードのclone
mkdir -p ~/work/{unikernel,kernel}
cd ~/work/unikernel
git clone https://github.com/Unikernel-Systems/DockerConEU2015-demo.git
```

## Kernelのビルド(省略可)

デモのコードにはKernel4.1.13用のパッチが含まれているので、Kernel4.1.13のビルドとパッチ当てを行います。このパッチは一つのホスト上で複数のUnikernelのインスタンスを立てる場合に必要になるものです。これを入れない場合でも1つのUnikernelインスタンスのみのデモは動かすことができます。
また、パッチの中身は非常にシンプルなので、Kernel入れ替えるのが面倒な場合は他のKernel用のを用意しても良いかもしれません。

今回は4.1.13を使うので、まずはUbuntu用のKernelのソースのv4.1.13をチェックアウトします。

```bash:Kernel4.1.13のソースを入手
cd ~/work/kernel
git clone git://git.launchpad.net/~ubuntu-kernel-test/ubuntu/+source/linux/+git/mainline-crack
cd mainline-crack
git checkout v4.1.13
```

続いて必要なパッチをダウンロード・適用します。

```bash:Kernelパッチの適用
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.1.13-wily/0001-base-packaging.patch
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.1.13-wily/0002-debian-changelog.patch
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.1.13-wily/0003-configs-based-on-Ubuntu-4.1.0-3.3.patch
patch -p1 < 0001-base-packaging.patch
patch -p1 < 0002-debian-changelog.patch
patch -p1 < 0003-configs-based-on-Ubuntu-4.1.0-3.3.patch
```

続いて今回のデモで必要なパッチを当てます。

```bash:デモ用のパッチを適用
patch -p1 drivers/net/macvtap.c < ~/work/unikernel/DockerConEU2015-demo/macvtap.patch
```

次にKernelのビルドに必要なパッケージをインストールします。

```bash:Kernelビルドに必要なパケージのインストール
apt-get fakeroot build-dep linux-image-`uname -r`
```

以上で準備が完了なので、実行権限を与えてビルドします。(めちゃくちゃ時間がかかります。)

```bash:Kernelのビルド
chmod a+x debian/rules
fakeroot debian/rules clean
DEB_BUILD_OPTIONS=parallel=12 fakeroot debian/rules binary-headers binary-generic
```

ビルドが完了したら今ビルドしたKernelに入れ替えます。

```bash:ビルドしたkernelのインストール
cd ../
dpkg -i linux-headers-4.1.13-040113* linux-image-*
```

続いてデフォルトで起動するkernelを今インストールしたものに変更します。まず、`/boot/grub/grub.cfg`のmenuentryから4.1.13になっている箇所の文字列を`/etc/default/grub`のGRUB_DEFAULTに記載します。今回は以下のようになりました。

```bash:/etc/defau/grubのGRUB_DEFAULT
GRUB_DEFAULT="gnulinux-advanced-a20e3e28-89be-4eb7-b0aa-a442c730eab0>gnulinux-4.1.13-040113-generic-advanced-a20e3e28-89be-4eb7-b0aa-a442c730eab0"
```
次に`update-grub`をして`reboot`します。再起動したら`uname -r`で正しいバージョンになっているか確認してください。
念の為`apt-get update`と`apt-get dist-upgrade`も実行しました。

## Unikernelを動かす

ということで、やっと動かせます。

まず今回のデモで必要なパッケージをインストールします。

```bash:今回のデモで必要なパッケージをインストール
apt-get install genisoimage makefs kvm make binutils
```

cloneしたディレクトリに移動して、`make pull`を実行します。

```bash:
cd ~/work/unikernel/DockerConEU2015-demo
make pull
```

するとエラーになってしまいました。dockerのstatusを見るとなんと起動していません。。。

```bash:dockerが起動していない
root@unikernel-demo:~# systemctl status docker
● docker.service
   Loaded: loaded (/etc/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Tue 2016-02-16 01:08:55 EST; 2s ago
  Process: 2242 ExecStart=/usr/bin/docker daemon -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver aufs --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=digitalocean (code=exited, status=1/FAILURE)
 Main PID: 2242 (code=exited, status=1/FAILURE)

Feb 16 01:08:55 unikernel-demo systemd[1]: Started docker.service.
Feb 16 01:08:55 unikernel-demo docker[2242]: time="2016-02-16T01:08:55.321222277-05:00" level=fatal msg="Error starting daemon: error initializing graphdriver: driver not supported"
Feb 16 01:08:55 unikernel-demo systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE
Feb 16 01:08:55 unikernel-demo systemd[1]: docker.service: Unit entered failed state.
Feb 16 01:08:55 unikernel-demo systemd[1]: docker.service: Failed with result 'exit-code'.
```

色々調べたら、どうやらKernelビルド時にaufsを含める必要があったようですが抜けていたようです。
もう一度ビルドするのは時間がもったいないので、今回はaufsではなくoverlayを使います。
ということで、`/etc/systemd/system/docker.service`を開いて`storage-driver`が`aufs`になっているところを`overlay`に変更してdockerのサービスを立ち上げ直します。

再び`make pull`すると無事実行できました。
Makefileを見ると分かりますが、https://hub.docker.com/r/mato/rumprun-packages-hw-x86_64/ をpullしているようです。中身はnginx, mysql, phpのrumprunのイメージを落としてくるようです。ちなみにrumprunはNetBSDベースのUnikernelの実装の一つです。[こちらのwiki](https://github.com/rumpkernel/wiki/wiki/Howto:-Using-prebuilt-Rumprun-toolchains-with-Docker)によるとrumprun自体はLinuxに限らず動くようですが、今回は後で実行するMakefileの中で使っているツールがLinuxのものなのでLinux限定になっているようです。

以下を実行するとmysql、nginx、phpそれぞれのunikernelのビルドおよび名前解決用のコンテナが立ち上がります。

```bash:
make
make rundns
```

続いてnginxのunikernelを立ち上げます。

```bash:nginxのunikernelを起動
./docker-unikernel run -P --hostname nginx unikernel/nginx
```

この時点でnginxのunikernelが立ち上がっているので`doker ps`で確認して

![Screenshot from 2016-02-15 22-57-20.png](https://qiita-image-store.s3.amazonaws.com/0/36003/128d706e-97d7-5ef2-20be-772593948d2c.png)

上記のポートにブラウザからアクセスすると以下のような画面が表示されます。

![Screenshot from 2016-02-15 22-58-16.png](https://qiita-image-store.s3.amazonaws.com/0/36003/7f765f92-520e-7a8a-540e-0c09cec742b5.png)

アクセスが確認できたら`docker rm -f adoring_dijkstra`でnginxのインスタンスは削除してしまいましょう。

ちなみに、Nginxのインスタンスのサイズを見ると以下の通り、OS込みで2.1MBと驚くほど小さいことが分かります。

```bash:nginxのunikernelのサイズ
docker run --rm -i -t unikernel/nginx du -h nginx.bin.bz2
2.1M    nginx.bin.bz2
```

ちなみに、起動も速いです(nginxだけじゃなくてこの後動かすphpもmysqlも)。

なお、ここまではUnikernelのインスタンスが1つなので、Kernelにパッチを当てていなくても実行できます。

続いて、mysql、nginx、phpで[nibbleblog](http://www.nibbleblog.com/)を立ち上げるデモを実行してみます。

まずはmysqlを立ち上げます。

```bash:mysqlのunikernelを立ち上げ
./docker-unikernel run --hostname mysql --name mysql unikernel/mysql
```

ちなみにmysql-clientを入れればmysqlに接続できます。

```bash:mysqlに接続
apt-get install mysql-client
mysql -h mysql -u rump
```

続いてnginxとphpを立ち上げます。

```bash:nginxとphpの立ち上げ
./docker-unikernel run --hostname php-blog --name php-blog unikernel/php-nibbleblog
./docker-unikernel run -P --hostname blog --name blog unikernel/nginx-nibbleblog
```

ちなみにKernelにパッチを当てていないとここでエラーになります。

docker psで確認して

![Screenshot from 2016-02-15 23-01-35.png](https://qiita-image-store.s3.amazonaws.com/0/36003/de4c4e72-f843-c9bc-ad28-a1f9363fca5c.png)

ブラウザでアクセスしてみると以下の通りnibbleblogが表示されました。

![Screenshot from 2016-02-15 23-02-11.png](https://qiita-image-store.s3.amazonaws.com/0/36003/e962461c-eb1c-856b-61e1-94a7c3cfea7d.png)

ということで一通り動かすことができました。

ぶっちゃけ、殆どUnikernel関係ないトラブルで時間を取られていましたｗ
Unikernelの理解はデモ動かすだけじゃなくて、中身ちゃんと追わないとダメっすねｗ

が、Unikernelは普通に使ったらメンドクセーんだろうなってことは分かったし、起動の速さやサイズの小ささ等のメリットも分かりました。これが今後dockerとの親和性が高まって、普通にコンテナを立ち上げる感覚で使えるようになったら流行りそうですね。