読者です 読者をやめる 読者になる 読者になる

TenForward

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

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

Container

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

先に書いた シェルスクリプトで書かれた軽量コンテナ MINCS がすばらしい (1) - TenForwardの日記 は私もびっくりの、このブログを書き始めて以来のはてぶ数に到達しました。MINCS がすばらしいからですね :-)

こないだ書いてからも MINCS はかなり活発に開発が進んでおり、かなり変化していますね。とりあえずもう少しデフォルトの動きを見てみようと思っておっかけたメモです (このエントリはあまり参考にならない気がします ^^;)。

minc コマンドの主要な処理を行っているのは libexec にインストールされる minc-exec です。

minc-exec は最後の行で以下のように unshare コマンドを実行しています。

$IP_NETNS unshare -iumpf $0 $@

つまり unshare の引数に自身 (minc-exec) を指定しており、自身の中に以下のように新しい Namespace で起動したときの処理を記述しています。

if [ $$ -eq 1 ]; then
  :
fi

この中の処理を少し追いましょう。

マウントの伝播を private に設定

systemd は mount propagation を shared にマークしてしまうので、せっかく新しい Mount Namespace を作成しても、Mount Namespace 内のマウントが他の Namespace に伝播してしまいます。これではコンテナにならないので、まずはこれを private にしています。

mount --make-rprivate /

overlayfs マウント

次に libexec/minc-coat を呼び、overlayfs でマウントを行います。minc-coat の該当部分を見ると、

mount -t overlay -o upperdir=$UD,lowerdir=$BASEDIR,workdir=$WD overlayfs $RD

こんな感じにマウントしています。コンテナ用のディレクトリはあらかじめ mktemp -d /tmp/minc$$-XXXXXX のように作られており、この下に upperdir, workdir, コンテナ用root を作成します。

特にオプションを指定せずに実行すると以下のようなマウントを実行します (minc1149-aEYmn0 は mktemp -d /tmp/minc$$-XXXXXX で作成したディレクトリ)。

mount -t overlay -o upperdir=/tmp/minc1149-aEYmnO/storage,lowerdir=/,workdir=/tmp/minc1149-aEYmnO/work overlayfs /tmp/minc1149-aEYmnO/root

つまり

  • overlayfs の upperdir は "/tmp/minc$$-XXXXXX/storage"
  • overlayfs の lowerdir は /
  • overlayfs の workdir は "/tmp/minc$$-XXXXXX/work"
  • overlayfs をマウントするディレクトリは "/tmp/minc$$-XXXXXX/root"

という感じになります。

ホスト名の設定

コンテナ名が指定されていたり、コンテナ内にコンテナ名を指定するファイルがあれば設定します。もちろん UTS Namespace が作成されているので、ホストには影響ありません。

  if [ "$MINC_UTSNAME" ]; then
    hostname $MINC_UTSNAME
    echo $MINC_UTSNAME > $MINC_TMPDIR/utsname
  elif [ -f $MINC_TMPDIR/utsname ]; then
    hostname `cat $MINC_TMPDIR/utsname`
  fi

/dev

"--usedev" オプションを指定しなければコンテナ内の /dev (/tmp/minc$$-XXXXXX/root/dev) は tmpfs でマウントされます。

そしてコンテナの /dev/pts はコンテナ専用に独立した /dev/pts としてマウントされます。ホストの /dev/pts を bind mount したりするとホストとコンテナの devpts が共通化されてしまい、コンテナから出力したらホストに出たりしてまずいので。この辺りはカーネル付属の filesytems/devpts.txt をどうぞ。

mkdir $RD/dev/pts
mount devpts -t devpts -onoexec,nosuid,gid=5,mode=0620,newinstance,ptmxmode=0666 $RD/dev/pts
ln -s /dev/pts/ptmx $RD/dev/ptmx

あとは適当に必要なデバイスファイルを bind mount します。

/proc

この時点ではホストの /proc は見えていますので、これを読み込み専用でマウントします。そしてコンテナ用の /proc をマウントします。以下のような処理を行っています。

mount -t proc -o ro,nosuid,nodev,noexec proc /proc
mount -t proc -o rw,nosuid,nodev,noexec,relatime proc $RD/proc

以上を実行する前の状態だと /proc は以下のような状態になっており、ホストのプロセスの状態が見えています (プロセスがたくさんあることからわかります)。

# ls -1F /proc
1/
10/
1019/
11/
12/
1200/
1201/
125/
13/
132/
134/
135/
137/
139/
  :()

処理後は /proc も以下のようになります。この例は、処理途中の様子を見るためにbashを実行しているので PID 1 の minc-exec の他に bash と ls のプロセスが存在しています。

# ls -1F  /proc
1/
36/
50/
  :()

この後に /proc 以下のセキュリティ的にヤバそうなファイルや /sys を bind mount しています。さきほどの 2 行の proc のマウントで /proc は読み込み専用でマウントしていましたので、bind mount するとこれらは読み込み専用になります。うまく考えられてますね。:-)

/ の変更

この後いよいよ pivot_root で / を変更します。

cd $RD
mkdir -p .orig
pivot_root . .orig
grepumount -e "^/\.orig/"

"pivot_root . .orig" で /tmp/minc$$-XXXXXX/root を新しい / にして、元を .orig にマウントします。この pivot_root 直後の状態は .orig 以下のものがたくさんマウントされた状態です。

# cat /proc/mounts 
/dev/mapper/vivid--vg-root /.orig ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
udev /.orig/dev devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
devpts /.orig/dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /.orig/dev/shm tmpfs rw,nosuid,nodev 0 0
hugetlbfs /.orig/dev/hugepages hugetlbfs rw,relatime 0 0
mqueue /.orig/dev/mqueue mqueue rw,relatime 0 0
tmpfs /.orig/run tmpfs rw,nosuid,noexec,relatime,size=101696k,mode=755 0 0
  :()
sysfs /.orig/sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
securityfs /.orig/sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /.orig/sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0
cgroup /.orig/sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
  :()
pstore /.orig/sys/fs/pstore pstore rw,nosuid,nodev,noexec,relatime 0 0
fusectl /.orig/sys/fs/fuse/connections fusectl rw,relatime 0 0
debugfs /.orig/sys/kernel/debug debugfs rw,relatime 0 0
proc /.orig/proc proc rw,nosuid,nodev,noexec,relatime 0 0
systemd-1 /.orig/proc/sys/fs/binfmt_misc autofs rw,relatime,fd=35,pgrp=0,timeout=300,minproto=5,maxproto=5,direct 0 0
/dev/vda1 /.orig/boot ext2 rw,relatime 0 0
overlayfs / overlay rw,relatime,lowerdir=/,upperdir=/tmp/minc1718-shaYgx/storage,workdir=/tmp/minc1718-shaYgx/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 /.orig/proc proc ro,nosuid,nodev,noexec,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

この状態で .orig をアンマウントしたいところですが、実はそれは以下のように一部失敗します。overlayfs の lowerdir を元の / にしているからですね。

# cut -f 2 -d " " < /proc/mounts | grep -e "^/\.orig/" | sort -r | xargs umount
umount: /.orig/proc/sys/fs/binfmt_misc: not mounted
umount: /.orig/proc: target is busy
        (In some cases useful info about processes that
         use the device is found by lsof(8) or fuser(1).)
# cat /proc/mounts
/dev/mapper/vivid--vg-root /.orig ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
proc /.orig/proc proc rw,nosuid,nodev,noexec,relatime 0 0
systemd-1 /.orig/proc/sys/fs/binfmt_misc autofs rw,relatime,fd=35,pgrp=0,timeout=300,minproto=5,maxproto=5,direct 0 0
overlayfs / overlay rw,relatime,lowerdir=/,upperdir=/tmp/minc1718-shaYgx/storage,workdir=/tmp/minc1718-shaYgx/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

/.orig とかマウントされたままです。umount はできませんでしたので、.orig 内に移動して再度 pivot_root します。

cd /.orig/
pivot_root . dev/

この2度目の pivot_root 直後はこんなです。

# cat /proc/mounts 
/dev/mapper/vivid--vg-root / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
  :()
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
  :()
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
  :()
/dev/vda1 /boot ext2 rw,relatime 0 0
overlayfs /dev overlay rw,relatime,lowerdir=/,upperdir=/tmp/minc1814-ESDwdP/storage,workdir=/tmp/minc1814-ESDwdP/work 0 0
tmpfs /dev/dev tmpfs rw,relatime 0 0
devpts /dev/dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
udev /dev/dev/console devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
udev /dev/dev/null devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
udev /dev/dev/zero devtmpfs rw,relatime,size=496816k,nr_inodes=124204,mode=755 0 0
mqueue /dev/dev/mqueue mqueue rw,relatime 0 0
proc /proc proc ro,nosuid,nodev,noexec,relatime 0 0
proc /dev/proc proc rw,nosuid,nodev,noexec,relatime 0 0
proc /dev/proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0
proc /dev/proc/sysrq-trigger proc ro,nosuid,nodev,noexec,relatime 0 0
proc /dev/proc/irq proc ro,nosuid,nodev,noexec,relatime 0 0
proc /dev/proc/bus proc ro,nosuid,nodev,noexec,relatime 0 0
sysfs /dev/sys sysfs rw,nosuid,nodev,noexec,relatime 0 0

この状態で /dev は元の / がマウントされていて、他に余計なものが見えない状態ですので chroot /dev して、ここを / にします。

RD=dev/
  :
exec $MINC_DEBUG_PREFIX chroot $OPT $RD $@

これで minc コマンドで指定されたコマンドが Namespace 内でコンテナ用の新しい / 以下で実行されますね。

と、ほとんど私が複数回 pivot_root 行われているけど、それぞれどういう意味だ? とおっかけた個人メモ的なわかりづらいエントリになってしまいました。

minc --debug /bin/bash

とやって実際どういうコマンドが実行されているのか観察したり、実際にやってることをおっかけたりすると良いと思いますよ。