appcコンテナを作ってみる

前回 Docker のイメージから ACI を作り、 Rocket で動かすということをやってみた。

今回は仕様を見ながらイメージを作ってみる。

appc の仕様はここにまとまっている。(リンクはこれを書いてる時点で見てたバージョン。v0.4.1)

https://github.com/appc/spec/blob/bf255efb12f2455c0f6eb81932458abc0ad80413/SPEC.md

App Container Image の部分を中心に見る。

ACI のファイルフォーマットのことをイメージアーカイブと呼んでいるらしい。 その構造について決まっていることはいくつかあって、

  • 拡張子は .aci じゃないといけない
  • 中身に重複がない tar ファイルフォーマットのファイルじゃないといけない
  • トップレベルに manifest というファイルと rootfs というディレクトリを持っていないといけない
  • イメージに入れるファイルは元々の属性を全部維持していないといけない
  • gzip, bzip2, xz のどれかで圧縮してもOK
  • AES対称暗号(?)で暗号化してもいい
  • PGPで署名されるべき
  • 署名の拡張子は .aci.asc

という感じ

ここによれば、最も単純な ACI は、単なる tar ファイルで、最低限 manifest と rootfs があればいいことがわかる。

今回はその最も単純な方法で、 nc を使った簡易ウェブサーバが動くか試してみる。

こういうシェルスクリプトを動かしたい。

#!/bin/sh

while true; do ( echo "HTTP/1.0 200 Ok"; echo; echo "Hello World" ) | nc -l 8080; [ $? != 0 ] && break; done

manifest ファイルはちょっとめんどいので、まずは入れ物のディレクトリを作る

mkdir rootfs

コンテナには必要なものを全部入れる必要がある。

ここでいうとこのスクリプトを動かす sh と、中で使っている nc が少なくとも必要になってくる。

というわけでまず rootfs に bin のディレクトリを作る。

で、そこに sh と nc を入れる

mkdir rootfs/bin
cp -a /bin/sh rootfs/bin/sh
cp -a /bin/dash rootfs/bin/dash
cp -a /bin/sh rootfs/bin/sh
cp -a /bin/nc.openbsd rootfs/bin/nc

最初ここでシンボリックリンク/bin/sh と /bin/nc しかコピーしてなくて、リンク先がなくて動かないという凡ミスを犯したw

nc は今回試した環境では↓こうなってたので、リンク先を名前変えて突っ込む

vagrant@vagrant-ubuntu-trusty-64:~$ ls -la /bin/nc
lrwxrwxrwx 1 root root 20 Jan 30 18:42 /bin/nc -> /etc/alternatives/nc
vagrant@vagrant-ubuntu-trusty-64:~$ ls -la /etc/alternatives/nc
lrwxrwxrwx 1 root root 15 Jan 30 18:42 /etc/alternatives/nc -> /bin/nc.openbsd

次に、実行するファイルが使うライブラリもないといけない。

vagrant@vagrant-ubuntu-trusty-64:~/test-container$ ldd rootfs/bin/sh
    linux-vdso.so.1 =>  (0x00007fff059e6000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb2255f9000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb225be8000)
vagrant@vagrant-ubuntu-trusty-64:~/test-container$ ldd rootfs/bin/nc
    linux-vdso.so.1 =>  (0x00007fffd20f4000)
    libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007fec3a421000)
    libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fec3a206000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fec39e3f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fec3a639000)

これらが全部必要なので、rootfs 以下の対応する場所にこれらをコピーしておく。

で、最後に動かしたいシェルファイルを適当な場所に置く。

今回は /usr/bin に置く。

やってみて、ディレクトリは以下のようになった

vagrant@vagrant-ubuntu-trusty-64:~/test-container$ tree .
.
├── manifest
└── rootfs
    ├── bin
    │   ├── dash
    │   ├── nc
    │   └── sh -> dash
    ├── lib
    │   └── x86_64-linux-gnu
    │       ├── ld-2.19.so
    │       ├── libbsd.so.0 -> libbsd.so.0.6.0
    │       ├── libbsd.so.0.6.0
    │       ├── libc-2.19.so
    │       ├── libc.so.6 -> libc-2.19.so
    │       ├── libresolv-2.19.so
    │       └── libresolv.so.2 -> libresolv-2.19.so
    ├── lib64
    │   └── ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.19.so
    └── usr
        └── bin
            └── tinyweb

7 directories, 13 files

次に manifest を作っていく

manifest のスキーマは以下にまとまっている。 https://github.com/appc/spec/blob/bf255efb12f2455c0f6eb81932458abc0ad80413/SPEC.md#manifest-schemas

具体的には Go のコード読んだほうがいいかも https://github.com/appc/spec/blob/bf255efb12f2455c0f6eb81932458abc0ad80413/schema/image.go

SPEC.md にある例をコピーして、必要ないところは消しつつ作ってみる

type ImageManifest struct {
    ACKind        types.ACKind       `json:"acKind"`
    ACVersion     types.SemVer       `json:"acVersion"`
    Name          types.ACName       `json:"name"`
    Labels        types.Labels       `json:"labels,omitempty"`
    App           *types.App         `json:"app,omitempty"`
    Annotations   types.Annotations  `json:"annotations,omitempty"`
    Dependencies  types.Dependencies `json:"dependencies,omitempty"`
    PathWhitelist []string           `json:"pathWhitelist,omitempty"`
}

ここで omitempty になるものはなくてもいいはず。

なので acKing, acVersion, name は書く。

labels はそのまま残しておこう。

app はなくてもいいが、ないと思ったとおりに実行できないので書く。

exec にさきほど用意した /usr/bin/tinyweb を指定する。

その他は特に今回は不要なので空にしておく

docker と違ってデフォルトではホストのネットワークを使うので、特にポートをバインドとかは必要ない。

で、結果として以下のような感じになった

vagrant@vagrant-ubuntu-trusty-64:~/test-container$ cat manifest
{
    "acKind": "ImageManifest",
    "acVersion": "0.4.1",
    "name": "test-container",
    "labels": [
        {
            "name": "version",
            "value": "1.0.0"
        },
        {
            "name": "arch",
            "value": "amd64"
        },
        {
            "name": "os",
            "value": "linux"
        }
    ],
    "app": {
        "exec": [
            "/usr/bin/tinyweb"
        ],
        "user": "0",
        "group": "0",
        "eventHandlers": [],
        "workingDirectory": "",
        "environment": [
        ],
        "isolators": [
        ],
        "mountPoints": [
        ],
        "ports": [
        ]
    },
    "dependencies": [
    ],
    "annotations": [
        {
            "name": "authors",
            "value": "totem3 <totem3@github>"
        },
        {
            "name": "documentation",
            "value": "https://github.com/totem3/containers"
        },
        {
            "name": "homepage",
            "value": "https://github.com/totem3/containers"
        }
    ]
}

これで作ってみよう。

vagrant@vagrant-ubuntu-trusty-64:~/test-container$ tar cvf test-container.aci manifest rootfs
manifest
rootfs/
rootfs/lib/
rootfs/lib/x86_64-linux-gnu/
rootfs/lib/x86_64-linux-gnu/libresolv-2.19.so
rootfs/lib/x86_64-linux-gnu/libresolv.so.2
rootfs/lib/x86_64-linux-gnu/libc.so.6
rootfs/lib/x86_64-linux-gnu/libbsd.so.0.6.0
rootfs/lib/x86_64-linux-gnu/libc-2.19.so
rootfs/lib/x86_64-linux-gnu/libbsd.so.0
rootfs/lib/x86_64-linux-gnu/ld-2.19.so
rootfs/lib64/
rootfs/lib64/ld-linux-x86-64.so.2
rootfs/usr/
rootfs/usr/bin/
rootfs/usr/bin/tinyweb
rootfs/bin/
rootfs/bin/sh
rootfs/bin/nc
rootfs/bin/dash

done

動かしてみよう。

rkt で動かす

vagrant@vagrant-ubuntu-trusty-64:~/test-container$ sudo rkt run test-container.aci
/etc/localtime is not a symlink, not updating container timezone.

動いた。 /etc/localtime がないと言われているが、これはとりあえず置いておこう

別のターミナルから8080番ポートを叩いてみる

vagrant@vagrant-ubuntu-trusty-64:~$ curl localhost:8080
Hello World

返ってきた。コンテナ側にもリクエストを受け付けた形跡がある

vagrant@vagrant-ubuntu-trusty-64:~/test-container$ sudo rkt run test-container.aci
/etc/localtime is not a symlink, not updating container timezone.
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: localhost:8080
Accept: */*

うん、動いていそう。

プロセスツリーはこうなってる。(このうえはsshとかshell)

root      9906  0.0  0.4  65736  2128 pts/0    S+   21:13   0:00  |\_ sudo rkt run test-container.aci
root      9907 28.0  0.2  27808  1216 pts/0    S+   21:13   0:01  |    \_ stage1/rootfs/usr/lib/ld-linux-x86-64.so.2 stage1/rootfs/usr/bin/systemd-nspawn --boot --register false --quiet --uuid=9a78fc07-b3cd-4fd3-8f84-749167e27f8e --directory=stage1/rootfs -- --
root      9912  0.0  0.4  22568  2224 ?        Ss   21:13   0:00  |        \_ /usr/lib/systemd/systemd --default-standard-output=tty --log-target=null --show-status=0
root      9913  0.0  0.0   4108   384 ?        Ss   21:13   0:00  |            \_ /usr/bin/sleep 9999999999d
root      9915  0.0  0.1   4444   660 ?        Ss   21:13   0:00  |            \_ /bin/sh /usr/bin/tinyweb
root      9917  0.0  0.0   9128   432 ?        S    21:13   0:00  |                \_ nc -l 8080

ランタイムの話はまた今度やろう

ちなみにコンテナの終了は ^] (ctrl+]) を3回