シェルスクリプトで書かれた軽量コンテナ MINCS がすばらしい (1)
これはだいぶ前に書いたエントリです。MINCS作者による最新の解説があるのでそちらもご覧ください。 (2016-11-21追記)
コンテナは使いたいけど、たくさんコンテナを起動すると結局それぞれのコンテナに対するセキュリティアップデートなどのメンテナンスは必要だし、コンテナ内独自のプログラムやライブラリ以外はホストと共有したいよね、って話が出てきたりします。みんな考えることは同じで、bind mount を使えば良いよね、って話はでてきてました。
- こないだもブログで紹介した kazuho さんの jailing
- 私が LXC でも結構簡単にできるよ、っていう提案を兼ねて作った lxc-bind
- こないだのLinuxConでもDockerでたくさんのオプションを並べて、色々工夫して bind mount を使ってやってる発表もありました (Using Docker for existing installed OS and applications, running half inside half outside the container ながっ)。
この 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
という感じで作っただけという感じなので、その辺りはなんとかする必要はありそうです。