にゃるにゃる

このブログに書かれている内容は CC BY-ND 4.0 の下に提供されています。

FreeBSD VIMAGE を使ってみた話 #lxcjp

この記事は 第7回 コンテナ型仮想化の情報交換会 @ 大阪 にて発表した内容を再編したものです。

やりたかったこと

最近 ConoHa が新しくなったので、「せっかくだから契約してみよう」と思って契約したのが始まりです。 ConoHa は 標準で 1個と追加で 16個 (合計 17個) の IPv6 アドレスを払い出してくれますが、 「せっかくなのでその IPv6 アドレスを有意義に使おう」と思い始めました。

その手段を考えているときに、「色んなアプリケーションを入れて動かしたい」が、「色々入れすぎると保守が面倒になりそう ...」 という相反する要求に答えてくれそうだったのが jail でした。 また、「jail を IPv4, IPv6 に両対応させたい (が、IPv4 はプライベートでも構わない)」ということを実現したい、と思っていました。

むずかしかったところ

普通、jail では prisoner (ゲスト) から jailer (ホスト) の NIC が見えるのですが、 prisoner から jailer の NIC に対して ifconfig が実行できるはずもなく、 「適当な NIC を 1つ追加して v4 を NAT すれば良いのでは ?」と思い実行してみると、 v4 での疎通性は確保できましたが、v6 での疎通性が確保出来ず (jail にアタッチ出来る NIC は 1つまで、かつ作った NIC に割り振られた v6 のアドレスを付けても外に出ていけない) 、 v6 での疎通性を確保しようと思うと、今度は v4 での疎通性が確保できず、「あちらを立てればこちらが立たず」のような状態になっていました。

解決手法

上記の問題を解決しようと思い調べてみると、VIMAGE というものがあると知り、 それを使うと上手く出来そう、と思い使ってみました (今回はそのお話です)。 具体的には epair(4)bridge(4) を使います。

epair(4) とは、

必要なもの

適当なソースを取ってきて (svnlite co svn:// ...) 、適当にカーネル再構築のファイルを書きます (/usr/src/sys/amd64/conf/<file>) 。

include GENERIC
ident VIMAGE
options VIMAGE

# 下は NAT 用の config
options IPFIREWALL
options IPFIREWALL_VERBOSE
options IPFIREWALL_DEFAULT_TO_ACCEPT
options IPDIVERT
options IPFIREWALL_NAT
options LIBALIAS

書いたら、カーネルを再構築して (cd /usr/src; make kernel KERNCONF=<file>) 、再起動 (reboot) します。

再起動する前に /boot/loader.conf に以下を書いたり、再起動後に kldload if_epair, kldload if_bridge すると良いでしょう:

if_epair_load="YES"
if_bridge_load="YES"

また、jail の設定を /etc/jail.conf に書きます:

<jail_name> {
    jid = 1;
    name = <jail_name>;
    path = /home/owata/jail/<jail_name>;
    host.hostname = <jail_name>;
    allow.raw_sockets;
    exec.start = "/bin/sh /etc/rc";
    exec.stop = "/bin/sh /etc/rc.shutdown";
    mount.devfs;
    vnet;
}

書いたら、jail 用の / を作ってそこに base.txz の中身を放り込み、設定が終わったら、一度 jail を起動してみると良いでしょう。

% mkdir -p jail/<jail_name>
% tar xzf base.txz -C jail/<jail_name>
# service jail start <jail_name>

その後、jailer で epairbridgeNIC を作り、epair の片側 (今回は epair0b) を jail に attach させます。

jailer # ifconfig epair create
jailer # ifconfig bridge create
jailer # ifconfig epair0b vnet <jail_name>

また、それぞれの NIC に適当なアドレスを設定します 。

jailer # ifconfig bridge0 inet 192.168.0.1/24
jailer # ifconfig epair0a inet 192.168.0.100/24
prisoner # ifconfig epair0b inet 192.168.0.200/24

同一 bridge に jailer の外向きの NIC (今回は vtnet0) と epair を追加し、prisoner のデフォゲを bridge (bridge0) に向けます。

jailer # ifconfig bridge0 addm vtnet0
jailer # ifconfig bridge0 addm epair0a
prisoner # route add default 192.168.0.1

その後、bridge (bridge0) から外向きの NIC (vtnet0) に出て行く v4 は NAT するようにします (/etc/rc.firewall) 。

#!/bin/sh
cmd="/sbin/ipfw -q"
wan_if="vtnet0"
lan_if="bridge0"
jail_subnet="192.168.0.0/24"

$cmd nat 1 config if $wan_if reset
$cmd add 10 nat 1 all from $jail_subnet to any via $wan_if out
$cmd add 20 nat 1 all from any to me via $wan_if in

最後に NAT 用に /etc/rc.conf に設定を書き出して、service ipfw start すると良いでしょう。

jail_enable="YES"
gateway_enable="YES"
natd_enable="YES"
natd_interface="vtnet0"
natd_flags="-dynamic -m"

firewall_enable="YES"
firewall_type="open"
firewall_script="/etc/rc.firewall"

試しに v4 と v6 で外に出られるか確認してみます。

v4

root@gochiusa:/ # ping -c 1 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=55 time=1.224 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 1.224/1.224/1.224/0.000 ms

v6

root@gochiusa:/ # ping6 -c 1 www.kame.net
PING6(56=40+8+8 bytes) <......> --> 2001:200:dff:fff1:216:3eff:feb1:44d7
16 bytes from 2001:200:dff:fff1:216:3eff:feb1:44d7, icmp_seq=0 hlim=53 time=112.734 ms

--- orange.kame.net ping6 statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 112.734/112.734/112.734/0.000 ms

まとめ

今回は全部手 (shell) でコマンドを実行していきましたが、jail.conf には exec.prestart や、 exec.start などのオプションがあるので、それらを有効活用して効率化していきたいです。

もし、 jail を本番環境とかで扱う (ことになった) のであれば、 jail.conf などをモダンな形で管理すれば (例えば git push すると自動的に prisoner が沸いてくるようにする、など) 、 infrastructure as code や immutable infrastructure みたいなことが出来るのかなぁ、などと妄想していました。

また、勉強会の中で @ten_forward が発表されていたように (発表資料, 発表の内容 (youtube) )、 Linux には veth という、今回でいう epair のような実装があるらしく、「docker でも似たようなことが出来るのかな ?」 などと考えていました。

参考資料

© 2014 おわたん All Rights Reserved.