MINCSコードリーディング
コンテナとは何か?
コンテナの基礎知識について非常によくまとめられている。おすすめ。
Namespace
cgroup
Capability
chroot/pivot_root
OverlayFS
MINCS
シェルスクリプトで実装されている軽量のコンテナランタイム。Docker などといった高度に抽象化されているコンテナランタイムを比較すると、コンテナを構成する基礎的な要素をシェルスクリプトで直接的に操作している。コンテナを構成する要素をどのように作成しているのか分かりやすく、学習用のコンテナランタイムとして優れている。
今回は一番最初のステーブルブランチ(minc-0.1)のソースコードを読んでいくことにする。master ブランチと比較すると機能は最小限のみになっていて、理解しやすいと判断。
予備知識
ソースコードを読むにあたってシェルスクリプトに関するいくつかの予備知識を整理しておく。
mount --bind
mount --bindバインドマウントを実施する。
https://linuxjm.osdn.jp/html/util-linux/man8/mount.8.html
以下のような動きになる。バインドマウントは、表面的な動作はシンボリックリンクと同じように見えるが違う。後述。
シンボリックリンクとバインドマウントの違い
What is the difference between ln -s and mount --bind?
上記の記事では(少なくとも)以下の点で異なるとのこと
chroot を用いた場合は、シンボリックリンクが chroot の外側にある場合はシンボリックリンクが無効になる。バインドマウントの場合は引き続きアクセスすることができる。
既存のディレクトリやファイルにディレクトまたはファイルをバインドマウントし、元のコンテンツを保護することができる。(別の場所からバインドマウントされていない限り、元のコンテンツにアクセスできなくなる)。シンボリックリンクでは元のコンテンツを移動または削除する必要がある。
シンボリックリンクはファイルだが、バインドマウントはカーネルがメモリ上に保持するレコードである。(再起動したときなどにそのまま保持されるかどうかなどの違いがある)
※全然関係ないけど、ここで表示されている 合計 xx は割り当てられているブロック数
$0
$0実行したシェルスクリプト名を取得できる。
$@
$@実行時のパラメータをそれぞれクオートして渡す。 $* の場合は全体として一つのパラメータとして渡す。
mount --make-rprivate /
以下のコマンドはプライベートとして再マウントするコマンド。
マウントの伝播を private に設定
systemd は mount propagation を shared にマークしてしまうので、せっかく新しい Mount Namespace を作成しても、Mount Namespace 内のマウントが他の Namespace に伝播してしまいます。これではコンテナにならないので、まずはこれを private にしています。
デフォルトのカーネルのマウントのモードだと、共有モード(MS_SHARED)でマウントされる。完全に独立したマウント名前空間を必要としている場合はマウントポイントをプライベート(MS_PRIVATE)にする必要があるとのこと。
mountpoint
マウントポイントかどうか調べるコマンド
/proc/self/mountinfo に存在するかどうかをチェックしている。マウントポイントの場合は返り値が 0 でそうでない場合は非 0 を返す。
http://man7.org/linux/man-pages/man1/mountpoint.1.html
mount -t overlay
overlayfsはunion filesystemの実装の1つで,ディレクトリを重ね合わせて1つのディレクトリツリーが構成できます。
https://docs.docker.com/storage/storagedriver/images/overlay_constructs.jpg
オプション
概要
lowerdir
重ねあわせの下層側のディレクトリ。複数指定可能。
upperdir
重ねあわせの上層側のディレクトリ。書き込み可能なローカルのファイルシステムの必要がある。
workdir
overlayfs が使う作業ディレクトリ。upperdir と同じファイルシステムの名前空間内に存在する必要がある。
キュメントには色々書いているが、Overlayfsの基本的な考え方は以下の通り
Readonlyな下の層と、Writableな上の層を重ね合わせ1つのファイルシステムに見せる
このとき、ディレクトリは重ね合わされ、ファイルは上から見ていく
コンフリクトしていたらファイルだと上が優先され、ディレクトリだとマージされる
下(readonly)にしかないファイルを編集すると上にコピーが作成される
https://www.slideshare.net/akachochin/overlayfsa-brief-report-of-overlayfs-source-code-reading
pivot_root
root ファイルシステムを変更するコマンド
pivot_root はカレントプロセスの root ファイルシステムを put_old ディレクトリに移動し、 new_root を新しい root ファイルシステムにする。 pivot_root(8) は pivot_root(2) を呼び出しているだけ
https://linuxjm.osdn.jp/html/util-linux/man8/pivot_root.8.html https://linuxjm.osdn.jp/html/LDP_man-pages/man2/pivot_root.2.html
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 は存在するディレクトリでなければならない)。
https://linuxjm.osdn.jp/html/gnumaniak/man1/chroot.1.html
ディレクトリ構成
全体的な処理の流れ
unshareによる、名前空間の(分岐という意味での)フォーク
mount/umountによる、新しいrootfsに対するoverlayや様々なマウントポイントの再整備
chroot(or pivot_root)による新しいrootfsへの移行と対象コマンドの実行
ソースコードの追い方
シェルスクリプトを上から読んでも問題ないが、一番初期のstableバージョン(minc-0.1)でも結構本格的な実装になっている。処理の動き的にはデバッグログを中心に見たほうがわかりやすい気がした。なので ./minc --debug /bin/bash として出力される内容から抜粋して紹介する。
実行時の主要なコマンド
1.unshare で名前空間を分離
カーネルの名前空間は全部で 6 つあった。minc の実装ではデフォルトだとネットワーク名前空間は分離されない。オプションで付与することは可能。
名前空間の名前
隔離されるリソース
unshare で対応するオプション
マウント名前空間
マウントの集合、操作
-m --mount
UTS名前空間
ホスト名、ドメイン名
-u --uts
PID名前空間
プロセスID
-p -pid
IPC名前空間
SysV IPCオブジェクト、POSIXメッセージキュー
-i --ipc
ユーザ名前空間
UID、GID
-u --uts
ネットワーク名前空間
ネットワークデバイス、アドレスポート、ルーティングテーブルなど
-U --user
unshare コマンドによって新しい名前空間上で子プロセスが実行される。が名前空間の実態は親の名前空間を指している。PID名前空間を新しく作成すると、プロセスを新しく作成しないと新しい名前空間に入らないよう。
2.プライベートマウント
上記の通り。コンテナホストとファイルシステムを分離する。
3.overlayfs でマウント
ディレクトリ名は minc の中で mktemp -d /tmp/minc$$-XXXXXX として作成している。
ディレクトリ レイヤー
デバッグ実行で対応するPath
upperdir
/tmp/minc12443-1ur2tR/storage
lowerdir
/
workdir
/tmp/minc12443-1ur2tR/work
overlayfs
/tmp/minc12443-1ur2tR/root
4.周辺ディレクトリ(/dev, sysfs, procfs)の再構築
コンテナホストと分離するために周辺ディレクトリ(/dev, sysfs, procfs)をバインドマウントする
5.2 回に分けて root ファイルシステムを変更
まずコンテナホストのルートファイルシステムを .orig としてマウントポイントを付け替える
この状態だと元のファイルシステムは .orig/ にマウントされている。
この段階で /.orig を umount したいがそれは失敗する。overlayfs の lowerdir を元の / にしているため。
umount できるマウントポイントは umount してしまう。残っているコンテナホストのマウントポイントは再度 pivot_root する。
ここまで来ると、/mnt ではコンテナホストの proc や sys といったマウントポイントは外れていて overlayfs として / が見えるだけになっている。また /dev や /dev/null は以下のように overlayfs 上にバインドマウントまたは tmpfs として作成したファイルシステムであるから、コンテナホストに影響はない。
ところで、v0.1.1 では元のルートファイルシステムを pivot_root で変更するディレクトリを mnt/ から dev/ に変更している。
理由はわからない。(/mnt は一時的なマウント用のディレクトリだからかな?) 作者のコメントを頼りにすると Since the simplest environment like busybox, there is no /mnt. という理由のためと考えられる。要するにポータビリティ。
6.root ファイルシステムの変更して実行
overlayfs 上の mnt/ をルートファイルシステムとすることで完全に隔離されたプロセス空間を生成することができる。ということで mnt/ をルートファイルシステムとしてプロセスを起動する。
ネットワーク名前空間だけは隔離していないので、コンテナホストと名前空間を共有している。ユーザー名前空間は同じになっているが、minc の起動時に --user tsuji などとすればユーザ名前空間も別のプロセスが生成される。
# ls -l /proc/$$/ns/ でコンテナとコンテナホストの名前空間の inode を確認しておく。
名前空間
inode(コンテナ)
inode(コンテナホスト)
ipc
4026532419
4026531839
mnt
4026532417
4026531840
net
4026531969
4026531969
pid
4026532420
4026531836
user
4026531837
4026531837
uts
4026532418
4026531838
シェルでお試し
羅列w
参考
Last updated