chroot(2)のアプローチを推し進めて、ファイルシステムの名前空間のみならず、ほかのリソースについても隔離できるようにした機能がFreeBSDのjail(2)だ。操作にはjail(8)コマンドやjexec(8)、jls(8)などを使用する。
隔離環境の作り方はchroot(8)の場合と同じだ。例えば次のようにすれば、現在の環境から隔離された環境を作ることができる。
# mkdir -p /jail/a # cp -Rp /bin /sbin /lib /libexec /usr /var /etc /jail/a/ # mkdir /jail/a/dev # mount -t devfs devfs /jail/a/dev
jail(8)コマンドは次のように使用する。この使い方で「chroot /jail/a /bin/sh」に相当している。
jail -c name=a path=/jail/a command=/bin/sh
現在どの程度のjailが作成されているかはjls(8)コマンドで確認できる。
$ jls JID IP Address Hostname Path 2 - /jail/a $
ちなみに隔離空間は現在のシステムからコピーしなくても、make buildworld installworldで次のようにオプションを指定することで実施することもできる。
# /usr/src # make buildworld # make DESTDIR=/jail/b installworld
さて、それではjail(2)がchroot(2)とはどう違うのかを調べてみよう。まず、ps(1)の出力の違いを調べてみる。
# ps PID TT STAT TIME COMMAND 1021 v0 Is 0:00.00 login [pam] (login) 1029 v0 I+ 0:00.03 -csh (csh) 1022 v1 Is+ 0:00.00 /usr/libexec/getty Pc ttyv1 1023 v2 Is+ 0:00.00 /usr/libexec/getty Pc ttyv2 1024 v3 Is+ 0:00.00 /usr/libexec/getty Pc ttyv3 1025 v4 Is+ 0:00.00 /usr/libexec/getty Pc ttyv4 1026 v5 Is+ 0:00.00 /usr/libexec/getty Pc ttyv5 1027 v6 Is+ 0:00.00 /usr/libexec/getty Pc ttyv6 1028 v7 Is+ 0:00.00 /usr/libexec/getty Pc ttyv7 1132 0 I 0:00.00 su -l 1133 0 S 0:00.02 -su (csh) 1188 0 R+ 0:00.00 ps # jail -c name=a path=/jail/a command=/bin/sh # ps PID TT STAT TIME COMMAND 1189 0 SJ 0:00.00 /bin/sh 1190 0 R+J 0:00.00 ps #
jailの内部に入ると、外部のプロセスが見えなくなっていることが確認できる。jail(2)はプロセスリストを隔離しており、jail以外のプロセスが見られなくなる。見えなくなるだけでなく、操作の対象とすることもできない。
試しにkill(1)を実行しても、そのようなプロセスは存在しないというエラーが返ってくる。
# kill 1024 kill: 1024: No such process #
ただし、プロセスリストを隔離しただけで、プロセス番号はシステムにおいて唯一であることに注意しておきたい。つまり、init(8)は必ずプロセス番号1として最初に起動されるから、プロセス番号1のinit(8)がjailの中に現れることは絶対になく、プロセス番号1がjailの中から見えることも絶対にないということになる。あくまでも隔離して仮想化されたように振る舞っているのであって、実際に動作しているカーネルは1つである。
rootの権限も一部降格となる。特に外部にアクセスするために使用できるシステムコールの権限が降格となり、さまざまな違いが現れる。
例えばdf(1)の出力の違いを比べてみる。jailに入る前は見えていたパーティション/マウント情報が、jailの内部から見えなくなっていることが分かる。
# df Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/ada0p2 127975772 3342736 114394976 3% / devfs 1 1 0 100% /dev devfs 1 1 0 100% /jail/a/dev # jail -c name=a path=/jail/a command=/bin/sh # df Filesystem 1K-blocks Used Avail Capacity Mounted on /dev/ada0p2 127975772 3342736 114394976 3% / #
jailの内部からはmount(8)コマンドを実行することもできない。
# mkdir dev2 # mount -t devfs devfs /dev2 mount: devfs : Operation not permitted #
jail(2)は基本的にプロセスを特定の隔離空間に閉じ込めるものだが、プロセスがない状態でもjailを作って保持しておくといったこともできる。また、システム起動時に自動的にjailが動作するようにするには、/etc/rc.confに次のような設定を追加しておけばよい。この場合、service jail startやservice jail stopでjail環境の有効、無効を切り替えることができる。
# jail jail_enable="YES" jail_list="a" jail_a_devfs_enable="YES" jail_a_rootdir="/jail/a" jail_a_hostname="a.jail.example.org" jail_a_interface="em0" jail_a_ip="192.168.1.201" jail_a="/bin/sh /etc/rc"
ただしjailを運用する場合、jail内部の/etc/rc.confの設定をよく考えておく方がよい。
例えば、時刻を設定するシステムコールsettimeofday(2)はjail内部では実行できないため、時刻を調整するコマンドが実行されないようにcron(8)を無効にするなり、該当するサービスをcron(8)経由で実行されないようにしておかないと、エラーメッセージが定期的にメールで送られてくるという面倒な事態になる。
FreeBSDのホスティングサービスを提供しているベンダは、実際にはjailで隔離空間を構築し、サービスを提供していることが多い。この方法は軽量で高速であり、1台のマシンに何中、何百というjailを構築して運用できるという特徴がある。前回紹介したRACCT/RCTL rctl(8)でリソースを制御すれば、そのままサービスとして提供できるレベルということになる。
FreeBSD jailはネットワークインターフェイスを別途割り当てる。
また、まだデフォルトでは有効になっていないが、それぞれのjailが独立したネットワークスタックを持てるようにする機能も追加されている。VIMAGEのカーネルオプションを追加してカーネルを再構築し、「jail -c vnet」でjailを作成すると、ネットワークスタックも独立するようになる。この機能を使うと、1つのマシン内に複数のネットワークを構築できるようになる。エンタープライズレベルのルータなどが実装している機能だ。
FreeBSDのjailは今後も個別リソースの隔離機能を進め、機能強化していく方向にある。今後もFreeBSDを特徴づける機能であり続ける見通しだ。ほかの仮想化技術と比較した場合のjailの特徴は、何といっても軽量さと高速さにあり、活用できれば費用対効果で優れた結果を期待できる。
chroot(2)はだいぶ古い機能だし、jail(2)もFreeBSDを特徴づける機能だが、最近になって仮想化機能を扱うようになった方は、こうした隔離機能を知らない方も多いのではないかと思い、今回はこの機能を紹介した。便利で面白い機能なので、ぜひ一度使ってみてほしい。
連載:FreeBSDのコレ知ってる?
第2回 軽量仮想化機能「chroot」と「jail」
Page 1
非ハイパーバイザ型の仮想化機能
リソース・コンパートメント(資源隔離)による仮想化
chroot(8)で使うファイルを用意
名前空間を隔離して上のパスへのアクセスを制限
Page 2
chroot(2)とよく似ているjail(2)の利用手順
ファイルシステムの名前空間以外も隔離できるjail
さらに隔離機能の強化が進むjail
Copyright © ITmedia, Inc. All Rights Reserved.