仮想環境とコンテナを絶対に理解したい で使用したコマンド

この記事では、「仮想環境とコンテナを絶対に理解したい」で使用した主なコマンドを紹介します。


ROOTFS=$(mktemp -d)

何をするか

一時ディレクトリ(空のディレクトリ)を作り、そのパスを ROOTFS 環境変数に格納する。ここにコンテナのファイルシステムを展開する。

その後の意味

以降の操作はこのディレクトリを「疑似ルート」 (/) として使うための準備です。


CID=$(sudo docker container create bash)
sudo docker container export $CID | tar -x -C $ROOTFS
ln -s /usr/local/bin/bash $ROOTFS/bin/bash
sudo docker container rm $CID

何をするか

イメージ bash を元にコンテナを作成する(ただし起動はしない)。戻り値(コンテナ ID)を CID に格納する。
作成したコンテナのルートファイルシステムを tar ストリームで出力し、tar -x -C $ROOTFS で $ROOTFS に展開する。
ホスト上の /usr/local/bin/bash(システム上の bash 実行ファイル)を $ROOTFS/bin/bash というシンボリックリンクとして作る。
先ほど作った一時的なコンテナを削除。ファイルシステムは既に展開済みなので不要となる。


UUID=$(uuidgen)
sudo cgcreate -t $(id -un):$(id -gn) -a $(id -un):$(id -gn) -g cpu,memory:$UUID
ls -l /sys/fs/cgroup/$UUID
cgset -r memory.max="10000000" $UUID
cgset -r cpu.max="300000 1000000" $UUID

何をするか

ユニークな識別子(UUID)を作り、UUID 変数に入れる。
cgcreate(cgroup-tools)で cpu と memory の cgroup(名前は $UUID)を作る。-t と -a はタスク所有者とアクセス権の指定。
作成した cgroup のディレクトリが存在するかを確認するために ls する。
cgroup にメモリ上限を設定する。
CPU 制限を設定。ここでは quota=300000 と period=1000000 → 0.3 → 理論上 30%。


CMD="/bin/sh"
sudo cgexec -g cpu,memory:$UUID \
unshare -muinpf /bin/sh -c "
  set -e

  # /proc
  mkdir -p $ROOTFS/proc
  mount -t proc proc $ROOTFS/proc

  # /dev/pts
  mkdir -p $ROOTFS/dev/pts
  mount -t devpts devpts $ROOTFS/dev/pts
  mkdir -p $ROOTFS/dev
  touch $ROOTFS/dev/ptmx
  mount --bind /dev/pts/ptmx $ROOTFS/dev/pts/ptmx
  ln -sf /dev/pts/ptmx $ROOTFS/dev/ptmx

  # /dev/null
  touch $ROOTFS/dev/null
  mount --bind /dev/null $ROOTFS/dev/null

  # hostname
  hostname $UUID

  # chroot + CMD
  exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'
"

何をするか

CMD 変数に /bin/sh を格納する。
cgexec で先に作った cgroup($UUID)にプロセスを所属させ、unshare で作るプロセス群をメモリ・CPU 制限の対象にする。
set -e でエラー時にシェルを終了させる。

mkdir -p $ROOTFS/proc
mount -t proc proc $ROOTFS/proc

新しい名前空間内で $ROOTFS/proc に proc 仮想ファイルシステムをマウントします。プロセス情報(/proc)がないと ps やプロセス関連のユーティリティが動かないため必要です。

mkdir -p $ROOTFS/dev/pts
mount -t devpts devpts $ROOTFS/dev/pts
mkdir -p $ROOTFS/dev
touch $ROOTFS/dev/ptmx
mount --bind /dev/pts/ptmx $ROOTFS/dev/pts/ptmx
ln -sf /dev/pts/ptmx $ROOTFS/dev/ptmx

擬似端末 (pty/pts) を $ROOTFS の中に作り、ptmx へのバインドやシンボリックリンクを作成してターミナル(tty)が使えるようにしている

touch $ROOTFS/dev/null
mount --bind /dev/null $ROOTFS/dev/null

/dev/null を作ってホストの /dev/null をバインドすることで $ROOTFS 内での /dev/null を確保

hostname $UUID

新しい名前空間内でホスト名を UUID に設定

exec capsh --chroot=$ROOTFS --drop=cap_sys_chroot -- -c 'exec $CMD'

chroot でルートディレクトリを $ROOTFS に変更し、capsh で cap_sys_chroot 権限を剥奪してから CMD(/bin/sh)を実行する。これにより、コンテナ内シェルが起動する。