TenForward

技術ブログ。はてなダイアリーから移転しました

シェルスクリプトで書かれた軽量コンテナ MINCS がすばらしい (1)

これはだいぶ前に書いたエントリです。MINCS作者による最新の解説があるのでそちらもご覧ください。 (2016-11-21追記)

コンテナは使いたいけど、たくさんコンテナを起動すると結局それぞれのコンテナに対するセキュリティアップデートなどのメンテナンスは必要だし、コンテナ内独自のプログラムやライブラリ以外はホストと共有したいよね、って話が出てきたりします。みんな考えることは同じで、bind mount を使えば良いよね、って話はでてきてました。

この LinuxCon のセッション中に、ホストと色々共有しつつ (*) 簡単にコンテナ環境を作成できる、シェルスクリプトで書かれたコンテナ実装を知りました。これが @mhiramat さんの MINCS です。セッション中に早速軽く見て、これは素晴らしいってことで帰ってから少し調べたりしていました。

(*) ホストと共有しないこともできそうです。

どうやってコンテナを作っているか

MINCS がコンテナを作成する方法のキモは以下の 3 つかと思います。

  • unshare コマンドと ip netns コマンドで Namespace を作成
  • overlayfs の下層側 (lowerdir) にホストの / (ルート) を使い、コンテナディレクトリをマウント
  • pivot_root でコンテナの / に移動

なのでキモの部分だけ抜き出すと

mount -t overlayfs ...
pivot_root ...
ip netns unshare -iumpf ...

の 3 行になってしまうシンプルさ(*)!! これだけでも素晴らしいのですが、これだけではコンテナ環境はできないので、それまでに色々細かい処理をやっています。その処理がシェルスクリプトなので簡単に追っていけるので、それも素晴らしいです。

(*) 実際は pivot_root を複数回行って chroot します。

軽くおためし

MINCS を使える環境は比較的新しい環境になります。それは、unshare コマンド (util-linux パッケージに入っています) でキチンと Namespace を作るには比較的新しい環境が必要なのと、カーネルが overlayfs をサポートしている必要があるためです (Ubuntu であれば 12.04 辺りからパッチ適用で overlayfs が使えます。ただし util-linux は古いです)。unshare は README.md にもあるように 2.24 以上が必要です。

(話はそれますが unshare コマンドについて少し:MINCS では使ってませんが、User Namespace をきちんと使おうとすると 2.26 が必要そうです。2.24 -> 2.25 間に便利なオプションが追加されてますが、2.26 にならないとちゃんと動きません --map-root-user。2.24 で追加されている --mount-proc も便利そう。)

私は Ubuntu 15.04 上で試しました。

インストールは git clone して、付属の install.sh を実行するだけです。/usr/local 以下に入ります。

一番簡単に使うには

$ sudo minc /bin/bash
root@mincs01:/#

こんな感じになります。README.md に書かれていないオプションもあるので確認しましょう。

$ minc --help
/usr/local/bin/minc - Run given command in a temporary namespace
Usage: /usr/local/bin/minc [options] <command> [argument...]
 options:
    -h or --help        Show this help
    -k or --keep        Keep the temporary directory
    -t or --tempdir <DIR>  Set DIR for temporary directory (imply -k)
                    <UUID> Reuse UUID named container
    -r or --rootdir <DIR>  Set DIR for original root directory
                    <UUID> Use UUID named container image
    -X or --X11         Export local X11 unix socket
    -n or --net         Use network namespace
    -c or --cpu <mask>  Set CPU mask
    -p or --pty         Assign new pty for the container
    --name <NAME>       Set <NAME> as container's name (hostname)
    --user <USER>[:GROUP]  specify user and group (ID or name) to use
    --simple            Simple chroot model (do not pivot_root)
    --usedev		Use devtmpfs for /dev (for loopback etc.)
    --debug             Debug mode

ホスト名が付いていたほうが分かりやすいので --name を付けてみましょう。

$ sudo minc --name container /bin/bash 
root@container:/# hostname
container

コンテナの中を少し見てみると、

root@container:/# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.5  22512  5240 ?        S    19:58   0:00 /bin/bash
root        48  0.0  0.2  18480  2636 ?        R+   19:59   0:00 ps aux

PID Namespace も分離されているようですし、

root@container:/# cat /proc/mounts 
overlayfs / overlay rw,relatime,lowerdir=/,upperdir=/tmp/minc1300-WwY7qz/storage,workdir=/tmp/minc1300-WwY7qz/work 0 0
tmpfs /dev tmpfs rw,relatime 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
udev /dev/console devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
udev /dev/null devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
udev /dev/zero devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
mqueue /dev/mqueue mqueue rw,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0
proc /proc/sysrq-trigger proc ro,nosuid,nodev,noexec,relatime 0 0
proc /proc/irq proc ro,nosuid,nodev,noexec,relatime 0 0
proc /proc/bus proc ro,nosuid,nodev,noexec,relatime 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0

/ は確かに overlayfs でマウントされているようですし、/proc 以下のヤバいファイルは Read-only でマウントされているようですね。

Network Namespace も分離するには -n か --net オプションを付けます。

$ sudo minc --net --name container /bin/bash 
root@container:/# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: vminc1369: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 6a:d9:17:92:bb:9a brd ff:ff:ff:ff:ff:ff

一応、veth ペアも作られて、コンテナに片方が割り当てられているようですね。アドレスは割りあたってませんし、ホスト側も

$ ip a
  :(snip)
3: veth1369: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 56:00:fe:13:93:28 brd ff:ff:ff:ff:ff:ff

という感じで作っただけという感じなので、その辺りはなんとかする必要はありそうです。

シェルスクリプトで書かれた軽量コンテナ MINCS がすばらしい (2) - TenForwardの日記』 へ続く