reading-books
  • Introduction
  • Computer Systems
    • はじめて読むマシン語
    • プロセッサを支える技術
    • オペレーティングシステム入門
  • Architecture
    • データ指向アプリケーションデザイン
    • Web API The Good Parts
  • Code Reading
    • MINCSコードリーディング
  • In Progress
    • Goによる並行処理
    • ビッグデータを支える技術
Powered by GitBook
On this page
  • コンテナとは何か?
  • MINCS
  • 予備知識
  • mount --bind
  • $0
  • $@
  • mount --make-rprivate /
  • mountpoint
  • mount -t overlay
  • pivot_root
  • chroot
  • ディレクトリ構成
  • 全体的な処理の流れ
  • ソースコードの追い方
  • 実行時の主要なコマンド
  • 1.unshare で名前空間を分離
  • 2.プライベートマウント
  • 3.overlayfs でマウント
  • 4.周辺ディレクトリ(/dev, sysfs, procfs)の再構築
  • 5.2 回に分けて root ファイルシステムを変更
  • 6.root ファイルシステムの変更して実行
  • シェルでお試し
  • 参考

Was this helpful?

  1. Code Reading

MINCSコードリーディング

コンテナとは何か?

  • コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう

コンテナの基礎知識について非常によくまとめられている。おすすめ。

  • Namespace

  • cgroup

  • Capability

  • chroot/pivot_root

  • OverlayFS

MINCS

mhiramat/mincs

シェルスクリプトで実装されている軽量のコンテナランタイム。Docker などといった高度に抽象化されているコンテナランタイムを比較すると、コンテナを構成する基礎的な要素をシェルスクリプトで直接的に操作している。コンテナを構成する要素をどのように作成しているのか分かりやすく、学習用のコンテナランタイムとして優れている。

今回は一番最初のステーブルブランチ(minc-0.1)のソースコードを読んでいくことにする。master ブランチと比較すると機能は最小限のみになっていて、理解しやすいと判断。

予備知識

ソースコードを読むにあたってシェルスクリプトに関するいくつかの予備知識を整理しておく。

mount --bind

バインドマウントを実施する。

https://linuxjm.osdn.jp/html/util-linux/man8/mount.8.html

mount --bind olddir newdir

以下のような動きになる。バインドマウントは、表面的な動作はシンボリックリンクと同じように見えるが違う。後述。

# 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
  • シンボリックリンクとバインドマウントの違い

What is the difference between ln -s and mount --bind?

上記の記事では(少なくとも)以下の点で異なるとのこと

  • chroot を用いた場合は、シンボリックリンクが chroot の外側にある場合はシンボリックリンクが無効になる。バインドマウントの場合は引き続きアクセスすることができる。

  • 既存のディレクトリやファイルにディレクトまたはファイルをバインドマウントし、元のコンテンツを保護することができる。(別の場所からバインドマウントされていない限り、元のコンテンツにアクセスできなくなる)。シンボリックリンクでは元のコンテンツを移動または削除する必要がある。

  • シンボリックリンクはファイルだが、バインドマウントはカーネルがメモリ上に保持するレコードである。(再起動したときなどにそのまま保持されるかどうかなどの違いがある)

※全然関係ないけど、ここで表示されている 合計 xx は割り当てられているブロック数

$0

実行したシェルスクリプト名を取得できる。

$@

実行時のパラメータをそれぞれクオートして渡す。 $* の場合は全体として一つのパラメータとして渡す。

mount --make-rprivate /

以下のコマンドはプライベートとして再マウントするコマンド。

mount --make-rprivate /

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

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

デフォルトのカーネルのマウントのモードだと、共有モード(MS_SHARED)でマウントされる。完全に独立したマウント名前空間を必要としている場合はマウントポイントをプライベート(MS_PRIVATE)にする必要があるとのこと。

  • Mount namespaces and shared subtrees

  • Documentation/filesystems/sharedsubtree.txt

mountpoint

マウントポイントかどうか調べるコマンド

/proc/self/mountinfo に存在するかどうかをチェックしている。マウントポイントの場合は返り値が 0 でそうでない場合は非 0 を返す。

mountpoint [-q] [-d] directory

http://man7.org/linux/man-pages/man1/mountpoint.1.html

mount -t overlay

overlayfsはunion filesystemの実装の1つで,ディレクトリを重ね合わせて1つのディレクトリツリーが構成できます。

mount -t overlayfs -o lowerdir=lower,upperdir=upper overlayfs overlay

オプション

概要

lowerdir

重ねあわせの下層側のディレクトリ。複数指定可能。

upperdir

重ねあわせの上層側のディレクトリ。書き込み可能なローカルのファイルシステムの必要がある。

workdir

overlayfs が使う作業ディレクトリ。upperdir と同じファイルシステムの名前空間内に存在する必要がある。

  • 第18回 Linuxカーネルのコンテナ機能 [7] ─ overlayfs

  • ちょっとOverlayfsの実装、読んでみました(A brief report of overlayfs source code reading)

実践! OverlayFS

キュメントには色々書いているが、Overlayfsの基本的な考え方は以下の通り

  1. Readonlyな下の層と、Writableな上の層を重ね合わせ1つのファイルシステムに見せる

  2. このとき、ディレクトリは重ね合わされ、ファイルは上から見ていく

    • コンフリクトしていたらファイルだと上が優先され、ディレクトリだとマージされる

  3. 下(readonly)にしかないファイルを編集すると上にコピーが作成される

pivot_root

root ファイルシステムを変更するコマンド

pivot_root はカレントプロセスの root ファイルシステムを put_old ディレクトリに移動し、 new_root を新しい root ファイルシステムにする。 pivot_root(8) は pivot_root(2) を呼び出しているだけ

pivot_root new_root put_old

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引数で指定したディレクトリにマウントされた状態になります.

CD-ROMだけで動作するオリジナルLinuxを作ろう

動作としては pivot_root と chroot に大きな違いはないが、適用するコンテキストが異なる。(OS 全体 or プロセス)

chroot

ルートディレクトリを変更してコマンドを実行するコマンド

コマンドや対話的シェルを特別なルートディレクトリで実行する。ルートディレクトリを NEWROOT に設定してコマンドを実行する。 通常、ファイル名はディレクトリ構造のルート (root=根)、つまり `/' を起点として調べられる。 chroot はこのルートをディレクトリ directory に変更して command を実行する (directory は存在するディレクトリでなければならない)。

chroot [OPTION] NEWROOT [COMMAND [ARG]...]

https://linuxjm.osdn.jp/html/gnumaniak/man1/chroot.1.html

ディレクトリ構成

/mincs
|--libexec ---------------- 主要なコマンドをまとめたディレクトリ
|  |--minc-coat ----------- オーバーレイファイルシステムをマウントするためのスクリプト
|  |--minc-exec ----------- コンテナランタイムを構築するための主要なコマンドを実行するスクリプト
|  |--minc-farm ----------- 見てない
|  |--minc-trapper -------- 見てない
|--minc ------------------- ユーザインターフェースになるスクリプト。オプションの解析などを実施し minc-exec を実行
|-- ...

全体的な処理の流れ

  1. unshareによる、名前空間の(分岐という意味での)フォーク

  2. mount/umountによる、新しいrootfsに対するoverlayや様々なマウントポイントの再整備

  3. chroot(or pivot_root)による新しいrootfsへの移行と対象コマンドの実行

ソースコードの追い方

シェルスクリプトを上から読んでも問題ないが、一番初期のstableバージョン(minc-0.1)でも結構本格的な実装になっている。処理の動き的にはデバッグログを中心に見たほうがわかりやすい気がした。なので ./minc --debug /bin/bash として出力される内容から抜粋して紹介する。

実行時の主要なコマンド

1.unshare で名前空間を分離

unshare -iumpf ./libexec/minc-exec /bin/bash

カーネルの名前空間は全部で 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名前空間を新しく作成すると、プロセスを新しく作成しないと新しい名前空間に入らないよう。

# unshare -iumpf /bin/bash
# echo $$
1

2.プライベートマウント

上記の通り。コンテナホストとファイルシステムを分離する。

mount --make-rprivate /

3.overlayfs でマウント

ディレクトリ名は minc の中で mktemp -d /tmp/minc$$-XXXXXX として作成している。

mount -t overlay -o upperdir=/tmp/minc12443-1ur2tR/storage,lowerdir=/,workdir=/tmp/minc12443-1ur2tR/work overlayfs /tmp/minc12443-1ur2tR/root

ディレクトリ レイヤー

デバッグ実行で対応する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 としてマウントポイントを付け替える

cd /tmp/minc12443-1ur2tR/root
mkdir -p .orig
pivot_root . .orig

この状態だと元のファイルシステムは .orig/ にマウントされている。

# 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 を元の / にしているため。

# 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 する。

# 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
# 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 として作成したファイルシステムであるから、コンテナホストに影響はない。

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/ に変更している。

  • minc-exec: Use /dev instead of /mnt for temporary root

理由はわからない。(/mnt は一時的なマウント用のディレクトリだからかな?) 作者のコメントを頼りにすると Since the simplest environment like busybox, there is no /mnt. という理由のためと考えられる。要するにポータビリティ。

6.root ファイルシステムの変更して実行

overlayfs 上の mnt/ をルートファイルシステムとすることで完全に隔離されたプロセス空間を生成することができる。ということで mnt/ をルートファイルシステムとしてプロセスを起動する。

exec chroot mnt/ /bin/bash

ネットワーク名前空間だけは隔離していないので、コンテナホストと名前空間を共有している。ユーザー名前空間は同じになっているが、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

# 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

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/

参考

  • MINCSによるLinuxコンテナ実装の裏側

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

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

PreviousCode ReadingNextIn Progress

Last updated 5 years ago

Was this helpful?

https://docs.docker.com/storage/storagedriver/images/overlay_constructs.jpg

https://www.slideshare.net/akachochin/overlayfsa-brief-report-of-overlayfs-source-code-reading

image
overlay_constructs.jpg