chrootとunshareを使い、シェル上でコマンド7つで簡易コンテナ

コンテナ型仮想化であるDockerは、様々な名前空間の分離やcgroupなどのLinuxの機能を使用して実現されています。


ここでは、簡易的に機能を「ルートディレクトリとPID名前空間も分離されている」と限定し、シェル上で7つのコマンドでコンテナを立ち上げてみます。


最終的な目標は、立ち上げたコンテナ上でpsを実行し、以下のようにPIDが1から割り振られていることを確認することです。

bash-4.3# ps
  PID TTY          TIME CMD
    1 pts/1    00:00:00 bash
    7 pts/1    00:00:00 ps
bash-4.3#

結論: 7つのコマンド

先に書いてしまうと、7つのコマンドは以下のとおりです。


まず、簡易コンテナの作成から、コンテナ内のbash起動まで。

$ mkdir test_container
$ cd test_container
$ cp -r /bin /lib /lib64 .
$ sudo unshare -pf chroot . bash


新たにbashが立ち上がりますので、
psを実行するためにprocとdevptsのマウント。

mkdir -p /proc /dev/pts
mount -t proc proc /proc
mount -t devpts devpts /dev/pts


以上で準備完了です。


psを実行してみると、PIDが新たに1から割り振られていることを確認できます。

bash-4.3# ps
  PID TTY          TIME CMD
    1 pts/1    00:00:00 bash
    5 pts/1    00:00:00 ps


また、"ls /"を試すと、"test_container"ディレクトリ内で作成したものしか無く、
ルートディレクトリを"test_container"へ変更できていることを確認できます。

bash-4.3# ls /
bin  dev  lib  lib64  proc

説明: chrootコマンドとunshareコマンド

今回重要となるコマンドは、4つ目で実行したchrootコマンドとunshareコマンドです。


chrootコマンドは、ルートディレクトリを変更するコマンドで、以下のように使用します。

# chroot 新たにルートとするディレクトリ ルートディレクトリ変更後に実行するコマンド

chrootを実行すると、第1引数のディレクトリを新たなルートディレクトリ(/)として、第2引数のコマンドを実行します。
なお、ルートディレクトリを変えたあと第2引数のコマンドを実行するので、第2引数のコマンドの実行バイナリ自体と、実行バイナリが参照する共有ライブラリは、第1引数のディレクトリ内に存在する必要があります。(そのため、3つ目のコマンドで/binと/lib・/lib64をコンテナ内へコピーしていました。)


また、unshareコマンドは、様々な名前空間の分離を行うコマンドで、以下のように使用します。

# unshare [オプション] コマンド [コマンドの引数]

オプションでは「何の名前空間を分離するか」を指定します。
今回は、以下の2つのオプションを使用しました。

p PID名前空間の分離
f 指定したプログラムを直接実行するのではなくunshareの子プロセスとしてfork


なお、chrootとunshare共に、同名のシステムコールが存在し、コマンドはシステムコールをシェル上から呼び出すものとなっています。

参考

今回の記事はunshareとchrootの紹介のような記事ですが、
もっとしっかりと、シェルスクリプトでコンテナ型仮想化を実現しているものとして、
MINCS」があります。


製作者の方の解説記事も勉強になります。