rktと戯れる

この記事は 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 を参考に gexpectsudo rkt run を叩いて goroutine で永続化するなど無駄に頑張ったりしてました。

そのあと別件で見かけた issuekubernetes では 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 以外にも、

とか rkt について触ってみた事柄はありますが、まあこれ以上グダグダ書いてもあれなのでやめておきます。

ということで「rkt 触ってみたよー」でした。綺麗に纏めるとか解説するとか出来なくて、コードとコンソール出力を貼り付けまくったアレな内容ですが、何かしら参考になるとかあれば幸いです。

明日は

癒し系魔法エンジニア(と自分は勝手に思っている) @p_chin さんです。お楽しみに!