この記事は tech.kayac.com Advent Calendar 2015 18日目です。
こんにちは、Splatoon にハマりすぎて amiibo 揃えたり
気づけば イカクッション を2個もゲットして
Splatoon 三昧な生活を送っている @mix3 です。イカ、特にガールが可愛くてしかたありません。みなさんも Let's Splatoon !!!
rkt とは
rkt とはなんぞや?という方は Dockerの諸問題とRocket登場の経緯 | SOTA とか AppcとCoreOS/Rocket | SOTA とか見るとなんとなく分かるかなと思います。
Docker は社内的に mirage を経由して開発環境でバリバリ使われていることもあって多少なりとも馴染みある感じになってきましたが、rkt は @acidlemon 製WAF rocket と名前が丸かぶりしてゲラゲラ笑ったぐらいで特に接点もなくスルーしていました。そんな rkt ですが最近少し目を引く更新があり Advent Calendar ネタにちょうど良さそうだということで触ってみたので「触ってみたよー」という話をしようかと思います。
rkt での image の作り方
Docker ではコンテナイメージは Dockerfile で作成するものかと思いますが rkt ではどうなのか紹介します。
actool
ルートファイルシステム と Manifest を用意して actool でビルドするのが rkt での image 作成の最も基本となる方法かなと思います。
具体的には以下の様にして一つ一つ温かみのある手作業でルートファイルシステムを用意し、
$ find . ./layout ./layout/manifest ./layout/rootfs ./layout/rootfs/bin ./layout/rootfs/bin/hello
以下の様な人の手の温かみを感じるManifestを用意し、
$ cat layout/manifest { "acVersion": "0.7.4", "acKind": "ImageManifest", "name": "hello-actool", "labels": [ {"name": "version", "value":"latest"}, {"name": "os", "value":"linux"}, {"name": "arch", "value":"amd64"} ], "app": { "exec": [ "/bin/hello" ], "user": "0", "group": "0" } }
以下の様にしてビルドすることで ACI(App Container Images) を作成することが出来ます。
$ actool build layout/ hello-actool-latest-linux-amd64.aci
/bin/hello
は 5000番ポートで起動して hello を返すだけの簡単なアプリで rkt run
で起動して確認出来ます。
$ sudo rkt --insecure-options=image run hello-actool-latest-linux-amd64.aci rkt: using image from local store for image name coreos.com/rkt/stage1-coreos:0.12.0 rkt: using image from file /home/vagrant/hello-actool/hello-actool-latest-linux-amd64.aci run: group "rkt" not found, will use default gid when rendering images
$ sudo rkt list UUID APP IMAGE NAME STATE NETWORKS ea019958 hello-actool hello-actool:latest running default:ip4=172.16.28.63 $ curl 172.16.28.63:5000 hello
とっても面倒ですね…
actool 番外編
さっきの例では /bin/hello
はワンバイナリで動くようになっていました。ldd
するとこんな感じ。
$ ldd hello not a dynamic executable
では、ワンバイナリで動かないものを動かすには? date コマンドだと ldd はこんな感じ。
$ ldd /bin/date linux-vdso.so.1 => (0x00007ffc5acfb000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f511fc30000) /lib64/ld-linux-x86-64.so.2 (0x00007f511fffa000)
これを元にルートファイルシステムを構成すると以下の様な感じになります。
$ find . ./layout ./layout/rootfs ./layout/rootfs/lib64 ./layout/rootfs/lib64/ld-linux-x86-64.so.2 ./layout/rootfs/lib ./layout/rootfs/lib/x86_64-linux-gnu ./layout/rootfs/lib/x86_64-linux-gnu/libc.so.6 ./layout/rootfs/bin ./layout/rootfs/bin/date
actool build
して rkt run
で確認してみると日付が出力されるのが確認できます。めでたしめでたし。
$ actool build layout/ date-actool-latest-linux-amd64.aci $ sudo rkt --insecure-options=image run date-actool-latest-linux-amd64.aci rkt: using image from local store for image name coreos.com/rkt/stage1-coreos:0.12.0 rkt: using image from file /home/vagrant/date/date-actool-latest-linux-amd64.aci run: group "rkt" not found, will use default gid when rendering images [174052.629569] date[4]: Thu Dec 17 05:54:21 UTC 2015
最高に面倒ですね…。
こんな感じで actool をそのまま使うとかなり厳しい感じなので他にもいくつか image を作成するツールが提供されています。
docker2aci
docker2aci は docker image を aci にしてくれるツールです。docker には Dockerfile という、イメージをスクリプトで表現する便利な方法が提供されているので、それに乗っかることが出来て非常に便利です。
以下、Dockerfile > docker build > docker save (exportの方が良い?) > docker2aci と aci を作って rkt run で動かすところまで試した内容になります。
$ cat Dockerfile FROM alpine:latest MAINTAINER mix3 RUN apk update COPY hello /bin/hello ENTRYPOINT ["/bin/hello"]
$ sudo docker build -t mix3/hello:latest . $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE mix3/hello latest d26349af3069 13 minutes ago 10.94 MB alpine latest 32653661039d 7 days ago 5.249 MB $ sudo docker save mix3/hello > hello.docker $ docker2aci hello.docker
$ sudo rkt --insecure-options=image run mix3-hello-latest.aci rkt: using image from local store for image name coreos.com/rkt/stage1-coreos:0.12.0 rkt: using image from file /home/vagrant/hello-docker2aci/mix3-hello-latest.aci run: group "rkt" not found, will use default gid when rendering images
$ sudo rkt list UUID APP IMAGE NAME STATE NETWORKS 855fded7 hello mix3/hello:latest running default:ip4=172.16.28.64 $ curl 172.16.28.64:5000 hello
サクっと docker image から aci を作ることが出来ます。素晴らしいですね。
他にも goaci という go project を aci にしてくれるものや deb2aci という debian package を aci にしてくれるものなど色々あります。
詳しくは公式のリストアップを参照してください https://github.com/appc/spec#what-is-the-promise-of-the-app-container-spec
acbuild
docker2aci は素晴らしいですが docker に乗っかっていてシックリ来ない感じだったところに acbuild という Dockerfile のようにスクリプトでイメージを記述できるタイプのツールが出てきました。
リポジトリに example がいくつか上がっているので、以下はそれを参考に書いてみました。
$ cat build-hello.sh #!/usr/bin/env bash set -e if [ "$EUID" -ne 0 ]; then echo "This script uses functionality which requires root privileges" exit 1 fi acbuild --debug begin trap "{ export EXT=$?; acbuild --debug end && exit $EXT; }" EXIT acbuild --debug set-name mix3.github.io/aci/hello acbuild --debug copy hello /bin/hello acbuild --debug set-exec /bin/hello acbuild --debug label add version latest acbuild --debug label add arch amd64 acbuild --debug label add os linux acbuild --debug write --overwrite hello-latest-linux-amd64.aci
$ sudo ./build-hello.sh Beginning build with an empty ACI Setting name of ACI to mix3.github.io/aci/hello Copying host:hello to aci:/bin/hello Setting exec command [/bin/hello] Adding label "version"="latest" Adding label "arch"="amd64" Adding label "os"="linux" Writing ACI to hello-latest-linux-amd64.aci Ending the build
$ sudo rkt list UUID APP IMAGE NAME STATE NETWORKS 01cce0f5 hello mix3.github.io/aci/hello:latest running default:ip4=172.16.28.63 $ curl 172.16.28.63:5000 hello
良い感じですね! もちろん Dockerfile の FROM のようにベースイメージを指定することが出来ます。
acbuild --debug dep add quay.io/coreos/alpine-sh acbuild --debug run -- apk update acbuild --debug run -- apk add bash curl
ベースイメージに alpine を使っていると rkt enter
でコンテナに入ろうとして「残念! bash がない!」ということが多いので alpine を使って試行錯誤したいときはとりあえず bash とか curl とかは入れておくと良さそうかなと思います。
ということで acbuild のおかげで docker2aci などのようにクッションを挟まず Dockerfile のようにイメージの構成をリポジトリで管理出来るようになるので、非常に捗りそうな予感がしますね。
以上、rkt での image の作り方でした。
API について
Docker には API service が備わっていてリモートから Docker の機能に触れることが出来る様になっています。@acidlemon 製、開発環境をドカドカあげるツール mirage もこれを利用して作られています。rkt にはこういった API は提供されていませんでしたが、これも acbuild と同時期に rkt に API service の機能が追加されました。(ただし現時点では Experimental で don't use it です)
API 定義
API は現状 Protocol Buffers で定義されていて、クライアントをサクっと作ることが出来る様になっています。以下は rkt image list
に相当するクライアントの実装になります。
$ find . ./main.go ./api ./api/v1alpha ./api/v1alpha/api.pb.go
$ cat main.go package main import ( "log" "./api/v1alpha" "github.com/k0kubun/pp" "golang.org/x/net/context" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:15441", grpc.WithInsecure()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := v1alpha.NewPublicAPIClient(conn) r, err := c.ListImages(context.Background(), &v1alpha.ListImagesRequest{ Filter: &v1alpha.ImageFilter{ Ids: []string{}, }, }) if err != nil { log.Fatalf("could not ListImages: %v", err) } pp.Println(r) }
rkt api-service
でAPIサーバを立てておきます。
$ sudo rkt api-service 2015/12/17 18:50:38 API service starting... 2015/12/17 18:50:38 API service running on localhost:15441...
rkt image list
で見れるような結果がずらずらと返ってきました。
$ go run main.go &v1alpha.ListImagesResponse{ Images: []*v1alpha.Image{ &v1alpha.Image{ BaseFormat: &v1alpha.ImageFormat{ Type: 1, Version: "0.7.3", }, Id: "sha512-3fc7e57d0c2853ee94747d10f075caa2e7b31e10847044764a1f621e96316c0e", Name: "mix3.github.io/aci/hello", Version: "latest", ImportTimestamp: 1450342909, Manifest: []uint8{}, }, &v1alpha.Image{ (snip)
簡単ですね。API サーバの起動オプションで listen addr を変更出来る雰囲気を出していますが、実装の方で localhost:15441
がハードコードされていて実際は変更できない、おちゃめプラグイン実装済みだったりします。Experimental なので仕方ないですね。
ちなみに rkt run に相当する API は今の所無い雰囲気
rkt にはそもそも daemonize の機能が無いのでそのためかなと思われます。rkt での daemonize はドキュメントで systemd-run もしくは systemd の unit の機能を使うことが説明されていますので、その辺り Docker とは勝手が違うことには注意が必要かもしれません。
まとめ
- acbuild という便利なやつが来たよ!
- rkt に API が来たよ!
ということで rkt の最近の気になる更新の紹介でした。
…と、これで終わっても良いかなという気分ですが rkt api (+systemd) を使って rkt 版 mirage を作ったりしてみたので、折角なのでそれの紹介もしようと思います。
rkt 版 mirage
ということで Experimental ではありますが API の機能が入ったので API を使って何かコード書きたいなと思って mirage の rkt 版を作ってみました。
https://github.com/mix3/phantasma
mirage と比べて設定をそぎ落としたり(複数 port マッピングなんかは無かったり)してますが、概ね mirage っぽく動くものが出来たかと思います。
使い方としては以下の様な感じで起動して
$ sudo ./phantasma --domain=phantasma.ubuntu --static-dir ./static 2015/12/17 19:55:41 [main] starting... 2015/12/17 19:55:41 [main] running on 127.0.0.1:5000 ...
イメージのリストを取得するAPIがあって
$ curl -H"Host: example.com" http://localhost:5000/api/image/list -s | jq . { "result": [ { "id": "sha512-3fc7e57d0c2853ee94747d10f075caa2e7b31e10847044764a1f621e96316c0e", "name": "mix3.github.io/aci/hello", "version": "latest" }, { (snip)
こんな感じでコンテナを起動して
$ curl -H"Host: example.com" http://localhost:5000/api/launch -d "subdomain=fuga" -d "image_id=sha512-3fc7e57d0c2853ee94747d10f075caa2e7b31e10847044764a1f621e96316c0e" -s | jq . { "result": "ok" } $ curl -H"Host: example.com" http://localhost:5000/api/launch -d "subdomain=fuga" -d "image_id=sha512-a8bf135728c718fd5eaaff5ff031458c285e997bb1db1d0db11eb36dbc6d5c4e" -s | jq . { "result": "ok" }
コンテナのリストが見られて(ちなみに mirage と違って起動に失敗して落ちてるコンテナもリストに出てきます)
$ curl -H"Host: example.com" http://localhost:5000/api/list -s | jq . { "result": [ { "uuid": "", "image": "", "subdomain": "fuga", "port": 0, "net": "", "host": "", "running": false, "env": [] }, { "uuid": "2a6bbb3a-dc21-4bcf-86ce-e896e3325322", "image": "mix3.github.io/aci/hello:latest", "subdomain": "hoge", "port": 5000, "net": "default", "host": "172.16.28.64", "running": true, "env": [] } ] }
コンテナを落とせて
$ curl -H"Host: example.com" http://localhost:5000/api/terminate -d "subdomain=fuga" -s | jq . { "result": "ok" } $ curl -H"Host: example.com" http://localhost:5000/api/list -s | jq . { "result": [ { "uuid": "2a6bbb3a-dc21-4bcf-86ce-e896e3325322", "image": "mix3.github.io/aci/hello:latest", "subdomain": "hoge", "port": 5000, "net": "default", "host": "172.16.28.64", "running": true, "env": [] } ] }
subdomainをつけるとそっちにリバースプロキシしてくれます
$ curl -H"Host: hoge.example.com" http://localhost:5000/ hello
と、こんな感じになっています。
また -d "port=[PORT_NUM]"
でプロキシ先のポートを指定できるようになっていたり /api/list のコンテナの情報に env がある通り、環境変数を指定することが出来る様になっています。
以下は環境変数で 3000番ポートで待ち受けるようにアプリを起動して、3000番ポートに転送する様にしている例になります。
$ curl -H"Host: example.com" http://localhost:5000/api/launch -d "subdomain=todo" -d "port=3000" -d "image_id=sha512-f2dadb54e769dbdb84e6f0497524912b8104543aed0d7e8c493e5950aecf30b9" -d "env=DB_HOST=172.16.28.1" -d "env=PORT=3000" -s | jq . { "result": "ok" } $ curl -H"Host: example.com" http://localhost:5000/api/list -s | jq . { "result": [ (snip) }, { "uuid": "6b8f03a3-f915-4df8-ae76-7e11707ce643", "image": "mix3.github.io/aci/todo:latest", "subdomain": "todo", "port": 3000, "net": "default", "host": "172.16.28.79", "running": true, "env": [ { "key": "DB_HOST", "val": "172.16.28.1" }, { "key": "PORT", "val": "3000" } ] } ] } $ curl -H"Host: todo.example.com" http://localhost:5000/ -s | head <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Tutorial</title> <script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
適当なフロントエンドも用意してあるのでいい感じに mirage っぽくなったのではないかと思います。
「宗教上の理由で Docker が使えない」といった境遇の人がいればこれを試してもらうと良いかもしれませんね。
rkt 版 mirage の特筆(?)すべきところ
Launch
前述した通り、rkt api-service には run に相当する機能がありません。なのでどうしようかということで、最初は rkt の test を参考に gexpect で sudo rkt run
を叩いて goroutine で永続化するなど無駄に頑張ったりしてました。
そのあと別件で見かけた issue で kubernetes では github.com/coreos/go-systemd/dbus を使って systemd を操作して rkt のコンテナ管理をしていることがわかったので「なるほどそちらの方が健全か」と思い コピペ 参考にしました。
/api/launch が叩かれると /etc/systemd/system/{APP_PREFIX}-{SUBDOMAIN}.service を生成して dbus.RestartUnit するようになっています。
Launch時の情報をPodのAnnotationに保存
mirage では leveldb を使って launch時の設定などを永続化しているのですが、rkt 版 mirage では Pod の Annotation に保存する様にしました。永続化を別でも持つと二重管理になって面倒臭いのでそのほうが良いかなと思います。(mirage も leveldb 無くしてそういう感じの対応を入れたいなぁという話は上がっています)
ただ Annotation の付加は rkt run
のオプションでは出来ないので aci 指定で起動せずに PodManifest を /tmp
以下に生成して krt run --pod-manifest /tmp/PodManifest
で起動するようにしました。
具体的には以下の様な感じになります。無理やり感が漂ってますが ExecStartPre=...
で Annotation を付加した状態の PodManifest を作ってそれを元に rkt run
しています。
$ sudo cat /etc/systemd/system/{APP_PREFIX}-{SUBDOMAIN}.service [Unit] Description=phantasma-hoge [Service] ExecStartPre=/bin/sh -c '/bin/echo \'{"acVersion":"0.7.4+git","acKind":"PodManifest","apps":[{"name":"hello","image":{"name":"mix3.github.io/aci/hello","id":"sha512-3fc7e57d0c2853ee94747d10f075caa2e7b31e10847044764a1f621e96316c0e","labels":[{"name":"version","value":"latest"},{"name":"arch","value":"amd64"},{"name":"os","value":"linux"}]},"app":{"exec":["/bin/hello"],"user":"0","group":"0"}}],"volumes":null,"isolators":null,"annotations":[{"name":"phantasma-is","value":"1"},{"name":"phantasma-subdomain","value":"hoge"},{"name":"phantasma-port","value":"5000"},{"name":"phantasma-net","value":"default"}],"ports":null}\' > /tmp/phantasma-hoge.manifest' ExecStart=/usr/local/bin/rkt --insecure-options=image run --pod-manifest=/tmp/phantasma-hoge.manifest KillMode=mixed
ということで、
rkt 版 mirage についてでした。 rkt api-service に興味が湧いたという人がいれば幸いです。
rkt の感想
「rkt 触ったよー」の感想としては「rkt 良いよ!」なのですが、一点思うことが一つ。
rkt sudo 必須なのは早く改善されて欲しいかなと思います。
もちろんすでに issue は上がっていて https://github.com/coreos/rkt/issues/820 いくつか対応も入って改善されてはいますがまだ完全には取り除けていないようです。rkt は Docker に対するアンチテーゼとしてセキュリティもうたっているので、これについては本当に頑張って欲しいなと思います。
おわりに
rkt 版 mirage 以外にも、
- ストレージに mysql を使う Todo の Webアプリを書いた
- mysql の aci と webapp の aci に分けておいて、協調して動く hello world よりは複雑なサンプル
- github pages を利用してネットワーク越しに
rkt fetch
できる様にしてみた
とか rkt について触ってみた事柄はありますが、まあこれ以上グダグダ書いてもあれなのでやめておきます。
ということで「rkt 触ってみたよー」でした。綺麗に纏めるとか解説するとか出来なくて、コードとコンソール出力を貼り付けまくったアレな内容ですが、何かしら参考になるとかあれば幸いです。
明日は
癒し系魔法エンジニア(と自分は勝手に思っている) @p_chin さんです。お楽しみに!