コンテナのしくみ
※ 本記事ではDockerやRailCar、LXC、Haconiwaなどの使用方法や実装については書いていない。
コンテナとは
- Kernelの機能により、リソースが隔離・制限された空間
- Kernelから見ると、ただのプロセス
コンテナは仮想化技術と言われるが、CPUやメモリなどのハードウェアを仮想化していない。独立したLinux環境を作ることができるので、仮想化技術の1つとされている。
リソースの隔離・制限を担うのはnamespaceとcgroupsである。
namespace
Kernel/OSのリソースを隔離する。以下の項目についてnamespaceを分離できる。
- プロセスID
- ネットワーク(インターフェース、ルーティングテーブル、ソケットなど)
- マウント(ファイルシステム)
- UTS(ホスト名 uname(2)が返す値の集合)
- IPC(セマフォ、MQ、共有メモリなどのプロセス間通信)
- ユーザー(UID、GID)
namespaceを分離した環境では、許可されたリソースしか見えなくなり、コンテナ内の要素だけが見えるようになる。
cgroups
タスクをグループ化したり、そのグループ内のタスクに対して様々な物理リソースを制限する。以下の項目の使用量とアクセスを制限できる。
- CPU
- メモリ
- ブロックデバイス(mmap可能なストレージとほぼ同義)
- ネットワーク
- /dev以下のデバイスファイル
コンテナ作成に関係するシステムコール
clone(2), unshare(2), chroot(2), pivot_root(2), mount(2)などを使用すれば、コンテナを作成できる。
実際にコンテナと呼べるであろう最低限の機能をRustで実装した。(今回はcgroupsよる制御は行っていないが、sys/fs/group 以下のファイルに情報を書き込むことで物理リソースを制限できる。)
肝となるのはchrootとunshareである。
chroot
プロセスから見えるファイルシステム自体を狭くする。カレントディレクトリをrootにし、pathの外のファイルが見えなくしている。
※実際にはCAP_SYS_CHROOT などの特定の特権で実行すると、実際のファイルシステムのルートへ抜け出ることができる。
unshare
forckやcloneなどで子プロセスを作成する際に、指定した属性を共有しないようにできる。
コンテナ作成手順
※ Ubuntu 16.04.4 LTS で動作確認している。
以下のようにdebootstrap
コマンドでDebianの環境を用意しておく。
debootstrap --arch amd64 jessie /var/lib/test_container/jessie-box http://ftp.jp.debian.org/debian
上に書いたRustのプログラムをrootで実行する。
root@hoge:/home/hoge/src/github/rtc# ./target/debug/rtc
root@test_container:/# pwd
/
root@test_container:/# hostname
test_container
root@test_container:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 20268 3304 ? S 13:26 0:00 /bin/bash -l
root 6 0.0 0.0 17504 2128 ? R+ 13:26 0:00 ps aux
root@test_container:/# cat etc/issue
Debian GNU/Linux 8 \n \l
ルートディレクトリとホスト名が変更され、プロセスIDが1から始まっており、ホストOSから独立した環境を手に入れることができたと言える。
軽量コンテナ
軽量コンテナの実装をいくつか挙げておく。普通の人がいきなりDockerの実装を読み切るのは困難であるため、学習する際は軽量なコンテナの実装から読み始めるのがよいと思われる。
参考書籍
- ふつうのLinuxプログラミング
- Linuxプログラミングインターフェース
- Goならわかるシステムプログラミング
- なるほどUnixプロセス ― Rubyで学ぶUnixの基礎