LXC で固定IPアドレスを持つコンテナを簡単に作れるようにした

More than 1 year has passed since last update.

LXC のデフォルトの設定だと、DHCP でコンテナに IP アドレスが割り振られるため、コンテナ同士で Riak クラスタを組んでみる、みたいなことがやりづらい。 IP アドレスを固定しようと思ったら普通に /etc/network/interfaces にそれっぽい設定を書けばいいんだけど、コンテナを作るたびに設定するのは面倒臭いし、間違えて同じ IP アドレスを異なるコンテナに割り当ててしまう事故も起こりやすい。また、IP アドレスを固定するのであれば、ついでに DNS にも登録して名前から IP アドレスを引けるようにしたい。

ということで、次のような設定をしたらすごく便利になった:

  1. ホストの /etc/hosts に全てのコンテナの名前と IP アドレスを書くことにした。
    • 一つのファイルに全てのコンテナの IP アドレスが集約されるので、どの IP アドレスを使ってないのかすぐにわかる。
  2. LXC の Ubuntu テンプレートを改造して、/etc/hosts から自分の IP アドレスを引いてきて、適切な $rootfs/etc/network/interfaces を吐き出すようにした。
  3. dnsmasq を立てて、/etc/hosts の内容を DNS 経由で引けるようにした。
  4. 簡単にコンテナを作れるように、補助シェルスクリプトを書いた。

1. /etc/hosts

こんな感じに、コンテナの IP アドレスを書いておく。

127.0.0.1   localhost
127.0.1.1   dev-3

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

# Local containers
10.0.3.1        web-1
10.0.3.2        web-2
10.0.3.3        db-1
(以下略)

2. LXC の Ubuntu テンプレートの改造

LXC はコンテナを作るときにテンプレートと呼ばれるスクリプトを実行する。Ubuntu のコンテナを作成するときは、/usr/share/lxc/templates/lxc-ubuntu が実行される。これを開いてみると中身はただの bash スクリプトになっていて、lxc-create で与えられた引数がこのスクリプトに渡ってくるようになっている。今回はこのスクリプトを改造して、/etc/hosts に与えられたコンテナ名に対応する IP アドレスが登録されている場合は、$rootfs/etc/network/interfaces をそれっぽく出力するようにする。

まず、lxc-ubuntu をコピーして lxc-ubuntu-nojima を作る。(ファイル名から lxc- を削った文字列がテンプレート名になる)

sudo cp /usr/share/lxc/templates/lxc-ubuntu{,-nojima}

次にテンプレート内の configure_ubuntu() の中身を書き換える。diff はこんな感じ:

--- /usr/share/lxc/templates/lxc-ubuntu 2014-04-15 00:49:58.000000000 +0900
+++ /usr/share/lxc/templates/lxc-ubuntu-nojima  2014-06-22 18:03:15.209620112 +0900
@@ -53,21 +53,41 @@
     release=$3
     user=$4
     password=$5

     # configure the network using the dhcp
-    cat <<EOF > $rootfs/etc/network/interfaces
+    ipaddr=$(sed -e 's/#.*$//' /etc/hosts | awk "\$2~/^${hostname}\$/ { print \$1 }")
+    if [ -z "$ipaddr" ]; then
+        cat <<EOF > $rootfs/etc/network/interfaces
 # This file describes the network interfaces available on your system
 # and how to activate them. For more information, see interfaces(5).

 # The loopback network interface
 auto lo
 iface lo inet loopback

 auto eth0
 iface eth0 inet dhcp
 EOF
+    else
+        echo "Set the IP address of $hostname to $ipaddr"
+        cat <<EOF > $rootfs/etc/network/interfaces
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet static
+    address $ipaddr
+    netmask 255.255.0.0
+    gateway 10.0.0.1
+    dns-nameservers 10.0.0.6
+EOF
+    fi

     # set the hostname
     cat <<EOF > $rootfs/etc/hostname
 $hostname
 EOF

一応解説しておくと、

ipaddr=$(sed -e 's/#.*$//' /etc/hosts | awk "\$2~/^${hostname}\$/ { print \$1 }")

の部分で、/etc/hosts から IP アドレスを取得している。 sed -e 's/#.*$//' /etc/hosts の部分が /etc/hosts のコメント以外の部分を主力していて、それを awk "\$2~/^${hostname}\$/ { print \$1 }" に突っ込んで、「スペースで区切られた 2 番目の部分が ${hostname} に一致していたら、1 番目の部分を出力」している。

そして

if [ -z "$ipaddr" ]; then
    cat <<EOF > $rootfs/etc/network/interfaces
...

$ipaddr が空文字列、つまり IP アドレスが取得できなかった場合は、デフォルトの interfaces を出力し、そうでない場合は、

    else
        echo "Set the IP address of $hostname to $ipaddr"
        cat <<EOF > $rootfs/etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
    address $ipaddr
    netmask 255.255.0.0
    gateway 10.0.0.1
    dns-nameservers 10.0.0.6
EOF
    fi

として、eth0 に固定 IP アドレスを割り当てている。 netmask, gateway に何を設定すべきかは環境によって異なるので、このテンプレートを流用する場合は環境に合わせてこの部分を書き換えないといけないことに注意。 dns-nameservers に関しては、今回ホストに DNS サーバを立てるので、ホストの IP アドレスにしている。

これで、ubuntu-nojima という名前のテンプレートを使って lxc-create することができるようになった。

例えば、web-1 というコンテナを作るときはこんな感じで指定する。いろいろくっついているオプションはお好みで。

sudo lxc-create -t ubuntu-nojima -n web-1 -- --user=nojima --password=secret --auth-key=/home/nojima/.ssh/id_rsa.pub --mirror="http://ftp.jaist.ac.jp/ubuntu"

3. dnsmasq

dnsmasq はお手軽 DNS サーバ兼 DHCP サーバで、デフォルトで /etc/hosts からホスト名と IP アドレスの対応を読んで勝手に DNS リクエストに答えたり、いい感じに上流の DNS にフォワードしてくれたりする。上流の DNS サーバは /etc/network/interfacesdns-nameservers の部分に書いておくと自動的に認識してくれる模様。
なので、何の設定もしなくても apt-get して起動するだけで DNS サーバとしてそれっぽく動いてくれる。

(普通だと、/etc/network/interfacesdns-nameservers に書くと、自動的に /etc/resolv.conf に書きだされてしまうと思うのだが、dnsmasq が動いている場合は謎のフックにより /etc/resolv.conf ではなく /var/run/dnsmasq/resolv.conf の方に書きだされ、/etc/resolv.conf の方は nameserver 127.0.0.1 が入るという挙動をしていた。ちゃんと動作を追っていないので詳細は不明。)

今回は
1. ホストでは、デフォルトの設定で dnsmasq を動かし、
2. コンテナでは、/etc/network/interfacesdns-nameservers にホストの IP アドレスを指定する
ことで、コンテナからもコンテナ名を DNS で引けるようにした。

4. シェルスクリプト化

2 で書いたコンテナ作成コマンドはあまりにもタイプするのが面倒くさいのと、コンテナを作成したらコンテナの起動と dnsmasq のリロードを行わないといけないので、手順を簡単にするためにシェルスクリプトを書いた。

#!/bin/sh
if [ $# -ne 1 ]; then
    echo "Usage: create-container NAME" 1>&2
    exit 1
fi
name=$1
ipaddr=$(sed -e 's/#.*$//' /etc/hosts | awk "\$2~/^${name}\$/ { print \$1 }")
if [ -z "$ipaddr" ]; then
    echo "ERROR: IP address of ${name} was not found in /etc/hosts." 1>&2
    exit 1
fi
set -xe
lxc-create -t ubuntu-nojima -n $name -- --user=nojima --password=secret --auth-key=/home/nojima/.ssh/id_rsa.pub --mirror="http://ftp.jaist.ac.jp/ubuntu"
service dnsmasq force-reload
lxc-start -d -n $name

これで、以下の手順で固定 IP アドレスを持つコンテナを作れるようになった。便利!!

sudo vim /etc/hosts           # ホスト名と IP アドレスの対応を追加する。
sudo create-container $NAME   # コンテナ作成 + 起動

その他

今回は IP アドレスの固定を行ったが、Ubuntu テンプレートの改造は他にもいろいろできる。とりあえず↓のようなことをやってみた。

  • --user で指定したユーザに sudo 権限を付与するようにした。
  • --password に空を指定することで、パスワードを disable したユーザを作成できるようにした。
    • つまりどんなパスワードを入れても認証に失敗するユーザになる。
  • /var/lib/lxc/$container_name/fstab /data data none bind,create=dir と書いてホストと /data を共有する。

とにかく汎用性は高いので、いじってみると楽しい。

ToDo

  • コンテナを作った後の設定をするのが面倒臭いので、Chef とかで自動化したい。
  • ファイルシステムを btrfs にしたい。