FreeBSDサーバ上に仮想サーバを構築する(jail)

対象プラットフォーム: FreeBSD 10.x
(下記手順を2017/10/17にFreeBSD 10.4-RELEASEで確認済み)


はじめに

FreeBSDでは、jailと呼ばれる仕組みを利用することにより、FreeBSD環境上に仮想のFreeBSD環境を複数構築することが出来ます。

jailはUNIXに古くから存在するchrootを発展させたもので、ホスト環境(以下、便宜上「jailer」と呼びます)上に構築したjail環境(以下、便宜上「prisoner」と呼びます)は、ホスト環境とは別に動作するFreeBSDマシンとして振舞います。

prisonerからはjailerおよび並列する別のprisonerに直接アタッチすることが出来ないため、システム管理者がシステムの一部の権利を他者に委譲しなくてはならない場合等に有効な解決策となります。

このページでは、このjailを利用した仮想FreeBSD環境の構築方法について説明します。

jailの特徴と用途

用途によって、jailが有効な場合とそうでない場合があります。
ここでは、jailの特徴と向いている用途について説明します。

jailの特徴

jailの主な特徴は以下の通りです。

・エミュレーションではないため、高速に動作する
・prisonerごとにjailerとは別のIPアドレスをひとつ振ることができる
・各prisonerごとのリソース配分を予め設定しておくことができない
・jailerとprisonerで同一のファイルシステムが利用されるため、UID/GIDの割り当てに注意する必要がある

どんなときに使うのか?

jailの一般的な用途としては、

・脆弱性などの懸念があるソフトウェアを安全に(jailerに影響を与えないように)動作させたいとき
・システム管理者がシステムの一部の権利を委譲したいとき

が挙げられます。
応用例として、「portsからのpackage構築を、jailerにpackageをインストールすることなく実行する」ようなことも可能です。

一方、各prisonerごとにIPアドレスがひとつ振られてしまうため、複数のサーバ名を持つWebサーバ(apache等)をネームベースのvirtualホストで構築するような用途には向きません。

jail環境の構築

それでは、早速jail環境の構築に入ります。

以下では、jailer内に4つのprisoner(prisoner1〜prisoner4)を構築する手順について説明しています。
自分が構築したいprisonerの数にあわせ、適宜読み替えてください。

prisonerのベースディレクトリの作成

まずはじめに、各prisonerのルートディレクトリになるディレクトリを作成します。

各prisonerのルートディレクトリの作成
$ mkdir /home/jail

$ mkdir /home/jail/prisoner1
$ mkdir /home/jail/prisoner2
$ mkdir /home/jail/prisoner3
$ mkdir /home/jail/prisoner4

上記の例では、各prisonerのルートを「/home/jail」下に用意していますが、必ずしもこの場所に作らなくてはいけないわけではありません。
自分のご趣味に合わせ、お好みの場所に作成してください。

ベースシステムのインストール方法

まず、各prisonerにベースシステムをインストールします。
ベースシステムのインストールは、配布されているベースシステムを展開する方法と、srcからビルドしてインストールする方法の2つがあります。

今回は、freebsd-updateでのメンテナンスが容易な前者の方法について説明します。

ベースシステムのインストール

FreeBSDのレポジトリからbase.txzを持ってきて、各prisonerに展開します。
レポジトリからbase.txzを取得する際には、jailerと同じアーキテクチャ(amd64/i386)のファイルを取得するようにしてください。

ベースシステムのインストール
$ fetch ftp://ftp.freebsd.org/pub/FreeBSD/releases/amd64/amd64/10.3-RELEASE/base.txz
$ tar -zxpf base.txz -C /home/jail/prisoner1
$ tar -zxpf base.txz -C /home/jail/prisoner2
$ tar -zxpf base.txz -C /home/jail/prisoner3
$ tar -zxpf base.txz -C /home/jail/prisoner4
$ rm base.txz

上記の例では、AMD64版のFreeBSD 10.3-RELEASEのベースシステムをダウンロードして各prisonerに展開しています。

「/etc/resolv.conf」のコピー

次に、各prisonerで名前解決ができるよう「resolv.conf」を用意します。
ほとんどのケースで「resolv.conf」の内容は、jailerと同じ設定で問題ないため、このファイルをjailer側からコピーします。

「/etc/resolv.conf」のコピー
$ cp -p /etc/resolv.conf /home/jail/prisoner1/etc/
$ cp -p /etc/resolv.conf /home/jail/prisoner2/etc/
$ cp -p /etc/resolv.conf /home/jail/prisoner3/etc/
$ cp -p /etc/resolv.conf /home/jail/prisoner4/etc/

prisonerでのkernel関連のログ出力の抑止

続いて、各prisonerのログに、kernel関係の出力がされないよう、「syslog.conf」を編集して設定します。

prisonerでのkernel関連のログ出力の抑止
$ vi /home/jail/prisoner1/etc/syslog.conf
$ vi /home/jail/prisoner2/etc/syslog.conf
$ vi /home/jail/prisoner3/etc/syslog.conf
$ vi /home/jail/prisoner4/etc/syslog.conf

それぞれ「syslog.conf」の「*.err;kern.warning;auth.notice;mail.crit /dev/console」の行をコメントアウトしてください。

adjkerntzの停止

続いて、各prisonerのcronによってkernelのタイムゾーンが設定されないよう、crontabを編集します。
(kernelのタイムゾーン設定はjailer側のcronで定期的に行われるため、prisonerでのcronによる設定は不要です。)

adjkerntzの停止
$ vi /home/jail/prisoner1/etc/crontab
$ vi /home/jail/prisoner2/etc/crontab
$ vi /home/jail/prisoner3/etc/crontab
$ vi /home/jail/prisoner4/etc/crontab

それぞれ「crontab」の「1,31 0-5 * * * root adjkerntz -a」の行をコメントアウトしてください。

prisonerの名前解決のための設定

各prisonerで、自身の名前を解決できるように「hosts」を編集します。

prisonerの名前解決のための設定
$ vi /home/jail/prisoner1/etc/hosts
$ vi /home/jail/prisoner2/etc/hosts
$ vi /home/jail/prisoner3/etc/hosts
$ vi /home/jail/prisoner4/etc/hosts

それぞれのファイルの一番下に「192.168.16.128 prisoner1.kishiro.local」のように、各prisonerに割り当てるIPアドレスとFQDNのペアを追記してください。

jailerとprisonerでUID/GIDを重複させないための設定

jailでは、jailerと各prisonerでひとつのファイルシステムを共有します。
このため、jailer側で利用しているUID/GIDをそのままprisoner側で利用してしまうと、jailer側のユーザがprisoner側のユーザのファイルを自分のものとして扱えてしまうことになります。

これを回避するためには、jailerと各prisonerで重複しないUID/GIDを振る必要があります。
下記のようにして、UID/GIDの帯域をそれぞれ設定してください。

jailerとprisonerでUID/GIDを重複させないための設定
$ vi /etc/pw.conf

$ vi /home/jail/prisoner1/etc/pw.conf
$ vi /home/jail/prisoner2/etc/pw.conf
$ vi /home/jail/prisoner3/etc/pw.conf
$ vi /home/jail/prisoner4/etc/pw.conf

ファイルの中には、以下のようにしてUID/GIDの範囲を指定します。
jailerおよび各prisonerごとにminuid/maxuid、およびmingid/maxgidを設定し、jailerおよび各prisoner間で同じUID/GIDが割り当てられないようにしてください。

「pw.conf」の設定
defaultpasswd no
minuid 10000
maxuid 19999
mingid 10000
maxgid 19999

上記の例では、UIDおよびGIDがそれぞれ10000〜19999の範囲に入るように割り当てられます。

ただし、この作業をしても「root」等の予め定義されているユーザ、および「pgsql」や「postfix」等のports経由で追加されるユーザについては、jailerとprisonerで同じUID/GIDが割り当てられます。
これらのユーザのUID/GIDを変更するには、「vipw」を利用して、直接UID/GIDを変更してください。
(「vipw」でUID/GIDを変更しても、これらのユーザが所有者となっているファイルのオーナーのUID/GIDは自動的には更新されません。自分でchown/chgrpしてやる必要があるので、注意してください。)

各prisonerのdevfsのマウント

次に各prisonerでデバイスが利用できるよう、各prisonerのdevをマウントしておきます。

各prisonerのdevfsのマウント
$ mount -t devfs devfs /home/jail/prisoner1/dev
$ mount -t devfs devfs /home/jail/prisoner2/dev
$ mount -t devfs devfs /home/jail/prisoner3/dev
$ mount -t devfs devfs /home/jail/prisoner4/dev

各prisonerでの細かな設定

上記までで、jailer側から可能なprisonerの設定は終わりました。
次は、「chroot」を利用して各prisonerのルートディレクトリに遷移し、必要な設定を行います。

「chroot /home/jail/prisoner1」のように、各prisonerごとにルートディレクトリに遷移し、以下の作業を行ってください。
(chrootしたディレクトリからjailer側に戻るときには「exit」を利用してください。)

各prisonerでの細かな設定
$ touch /etc/wall_cmos_clock
$ ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
$ passwd root

一番最後の処理では、rootのパスワードを変更しています。
そのprisonerにおけるrootのパスワードを設定してください。(このパスワードはjailerのrootのパスワードとは区別されます。)

jail環境の起動のための設定

上記までで、各prisoner内の設定は完了しました。
最後に、jailer側で各prisoner側に割り当てるIPアドレス、FQDN等を設定します。

「/etc/rc.conf」に以下のように追記してください。

「/etc/rc.conf」の設定
ifconfig_fxp0_alias0="inet 192.168.16.128 netmask 255.255.255.255"
ifconfig_fxp0_alias1="inet 192.168.16.129 netmask 255.255.255.255"
ifconfig_fxp0_alias2="inet 192.168.16.130 netmask 255.255.255.255"
ifconfig_fxp0_alias3="inet 192.168.16.131 netmask 255.255.255.255"

jail_enable="YES"
jail_list="prisoner1 prisoner2 prisoner3 prisoner4"
#jail_sysvipc_allow="YES"

FreeBSD 8.xでは、各prisonerの設定は「/etc/rc.conf」に直接記述していましたが、FreeBSD 9.1より「/etc/jail.conf」に分けて記述するようになりました。
各prisonerの設定を下記の通り、「/etc/jail.conf」に記述します。

「/etc/jail.conf」の設定
prisoner1
{
        jid=1;
        name=prisoner1;
        path=/home/jail/prisoner1;
        ip4.addr=192.168.16.128;
        host.hostname=prisoner1.kishiro.local;
        allow.chflags;
        allow.raw_sockets;
        exec.start="/bin/sh /etc/rc";
        exec.stop="/bin/sh /etc/rc.shutdown";
        interface=fxp0;
        mount.devfs;
}

prisoner2
{
        jid=2;
        name=prisoner2;
        path=/home/jail/prisoner2;
        ip4.addr=192.168.16.129;
        host.hostname=prisoner2.kishiro.local;
        allow.chflags;
        allow.raw_sockets;
        exec.start="/bin/sh /etc/rc";
        exec.stop="/bin/sh /etc/rc.shutdown";
        interface=fxp0;
        mount.devfs;
}

prisoner3
{
        jid=3;
        name=prisoner3;
        path=/home/jail/prisoner3;
        ip4.addr=192.168.16.130;
        host.hostname=prisoner3.kishiro.local;
        allow.chflags;
        allow.raw_sockets;
        exec.start="/bin/sh /etc/rc";
        exec.stop="/bin/sh /etc/rc.shutdown";
        interface=fxp0;
        mount.devfs;
}

prisoner4
{
        jid=4;
        name=prisoner4;
        path=/home/jail/prisoner4;
        ip4.addr=192.168.16.131;
        host.hostname=prisoner4.kishiro.local;
        allow.chflags;
        allow.raw_sockets;
        exec.start="/bin/sh /etc/rc";
        exec.stop="/bin/sh /etc/rc.shutdown";
        interface=fxp0;
        mount.devfs;
}

jailでは、jailerで認識しているネットワークデバイス(IPアドレスが割り当て済みのもの)に対し、別のIPアドレスを複数割り当て(alias)、それらのIPアドレスを各prisonerに設定します。
上記の「/etc/rc.conf」の例では、「fxp0」に対し、jailer側で割り当てられているIPアドレスとは別に、「192.168.16.128〜192.168.16.131」のIPアドレスを割り当てています。

また、aliasされたIPアドレスは「/etc/jail.conf」の中の各prisoner設定内の「ip4=」の部分で各prisonerに割り当てています。

「/etc/rc.conf」内の「jail_sysvipc_allow=」は、prisoner上のプロセスからシェアドメモリにアクセスさせるか否かの設定です。
セキュリティの観点からすると「NO」にしておくのがベストですが、PostgreSQLなどのシェアドメモリを利用するソフトウェアを動かす際には「YES」に設定してください。

また、デフォルトの状態では、prisonerではtracerouteやping、bpfを利用したWIDE-DHCP等のraw socketを利用するプログラムが正常に動作しません。
これらのプログラムを利用するために、jailerの「/etc/sysctl.conf」に以下の記述を追記してください。

「etc/sysctl.conf」への追記
security.jail.allow_raw_sockets=1

以上で設定は完了です、システムを再起動させて各prisonerを有効にしてください。

jailがうまく起動しない場合のトラブルシューティング

上記の通り設定しシステムを再起動しても、コンソールに「cannot start jail」等と表示され、上手く動かない場合があります。
この場合は、jailの起動スクリプトである「/etc/rc.d/jail」の中身を確認し、どこでエラーが出ているのかを確認してください。

私も、FreeBSD 8.4-RELEASEで「cannot start jail」というメッセージが表示され、jailが起動できない現象に見舞われましたが、原因はFreeBSD 8.4-RELEASEで書き換えられた「/etc/rc.d/jail」の下記部分でした。

「/etc/rc.d/jail」の核心部分
eval ${_setfib} jail -n ${_jail} ${_flags} -i -c path=${_rootdir} host.hostname=${_hostname} ip4.addr=\"${_addrl}\" ip6.addr=\"${_addr6l}\" ${_parameters} command=${_exec_start} > ${_tmp_jail} 2>&1

最後のリダイレクト部分を取ると、コンソールにエラーメッセージが表示されます。

私の場合は、予めシステムをIPv4のみでビルドしていたため、jailから「ip6.addr=」パラメータのサポートが無くなったことによるエラーでした。(未知のパラメータが指定されている旨のエラーが出ていました。)

上記の「ip6.addr=\"${_addr6l}\"」部分を削除し、無事に起動するようになりました。

prisonerを削除するには

prisoner内のportsが古くなったり、設定を誤ったりして、prisoner環境を再度一から構築しなおす場合には、以下のようにしてprisonerを削除し、また上記手順を踏んで環境を再構築してください。

prisonerの削除
$ /etc/rc.d/jail stop prisoner1

$ chflags -R noschg /home/jail/prisoner1/*

$ rm -R /home/jail/prisoner1/*

上記の例では、prisoner1を停止させ、そのルートディレクトリ下のファイルをすべて削除します。

jailerで利用できるjail関連のコマンド

jailerから各prisonerを制御するためのコマンドがいくつか用意されています。

「jls」:稼動中のprisonerの一覧表示

jailer上で「jis」と入力すると、現在稼動中のprisonerの一覧を下記のように表示させることができます。

「jls」の実行結果の例
   JID  IP Address      Hostname                      Path
     1  192.168.16.128  prisoner1.kishiro.local       /home/jail/prisoner1
     2  192.168.16.129  prisoner2.kishiro.local       /home/jail/prisoner2
     3  192.168.16.130  prisoner3.kishiro.local       /home/jail/prisoner3
     4  192.168.16.131  prisoner4.kishiro.local       /home/jail/prisoner4

一番左側に表示される「JID」はprisonerに割り振られたIDで、後述する「jexec」で遷移するprisonerを特定する際に利用します。

「jexec」:jailerからprisonerに遷移

jailer上から「jexec」を利用することによって、任意のprisonerにrootとして遷移することができます。
このコマンドはrootのみ実行できます。

「jexec」の利用方法の例
$ jexec 1 /bin/csh

上記の例では、JIDが「1」のprisonerに「/bin/csh」のシェルを利用して遷移します。

「/etc/rc.d/jail」:prisonerの起動/停止

prisonerの開始/停止については、「/etc/rc.d/jail」を利用して実現できます。
prisonerすべてを停止させるには、以下のようにします。

全prisonerの停止
$ /etc/rc.d/jail stop

逆に、すべてのprisonerを開始するには、以下のようにします。

全prisonerの開始
$ /etc/rc.d/jail start

すべてのprisonerを開始/停止させるだけでなく、特定のprisonerを指定して開始/停止させることも可能です。
特定のprisonerを指定して開始/停止させるには、以下のようにします。

特定のprisonerを指定して開始
$ /etc/rc.d/jail start prisoner1

特定のprisonerを指定して停止
$ /etc/rc.d/jail stop prisoner1

prisonerでユーザ/グループを追加するには

上記手順で作成したprisoner環境では、「sysinstall」が利用できないため、このユーティリティを利用したユーザ/グループの追加ができません。
prisoner環境でユーザ/グループを追加するには、以下のようにしてください。

ユーザの追加
$ adduser
グループの追加
$ pw groupadd user

上記の例では「user」という名のグループを作成しています。

その他、ユーザを削除する「rmuser」、ホームディレクトリの場所などのユーザの設定情報を変更できる「chpass」などのコマンドが便利です。

prisonerでsshd/apacheを動作させるときの注意点

prisoner上では無論sshdやapache等を動作させることが可能ですが、その際に1つだけ注意する点があります。
それは、prisoner上やjailer上で動作させるsshd/apacheの設定で、明示的にListenするIPアドレスおよびPort番号を明示しなくてはいけない点です。

たとえば、jailer側でsshdを動作させている場合、設定ファイルの中でListenするIPアドレスを明示的に指定していないことが多いと思います。
ListenするIPアドレスを明示していない場合、sshdはそのマシンに割り当てられているIPアドレスのすべてでListenします。
aliasしたIPアドレスについても「マシンに割り当てられているIPアドアドレス」にあたるため、prisonerに割り当てられているIPアドレスでjailer上のsshdがListenしてしまうことになり、結果的にprisoner上でsshdが起動できなくなってしまいます。

これを回避するために、jailerの「/etc/ssh/sshd_config」に下記の一行を追加してください。

「/etc/ssh/sshd_config」内の設定
ListenAddress 192.168.16.128

「192.168.16.128」にはjailerのIPアドレスを指定してください。
また、prisoner上でsshdを動作させる際にも、prisonerの「/etc/ssh/sshd_config」内の「ListenAddress」に、そのprisonerのIPアドレスを指定するようにしてください。

上記はsshdの例でしたが、apacheでも同様にjailer上のapacheがListenするIPアドレス、およびprisoner上のapacheがListenするIPアドレスを明示的に指定する必要があります。

apacheではjailer上およびprisoner上の「/usr/local/etc/apache/httpd.conf」に下記の一行を追加してください。

「/usr/local/etc/apache/httpd.conf」内の設定
Listen 192.168.16.128:80
Listen 192.168.16.128:443

「192.168.16.128」の部分には、それぞれjailerのIPアドレス、prisonerのIPアドレスを指定してください。
また、80番ポートはHTTP、443ポートはHTTPSになりますので、必要なもののみ適宜してください。

prisonerでPostgreSQLを動作させる際の注意点

次に、prisoner上でPostgreSQLを動作させる際の注意点ですが、PostgreSQLはOSのシェアドメモリを利用するため、設定が多少煩雑です。
まずはじめに、jailerの「/etc/rc.conf」に以下の記述があるか確認し、なければ追記してください。

jailerの「/etc/rc.conf」内の設定
jail_sysvipc_allow="YES"

上記の設定は前述した通り、prisoner内のプロセスからjailerのシェアドメモリにアクセスさせるための設定です。

次に、複数のprisoner上およびjailer上で同時にPostgreSQLを動作させるには、このシェアドメモリ内の利用が重複しないよう、各prisoner上およびjailer上のpgsqlユーザのUID/GIDを重複がないように修正してやる必要があります。

PostgreSQLを動作させるprisonerごとに、prisoner内で「vipw」を利用し、「pgsql」ユーザのUID/GIDを重複しないよう変更します。

「vipw」による「pgsql」ユーザのUID/GID変更
pgsql:*:10070:10070:PostgreSQL Daemon:/usr/local/pgsql:/bin/sh

続いて「/etc/group」を編集し、pgsqlのグループIDを変更してください。

「/etc/group」のGIDの変更
pgsql:*:10070:

「vipw」でUID/GIDを変更しても、これらのユーザが所有者となっているファイルのオーナーのUID/GIDは自動的には更新されません。
PostgreSQLをportsからインストールした直後の状態では、「pgsql」がオーナーのファイルは「/usr/local/pgsql」フォルダのみですので、下記のようにしてオーナーをpgsqlに変更しなおしてください。

「pgsql」のUID/GID変更に伴うファイルオーナーの再設定
$ cd /usr/local

$ chown -R pgsql pgsql
$ chgrp -R pgsql pgsql

あとは、「PostgreSQL環境を構築する(PostgreSQL 8.x)」の内容を参考にして、DB等の設定を行ってください。

あと、prisoner上のPHP環境等から「localhost」上のDBに接続を試みても、同じprisoner上で動作するPostgreSQLに接続できない場合があります。
(その際、該当するprisonerの「/var/log/message」に以下のメッセージが表示されます)

「/var/log/message」内のエラーメッセージ
FATAL:  no pg_hba.conf entry for host "192.168.16.128", user "pgsql", database "xxxx"

この問題を回避するには、「/usr/local/pgsql/data/pg_hba.conf」の最後に以下の一行を追加してください。

「/usr/local/pgsql/data/pg_hba.conf」へのパーミッション設定の追記
host    all         all         192.168.16.128/32       trust

「192.168.16.128」の部分には、PostgreSQLを動作させているprisonerのIPアドレスを指定してください。

特定のprisonerにデバイスへのフルアクセスを許可するには

prisonerでデフォルトで利用できるデバイスは、devfsの「devfsrules_jail」というプロフィールで規定された最低限のものに限られています。
このため、標準の状態では、prisonerにインストールしたmplayerでMP3を鳴らしたり、prisonerにインストールしたffmpegにbktrドライバ経由でTV画像をキャップチャさせ、ストリーミング配信したりすることはできません。

これらを実現するためには、特定のprisonerにデバイスへのアクセスを許可する必要があります。

特定のprisonerにデバイスへのアクセスを許可する方法については、「jail環境でデバイスへのフルアクセスを許可する」を参照してください。

複数のprisonerでファイルシステムを共有するには

portsツリーなど、ファイル・ディレクトリを複数のprisonerで共有したいというニーズは多いのではないかと思います。
複数のprisonerでファイルシステムを共有するには、「nullfs」と呼ばれる特殊なファイルシステムを利用します。

「nullfs」を利用して複数のprisonerでファイルシステムを共有する方法については、「複数のjailクライアント(prisoner)からファイルシステムを共有する (nullfs)」を参照してください。

変更履歴

2017/06/21

・FreeBSD 10.xをターゲットとして全面的に書き直し。
(FreeBSD 8.x向け構築方法のバックナンバーはこちら

2013/09/27

・「jailがうまく起動しない場合のトラブルシューティング」を追記。
・prisonerでユーザを削除したり、ユーザの設定情報を変更したりする場合に便利なコマンド「rmuser」「chpass」に関する記述を追記。
・不要ファイルリストの公開停止に伴い、「不要なファイルのリストの取得」の項目を削除。
・prisoner設定用のシェルスクリプトを修正。

2010/04/05

・「複数のprisonerでファイルシステムを共有するには」を追加。
・FreeBSD 7.x以降では「mount_devfs」が無くなっている為、「mount -t devfs」を使ってdevfsをマウントする方法を追記。

2010/03/31

・「/usr/local/pgsql/data/pg_hba.conf」への追記を促す箇所の凡例のキャプション文言が間違っていたのを訂正。


あなたの探し物は見つかりましたか?
まさにこれだ
参考になった
ちょっと違う
これじゃない

何かメッセージがあればお願いします

このメッセージを非公開にする

ご注意

・頂いたメッセージは管理者のチェックの後、公開されます。
・メッセージの公開を希望されない場合には、「このメッセージを非公開にする」にチェックを入れてください。
・管理者が不適切と判断したメッセージは公開しませんので、予めご了承ください。


まさにこれだ
1 (100%)

表示できるメッセージはありません。


目次に戻る
image