log.fstn

技術よりなことをざっくばらんにアウトプットします。

runCをひと通り使ってみた

runCが発表されてから1ヶ月ほど経ちました。 使ってみた系の記事が幾つか出ていますが、ぼくもとりあえずひと通り触ってみたのでやってみたことを簡単にまとめます。

runCとは

そもそもrunCとは、Open Container Project によって定義された Open Container Format のランタイムです。 詳しくは下記の記事をご参照頂くとわかるかと思います。

www.publickey1.jp

www.publickey1.jp

runC を動かしてみる

https://runc.io/ に Getting started が載ってますので、こちらに沿ってとりあえず動かしてみます。 なお今回は物理マシン上の trusty で検証しています。

インストール

2015/08/01 時点で v0.0.2 までタグが打たれていますが、runc checkpoint でコケたので master ブランチにチェックアウトします。 ここで試したのは https://github.com/opencontainers/runc/tree/602e8331a0cad8e8aef18e2c962dea77a696f04d になります。

$ git clone git@github.com:opencontainers/runc.git
$ cd runc
$ make
$ make install

バイナリでは提供されていませんが、make は数秒で終わるのでインストールは簡単です。

rootfs の準備

debootstrap などを利用してもいいのですが、既存のDockerイメージを利用すると楽なので、こちらを使います。

$ docker pull memcached
$ docker export memcached > memcached.tar
$ mkdir -p memcached/rootfs
$ tar xvf memcached.tar -C memcached/rootfs

runc 起動

$ cd memcached
$ runc spec > config.json
$ sudo runc
# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4328   764 ?        Ss   18:35   0:00 sh
root         7  0.0  0.0  17492  2080 ?        R+   18:36   0:00 ps aux

runc spec で生成されるデフォルトの設定ファイルを利用すると、コンテナで sh コマンドが実行されてterminalに入ります。 中で ps コマンドを実行すると、PID 1 のプロセスが sh になっていることが確認できます。

memcachedコンテナを固定IPで起動してみる

ネットワークの準備

enakai00.hatenablog.com

こちらを参考に必要なものを用意します。

$ sudo brctl addbr runc0
$ sudo ip link set runc0 up
$ sudo ip addr add 192.168.10.1/24 dev runc0
$ sudo ip link add name veth-host type veth peer name veth-guest
$ sudo ip link set veth-host up
$ sudo brctl addif runc0 veth-host
$ sudo ip netns add runc
$ sudo ip link set veth-guest netns runc
$ sudo ip netns exec runc ip link set veth-guest name eth1
$ sudo ip netns exec runc ip addr add 192.168.10.101/24 dev eth1
$ sudo ip netns exec runc ip link set eth1 up
$ sudo ip netns exec runc ip route add default via 192.168.10.1

細かい説明は省きますが、ここで出来上がるものは下記のものです(Dockerと同じ構成ですね)。

f:id:foostan:20150801215632p:plain

  • runc0(bridge)
  • veth-host(interface)
  • runc(network namespace)
  • eth1(interface)

なお、runc0 と eth0 のルーティングの設定は行っていないので、コンテナからホスト外のネットワークには出れませんが、今回はそこの検証はしないのでよしとします。

runc 設定変更

config.jsonを下記のように修正します。 起動時に memcached のコマンドを実行するようにし、さらに上記で作った netns を割り当てています。

@@ -5,14 +5,16 @@
        "arch": "amd64"
    },
    "process": {
-      "terminal": true,
+       "terminal": false,
        "user": {
            "uid": 0,
            "gid": 0,
            "additionalGids": null
        },
        "args": [
-          "sh"
+           "/usr/local/bin/memcached",
+           "-u",
+           "root"
        ],
        "env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
@@ -22,7 +24,7 @@
    },
    "root": {
        "path": "rootfs",
-      "readonly": true
+       "readonly":false
    },
    "hostname": "shell",
    "mounts": [
@@ -113,7 +115,7 @@
            },
            {
                "type": "network",
-              "path": ""
+               "path": "/run/netns/runc"
            },
            {
                "type": "ipc",

runc 起動 / 確認

$ sudo runc

別のターミナルを起動して接続確認をしてみます。

$ telnet 192.168.10.101 11211
Trying 192.168.10.101...
Connected to 192.168.10.101.
Escape character is '^]'.
set test 0 0 4
test
STORED
get test
VALUE test 0 4
test
END
quit
Connection closed by foreign host.

無事に起動しているようです。

memcachedコンテナをホストIPで起動してみる

namespace の network の設定を削除します。

@@ -122,10 +122,6 @@
                "path": ""
            },
            {
-              "type": "network",
-              "path": "/run/netns/runc"
-          },
-          {
                "type": "mount",
                "path": ""
            }

runc を起動して

$ sudo runc

別ターミナルで確認してみます。

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
set test 0 0 4
test
STORED
get test
VALUE test 0 4
test
END
quit
Connection closed by foreign host.

問題ないようです。

checkpoint / restore を試す

runC は 最初から CRIU( http://criu.org/Main_Page ) に対応している点が特徴的と言えます。 CRIUはプロセスの停止/再開を行うためのツールであり、コンテナの状態を保持しつつ停止/再開(別ホストで起動など)が出来るようになります。

コンテナ(Docker) と CRIU の組み合わせの事例としては下記のようなものがあります。

www.slideshare.net

checkpoint

checkpoint をするにあたって下記の設定に注意してください(デフォルトではどちらも true になっています)。

  • process の terminal が false になっているか
  • root の readonly が false になっているか

とりあえず runc を起動します。

sudo runc

checkpoint/restoreの検証用に別ターミナルにてmemcachedにデータを保存しておきます。

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
set test 0 0 4
test
STORED
quit
Connection closed by foreign host.

checkpoint を実行します

$ sudo runc checkpoint
$ tree checkpoint
checkpoint/
├── cgroup.img
├── core-1.img
├── core-5.img
├── core-6.img
├── core-7.img
├── core-8.img
├── core-9.img
├── creds-1.img
├── descriptors.json
├── eventpoll.img
├── fdinfo-2.img
├── fs-1.img
├── ids-1.img
├── inetsk.img
├── inventory.img
├── ipcns-var-8.img
├── mm-1.img
├── mountpoints-10.img
├── pagemap-1.img
├── pages-1.img
├── pipes-data.img
├── pipes.img
├── pstree.img
├── reg-files.img
├── sigacts-1.img
├── tmpfs-dev-48.tar.gz.img
├── tmpfs-dev-50.tar.gz.img
├── tmpfs-dev-51.tar.gz.img
├── unixsk.img
└── utsns-9.img

0 directories, 30 files

checkpoint ディレクトリに状態が保持されていることが確認できます。

restore

$ sudo runc restore

別ターミナルにて確認します。

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
get test
VALUE test 0 4
test
END
quit
Connection closed by foreign host.

データを保持した状態で restore されたことが確認できました。

コンテナの状態を監視する

events コマンドで状態を確認できます。

sudo runc events

下記のような情報が取れます。

{
  "type": "stats",
  "id": "memcached",
  "data": {
    "Interfaces": null,
    "CgroupStats": {
      "cpu_stats": {
        "cpu_usage": {
          "total_usage": 58612299,
          "percpu_usage": [
            22633414,
            12378373,
            22347540,
            1252972
          ],
          "usage_in_kernelmode": 10000000,
          "usage_in_usermode": 20000000
        },
        "throttling_data": {}
      },
      "memory_stats": {
        "cache": 40960,
        "usage": {
          "usage": 40960,
          "max_usage": 1953792,
          "failcnt": 0
        },
        "swap_usage": {
          "failcnt": 0
        },
        "kernel_usage": {
          "failcnt": 0
        },
        "stats": {
          "active_anon": 40960,
          "active_file": 0,
          "cache": 40960,
          "hierarchical_memory_limit": 18446744073709552000,
          "inactive_anon": 0,
          "inactive_file": 0,
          "mapped_file": 0,
          "pgfault": 608,
          "pgmajfault": 0,
          "pgpgin": 525,
          "pgpgout": 515,
          "rss": 0,
          "rss_huge": 0,
          "total_active_anon": 40960,
          "total_active_file": 0,
          "total_cache": 40960,
          "total_inactive_anon": 0,
          "total_inactive_file": 0,
          "total_mapped_file": 0,
          "total_pgfault": 608,
          "total_pgmajfault": 0,
          "total_pgpgin": 525,
          "total_pgpgout": 515,
          "total_rss": 0,
          "total_rss_huge": 0,
          "total_unevictable": 0,
          "total_writeback": 0,
          "unevictable": 0,
          "writeback": 0
        }
      },
      "blkio_stats": {},
      "hugetlb_stats": {
        "2MB": {
          "failcnt": 0
        }
      }
    }
  }
}

コンテナのデータ/情報はどこにあるか

/run/oci/<コンテナID> にあります。

/run/oci/memcached
├── criu.work
│   ├── dump.log
│   ├── restore.log
│   ├── stats-dump
│   └── stats-restore
└── state.json

1 directory, 5 files

ちなみにコンテナIDには runc を起動したディレクトリ名がデフォルト値として使われるようです。 何かしらのバグを踏んだり、正しく終了しないとここにデータが残り、再度起動するときにこけたりするので、その場合はここのディレクトリごと削除すればうまくいきます。

コンテナの中に入る

docker exec のようなコマンドは用意されていないので、nsenter( https://github.com/jpetazzo/nsenter ) を利用します。

nsenter は各 namspace に入るためのツールで、runc でも問題なく利用できます。

まず起動しているコンテナの namespace を確認します。

$ sudo cat /run/oci/memcached/state.json | jq '.namespace_paths'
{
  "NEWIPC": "/proc/4107/ns/ipc",
  "NEWNET": "/proc/4107/ns/net",
  "NEWNS": "/proc/4107/ns/mnt",
  "NEWPID": "/proc/4107/ns/pid",
  "NEWUSER": "/proc/4107/ns/user",
  "NEWUTS": "/proc/4107/ns/uts"
}

PID 4107 で起動しているのでここに入ります。

$ sudo /usr/bin/nsenter --target 4107 --mount --uts --ipc --net --pid
root@shell:/# ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        17  0.0  0.0  20256  3200 ?        S    13:52   0:00 -sh
root        20  0.0  0.0  17488  2064 ?        R+   13:52   0:00  \_ ps auxf
root         1  0.0  0.0 322152  1664 ?        Ssl  13:30   0:00 /usr/local/bin/memcached -u root

無事に入れました。

さいごに

現時点でrunCでできる事をひと通り試してみました。 今回検証するにあたってもバグを幾つか踏んだので、本格的に利用できるのはまだまだ先になるかと思います(出て来たばかりなので当たり前ですが)。 ここまでシンプルなものであれば、既存のツールとの連携や、新規ツール等を開発するなど容易にできそうです。 コンテナを利用するためのひとつの選択肢にはなり得るので今後もチェックしていこうかと思います。