👌

コンテナ自作メモ

2024/07/23に公開

コンテナ技術入門

以下をやってみたメモ
https://eh-career.com/engineerhub/entry/2019/02/05/103000/?PK=492D00

コマンド実行

$ 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'
   "

https://blog.amedama.jp/entry/linux-uts-namespace
cgexec: run the task in given control groups
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

https://www.tweeeety.blog/entry/2014/05/23/175129
ttyは標準入力と接続された制御端末のファイル名を出力する
例えば、以下の/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になる。

それぞれのオプションの意味合いを実感する

-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