コンテナ自作メモ
コンテナ技術入門
以下をやってみたメモ
コマンド実行
$ CMD="/bin/sh"
$ cgexec -g cpu,memory:$UUID \
unshare -muinpfr /bin/sh -c "
mount -t proc proc $ROOTFS/proc &&
touch $ROOTFS$(tty); mount --bind $(tty) $ROOTFS$(tty) &&
touch $ROOTFS/dev/pts/ptmx; mount --bind /dev/pts/ptmx $ROOTFS/dev/pts/ptmx &&
ln -sf /dev/pts/ptmx $ROOTFS/dev/ptmx &&
touch $ROOTFS/dev/null && mount --bind /dev/null $ROOTFS/dev/null &&
/bin/hostname $UUID &&
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
"
unshareコマンドのオプションは以下の通り。それぞれのオプションにファイル名を与えると、bind mountによってnamespaceが作成される。ファイル名を与えない場合、namespaceをunshareする。
namespace分離用のオプション
-m: mount
-u: uts→ hostname
-i: ipc
-n: network
-p: pid
それ以外のオプション
-f: fork the specified program as a child process of unshare rather than running it directly
-r, --map-root-user: 新しく作成されたuser namespaceでのsuperuser UID/GIDに、現在の実行ユーザーのUID, GIDをマップする。これによって、user namespace内の任意のリソースの管理権原を与えたりできる。
capsh: capability shell wrapper
--chroot: Execute the chroot(2) system call with the new root-directory(/) equal to path
--drop: Remove the listed capabilities from the prevailing bounding set.
-- : execute /bin/bash with trailing arguments
例えば、以下の/dev/pts/3は、コンテナの標準入力を表す。
vagrant@ubuntu-bionic:~$ ps f
PID TTY STAT TIME COMMAND
3218 pts/4 Ss 0:00 -bash
4665 pts/4 R+ 0:00 \_ ps f
3135 pts/3 Ss 0:00 -bash
3291 pts/3 S 0:00 \_ bash
4606 pts/3 S 0:00 \_ unshare -muinpfr /bin/sh -c mount -t proc proc /tmp/tmp.jHo8inuyvO/proc && touch /tmp/tmp
4607 pts/3 S+ 0:00 \_ /bin/sh
以下のようにすると、コンテナの標準入力にhogeが表示される
echo hoge > /dev/pts/3
親プロセス側
コンテナ側
pts(ptmx)
プロセスが /dev/ptmx をオープンすると、そのプロセスには 擬似端末マスタ (pseudo-terminal master; PTM) へのファイル・ ディスクリプタが返され、 /dev/pts ディレクトリに擬似端末スレーブ (pseudo-terminal slave; PTS) デバイスが作成される。 /dev/ptmx をオープンして得られるファイル・ディスクリプタは それぞれ独立の PTM であり、対応する PTS を各々持つ。 PTS のパス名は、PTM のファイル・ディスクリプタを ptsname (3) に渡すと知ることができる。
ここまでのまとめ
- 子プロセスでmountやnamespaceの作成を行う。
- そのプロセスの中でchrootし、/bin/bash -c 'exec $CMD'を実行する。
- /bin/bash -cは、別の子プロセスを生成するわけではない。だから、子プロセスの実行プログラム自体が$CMDになる。
- そのプロセスの中でchrootし、/bin/bash -c 'exec $CMD'を実行する。
それぞれのオプションの意味合いを実感する
-uを省略する場合、/bin/hostname $UUID &&
を省略する。すると、コンテナ内でも親と同じホスト名となる。
# 親
vagrant@ubuntu-bionic:~$ hostname
ubuntu-bionic
# コンテナ
vagrant@ubuntu-bionic:~$ cgexec -g cpu,memory:$UUID unshare -minpfr /bin/sh -c "
mount -t proc proc $ROOTFS/proc &&
touch $ROOTFS$(tty); mount --bind $(tty) $ROOTFS$(tty) &&
touch $ROOTFS/dev/pts/ptmx; mount --bind /dev/pts/ptmx $ROOTFS/dev/pts/ptmx &&
ln -sf /dev/pts/ptmx $ROOTFS/dev/ptmx &&
touch $ROOTFS/dev/null && mount --bind /dev/null $ROOTFS/dev/null &&
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
"
/ # hostname
ubuntu-bionic
-mを省略する場合、以下のようにする。この場合procもmountされていないので何もわからない。procだけをmountしようとするとPermission Deniedになる。
vagrant@ubuntu-bionic:~$ cgexec -g cpu,memory:$UUID \
> unshare -uinpfr /bin/sh -c "
> /bin/hostname $UUID &&
> exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec ls'
> "
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
Namespace
Namespaceは、PID番号空間、マウントポイントなどをほかのプロセスと隔離し、独立したOS環境のように見せる機能。
Namespaceには現在7種類ある。
mount, pid, network, ipc, uts, user, cgroup
namespace操作用のコマンド/システムコール
- unshareシステムコール(unshareコマンド): 現在のプロセスを新しいnamespaceに紐づける
- setnsシステムコール(nsenterコマンド): 現在のプロセスを既存のnamespaceに紐づける
- clone: 新しくプロセスを作成する際に、そのプロセスを新しいnamespaceに関連付ける
- ip netnsコマンド: network namespaceを管理する
親プロセスと、PID1は同じnamespace。コンテナはcgroup以外異なるnamespaceにある。
親プロセスのns
vagrant@ubuntu-bionic:~$ ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 net -> 'net:[4026531993]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 user -> 'user:[4026531837]'
lrwxrwxrwx 1 vagrant vagrant 0 Sep 29 22:17 uts -> 'uts:[4026531838]'
コンテナのns
/ # ls -l /proc/$$/ns
total 0
lrwxrwxrwx 1 root root 0 Sep 29 22:17 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 ipc -> ipc:[4026532178]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 mnt -> mnt:[4026532176]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 net -> net:[4026532181]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 pid -> pid:[4026532179]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 pid_for_children -> pid:[4026532179]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 user -> user:[4026532175]
lrwxrwxrwx 1 root root 0 Sep 29 22:17 uts -> uts:[4026532177]
PID1のns
vagrant@ubuntu-bionic:~$ sudo ls -l /proc/1/ns
total 0
lrwxrwxrwx 1 root root 0 Sep 29 22:19 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 net -> 'net:[4026531993]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Sep 29 22:19 uts -> 'uts:[4026531838]'
コンテナに入る
nsenterで、既存のコンテナに入ることができる
$ unshare -ur /bin/sh -c 'hostname foobar; sleep 15' &
$ PID=$(jobs -p)
$ sudo nsenter -u -t $PID hostname
foobar
Namespaceを維持する
ns_ntsファイルを指定するとbind mountされる。(=namespaceが維持される)
$ touch ns_uts
$ sudo unshare --uts=ns_uts /bin/sh -c 'hostname foobar'
unshareの--uts: if file is specified, then a persistent namespace is created by a bind mount.
nsenterでfileを指定すると維持されたNamespaceに関連付けられる。
$ sudo nsenter --uts=ns_uts hostname
foobar
nsenter --uts: If no file is specified, enter the UTS namespace of the target process. If file is specified, enter the UTS namespace specified by file.
cgroup
cgroupはグループ化したプロセスに対してカーネルリソースやハードウェアリソースを制限できる。
cgroupは一般的に/sys/fs/cgroup
や /cgroup
にマウントされる
/proc/$$/cgroup
で現在のプロセスが属するグループを確認できる
Capability
-
Permitted
: EffectiveとInheritableで持つことが許可されるCapability Set。一度OFFにしたものは自力で再セットできない。 - Inheritable: execve(2)した際に継承するCapability Set
- Effective: 実際に判定されるCapability Set
- Ambient: 特権のないプログラムをexecve(2)した際に保持されるCapability Set
- Bounding Set: 取得できるCapabilityを制限するための集合
chroot,pivot_root
プロセスのルートファイルシステムを隔離する。Mount Namespaceと合わせて使うことでほかのプロセスと隔離された専門のファイルシステムを持てる。
- chroot: プロセスのルートディレクトリが指すパスを変更する。アクセス可能な範囲を限定させることでファイルシステムを隔離する。
- pivot_root: プロセスのルートファイルシステムそのものを入れ替えることでファイルシステムを隔離する。
Discussion