はじめに
業務で Docker や Kubernetes などのコンテナ技術を日々利用しているが、その要素技術についてきちんと学んだことがなかったため、今更ながら「コンテナ技術入門」をやってみた
環境
M1, 2020
macOS sonoma バージョン 14.2.1
仮想サーバの用意
colima を利用して仮想サーバを立ち上げる
バージョンは 0.6.8
を利用
仮想サーバに割り当てる CPU とメモリはそれぞれ 4CPUs、8GB とする (自身のローカル環境に合わせて適宜調整すること)
$ colima start --cpu 4 --memory 8 $ colima list PROFILE STATUS ARCH CPUS MEMORY DISK RUNTIME ADDRESS default Running aarch64 4 8GiB 60GiB docker $ colima ssh
起動した仮想サーバは Ubuntu 23.10
$ cat /etc/os-release PRETTY_NAME="Ubuntu 23.10" NAME="Ubuntu" VERSION_ID="23.10" VERSION="23.10 (Mantic Minotaur)" VERSION_CODENAME=mantic ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" UBUNTU_CODENAME=mantic LOGO=ubuntu-logo
ローカル環境の cgroup バージョンは v2 となっているが「コンテナ技術入門」では cgroup v1 を利用していると思われるため一部読み替えて実行していく
使用しているLinux環境のcgroupが、v1なのかv2なのかを確認する - CLOVER🍀
$ stat -fc %T /sys/fs/cgroup/ cgroup2fs
まずは必要となるコマンドをインストールする
libcap
パッケージのリンクが切れていたのでリポジトリ URL はミラーリポジトリに変更する
$ sudo apt update $ sudo apt install cgdb cgroup-tools make gcc $ sudo git clone https://kernel.googlesource.com/pub/scm/linux/kernel/git/morgan/libcap /usr/src/libcap $ cd /usr/src/libcap && sudo make && sudo make install
コンテナを作成する
仮想サーバの準備ができたら Docker コンテナの bash イメージを利用してファイルシステムを用意する
まずはファイルシステムとして利用するテンポラリディレクトリを作成する
$ ROOTFS=$(mktemp -d)
bash イメージのコンテナを作成し、コンテナ ID を取得する
$ CID=$(sudo docker container create bash)
コンテナのファイルシステムを tar アーカイブで出力し、作成したディレクトリに展開する
$ sudo docker container export $CID | tar -x -C $ROOTFS
/usr/local/bin/bash
にシンボリックリンクを作成する
$ ln -s /usr/local/bin/bash $ROOTFS/bin/bash
不要になったコンテナを削除する
$ sudo docker container rm $CID
これでこれから作成するコンテナのファイルシステムが用意できた
続いて CPU とメモリを制限するサブグループを作成しようとしたところ、uuidgen
コマンドが実行できなかった
$ UUID=$(uuidgen) bash: uuidgen: command not found
util-linux
パッケージに含まれているらしいが、既にバージョン 2.39.1
をインストール済みとのこと
Ubuntu Manpage: uuidgen - create a new UUID value
$ dpkg -l | grep util-linux ii util-linux 2.39.1-4ubuntu2 arm64 miscellaneous system utilities
調べてみたところソースコードを落としてきてビルドすると util-linux
に含まれるコマンド諸々が利用できるとのこと
試してみたところ問題なさそう
Linux: util-linux を gdb でデバッグする - CUBE SUGAR CONTAINER
$ cd ~ $ wget -O - https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.39/util-linux-2.39.1.tar.gz | tar zxvf - $ cd util-linux-2.39.1/ $ ./configure && make $ ./uuidgen --version uuidgen from util-linux 2.39.1
続いて cgcreate
で実際にサブグループを作成してみる
いくつかオプションがあるが、-t
と -a
でタスク (プロセスやスレッド) とサブグループおよび関連するファイルのオーナーを指定し、-g
でサブシステム (cpu,memory など) とそれらを管理するサブグループを指定している
$ UUID=$(./uuidgen) $ sudo cgcreate -t $(id -un):$(id -gn) -a $(id -un):$(id -gn) -g cpu,memory:$UUID
作成されたサブグループは /sys/fs/cgroup/<subgroup>
で確認できる
$ ls -l /sys/fs/cgroup/$UUID total 0 -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.controllers -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.events -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.freeze --w------- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.kill -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.max.depth -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.max.descendants -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.pressure -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.procs -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.stat -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.subtree_control -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.threads -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cgroup.type -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.idle -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.max -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.max.burst -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.pressure -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.stat -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.uclamp.max -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.uclamp.min -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.weight -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpu.weight.nice -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpuset.cpus -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpuset.cpus.effective -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpuset.cpus.partition -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpuset.mems -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 cpuset.mems.effective -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 io.max -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 io.pressure -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 io.prio.class -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 io.stat -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 io.weight -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.current -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.events -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.events.local -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.high -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.low -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.max -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.min -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.numa_stat -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.oom.group -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.peak -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.pressure --w------- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.reclaim -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.stat -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.swap.current -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.swap.events -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.swap.high -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.swap.max -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.swap.peak -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.zswap.current -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 memory.zswap.max -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 pids.current -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 pids.events -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 pids.max -r--r--r-- 1 xxxxx xxxxx 0 Mar 1 06:56 pids.peak
次に作成したグループに CPU 30%、メモリ 10MB の制限を設けてみる
cgroup v2 では読み替えが必要で memory.limit_in_bytes
は memory.max
で cfs_period_us
と cpu.cfs_quota_us
は cpu.max
となる
https://www.kernel.org/doc/Documentation/cgroup-v2.txt
# cgorup v1 のコマンド $ cgset -r memory.limit_in_bytes=10000000 $UUID # cgorup v2 のコマンド $ cgset -r memory.max="10000000" $UUID # cgorup v1 のコマンド $ cgset -r cpu.cfs_period_us=1000000 $UUID $ cgset -r cpu.cfs_quota_us=300000 $UUID # cgorup v2 のコマンド $ cgset -r cpu.max="300000 1000000" $UUID
続いて CPU とメモリに制限を設けたサブグループにコンテナを作成しようとしたが、cgroup change of group failed
エラーが発生した
$ 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' " cgroup change of group failed
以下を参考にログレベルを変えてみたところ /sys/fs/cgroup/$UUID/cgroup.procs
への書き込みで Permission denied が発生しているようだ
linux - Using cgroups v2 without root - Unix & Linux Stack Exchange
$ CGROUP_LOGLEVEL=DEBUG 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' " Found cgroup option cpuset, count 0 Found cgroup option cpu, count 1 Found cgroup option io, count 2 Found cgroup option memory, count 3 Found cgroup option hugetlb, count 4 Found cgroup option pids, count 5 Found cgroup option rdma, count 6 Found cgroup option misc, count 7 My euid and egid is: 501,1000 Will move pid 3200 to cgroup '21f28fdd-f5bc-4453-bd03-995bb6dde1e7' Adding controller cpu Adding controller memory cgroup build procs path: /sys/fs/cgroup/21f28fdd-f5bc-4453-bd03-995bb6dde1e7/cgroup.procs Warning: cannot write tid 3200 to /sys/fs/cgroup/21f28fdd-f5bc-4453-bd03-995bb6dde1e7/cgroup.procs:Permission denied Warning: cgroup_attach_task_pid failed: 50016 cgroup change of group failed
/sys/fs/cgroup/$UUID/cgroup.procs
の Permission を確認してみたが問題はなさそう
$ ls -l /sys/fs/cgroup/$UUID/cgroup.procs -rw-r--r-- 1 xxxxx xxxxx 0 Mar 1 06:59 /sys/fs/cgroup/21f28fdd-f5bc-4453-bd03-995bb6dde1e7/cgroup.procs
なにやら Linux Kernel のドキュメントによると、サブグループの cgorup.procs だけでなく、親となる cgroup.procs への書き込み権限も必要らしい
Control Group v2 — The Linux Kernel documentation
A PID can be written to migrate the process associated with the PID to the cgroup. The writer should match all of the following conditions.
- It must have write access to the "cgroup.procs" file.
- It must have write access to the "cgroup.procs" file of the common ancestor of the source and destination cgroups.
cgroup.procs への書き込み権限を付与して再度コマンドを実行してみると無事コンテナを作成できた
$ sudo chmod o+w /sys/fs/cgroup/cgroup.procs -rw-r--rw- 1 root root 0 Mar 1 07:26 /sys/fs/cgroup/cgroup.procs $ 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' "
コンテナを作成したコマンドをみていくと・・・
cgexec
で cgcreate
で作成したサブグループ内でプロセスを実行している
cgexec -g cpu,memory:$UUID
実行しているプロセス unshare
は隔離した Namespace でプロセスを実行するもので、ここでは /bin/sh -c
に続くコマンドを実行している
オプション -muinpfr
は Namespace での隔離対象にファイルシステムのマウントポイント、UTS (ホストネームもしくはドメイン名)、IPC (POSIX メッセージキュー、セマフォセット、共有メモリ)、ネットワーク (ルートテーブル、ファイアウォールルール、ソケットなど)、PID を含め、実行するプロセスは unshare の子プロセスとしてフォークし、現在のユーザの UID:GID を新しい Namespace では root ユーザの UID:GID にマッピングするというものらしい
unshare -muinpfr /bin/sh -c
更にコマンドを見ていくと
隔離された Namespace 上の /proc
と、仮想サーバ上の $ROOTFS/proc
をマウントしている
mount -t proc proc $ROOTFS/proc
仮想サーバ上のファイルシステムに tty
を作成し、隔離した Namespace 上の tty
とマウントする
touch $ROOTFS$(tty); mount --bind $(tty) $ROOTFS$(tty)
同様に ptmx
も作成してマウントする
touch $ROOTFS/dev/pts/ptmx; mount --bind /dev/pts/ptmx $ROOTFS/dev/pts/ptmx
マウントした ptmx
にシンボリックリンクを作成する
これでターミナルからコンテナの操作が実行できそう
ln -sf /dev/pts/ptmx $ROOTFS/dev/ptmx
続いて /dev/null
を作成してマウントする
touch $ROOTFS/dev/null && mount --bind /dev/null $ROOTFS/dev/null
ホストネームに $UUID
を指定する
/bin/hostname $UUID
capsh
はケイパビリティを設定するコマンドで、--chroot
オプションでプロセスのルートディレクトリを冒頭で作成したファイルシステムとし、ファイルシステムを隔離している
--drop
で子プロセスの chroot
ケイパビリティを剥奪している
最後に exec $CMD
で /bin/sh
を実行している
exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
作成できたコンテナ内でホスト名、ユーザ、プロセステーブル、マウントテーブルを確認してみる
ホスト名は UUID に書き換わっていることを確認できる
# uname -n
21f28fdd-f5bc-4453-bd03-995bb6dde1e7
ユーザは昇格したとおり root になっている
# id uid=0(root) gid=0(root) groups=65534(nobody),0(root)
プロセスも隔離されており、プロセス /bin/sh
が PID 1 となっている
# ps aux PID USER TIME COMMAND 1 root 0:00 /bin/sh 13 root 0:00 ps aux
マウントした /proc
、tty
、ptmx
、/dev/null
も確認できる
# mount proc on /proc type proc (rw,relatime) devpts on /dev/pts/0 type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) devpts on /dev/pts/ptmx type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000) devtmpfs on /dev/null type devtmpfs (rw,nosuid,noexec,relatime,size=4034036k,nr_inodes=1008509,mode=755,inode64)
最後にリソースが制限されているかを確認するため yes
コマンドを実行してみる
# yes >/dev/null
別途ターミナルを立ち上げて仮想サーバに接続してからプロセスを確認してみると、unshare
、/bin/sh
、yes
プロセスが確認できる
$ ps f PID TTY STAT TIME COMMAND 8339 pts/1 Ss 0:00 /bin/bash --login 8367 pts/1 R+ 0:00 \_ ps f 2344 pts/0 Ss 0:00 /bin/bash --login 8053 pts/0 S 0:00 \_ unshare -muinpfr … 8054 pts/0 S 0:00 \_ /bin/sh 8323 pts/0 R+ 0:02 \_ yes
top
コマンドで CPU 使用率を確認してみると 30% に制限されていることも確認できた
$ top -p 8323 top - 07:30:42 up 38 min, 2 users, load average: 0.00, 0.00, 0.00 Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie %Cpu(s): 7.3 us, 0.2 sy, 0.0 ni, 92.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 7922.4 total, 7127.7 free, 299.2 used, 656.9 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 7623.2 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8323 xxxxx 20 0 1704 768 768 R 29.9 0.0 0:21.89 yes
一通り確認できたので、コンテナを終了して
# exit
サブグループとファイルシステムも削除する
$ sudo cgdelete -r -g cpu,memory:$UUID $ rm -rf $ROOTFS
さいごに
日頃、コンテナを起動する際にどんなことが行われているかをなんとなく理解することはできた
「コンテナ技術入門」のコンテンツはまだ続いているため、引き続きコンテナの要素技術について学び、纏めようと思う
しかし「コンテナ」とは良く言ったものだ