Overlay File Systemについて - T3Org/go-container GitHub Wiki
COW(Copy On Write)とは、ベースとなるファイルシステムを読み取り専用とし、ここに含まれるファイルに対する書き込みが発生したときに該当のファイルをコピーして書き込みを行う、という仕組みを持ったファイルシステムです。
overlay file systemはこのCOWファイルシステム(unioning filesystem)です.
※union file systemについてhttps://ja.wikipedia.org/wiki/UnionFS
※overlay file systemにつてlinuxのカーネル文書の和訳https://sites.google.com/site/kandamotohiro/linux/overlayfs-txt
Overlay File systemは2つのディレクトリを重ね合わせてひとつのディレクトリとして見せるファイルシステムで,以下のディレクトリで構成される.
- lowerdir 下位に位置するディレクトリでファイルシステムのベースとなるディレクトリ.このディレクトリは読み取り専用.
- upperdir 上位に位置するディレクトリで,lowerdirに対して新規作成,変更,削除されたファイルが書き出されるディレクトリ.
- merged lowerdirとupperdir結合されたディレクトリで,OverlayFileSystemをマウントしたプロセスでは,作業者が閲覧するのはこのディレクトリとなる.
- work OverlayFileSystemの内部で使用される作業用ディレクトリ.
OverlayFilkeSystemは通常のファイルシステム同様、mountシステムコールやmountコマンドで操作します。 以下はmountコマンドでOverlayFileSystemを利用する例です。
sudo mount
-t overlay \
-o lowerdir=/path/to/base,upperdir=/path/to/upper/diff,workdir=/path/to/upper/work \
overlay \
/path/to/upper/merged
Dockerコンテナを作成作成する際にそのベースファイルとなるDockerイメージはOverlayFileSystemのようにレイヤを重ね合わせた形で提供されている.
ベースとなるOSレイヤ,
そこにアプリケーションに必要なツールやライブラリをインストールしたレイヤ,
さらにアプリケーションをインストールしたレイヤ,
など、複数のレイヤを重ね合わせてDockerイメージとなります.
ここからはOverlayFileSystemで以下のDockerfileのようなコンテナイメージを作り,そのイメージをルートファイルシステムとしてアプリケーションを実行する例を紹介します.
まずはベースとなるLAYER1を作ります.ここはDockerのalpine:latestイメージを展開して作成していきます.
(Alpineとは C標準ライブラリとしてmusllibcを採用し,BusyBoxをベースに利用して構築されたLinuxディストリビューション)
# LAYER1
FROM alpine:latest
# LAYER2
RUN apk add --no-cache curl
ENTRYPOINT ["curl"]
続いてLAYER2.
LAYER1 を先ほどのOverFileSystemのlowerdirとしてマウントし,curlをインストールします.このレイヤにはcurlのインストールで作成された差分ファイル(追加データ)のみが保存されます.
# mktempコマンドは、ファイル名に「XXXXXX」のように大文字の「X」を6つ連続して指定すると、その部分にランダムな文字や数字を充ててファイルを作成する
# -dでディレクトリになり,パーミッションは700になる.
# HOGE=$(コマンド)でコマンドを設定,$HOGEで実行.
$ LAYER1=$(mktemp -d)
$ mkdir $LAYER1/diff
# ローカルにalpineが無ければPullしてくれる.
$ CID=$(sudo docker container create alpine:latest)
# tar -xアーカイブからファイルを抽出する
# tar -C ディレクトリでカレントディレクトリを指定したディレクトリに変更してから実行する.
$ sudo docker export $CID | tar -x -C $LAYER1/diff
$ sudo docker rm $CID
作成したレイヤ(LAYER1, LAYER2)を組み合わせたものがコンテナイメージとなります.下記のようにLAYER1, LAYER2をOverlayFSのlowerdirとしてコンテナイメージを用意し,これをルートファイルシステムとしてコンテナを作成し,アプリケーションを実行します.
$ LAYER2=$(mktemp -d)
$ mkdir $LAYER2/{diff,work,merged}
# mount -tでマウントするファイルシステムの種類を指定する.
# mount -oでオプションを指定する,で区切る
$ sudo mount \
-t overlay \
-o lowerdir=$LAYER1/diff,upperdir=$LAYER2/diff,workdir=$LAYER2/work \
overlay \
$LAYER2/merged
# --bind サブツリーを別の場所に再マウントする.つまり任意のディレクトリを別のディレクトリ下にマウントできる.
$ sudo mount --bind /etc/resolv.conf $LAYER2/merged/etc/resolv.conf
# -r 現在の実効 (effective) ユーザー ID とグループ ID を,新しく作られた名前空間のスーパーユーザーの UID と GID にマッピングした後で,プログラムを実行する.
$ unshare -r chroot $LAYER2/merged apk add --no-cache curl
$ sudo umount -R $LAYER2/merged
$ rm -rf $LAYER2/{work,merged}
※UID、GIDのマッピング ユーザ名前空間を作成すると,名前空間内と名前空間外で二重にIDを持つことになります.名前空間を作成した後にこの二重のIDの間をひもづけるためのマッピングを作成します.
OverlayFSを利用しても,コンテナごとにルートファイルシステムを用意する必要はありますが,ベースとなるコンテナイメージさえあればコンテナ内に含まれるファイルを“全て”用意コピーする必要はないため,ディスクスペースに無駄がなく起動時間も改善されます.
$ CONTAINER=$(mktemp -d)
$ mkdir $CONTAINER/{diff,work,merged}
$ ROOTFS=$CONTAINER/merged
$ sudo mount -t overlay \
-o lowerdir=$LAYER2/diff:$LAYER1/diff,upperdir=$CONTAINER/diff,workdir=$CONTAINER/work \
overlay \
$ROOTFS
$ sudo mount --bind /etc/resolv.conf $ROOTFS/etc/resolv.conf
$ ARGS="http://httpbin.org/uuid"
# --mount-procは/procディレクトリをマウントする設定でこれを指定しないと親のpid名前空間の情報を出力してしまう.
# /procディレクトリは、システムの状態などが保管されている特殊なディレクトリで、これらはメモリ上に保存される仮想ファイルと呼ばれるものです.
# --forkは指定されたプログラムを直接実行するのではなく、unshareの子プロセスとしてフォークします。(呼び出し元プロセスを複製して新しいプロセスを生成する)
# -uipr -u, -i, -p, -r
# -u UTS名前空間を指定してunshare実行(ドメイン名を隔離する仕組みを提供している)
# -i system v ipc名前空間を指定してunshare実行
# -p pid名前空間を指定してunshare実行 PID Namespace によって,PID のリソースをシステムから隔離して扱うことができる.
# -r カレントユーザーをrootにマッピングする
$ unshare \
-uipr \
--mount-proc \
--fork \
chroot $ROOTFS /bin/sh -c "mount -t proc proc /proc && exec curl $ARGS"