AppcとCoreOS/Rocket

Dockerの諸問題とRocket登場の経緯

Rocketはリリースした直後にちょっと触ってそのまま放置していた.App containerの一連のツールとRocketが現状どんな感じかをざっと触ってみる.まだまだ全然使えると思えないが今後差分だけ追えるようにしておく.

なお,今回試した一連のツールをすぐに試せるVagrantfileをつくったので触ってみたいひとはどうぞ.

https://github.com/tcnksm/vagrant-appc

概要

App Container SpecやRocketが登場の経緯は前回書いたのでここでは省略し,これらは一体何なのかを簡単に書いておく.

まず,App Container(appc)Specはコンテナで動くアプリケーションの”仕様”である.なぜ仕様が必要かというと,コンテナという概念は今まで存在したが曖昧なものだったため.namespaceやcgroupを使った..という何となくのものはあったが,統一的なものは存在しなかったため.appc specはOpenかつSecure,Composable,Simpleであることを理念に掲げて作成されている.

appcには仕様だけではなくいくつかのツールも提供されている.例えば,appcの元になるApp Container Image (ACI)の構築と検証を行うactoolや,DockerイメージからACIをつくるdocker2aci,Go言語のバイナリからACIをつくるgoaciなどがある.

では,Rocketは何かというと,そのappcを動かすruntimeの実装の1つである.つまりappcとRocketは別のものであり実装は他にも存在する.例えば,現時点ではFreeBSDのJail/ZFSとGo言語で実装されたJetpackや,C++のライブラリとしてlibappcとそれを使ったruntimeであるNose Coneなどがある.

今回はこれらのappc関連ツールとRocketを実際に触ってみる.

Appc tools

まず,https://github.com/appcにあるAppcの一連のツールを触ってみる.

actoolによるイメージのbuild

actoolはRootファイルシステムとjsonで既述されるmanifestファイルを基にACIをビルドするツール.ビルドだけではなく,manifestやACIが仕様通りであるかの検証を行うこともできる.

例として,以下のGo言語で書かれてサンプルWebアプリケーションを動かすためのACIを作成する.

package main

import (
    "log"
    "net/http"
)

func main()
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Request from %vn", r.RemoteAddr)
        w.Write([]byte("Hello from App Container"))
    })
        
    log.Fatal(http.ListenAndServe(":5000", nil))
}

ルートファイルシステムを準備する.

$ mkdir hello
$ mkdir hello/rootfs
$ mkdir hello/rootfs

サンプルアプリケーションを静的リンクでビルドする(go1.4の場合は-installsuffixが必要).

$ CGO_ENABLED=0 GOOS=linux go build -a -tags netgo -ldflags '-w' -o hello-web
$ file hello-web
hello-web: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), statically linked, not stripped

バイナリをRootFS内に配置する.

$ mv hello-web hello/rootfs/bin/.

次にイメージのmanifestファイルを作成する.

$ cat << EOF > hello/manifest
{
  "acKind": "ImageManifest",
  "acVersion": "0.3.0",
  "name": "hello",
  "labels": [
    "name": "os", "value": "linux",
    "name": "arch", "value": "amd64"
   ],
  "app":
    "exec": [
      "/bin/hello-web"
    ],
  "user": "0",
  "group": "0"
}
EOF

actool validateを使うと仕様通りにmanifestファイルが作成されているかを検証することができる.

$ actool -debug validate hello/manifest
hello/manifest: valid ImageManifest

また同じくactool validateを使いイメージのレイアウトが仕様通りであるかを検証する.

$ actool -debug validate hello
hello: valid image layout

actool buildでイメージをビルドする.

$ actool build hello/ hello.aci

actool validateでACIが仕様通りであるかを検証する.

$ actool -debug validate hello.aci
hello.aci: valid app container image

ちなみにACIはただのtarファイルである.

$ tar xvf hello.aci
rootfs
rootfs/bin
rootfs/bin/hello-web
manifest

またappc/specにはACIを実行するApp Container Executor(例えばRocket)を検証するためのACIも提供されている.

$ EXECUTOR run ace_validator.aci

actoolによるイメージのdiscovery

DockerはDockerHubやDocker registryによりコンテナのイメージを配布するということを当たり前にした.が,イメージを配置するだけなのに専用のregistryプロトコルを使わないといけなかったり,イメージがちゃんとダウンロードされたかを検証する方法をちゃんと提供していないなど問題がいくつかある.

appc specでは,インターネット上に配置したACIとその検証を行うための署名のURLをACIの名前から解決する方法も仕様として定めている(この仕様はなかなか面白いので後で別途記事を書く予定).

actool discoverを使うとACIの名前から適切にACIとその署名,公開鍵のURLを見つけられるかを確認することができる(ASCは署名でKeysは公開鍵).

$ actool discover -insecure coreos.com/etcd
ACI: https://github.com/coreos/etcd/releases/download/latest/etcd-latest-linux-amd64.aci
ASC: https://github.com/coreos/etcd/releases/download/latest/etcd-latest-linux-amd64.aci.asc
Keys: https://coreos.com/dist/pubkeys/aci-pubkeys.gpg

docker2aci

actoolによるイメージのbuildは現時点では正直しんどい.それに対してDockerはDockerfileで簡単にイメージを作ることができるし,DockerHubには既に多くの良いDockerイメージが存在している.この利点を活かすためにdocker2aciというツールが提供されている.

例えば,Dockerhubにあるcrosbymichael/redisからACIを作成するには以下を実行すれば良い.crosbymichael-redis-latest.aciが作成される.

$ docker2aci crosbymichael/redis:latest

Dockerのイメージはやはり便利らしく,CloudFoundryのコンテナruntimeであるGardenもDocker imageをRootFSとして利用できるようにしているらしい(参考: 新しいDiegoの仕組み入門).

ちなみに,逆にDockerでACIを使えるようにするというPRもある(https://github.com/docker/docker/pull/10776).

$ docker pull --format aci coreos.com/etcd:v2.0.0
$ docker run --format aci coreos.com/etcd

が,DockerのCTOのSolomon Hykes氏が「ユーザに何のメリットがあるの?」とかコメントしていて感慨深い.

goaci

上では自分でRootFSを作ってGo言語のサンプルアプリケーションをビルドするなどしたが,go getのようにGo言語のアプリケーションをACIに変換するツールも提供されている.

$ acigo github.com/coreos/etcd

$GOPATHを書き換えてgo getを実行している.そして,静的リンクでコンパイルを実行し,デフォルト値で基本的なmanifestファイルを準備してACIのビルドを行っているだけ.

Rocket

Rocket Commands

現時点(v0.4.0)のRocketでやれることを一通りやってみる.

trust, fetch

上述したappcのdiscoveryと署名の仕様に従ってインターネット上からイメージを取得することができる.ここでは例としてcoreos.com/etcdというACIを取得する.

まず,rkt trustコマンドで取得するACIの公開鍵を取得する.

$ sudo rkt trust --insecure-allow-http --prefix coreos.com/etcd
...
Added key for prefix "coreos.com/etcd" at "/etc/rkt/trustedkeys/prefix.d/coreos.com/etcd/8b86de38890ddb7291867b025210bd8888182190"

次にrkt fetchコマンドでACIを取得する.署名も同時にダウンロードして上で取得した公開鍵をつかって署名の検証も行う.

$ sudo rkt fetch coreos.com/etcd:v2.0.0

もちろんイメージのURLを知っていればそれをそれを直接指定することもできる.

$ sudo rkt fetch https://github.com/coreos/etcd/releases/download/v2.0.0/etcd-v2.0.0-linux-amd64.aci

run

rkt runでコンテナを起動する.上でactoolを使って作成したhello.aciを動かすには以下のようにする.

$ sudo rkt run hello.aci &

コンテナにアクセスしてみる.

$ curl localhost:50000
Hello from App Container

コンテナを殺すには普通にプロセスをkillすればよい(フォアグランドで実行した場合は,^]を3回叩けば死ぬ).

Dockerと比較した場合のRocketの大きな特徴は中央集権デーモンが存在しないことである.Rocketでコンテナをつくればそれは1つのプロセスとして存在することになる.そのためupstartやsystemdといった既存のツールで個々のコンテナプロセスを管理することができる.

ちなみに署名の検証を無視すればDockerHub上のイメージを使うこともできる

$ sudo rkt --insecure-skip-verify run docker://redis

list, status

rkt listでコンテナの一覧を確認できる.

$ sudo rkt list
UUID                                    ACI             STATE
2bbe6aaa-a41d-43cd-b5b2-ff8058662bb6    hello           active
b1c946f5-bd54-43e6-b241-289077adf12f    coreos.com/etcd inactive

rkt statusでコンテナの状態(PIDと終了状態)を確認できる.

$ sudo rkt status 2bbe6aaa-a41d-43cd-b5b2-ff8058662bb6
pid=21967
exited=false

gc

rkt gcで古いinactiveなコンテナを破棄することができる.-grace-period でinactiveからどれだけ時間の経過したものを破棄するかを指定できる.

$ sudo rkt gc -grace-period=10s

これをsystemdのOnCalendarで定期実行してゴミ捨て場にならないようにする.

enter

rkt enterでコンテナのnamespace内に入ることができる.

$ sudo rkt enter 29d47fda-23f5-423f-9457-708d775ee9d9
No command specified, assuming "/bin/bash"
root@rootfs:/#

Rocketのアーキテクチャ

Rocketの内部について簡単にまとめておく.Rocketはrktコマンドのみで構成され,Dockerのようなデーモンはない.そのため,既に起動しているコンテナに影響を与えることなくRocketそのものをアップデートすることができる.

Rocketの起動はstage0 -> stage1 -> stage2の3つのstageに分けられる.各stageはモジュラーな構成になっている.これらが具体的に何をしているのかを簡単に説明する.

Stage0

Stage0はrktがコンテナを動かすための初期設定を行う.

  • ACIの取得
  • もし--stage1-imageが指定されたらStage1のACIの取得(デフォルトはrktと同じディレクトリのstage1.aci
  • コンテナのUUIDの生成
  • コンテナのRuntime Manifestの生成
  • コンテナのためのファイルシステムの作成
  • Stage1とStage2用のディレクトリの作成
  • Stage1のACIのコンテナファイルシステムへの展開
  • ACIの展開とアプリケーションのStage2ディレクトリへのコピー

Stage1

Stage1では,cgroupやnamespaceの設定やプロセスの起動,ホストのルートとしての各種オペレーションを実行する.

  • コンテナのRuntime ManifestからsystemdのUnitファイルの生成
  • 外部Volumeの準備
  • root systemdの起動

以下のようなコマンドを実行している.

stage1/rootfs/usr/lib/ld-linux-x86-64.so.2 \
stage1/rootfs/usr/bin/systemd-nspawn \
    --boot \
    --register false \
    --quiet \
    --uuid=81387c20-df38-4e17-9bab-985269148fbb \
    --directory=stage1/rootfs \
    -- \
    --default-standard-output=tty \
    --log-target=null \
    --show-status=0

systemd-nspawnを使っているのがわかる.これについては以下が詳しい.

Stage2

アプリケーションの起動.例えば上で作成したhello.aciの場合は以下のプロセスが起動する.

/bin/hello-web

まとめ

今週末の日曜日(3/15)にrebuild.fmで喋ります.

参考