[DIY] 一日限りの復活(?) 家を建てる日記 番外編 - 年に一度の恒例のお手入れ
実は記事数は少ないものの、このブログの一番のアクセス数を稼いでいるDIYカテゴリ。久々に帰ってきました。
むかーし家を建ててる時の現場の写真を PukiWiki に簡易的なブログ的に記録してたことがありました。その Wiki はもう全く更新していないのですが、その続編みたいな。
最近、mizzy さんの フリーランスが家を建てる日記 が更新されるたびに楽しく拝見してます。人の家でもどんどん出来上がっていくの見るのは楽しいですね。
それに触発されて、年に一回、家の一階のワックスがけをやるのですが、先日それをやった様子を書いておきます。
まずはリビング、ダイニング、キッチンのものを全て別の部屋に移動します。今流行りのミニマリストの家みたいになりました。ウチってこんなに広かったんだ!!
これが我が家のワックスがけセット。
「床クリーナー」をぬるま湯で薄めて、床を水拭きするわけですね。このクリーナーはワックスを剥離できるので、ワックス表面についた汚れも取れますね。ひどいところは原液を垂らしてゴシゴシすると、表面のワックスが面白いように剥がれて汚れが取れますね。床に直接膝をついて作業してると痛くなってくるので、毛布に乗ってやるわけです。毛布はリビングのテレビとか家具を移動させるときにも使いますよ。
定規は床にこべりついたものを剥がすのに使います。剥がれたゴミはコロコロで取ります。定規をヘラのように使うと表面のワックスもはがせます。
この水拭きに数時間かかって、その後のワックスはほんの 30 分ほどで済んでしまいますね。ハイ、出来上がり。
Linux 4.3 の Process Number Controller (1)
連載書いたり、勉強会で発表したりしているとなかなかブログが更新できませんね。久々の更新です。
これを書いている時点では Linux カーネルの 4.3-rc1 がリリースされていますが、久々に cgroup に新しいコントローラが追加されそうですね。
説明が不要なほどシンプルで分かりやすいです。このブログエントリ読まなくても使えるはず。:-)
軽く使ってみました。大体は上記ドキュメントに沿って試してるだけです。
カーネル
カーネルの config では Cgroup 関連の設定以下に "PIDs cgroup subsystem" という項目が新設されているので、ここを有効にしてカーネルを作成するだけです。
General setup ---> Control Group support ---> [*] PIDs cgroup subsystem
起動すると /proc/cgroups に pids が現れます。
# cat /proc/cgroups #subsys_name hierarchy num_cgroups enabled cpuset 1 1 1 cpu 2 1 1 cpuacct 3 1 1 blkio 4 1 1 memory 5 1 1 devices 6 1 1 freezer 7 1 1 net_cls 8 1 1 perf_event 9 1 1 net_prio 10 1 1 hugetlb 11 1 1 pids 12 2 1 debug 13 1 1
マウント
まずはマウントしてみましょう。単一階層構造でなく、現在の仕様の cgroup でも使えるようです。
マウントは他のコントローラと同じです。例えば
# mkdir -p /sys/fs/cgroup/pids # mount -t cgroup -o pids none /sys/fs/cgroup/pids
なかにはどんなファイルがあるかな? ルートは以下のような感じでした。
# ls /sys/fs/cgroup/pids/ cgroup.clone_children cgroup.sane_behavior pids.current tasks cgroup.procs notify_on_release release_agent
"pids.current" が PIDs サブシステム独自のファイルのようですね。
cgroup の作成
グループを作成してみます。ディレクトリを作ることに変わりはありません。
# mkdir /sys/fs/cgroup/pids/test01 # ls /sys/fs/cgroup/pids/test01 cgroup.clone_children notify_on_release pids.max cgroup.procs pids.current tasks
"pids.current" と "pids.max" という 2 つのファイルがこのコントローラの独自のファイルのようですね。
制限の設定
ファイル名を見ただけで大体わかると思いますが、それぞれのファイルの役割は以下です。
- pids.current
- 現在のプロセス(タスク)数
- pids.max
- 許可するプロセス(タスク)数の最大値
先に作った "test01" の作成直後、つまり初期値は以下のようになっています。
# cat /sys/fs/cgroup/pids/test01/pids.current 0 # cat /sys/fs/cgroup/pids/test01/pids.max max
"tasks" ファイルに何も追加していないので、当然現在値は 0 です。制限値なしにするには "pids.max" に "max" と書くようで、これが初期値です。
制限値を 2 にしてみましょう。
# echo 2 > /sys/fs/cgroup/pids/test01/pids.max # cat /sys/fs/cgroup/pids/test01/pids.max 2
きちんと設定されました。
現在のシェルをグループに追加します。
# echo $$ | tee /sys/fs/cgroup/pids/test01/tasks 5600 # cat /sys/fs/cgroup/pids/test01/pids.current 2
作成後に現在値を見ると、追加したシェルと実行した cat コマンドで 2 となっていますね。では、ここで 2 つ同時にコマンドを実行してみましょう。
# ( /bin/echo "Here's some processes for you." | cat ) bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: Resource temporarily unavailable Terminated
シェルで 1 消費している状態で 2 つ実行しようとしたので、プロセスを生成できませんでした。
当たり前ですが、ちゃんと制限が効いていますね。
階層構造
上記の "test01" の子グループとして "test02" を作り、"test01" には制限をかけ、"test02" には制限をかけない状態にしてみます。
# mkdir /sys/fs/cgroup/pids/test01/test02 # echo 2 > /sys/fs/cgroup/pids/test01/pids.max # cat /sys/fs/cgroup/pids/test01/pids.max (test01の制限値は2) 2 # cat /sys/fs/cgroup/pids/test01/test02/pids.max (test02は無制限) max
以下のような設定です。
/sys/fs/cgroup/pids/ └── test01 --> (pids.max = 2) └── test02 --> (pids.max = max)
この状態で "test02" で "test01" の制限を超えるプロセスを起動してみます。
# echo $$ > /sys/fs/cgroup/pids/test01/test02/tasks (1 つ追加) # ( /bin/echo "Here's some processes for you." | cat ) bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: retry: No child processes bash: fork: Resource temporarily unavailable Terminated
起動しませんね。つまり複数の階層がある場合は、上位の階層の制限にひとつでも引っかかればダメということです。
制限を設定した状態でグループのタスクを増やす
"pids.max" を設定した状態で、グループの "tasks" にたくさんプロセスを追加していったらどうなるでしょう?
# echo 2 > /sys/fs/cgroup/pids/test01/pids.max # cat /sys/fs/cgroup/pids/test01/pids.max 2
"test01" に 2 の制限を設定しました。別途起動してあるプロセスを追加していってみましょう。
# echo 5954 > /sys/fs/cgroup/pids/test01/tasks # echo 5955 > /sys/fs/cgroup/pids/test01/tasks # echo 5956 > /sys/fs/cgroup/pids/test01/tasks #
おや、特にエラーにならずに、追加できてしまっています。
# cat /sys/fs/cgroup/pids/test01/tasks (追加したタスクは全部存在する) 5954 5955 5956 # cat /sys/fs/cgroup/pids/test01/pids.max (制限値は2に設定されている) 2 # cat /sys/fs/cgroup/pids/test01/pids.current (現在値は3で、制限値より多い) 3
つまり fork() や clone() で新しいプロセスを起動しようとする時にだけ制限が効くということですね。
存在するプロセス数以下の制限値を設定する
では、プロセスが既にグループに存在する状態で、その数以下に "pids.max" を設定してみます。
# echo 2 > /sys/fs/cgroup/pids/test01/pids.max (制限値は2) # cat /sys/fs/cgroup/pids/test01/tasks 5968 5970 # cat /sys/fs/cgroup/pids/test01/pids.current (現在2つのタスクが存在) 2
制限値以下であるふたつのタスクが存在していますが、ここで制限値を 1 に減らしてみましょう。
# echo 1 > /sys/fs/cgroup/pids/test01/pids.max (制限値を1に下げる) # cat /sys/fs/cgroup/pids/test01/tasks 5968 5970 # cat /sys/fs/cgroup/pids/test01/pids.current (タスクは2つのまま) 2
タスクは 2 つのままですね。
まとめ
Linux 4.3-rc1 環境で PIDs コントローラを試してみました。
- 制限値を設定しておくと、グループ内で fork() や clone() で新たに起動するプロセスを制限できる
- グループの "tasks" にプロセスを追加したり、制限値を変更したりしても、制限にひっかからない。つまり "pids.max" < "pids.current" となることもある
- グループに属することのできるタスク数は、そのグループの上位(祖先)のグループの制限を受ける
つづくかも...
シェルスクリプトで書かれた軽量コンテナ MINCS がすばらしい (2)
これはだいぶ前に書いたエントリです。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
とやって実際どういうコマンドが実行されているのか観察したり、実際にやってることをおっかけたりすると良いと思いますよ。
aufs を使った一般ユーザ権限で起動するコンテナ
LXC ではコンテナのクローンを行う際に色々なストレージバックエンドの特徴を生かしたスナップショットクローンを行えます。この辺りは 連載の第 19 回〜 22 回 辺りで詳しく解説しています。
今まで、非特権LXCコンテナでストレージバックエンドの特徴を生かしたスナップショットクローンは、btrfs か overlayfs でしか行えませんでした。
先日、私の送ったパッチで aufs を使ったスナップショットクローンが一般ユーザ権限でもできるようになりました。
- aufs: Support unprivileged clone, mount · lxc/lxc@31a882e · GitHub
- Support unprivileged ephemeral container using aufs · lxc/lxc@dc80aa9 · GitHub
前者は liblxc 側の変更で lxc-clone や lxc-start 側で関係するパッチ、後者は lxc-start-ephemeral コマンドに関係するパッチです。
これで一般ユーザでも aufs を使った非特権クローンとコンテナが起動できるようなりました。簡単に紹介しておきます。
Plamo 5.3.1 に aufs パッチを当てた 4.1.1 カーネルを使っています。
$ lsb_release -d Description: Plamo Linux release 5.3.1 $ uname -r 4.1.1-plamo64-aufs
最近の aufs では allow_userns というモジュールオプションを Y にすると User Namespace 内の特権ユーザが aufs をマウントできるようになります。マニュアルにも記載があります。
私も手元では以下のように設定しています。
$ cat /etc/modprobe.d/aufs.conf options aufs allow_userns=1
こんな一般ユーザで操作しています。
$ id uid=1000(karma) gid=100(users) groups=100(users),26(audio),28(dialout),29(video),32(cdrom),36(kvm),38(pulse),39(pulse-access),44(mlocate),47(libvirt),60(docker),1000(sudo)
まず、普通に dir バックエンドを使ったコンテナを作成します。以下がその config の rootfs の設定。
$ grep lxc.rootfs ~/.local/share/lxc/ct01/config lxc.rootfs = /home/karma/.local/share/lxc/ct01/rootfs
このコンテナのクローンを作成します。
$ lxc-clone -o ct01 -n aufs01 -s -B aufs Created container aufs01 as snapshot of ct01 $ lxc-ls -f NAME STATE IPV4 IPV6 GROUPS AUTOSTART ---------------------------------------------- aufs01 STOPPED - - - NO ct01 STOPPED - - - NO
クローンは成功しています。
$ grep lxc.rootfs ~/.local/share/lxc/aufs01/config lxc.rootfs = aufs:/home/karma/.local/share/lxc/ct01/rootfs:/home/karma/.local/share/lxc/aufs01/delta0
こんな感じに aufs を使うコンテナのルートファイルシステムの定義がされています。
起動してみます。Plamo では cgmanager とか systemd-logind とかないので、起動前には自分で一般ユーザ権限の cgroup を作成して、現在のシェルの PID を登録してから起動しています。
$ lxc-start -n aufs01 -d $ lxc-ls -f NAME STATE IPV4 IPV6 GROUPS AUTOSTART ------------------------------------------------------ aufs01 RUNNING 10.0.100.179 - - NO ct01 STOPPED - - - NO
無事起動しましたね。
なぜ今 aufs ?
以上が動きと機能の紹介でした。でも、overlayfs がカーネルにマージされた今、なぜ aufs なのか? ってところですが...
色々なファイルシステムを扱う場合は特権が必要となります。例え User Namespace を使った Namespace 内の特権であっても、ファイルシステムをマウントできなかったりします。
User Namespace 内の特権ユーザがファイルシステムをマウントするには、カーネル内でファイルシステムのフラグに "FS_USERNS_MOUNT" というフラグが指定されている必要があります。(参考: overlayfs と LXC 非特権コンテナの snapshot によるクローン - TenForwardの日記)
このフラグ、4.1.1 カーネルで指定されているファイルシステムを調べてみると
$ find fs/ -type f | xargs grep FS_USERNS_MOUNT fs/devpts/inode.c: .fs_flags = FS_USERNS_MOUNT | FS_USERNS_DEV_MOUNT, fs/proc/root.c: .fs_flags = FS_USERNS_MOUNT, fs/ramfs/inode.c: .fs_flags = FS_USERNS_MOUNT, fs/sysfs/mount.c: .fs_flags = FS_USERNS_MOUNT, fs/namespace.c: if (!(type->fs_flags & FS_USERNS_MOUNT)) {
最後の (fs/namespace.c) は関係ないので devpts, proc, ramfs, sysfs だけです。この辺りはマウントできないとそもそも非特権のシステムコンテナが起動できなかったりするので指定されていて当然という気がしますね。つまり普通のファイルシステムはこれがそもそも指定されていないわけです。
overlayfs もこのフラグは指定されていません。なのに LXC で非特権 overlayfs がサポートされているのはなぜか? という話ですが、これは Ubuntu のカーネルにパッチが当たっているからです。パッチがあたっていないバニラカーネルだと当然これは失敗します。
$ lxc-clone -o ct01 -n overlay01 -s -B overlayfs clone failed
私の使っている 4.1.1 の overlayfs には特に何の変更も加えていないので、このように失敗します。
一方、aufs はカーネルにはマージされていないものの、最新の aufs が使える環境では素の aufs で User Namespace 内の特権ユーザが aufs をマウントできるわけです。この辺りからでしょうか。
まあ、パッチの量の大小の差はあるにせよ、overlayfs も aufs もバニラカーネルにパッチを当てないと非特権コンテナでは使えないわけで、なら aufs の非特権コンテナサポートを追加することで少しでも非特権コンテナのサポートする範囲が広がれば良いかなと思ってパッチを作りました。
あ、ちなみに Ubuntu のカーネルに当たっている aufs は古いバージョンなので、allow_userns は使えない模様です。Ubuntu は aufs 止めちゃうみたいですものね。
ま、LXC の overlayfs 周りとかは以前もパッチ送ったことがあってコードをよく知ってたので、非特権 aufs 対応はさほど難しくないって分かってたから作ったんですけどね。(^_^;)
シェルスクリプトで書かれた軽量コンテナ 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
という感じで作っただけという感じなので、その辺りはなんとかする必要はありそうです。