~nabeken/diary/

Gentoo Linux(6年くらい)とFreeBSD(1年くらい)とOpenBSD(新参者)を使う日々。


IHANet BGP peering overview

ConoHaで使えるIPv6アドレスをDockerで有効活用する

Posted on Tue Feb 04 13:10:04 +0900 2014 by nabeken

ConoHaではIPv6アドレスが標準1個に加えて追加で16個使用できます。 追加IPv6アドレスをなるべく簡単にDockerで使える方法をUbuntu 13.10 (amd64)で検討しました。

Dockerのネットワーク構成

dockerをインストールするとブリッジインターフェース docker0 が作成されます。

$ ifconfig docker0
docker0   Link encap:Ethernet  HWaddr be:d2:8e:4b:02:46
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::bcd2:8eff:fe4b:246/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:668 (668.0 B)

コンテナ(LXC)の eth0 はホスト側の vethXXXX とペアになっています。 そして、 vethXXXXdocker0 へ追加されています。

IPv4の場合はiptablesによってNATすることでコンテナからインターネットへの通信ができます。 一方でインターネットからコンテナへは起動時にポート単位で指定しNATするようになっています。

docker0はdockerの標準的なセットアップなので、IPv6アドレス対応でもこの枠組みを使うことにします。

ConoHaにおける追加IPv6アドレスの扱い

http://blog.livedoor.jp/goldfish_and_laser/archives/4548352.html にあるように、 追加アドレスはDHCPv6で取得したIPv6アドレスから機械的に算出できます。

追加アドレスはホスト側のインターフェースには付与せず、コンテナの eth0 に付与します。 作業の前にホスト側に設定されている現在のデフォルトゲートウェイをメモしておきます。

$ ip -6 route | grep default
default via fe80::1052:1 dev eth0  metric 1024

おそらく fe80::1052:1 になっていると思います。

docker run時にIPv6アドレスを指定する

http://zargony.com/2013/10/13/ipv6-in-docker-containers を参考に以下で指定します。

$ docker run -i -t -lxc-conf="lxc.network.ipv6 = 2001:db8::1/128" ubuntu /bin/bash

LXCの設定ではデフォルトゲートウェイも設定することができます。 しかし、/128ということはこのリンク上には自分以外誰もいないため、 デフォルトゲートウェイは別のネットワークから指定する必要があります。 IPv6にはリンク上でのみ使えるアドレスとしてlink-localアドレスがありますが、 docker0のMACアドレスはdockerが起動する度に変化するため、 そのままではデフォルトゲートウェイのアドレスとしては不適当です。

さらに、Ubuntu 13.10のlxcでは lxc.network.ipv6.gateway にはlink-localアドレスを指定できませんでした。

今回はdocker0のlink-localアドレスを固定するのではなく、RA(ルータ広告)を使用し、 デフォルトゲートウェイだけ自動で配布するようにします。

コンテナにデフォルトゲートウェイを通知する

RAを送信するということはホスト側はルータとなり、ホストとコンテナ間でパケットを転送することを意味します。 Linuxでパケット転送をするためにsysctlを忘れずに設定します。

注意: ルータはRAを無視しなければならないため、この時点でホストはデフォルトゲートウェイをRAから取得できなくなります。 場合によってはDHCPv6で取得した標準のIPv6アドレスへの到達性を失います。作業時は注意してください。 作業前に控えたデフォルトゲートウェイを元に手動で再設定します。

$ grep ipv6.conf.all.forwarding /etc/sysctl.conf
net.ipv6.conf.all.forwarding=1

$ sudo sysctl -p
$ sudo ip -6 route add default via fe80::1052:1 dev eth0

RAの送信には radvd パッケージを使用します。

$ sudo apt-get install radvd
$ cat /etc/radvd.conf
interface docker0
{
   AdvSendAdvert on;
};

$ sudo service radvd start

ここまででコンテナ側の設定は完了しました。試しに起動してみます。

$ sudo docker run -i -t -lxc-conf="lxc.network.ipv6 = 2001:db8::1/128" ubuntu /bin/bash
# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 86:d6:18:21:43:a5
          inet addr:172.17.0.4  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::84d6:18ff:fe21:43a5/64 Scope:Link
          inet6 addr: 2001:db8::1/128 Scope:Global
          UP BROADCAST RUNNING  MTU:1500  Metric:1
          RX packets:10 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:916 (916.0 B)  TX bytes:446 (446.0 B)

# ip -6 route
2001:db8::1 dev eth0  proto kernel  metric 256
fe80::/64 dev eth0  proto kernel  metric 256
default via fe80::bcd2:8eff:fe4b:246 dev eth0  proto ra  metric 1024  expires 1791sec

デフォルトゲートウェイが設定されていることを確認してください。また、アドレスがホスト側の docker0 に付与 されたlink-localアドレスであることを確認してください。

これでコンテナからインターネットへの通信はすべてホスト側のdocker0へルーティングされるようになりました。 しかし、これだけでは外部からの追加アドレス宛通信はコンテナへルーティングされません。

追加アドレスをコンテナへルーティングする

まずはホスト側からコンテナへルーティングする必要があります。 ホスト側のルーティングテーブルにコンテナで使用している/128への経路を追加します。

$ sudo ip -6 route add 2001:db8::1/128 dev docker0

これでホストからコンテナへpingが通るようになります。

$ ping6 -c 1 2001:db8::1
PING 2001:db8::1(2001:db8::1) 56 data bytes
64 bytes from 2001:db8::1: icmp_seq=1 ttl=64 time=0.064 ms

--- 2001:db8::1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.064/0.064/0.064/0.000 ms

次に、docker0に閉じ込もっているコンテナに代わりホスト側で追加アドレスに対するND(近隣探索)に 代理応答するようにします。 これにより、コンテナへのトラフィックを一旦ホスト側で受信できるようになります。

$ sudo ip -6 neigh add proxy 2001:db8::1 dev eth0

ndpプロキシはsysctlで有効にする必要があります。 eth0 が接続されているネットワークからのみ代理応答するように設定します。

$ grep ipv6.conf.eth0.proxy_ndp /etc/sysctl.conf
net.ipv6.conf.eth0.proxy_ndp=1

$ sudo sysctl -p

これでdockerで起動したコンテナに対して外部からpingが通るはずです。

注意: 作業中、コンテナのシェルから抜けるとコンテナは停止してしまうため試験時は起動しなおすのを忘れずに (C-p C-qでdetachできます)。

$ docker run -i -t -lxc-conf="lxc.network.ipv6 = 2001:db8::1/128" ubuntu /bin/bash

https://mebsd.com/ipv6-ping-and-traceroute などを利用し、外部からpingを打って疎通を確認してください。

起動時に設定する

追加IPv6アドレス16個に対して経路とNDの代理応答を /etc/rc.local で設定します(場所が気にいらない場合はどこか適当な場所へ)。

# wait for docker ready
sleep 10

FIRST=$(/sbin/ifconfig eth0 | grep -i global | awk '{ print $3 }' | cut -d":" -f1-4)
SECOND=$(/sbin/ifconfig eth0 | grep -i global | cut -d":" -f6,7,8,9 | cut -d"/" -f1)
IPV6=${FIRST}:a${SECOND}

for SUFFIX in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
        ip -6 route add "${IPV6}${SUFFIX}"/128 dev docker0
        ip -6 neigh add proxy "${IPV6}${SUFFIX}" dev eth0
done
ip -6 route add default via fe80::1052:1 dev eth0

再起動後 docker run するだけで簡単に追加IPv6アドレスがdockerで使えるようになります。 実際に使用する際はip6tablesで適切にパケットフィルターを設定してください。 ufwはIPv6向けにもきちんとしたデフォルトのフィルターがあるのでオススメです。

今回は docker0 を残したままIPv6対応をしたので docker run -p によるIPv4アドレスに対するポート設定とも併用できます。 IPv4アドレスは1つしかありませんが、IPv6なら16個も使えます。やったね!!

更新履歴

  • 初稿 (Tue, 4 Feb 2014 13:10:04 +0900)