🐥

dockerコマンドを使わずにコンテナを作る - 1

2023/03/06に公開

概要

dockerコマンドを使わないでシェルコマンドだけでコンテナを作ることでどのようにしてコンテナが作られているのかの理解を深めたい.
参照のコンテナ技術入門を見れば大体分かるが, 詳細を少し省いて自分なりにわかりやすいように理解を深める形でメモを取る.

ちなみに参照先のコンテナ技術入門ではcgroup v1を使っているが, 現在のubuntuのバージョン(20.04)だとデフォルトでv2のみになっている.

イメージとしては非常にシンプルな以下のような構成のコンテナを作る.

環境

Ubuntu 22.04 LTS(EC2無料枠)

準備

init.sh
apt update
apt install apt-transport-https ca-certificates curl software-properties-common jq
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

apt update
apt install -y docker-ce
apt install -y cgdb
apt install -y cgroup-tools

apt install -y make gcc
git clone git://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git /usr/src/libcap
(cd /usr/src/libcap && make && make install)

実践

1. 基点となるルートフォルダの作成

ここでdockerコマンドを使ってしまっているが, これは構造化されたファイルシステムを楽に取ってくるために使用している. 他の方法でもできるがこのやり方が楽だと思われる.

# 後にchrootするためのルートフォルダ. rootユーザで作成する.
ROOTFS=$(sudo mktemp -d)

# コンテナを作り, 作ったコンテナのファイルシステムを$ROOTFSに展開する
# ただし全てext4になるため, 後で必要最低限のファイルシステムをマウントする必要がある 
CID=$(sudo docker container create bash)
sudo docker container export $CID | sudo tar -x -C $ROOTFS
sudo docker rm $CID

2. cgroupの作成

sudo cgcreate -a ubuntu -t ubuntu -g cpu,memory:chemimotty

# cpuを30%までに制限する
echo "30000 100000" > /sys/fs/cgroup/chemimotty/cpu.max

3. namespace内でプロセス実行

namespaceを切り, 内部で$ROOTFSをルートとしてbashを起動する.
起動したbashプロセスは作成したcgroup内でリソース制限を行う.これでかなり簡単なコンテナっぽいものができる.

unshareのrオプションでrootユーザを新しいnamespaceにマッピングする. これにより新しいnamespace内でもrootユーザで実行できる

sudo cgexec -g cpu,memory:chemimotty /bin/bash -c "unshare -pfumr chroot $ROOTFS /usr/local/bin/bash"

4. ファイルシステムのマウント

procのマウント. この後にexitでnamespaceを抜けても$ROOTFS/procはext4でマウントされている. これはunshareコマンドのmオプションによりmountも分離されているため.

# 作成したコンテナ内にて
mount -t proc proc /proc

# unshareの-pオプションを使っていることにより, pidの分離ができている(1からインクリメントされている)
ps
PID   USER     TIME  COMMAND
    1 root      0:00 /usr/local/bin/bash
    3 root      0:00 ps

5. hostname変更

hostname変更後にexitでnamespaceを抜けてもhostnameは変更されていない. これはunshareコマンドのuオプションによりutsが分離されているため.

# 作成したコンテナ内にて
hostname chemimotty
uname -n
chemimotty

実験

リソース制限

CPUリソースについて. cpu使用率が大体30%に制限されていることが分かる.
chemimottyというcgroupに所属していおり, ここでのcpu使用率が30%までに設定されているためこうなる.

# コンテナ内において
while true; do : ; done

# 別スクリーンにて, $pidは独立したコンテナ上で動いているひたすら無限ループしているbashの子プロセス
top -p $pid
top - 01:23:53 up 5 days,  1:14,  6 users,  load average: 0.26, 0.13, 0.04
Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s): 32.4 us,  0.3 sy,  0.0 ni, 67.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :    966.2 total,     64.1 free,    259.3 used,    642.8 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    547.6 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                                                                                                                 
  32824 root      20   0    2812   2452   1932 R  30.3   0.2   0:09.87 bash  

所感

相当勉強になった.
chrootを使うと脱獄できてしまい, セキュリティ的に問題があるのでpivot_rootを使っている(runcでは--no-pivotオプションを指定すればchrootで作られる).
誤解を恐れずに言えばchrootとpivot_rootの挙動は違うがルートを変更するという考え方自体は近く, どちらでもコンテナの挙動を実現できると考えたため今回はchrootの方で試すことにした.

参照

コンテナ技術入門
LXCで学ぶコンテナ入門
pivot_root できる条件
【コンテナ要素技術】pivot_rootについて例をまじえて説明します

次回

コンテナの触り程度の機能しかないため, overlayfsおよびnetwork namespaceにも触れていく(それでも足りないであろうが).

Discussion