IPv6 と Docker と NAT
IPv6 のアドレスが1つしかない状況で、ネットワークが分離されたコンテナから IPv6 で通信しようとすると IPv6 だろうと NAT が必要になる。 このへん Docker がどう扱うのかよくわかってなくて、結局 Docker の外側で docker0 とか ip6tables を管理することで動いた……
まず docker0 に使うレンジを決める。ここでは fdbb:3f26:ceda::/48
と 10.11.0.0/16
とする。
docker0 を systemd-networkd で作る。
% cat /etc/systemd/network/docker0.netdev [NetDev] Name=docker0 Kind=bridge % cat /etc/systemd/network/docker0.network [Match] Name=docker0 [Network] Address=10.11.0.1/16 Address=fdbb:3f26:ceda::1/48
dockerd を起動する。このときネットワーク系のオプションを切っておく。
% cat /etc/systemd/system/docker.service.d/override.conf [Service] ExecStart= ExecStart=/usr/bin/dockerd --ipv6 --fixed-cidr-v6=fdbb:3f26:ceda::/48 --bridge=docker0 --ip-forward=false --ip-masq=false --iptables=false -H fd:// # systemctl start docker.service
デフォルトだと dockerd が IPv4 でやっていたことを、IPv4 と IPv6 の両方について自分でやる。
% cat /etc/sysctl.d/30-ip-forward.conf net.ipv4.conf.all.forwarding = 1 net.ipv4.conf.default.forwarding = 1 net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.default.forwarding = 1
# iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # iptables -A FORWARD -s 10.11.0.0/16 -i docker0 ! -o docker0 -j ACCEPT # iptables -A POSTROUTING -s 10.11.0.0/16 ! -o docker0 -j MASQUERADE # ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # ip6tables -A FORWARD -s fdbb:3f26:ceda::/48 -i docker0 ! -o docker0 -j ACCEPT # ip6tables -A POSTROUTING -s fdbb:3f26:ceda::/48 ! -o docker0 -j MASQUERADE
これで Docker コンテナ内から IPv6 で通信できるようになる。
% cat Dockerfile FROM ubuntu:16.04 RUN apt update && apt install -y curl % docker build -t ubuntu-curl . % docker run --rm ubuntu-curl curl -sv -o /dev/null https://ipv6.google.com/ * Trying 2404:6800:4004:816::200e... * Connected to ipv6.google.com (2404:6800:4004:816::200e) port 443 (#0) (snip)
ただ、ULA なアドレスを使っているせいで A も AAAA も持っているようなドメインの場合、デフォルトだと IPv4 が優先されてしまう。
% docker run --rm ubuntu-curl curl -sv -o /dev/null https://google.com/ * Trying 172.217.25.78... * Connected to google.com (172.217.25.78) port 443 (#0) (snip)
この優先度は /etc/gai.conf で決められているので、とりあえずそこを変えれば IPv6 が優先されるようになる *1。
% cat gai.conf label ::1/128 0 label ::/0 1 label 2002::/16 2 label ::/96 3 label ::ffff:0:0/96 4 % docker run -v $PWD/gai.conf:/etc/gai.conf:ro --rm ubuntu-curl curl -sv -o /dev/null https://google.com/ * Trying 2404:6800:4004:819::200e... * Connected to google.com (2404:6800:4004:819::200e) port 443 (#0) (snip)
今回は Docker でやったけど、systemd-nspawn (machinectl) を使うときもやることは同じ。
docker0 と同じ要領で br0 を作って systemd-nspawn --network-bridge=br0
で起動する。
systemd-nspawn の場合は勝手にアドレスを割り当てたりしてくれないので、それはコンテナ内の systemd-netword でやるようにする。
このときデフォルトだと host0 に対して余計な設定があるので、コンテナ内で ln -s /dev/null /etc/systemd/network/80-container-host0.network
で切っておく。