コンテナとは何か?
コンテナの基礎知識について非常によくまとめられている。おすすめ。
MINCS
シェルスクリプトで実装されている軽量のコンテナランタイム。Docker などといった高度に抽象化されているコンテナランタイムを比較すると、コンテナを構成する基礎的な要素をシェルスクリプトで直接的に操作している。コンテナを構成する要素をどのように作成しているのか分かりやすく、学習用のコンテナランタイムとして優れている。
今回は一番最初のステーブルブランチ( )のソースコードを読んでいくことにする。master ブランチと比較すると機能は最小限のみになっていて、理解しやすいと判断。
予備知識
ソースコードを読むにあたってシェルスクリプトに関するいくつかの予備知識を整理しておく。
mount --bind
バインドマウントを実施する。
Copy mount --bind olddir newdir
以下のような動きになる。バインドマウントは、表面的な動作はシンボリックリンクと同じように見えるが違う。後述。
Copy # mkdir -p source dest
# ls -l source/
合計 0
# ls -l dest/
合計 0
# echo "This is source" > source/example.conf
# ls -l source/
合計 4
-rw-r--r-- 1 root root 15 12月 13 08:43 example.conf
# ls -l dest/
合計 0
# mount --bind source/ dest/
# ls -l dest/
合計 4
-rw-r--r-- 1 root root 15 12月 13 08:43 example.conf
上記の記事では(少なくとも)以下の点で異なるとのこと
chroot を用いた場合は、シンボリックリンクが chroot の外側にある場合はシンボリックリンクが無効になる。バインドマウントの場合は引き続きアクセスすることができる。
既存のディレクトリやファイルにディレクトまたはファイルをバインドマウントし、元のコンテンツを保護することができる。(別の場所からバインドマウントされていない限り、元のコンテンツにアクセスできなくなる)。シンボリックリンクでは元のコンテンツを移動または削除する必要がある。
シンボリックリンクはファイルだが、バインドマウントはカーネルがメモリ上に保持するレコードである。(再起動したときなどにそのまま保持されるかどうかなどの違いがある)
※全然関係ないけど、ここで表示されている 合計 xx
は割り当てられているブロック数
$0
実行したシェルスクリプト名を取得できる。
$@
実行時のパラメータをそれぞれクオートして渡す。 $*
の場合は全体として一つのパラメータとして渡す。
mount --make-rprivate /
以下のコマンドはプライベートとして再マウントするコマンド。
Copy mount --make-rprivate /
マウントの伝播を private に設定
systemd は mount propagation を shared にマークしてしまうので、せっかく新しい Mount Namespace を作成しても、Mount Namespace 内のマウントが他の Namespace に伝播してしまいます。これではコンテナにならないので、まずはこれを private にしています。
デフォルトのカーネルのマウントのモードだと、共有モード(MS_SHARED)でマウントされる。完全に独立したマウント名前空間を必要としている場合はマウントポイントをプライベート(MS_PRIVATE)にする必要があるとのこと。
mountpoint
マウントポイントかどうか調べるコマンド
/proc/self/mountinfo
に存在するかどうかをチェックしている。マウントポイントの場合は返り値が 0 でそうでない場合は非 0 を返す。
Copy mountpoint [-q] [-d] directory
mount -t overlay
overlayfsはunion filesystemの実装の1つで,ディレクトリを重ね合わせて1つのディレクトリツリーが構成できます。
Copy mount -t overlayfs -o lowerdir=lower,upperdir=upper overlayfs overlay
重ねあわせの上層側のディレクトリ。書き込み可能なローカルのファイルシステムの必要がある。
overlayfs が使う作業ディレクトリ。upperdir と同じファイルシステムの名前空間内に存在する必要がある。
キュメントには色々書いているが、Overlayfsの基本的な考え方は以下の通り
Readonlyな下の層と、Writableな上の層を重ね合わせ1つのファイルシステムに見せる
このとき、ディレクトリは重ね合わされ、ファイルは上から見ていく
コンフリクトしていたらファイルだと上が優先され、ディレクトリだとマージされる
下(readonly)にしかないファイルを編集すると上にコピーが作成される
pivot_root
root ファイルシステムを変更するコマンド
pivot_root はカレントプロセスの root ファイルシステムを put_old ディレクトリに移動し、 new_root を新しい root ファイルシステムにする。 pivot_root(8) は pivot_root(2) を呼び出しているだけ
Copy pivot_root new_root put_old
Linuxのkernel 2.4系にはpivot_rootという仕組みがあり, これによってrootファイルシステムそのものを取り替えてしまうことができます. これは,rootディレクトリを変更するという点ではchrootにも似ていますが, chrootとは違って一部のプロセスだけでなく,OS全体のrootファイルシステムが 根本的に変更されます.
pivot_rootを実行すると,以前のrootファイルシステムはpivot_rootコマンドの 第2引数で指定したディレクトリにマウントされた状態になります.
動作としては pivot_root
と chroot
に大きな違いはないが、適用するコンテキストが異なる。(OS 全体 or プロセス)
chroot
ルートディレクトリを変更してコマンドを実行するコマンド
コマンドや対話的シェルを特別なルートディレクトリで実行する。ルートディレクトリを NEWROOT に設定してコマンドを実行する。 通常、ファイル名はディレクトリ構造のルート (root=根)、つまり `/' を起点として調べられる。 chroot はこのルートをディレクトリ directory に変更して command を実行する (directory は存在するディレクトリでなければならない)。
Copy chroot [OPTION] NEWROOT [COMMAND [ARG]...]
ディレクトリ構成
Copy /mincs
|--libexec ---------------- 主要なコマンドをまとめたディレクトリ
| |--minc-coat ----------- オーバーレイファイルシステムをマウントするためのスクリプト
| |--minc-exec ----------- コンテナランタイムを構築するための主要なコマンドを実行するスクリプト
| |--minc-farm ----------- 見てない
| |--minc-trapper -------- 見てない
|--minc ------------------- ユーザインターフェースになるスクリプト。オプションの解析などを実施し minc-exec を実行
|-- ...
全体的な処理の流れ
unshareによる、名前空間の(分岐という意味での)フォーク
mount/umountによる、新しいrootfsに対するoverlayや様々なマウントポイントの再整備
chroot(or pivot_root)による新しいrootfsへの移行と対象コマンドの実行
ソースコードの追い方
シェルスクリプトを上から読んでも問題ないが、一番初期のstableバージョン(minc-0.1)でも結構本格的な実装になっている。処理の動き的にはデバッグログを中心に見たほうがわかりやすい気がした。なので ./minc --debug /bin/bash
として出力される内容から抜粋して紹介する。
実行時の主要なコマンド
1.unshare で名前空間を分離
Copy unshare -iumpf ./libexec/minc-exec /bin/bash
カーネルの名前空間は全部で 6 つあった。minc の実装ではデフォルトだとネットワーク名前空間は分離されない。オプションで付与することは可能。
SysV IPCオブジェクト、POSIXメッセージキュー
ネットワークデバイス、アドレスポート、ルーティングテーブルなど
unshare コマンドによって新しい名前空間上で子プロセスが実行される。が名前空間の実態は親の名前空間を指している。PID名前空間を新しく作成すると、プロセスを新しく作成しないと新しい名前空間に入らないよう。
Copy # unshare -iumpf /bin/bash
# echo $$
1
2.プライベートマウント
上記の通り。コンテナホストとファイルシステムを分離する。
Copy mount --make-rprivate /
3.overlayfs でマウント
ディレクトリ名は minc
の中で mktemp -d /tmp/minc$$-XXXXXX
として作成している。
Copy mount -t overlay -o upperdir=/tmp/minc12443-1ur2tR/storage,lowerdir=/,workdir=/tmp/minc12443-1ur2tR/work overlayfs /tmp/minc12443-1ur2tR/root
/tmp/minc12443-1ur2tR/storage
/tmp/minc12443-1ur2tR/work
/tmp/minc12443-1ur2tR/root
4.周辺ディレクトリ(/dev, sysfs, procfs)の再構築
コンテナホストと分離するために周辺ディレクトリ(/dev, sysfs, procfs)をバインドマウントする
5.2 回に分けて root ファイルシステムを変更
まずコンテナホストのルートファイルシステムを .orig としてマウントポイントを付け替える
Copy cd /tmp/minc12443-1ur2tR/root
mkdir -p .orig
pivot_root . .orig
この状態だと元のファイルシステムは .orig/ にマウントされている。
Copy # cut -f 2 -d " " < /proc/mounts | grep '^/\.orig/'
/.orig/dev
/.orig/dev/shm
/.orig/dev/pts
/.orig/dev/mqueue
/.orig/dev/hugepages
/.orig/proc
/.orig/proc/sys/fs/binfmt_misc
/.orig/proc/fs/nfsd
/.orig/sys
/.orig/sys/kernel/security
/.orig/sys/fs/cgroup
/.orig/sys/fs/cgroup/systemd
/.orig/sys/fs/cgroup/blkio
/.orig/sys/fs/cgroup/freezer
/.orig/sys/fs/cgroup/memory
/.orig/sys/fs/cgroup/cpu,cpuacct
/.orig/sys/fs/cgroup/perf_event
/.orig/sys/fs/cgroup/hugetlb
/.orig/sys/fs/cgroup/net_cls
/.orig/sys/fs/cgroup/cpuset
/.orig/sys/fs/cgroup/devices
/.orig/sys/fs/pstore
/.orig/sys/kernel/config
/.orig/sys/kernel/debug
/.orig/run
/.orig/run/user/988
/.orig/run/user/0
/.orig/var/lib/nfs/rpc_pipefs
/.orig/home
/.orig/boot
/.orig/tmp/minc12443-1ur2tR/root
/.orig/proc
この段階で /.orig を umount したいがそれは失敗する。overlayfs の lowerdir を元の / にしているため。
Copy # umount /.orig/
umount: /.orig: target is busy.
(In some cases useful info about processes that use
the device is found by lsof(8) or fuser(1))
umount できるマウントポイントは umount してしまう。残っているコンテナホストのマウントポイントは再度 pivot_root する。
Copy # cut -f 2 -d " " < /proc/mounts | grep '^/\.orig/' | sort -r | xargs umount 2>/dev/null
# df -PTh
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
/dev/mapper/centos-root xfs 50G 29G 22G 57% /.orig
overlayfs overlay 50G 29G 22G 57% /
tmpfs tmpfs 1.8G 0 1.8G 0% /dev
devtmpfs devtmpfs 1.8G 0 1.8G 0% /dev/null
Copy # cd /.orig/
# pivot_root . mnt/
# df -PTh
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
/dev/mapper/centos-root xfs 50G 29G 22G 57% /
overlayfs overlay 50G 29G 22G 57% /mnt
tmpfs tmpfs 1.8G 0 1.8G 0% /mnt/dev
devtmpfs devtmpfs 1.8G 0 1.8G 0% /mnt/dev/null
# mount
/dev/mapper/centos-root on / type xfs (rw,relatime,attr2,inode64,noquota)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=36,pgrp=0,timeout=0,minproto=5,maxproto=5,direct)
nfsd on /proc/fs/nfsd type nfsd (rw,relatime)
overlayfs on /mnt type overlay (rw,relatime,lowerdir=/,upperdir=/tmp/minc12443-1ur2tR/storage,workdir=/tmp/minc12443-1ur2tR/work)
tmpfs on /mnt/dev type tmpfs (rw,relatime)
devpts on /mnt/dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
devtmpfs on /mnt/dev/console type devtmpfs (rw,nosuid,size=1836168k,nr_inodes=459042,mode=755)
devtmpfs on /mnt/dev/null type devtmpfs (rw,nosuid,size=1836168k,nr_inodes=459042,mode=755)
devtmpfs on /mnt/dev/zero type devtmpfs (rw,nosuid,size=1836168k,nr_inodes=459042,mode=755)
mqueue on /mnt/dev/mqueue type mqueue (rw,relatime)
proc on /mnt/proc type proc (rw,nosuid,nodev,noexec,relatime)
proc on /mnt/proc/sys type proc (ro,nosuid,nodev,noexec,relatime)
proc on /mnt/proc/sysrq-trigger type proc (ro,nosuid,nodev,noexec,relatime)
proc on /mnt/proc/irq type proc (ro,nosuid,nodev,noexec,relatime)
proc on /mnt/proc/bus type proc (ro,nosuid,nodev,noexec,relatime)
sysfs on /mnt/sys type sysfs (rw,nosuid,nodev,noexec,relatime)
ここまで来ると、/mnt ではコンテナホストの proc や sys といったマウントポイントは外れていて overlayfs として / が見えるだけになっている。また /dev や /dev/null は以下のように overlayfs 上にバインドマウントまたは tmpfs として作成したファイルシステムであるから、コンテナホストに影響はない。
Copy mount -t tmpfs tmpfs /tmp/minc12443-1ur2tR/root/dev
mount --bind /dev/null /tmp/minc12443-1ur2tR/root/dev/null
ところで、v0.1.1
では元のルートファイルシステムを pivot_root で変更するディレクトリを mnt/
から dev/
に変更している。
理由はわからない。(/mnt は一時的なマウント用のディレクトリだからかな?) 作者のコメントを頼りにすると Since the simplest environment like busybox, there is no /mnt.
という理由のためと考えられる。要するにポータビリティ。
6.root ファイルシステムの変更して実行
overlayfs 上の mnt/ をルートファイルシステムとすることで完全に隔離されたプロセス空間を生成することができる。ということで mnt/ をルートファイルシステムとしてプロセスを起動する。
Copy exec chroot mnt/ /bin/bash
ネットワーク名前空間だけは隔離していないので、コンテナホストと名前空間を共有している。ユーザー名前空間は同じになっているが、minc の起動時に --user tsuji
などとすればユーザ名前空間も別のプロセスが生成される。
# ls -l /proc/$$/ns/
でコンテナとコンテナホストの名前空間の inode を確認しておく。
Copy # ps
PID TTY TIME CMD
1 ? 00:00:00 bash
90 ? 00:00:00 ps
# mount
overlayfs on / type overlay (rw,relatime,lowerdir=/,upperdir=/tmp/minc12443-1ur2tR/storage,workdir=/tmp/minc12443-1ur2tR/work)
tmpfs on /dev type tmpfs (rw,relatime)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
devtmpfs on /dev/console type devtmpfs (rw,nosuid,size=1836168k,nr_inodes=459042,mode=755)
devtmpfs on /dev/null type devtmpfs (rw,nosuid,size=1836168k,nr_inodes=459042,mode=755)
devtmpfs on /dev/zero type devtmpfs (rw,nosuid,size=1836168k,nr_inodes=459042,mode=755)
mqueue on /dev/mqueue type mqueue (rw,relatime)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
proc on /proc/sys type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/sysrq-trigger type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/irq type proc (ro,nosuid,nodev,noexec,relatime)
proc on /proc/bus type proc (ro,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
# df -PTh
ファイルシス タイプ サイズ 使用 残り 使用% マウント位置
overlayfs overlay 50G 29G 22G 57% /
tmpfs tmpfs 1.8G 0 1.8G 0% /dev
devtmpfs devtmpfs 1.8G 0 1.8G 0% /dev/null
# ifconfig
enp0s31f6: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.24 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::d250:99ff:fea8:65af prefixlen 64 scopeid 0x20<link>
inet6 2409:11:480:800:d250:99ff:fea8:65af prefixlen 64 scopeid 0x0<global>
ether d0:50:99:a8:65:af txqueuelen 1000 (Ethernet)
RX packets 36595 bytes 3464055 (3.3 MiB)
RX errors 0 dropped 21768 overruns 0 frame 0
TX packets 9141 bytes 1237881 (1.1 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 16 memory 0xdf100000-df120000
シェルでお試し
羅列w
Copy mkdir -p /tmp/minc12443-1ur2tR/storage /tmp/minc12443-1ur2tR/work /tmp/minc12443-1ur2tR/root
unshare -iumpf /bin/bash
mount --make-rprivate /
mount -t overlay -o upperdir=/tmp/minc12443-1ur2tR/storage,lowerdir=/,workdir=/tmp/minc12443-1ur2tR/work overlayfs /tmp/minc12443-1ur2tR/root
mount -t tmpfs tmpfs /tmp/minc12443-1ur2tR/root/dev
mkdir /tmp/minc12443-1ur2tR/root/dev/pts
mount devpts -t devpts -onoexec,nosuid,gid=5,mode=0620,newinstance,ptmxmode=0666 /tmp/minc12443-1ur2tR/root/dev/pts
ln -s /dev/pts/ptmx /tmp/minc12443-1ur2tR/root/dev/ptmx
touch /tmp/minc12443-1ur2tR/root/dev/console
mount --bind /dev/console /tmp/minc12443-1ur2tR/root/dev/console
touch /tmp/minc12443-1ur2tR/root/dev/null
mount --bind /dev/null /tmp/minc12443-1ur2tR/root/dev/null
touch /tmp/minc12443-1ur2tR/root/dev/zero
mount --bind /dev/zero /tmp/minc12443-1ur2tR/root/dev/zero
mkdir /tmp/minc12443-1ur2tR/root/dev/mqueue
mount --bind /dev/mqueue /tmp/minc12443-1ur2tR/root/dev/mqueue
mount -t proc -o ro,nosuid,nodev,noexec proc /proc
mount -t proc -o rw,nosuid,nodev,noexec,relatime proc /tmp/minc12443-1ur2tR/root/proc
mount --bind /proc/sys /tmp/minc12443-1ur2tR/root/proc/sys
mount --bind /proc/sysrq-trigger /tmp/minc12443-1ur2tR/root/proc/sysrq-trigger
mount --bind /proc/irq /tmp/minc12443-1ur2tR/root/proc/irq
mount --bind /proc/bus /tmp/minc12443-1ur2tR/root/proc/bus
mount --bind /sys /tmp/minc12443-1ur2tR/root/sys
cd /tmp/minc12443-1ur2tR/root
mkdir -p .orig
pivot_root . .orig
cut -f 2 -d " " < /proc/mounts | grep '^/\.orig/' | sort -r | xargs umount 2>/dev/null
cd /.orig/
pivot_root . mnt/
exec chroot mnt/
参考