はじめてのRocket

# はじめてのRocket

先日、[CoreOS](https://coreos.com) が[Rocket](https://github.com/coreos/rocket/)というコンテナ実行エンジンを発表して話題になりますた。
Rocketを少し触ってみたので簡単なまとめを書いておこうと思います。

## チュートリアル

簡単にRocketでコンテナ起動までをやってみましょう。
簡単にREADMEに目を通しておくことをすすめます。
今回はfilesetを作るのがめんどいのでDockerコンテナを流用します。
そのためDockerをインストールしておきます。

### インストール

早速Rocketをインストールしてみましょう。
ビルドするには以下のものが必要です。

- cpio
- squashfs-(util)
- go-bindana
（他にもあるかな？）

```
$ git clone https://github.com/coreos/rocket.git
$ cd rocket
$ ./build
```

少し前までgo-bindataがらみでメモリが足りずにビルドによく失敗していましたが、今は修正されています。
(8Gでもビルドできない)
`bin`以下に`rkt`などが作成されるはずです。

### App Container の作成

せっかくなので今回は`nginx`のコンテナを立ち上げてみます。
filesetの作成をイチからやるのはしんどいのでDockerから流用します。

```
$ sudo docker run --name nginx nginx
```

nginxのコンテナを一度起動し、停止させます。
これでnginxのコンテナをexportできるようになります。

```
$ mkdir rootfs
$ sudo docker export nginx | sudo tar -x -C rootfs -f - 
```

`manifest.json`は以下です。

```
{
    "acVersion": "1.0.0",
    "acKind": "AppManifest",
    "name": "nginx",
    "os": "linux",
    "arch": "amd64",
    "version": "1.0.0",
    "exec": [
        "/usr/sbin/nginx -g 'daemon off;'"
    ],
    "ports": [
        {
            "name": "nginx",
            "port": 80
        }
    ]
}

```

`actool`を使い、ACIを作成します。

```
$ sudo actool build --app-manifest manifest.json rootfs nginx.aci
```

### 起動

ではこのコンテナを起動させてみましょう。

```
$ sudo rkt --debug run nginx.aci
```

`debug`オプションをつけて起動するとDebug Logを出すことができます。

```
2014/12/05 18:33:09 Unpacking stage1 rootfs
2014/12/05 18:33:10 Writing stage1 init
2014/12/05 18:33:10 Wrote filesystem to /var/lib/rkt/containers/b879414c-33bd-4afd-bba0-0e3c50e4a767
2014/12/05 18:33:10 Loading image sha256-aed279320c457e4f11bc8c717cc785284883dd525476342c813878ad3e7d224f
2014/12/05 18:33:11 Writing container manifest
2014/12/05 18:33:11 Pivoting to filesystem /var/lib/rkt/containers/b879414c-33bd-4afd-bba0-0e3c50e4a767
2014/12/05 18:33:11 Execing stage1/init
Spawning container stage1 on /var/lib/rkt/containers/b879414c-33bd-4afd-bba0-0e3c50e4a767/stage1.
Press ^] three times within 1s to kill container.
Timezone Asia/Tokyo does not exist in container, not updating container timezone.
systemd 215 running in system mode. (-PAM -AUDIT -SELINUX +IMA -SYSVINIT +LIBCRYPTSETUP -GCRYPT -ACL -XZ +SECCOMP -APPARMOR)
Detected virtualization 'systemd-nspawn'.
Detected architecture 'x86-64'.

Welcome to Linux!

Initializing machine ID from container UUID.
[  OK  ] Created slice -.slice.
[  OK  ] Created slice system.slice.
         Starting Graceful exit watcher...
[  OK  ] Started Graceful exit watcher.
         Starting nginx...
[  OK  ] Started nginx.
[  OK  ] Reached target Rocket apps target.
```

http://localhost にアクセスして`nginx`のindex.htmlが表示されてばOKです。
コンテナの終了はログにも出ている通りエスケープを3回叩けばよいです。

```
Press ^] three times within 1s to kill container.
```

Dockerと異なり、ネットワークはホストのものを使用します。
そのため、IPやポートはそのまま使用できます。

## Rocket の基礎

Rocketはコンテナ実行エンジンとしてどのような処理を行ってコンテナ化を実現しているのでしょうか？
READMEにも書いてありますが、Rocketの処理は以下の段階に分かれます。
基本的なことはREADMEにも記載してあるので補足情報をつけ、順におってみてみます。

- stage0
- stage1
- stage2

なお、Rocketはまだプロトタイプなので今後変更が入るかもしれません。

### stage0

`stage0`はコンテナの土台となる最初のステージです。
コンテナの土台になるディレクトリの作成、stage1/stage2のセットアップ
コンテナの実行エンジン部になるstage1のinitのコピーなどを行います。
ディレクトリの作成系が主な処理です。

ユーザーが指定しているApp Container部はstage2の部分にあたります。
このステージでACIをダウンロード、キャッシュ、コンテナ内へのstage2部へのコピーを行います。

Logで見るとこの部分ぐらいが`stage0`です。

```
2014/12/05 11:49:44 Unpacking stage1 rootfs
2014/12/05 11:49:44 Writing stage1 init
2014/12/05 11:49:45 Wrote filesystem to /var/lib/rkt/containers/334f58db-4fbc-47d6-b48d-8228df401152
2014/12/05 11:49:45 Loading image sha256-f9215c18b86f406c7cec4c7b45fd8752b5bfd1a492507d647821c2ce593fbf31
2014/12/05 11:49:45 Writing container manifest

```

Rocketのコンテナ実行エンジン部(ACE)は以下の構成のディレクトリで動作します。

```
/container
/stage1
/stage1/init
/stage1/opt
/stage1/opt/stage2/sha256-8a30f14877cd8065939e3912542a17d1a5fd9b4c
```

- /container     ACI内manifest.jsonです
- /stage1        実行エンジン部になるOSのルートディレクトリです。
- /stage1/init   実行エンジン部になるOSのinitです。
- /stage1/opt/stage2/xxxxxx App Containerルートです。

このあと`stage1`へ移行します。
現状では起動毎にコンテナを作成します。

### stage1

コンテナを起動するステージです。
簡単に説明すると以下にような流れになります。

1. `manifest.json` を元にApp Container起動用の`systemd unit`の作成
2. ホストとのやりとりができるように`volume`を設定
3. stage1上の`systemd-nspawn`を起動する
4. `systemd` が起動

stage1では具体的な処理を行います。
ディレクトリ上のstage1は実行エンジンOSのルートディレクトリにあたります。
現状実行エンジンのOSはCoreOSをベースしたものになっています。
(production_pxe_cpio.img)

順番は前後しますが、問題どうやってこの`stage1`のCoreOSを起動するかです。
Systemd Ready なOSならばそのまま`systemd-nspawn`を起動すれば良さそうですがRocketはUbuntuなどでも動作します。
どうやっているのでしょうか？

Rocketは`ld-linux-x86-64.so.2`経由で直接`systemd-nspawn`を起動しています。
コマンドラインだと以下のような感じになります。

```
stage1/usr/lib/ld-linux-x86-64.so.2 stage1/usr/bin/systemd-nspawn --boot --register false --uuid=32c9578e-0f6d-45b4-9890-c396198dcef3 --directory=stage1 -- --default-standard-output=tty
```

stage1のディレクトリをルートとし、コンテナを立ち上げOSを起動します。
`boot`フラグを指定しているのでOS内のinitを自動で検出し、起動します。
OS部はCoreOSベースなので`init`部は`systemd`になります。
`machinectl`で管理する必要がないので`register`は`false`です。

Log だと以下の部分がstage1の部分になります。

```
2014/12/05 11:49:45 Pivoting to filesystem /var/lib/rkt/containers/334f58db-4fbc-47d6-b48d-8228df401152
2014/12/05 11:49:45 Execing stage1/init
Spawning container stage1 on /var/lib/rkt/containers/334f58db-4fbc-47d6-b48d-8228df401152/stage1.
Press ^] three times within 1s to kill container.
Timezone Asia/Tokyo does not exist in container, not updating container timezone.
systemd 215 running in system mode. (-PAM -AUDIT -SELINUX +IMA -SYSVINIT +LIBCRYPTSETUP -GCRYPT -ACL -XZ +SECCOMP -APPARMOR)
Detected virtualization 'systemd-nspawn'.
Detected architecture 'x86-64'.

Welcome to Linux!

Initializing machine ID from container UUID.
[  OK  ] Created slice -.slice.
[  OK  ] Created slice system.slice.
         Starting Graceful exit watcher...
[  OK  ] Started Graceful exit watcher.

```

stage1/init はgoで実装されているのでソース見ることができます。
実際のstage1/initは rktバイナリ内に`go-bindata`埋め込まれています。

このstage1のOS内`systemd`経由でApp Containerを起動します。
前後しましたがApp Container→systemd unitへ変換し、stage1のinitで起動できるようにコピーしておき、起動させます。
App Containerのsystemd unitには`RootDirectory`を設定してあります。
`RootDirectory`にはstage2のルート、App Containerのrootfsが指定します。
`RootDirectory`を設定するとchrootした状態でserviceが起動します。
これでApp Containerのルートでコンテナが起動できるわけです。

### stage2

stage2はApp Containerの起動です。
stage1で説明したとおりsystemd serviceとして起動します。

Logで見ると以下の部分になります。

```
         Starting coreos.com/etcd...
[  OK  ] Started coreos.com/etcd.
[  OK  ] Reached target Rocket apps target.
```

と簡単に起動までの流れを書いてみました。

## Rocketの利点

Rocketは起動までの流れを見る限り既存の技術を組み合わせてコンテナを実行させています。
（コードの行数は7000行もありません）
つまりRokcetはいろいろなところをプラガブルに変更できる設計になっているということです。
Dockerは全て自前で実現しているため、何か変更を入れるにしても複雑で、影響を考えたりしなければいけません。
コンテナの作成もDockerのものも流用できますし、自前で作成することできます。

まだオーケストレーションなどの機能やセキュリティなどはこれからになりますが今後に期待したいと思います。

参考: Dockerを使わない場合

fedoraのシンプルなコンテナの作成

```
$ yum -y --releasever=19 --nogpg --installroot=/srv/mycontainer --disablerepo='*' --enablerepo=fedora install systemd passwd yum fedora-release vim-minimal
```

debianコンテナ

```
$ debootstrap --arch=amd64 unstable ~/debian-tree/
```

参考: systemd

http://www.freedesktop.org/software/systemd/man/systemd-nspawn.html
http://www.freedesktop.org/software/systemd/man/systemd.service.html
http://www.freedesktop.org/software/systemd/man/systemd.exec.html