Help us understand the problem. What is going on with this article?

Dockerで運用しているMastodonのIPv6対応

More than 3 years have passed since last update.

Docker-composeで作成されるネットワークはデフォルトではIPv4アドレスしか割り当てられないため、Mastodonインスタンス間の通信ではIPv6を使えません。
とりあえずDockerコンテナにIPv6 ULA(RFC4193)を割り当ててNAT66(IP masquerade)を使用してコンテナから外部へIPv6で喋れるようにしました。

環境

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 9.0 (stretch)
Release:        9.0
Codename:       stretch

$ uname -r
4.9.0-3-amd64

$ docker -v
Docker version 17.06.0-ce, build 02c1d87

$ docker-compose -v
docker-compose version 1.14.0, build c7bdf9e

FORWARDとNATのルールを追加

ULAは例でfd91:7981:3415::/64としますが生成したものと全て置き換えてください。

# ip6tables -I FORWARD -i br+ -s fd91:7981:3415::/64 -m conntrack --ctstate NEW -j ACCEPT
# ip6tables -I FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# ip6tables -t nat -I POSTROUTING -s fd91:7981:3415::/64 -j MASQUERADE

docker-compose.yml

Compose file version 3 reference | Docker DocumentationのIPv6の部分にはCompose file version 2の設定例しかなくversion 3ではenable_ipv6が使えなかったので、予めdocker network createでネットワークを作成してからコンテナに接続することにしました。

$ docker network create --attachable --ipv6 --subnet=fd91:7981:3415::/64 mastodon_ipv6

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
becf15cc01a8        bridge              bridge              local
32bad14627e6        host                host                local
4e3ea6b2b0ba        mastodon_ipv6       bridge              local
cd479a9cd90a        none                null                local

--ipv6を指定してネットワークを作成するとsysctlのnet.ipv6.conf.all.forwardingが有効になるためRAが無効になります。
RAを使用するインターフェースがある場合はaccept_raを2に設定してください。

/etc/sysctl.conf
net.ipv6.conf.eth0.accept_ra=2
docker-compose.yml
@@ -7,6 +7,8 @@
 ### Uncomment to enable DB persistance
     volumes:
       - ./postgres:/var/lib/postgresql/data
+    networks:
+      - default

   redis:
     restart: always
@@ -14,6 +16,8 @@
 ### Uncomment to enable REDIS persistance
     volumes:
       - ./redis:/data
+    networks:
+      - default

   web:
     build: .
@@ -30,6 +34,9 @@
       - ./public/assets:/mastodon/public/assets
       - ./public/packs:/mastodon/public/packs
       - ./public/system:/mastodon/public/system
+    networks:
+      - default
+      - mastodon_ipv6

   streaming:
     build: .
@@ -42,6 +49,8 @@
     depends_on:
       - db
       - redis
+    networks:
+      - default

   sidekiq:
     build: .
@@ -54,3 +63,10 @@
       - redis
     volumes:
       - ./public/system:/mastodon/public/system
+    networks:
+      - default
+      - mastodon_ipv6
+
+networks:
+  mastodon_ipv6:
+    external: true

コンテナから外部へIPv6で喋れるか確認

$ docker network inspect mastodon_ipv6
[
    {
        "Name": "mastodon_ipv6",
        "Id": "af17c9b4af1732769e88165c988bba473c10d8a3af89cce6c1d2ca10884995d0",
        "Created": "2017-05-28T08:06:18.985725893+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": true,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                },
                {
                    "Subnet": "fd91:7981:3415::/64"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "Containers": {
            "290105e8d2d8f851f2691cb48de1451f66165f0afa06991e3cb047e649693e6c": {
                "Name": "mastodon_web_1",
                "EndpointID": "c95a7cd9be5f4bfc58ffe7f76b28425b677728bbeb2f292f862a5db8f6a41e22",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": "fd91:7981:3415::2/64"
            },
            "fa12db023c789d5967d43731b4b9894d4a3056b9fa4dc27391309eec0f7b0b23": {
                "Name": "mastodon_sidekiq_1",
                "EndpointID": "5e86e412fbccf73ecee590facc252afca4e8e2e890566821a0d8bf7b6ea577a8",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": "fd91:7981:3415::3/64"
            }
        },
        "Options": {},
        "Labels": {}
    }
]

$ docker exec -it mastodon_sidekiq_1 sh

/mastodon # ping6 google.co.jp
PING google.co.jp (2404:6800:4004:807::2003): 56 data bytes
64 bytes from 2404:6800:4004:807::2003: seq=0 ttl=55 time=0.938 ms
64 bytes from 2404:6800:4004:807::2003: seq=1 ttl=55 time=0.871 ms
64 bytes from 2404:6800:4004:807::2003: seq=2 ttl=55 time=0.932 ms

/mastodon # traceroute6 google.co.jp
traceroute to google.co.jp (2404:6800:4004:807::2003), 30 hops max, 24 byte packets
 1  fd91:7981:3415::1 (fd91:7981:3415::1)  0.011 ms  0.005 ms  0.002 ms
 2  2400:8500:1301:746::1 (2400:8500:1301:746::1)  28.441 ms  21.597 ms  2.113 ms
 3  2400:8500:1300:1028::2 (2400:8500:1300:1028::2)  0.884 ms  0.818 ms  0.803 ms
 4  2400:8500:1300:1027::1 (2400:8500:1300:1027::1)  2.424 ms  1.909 ms  1.954 ms
 5  2400:8500:1300:1077::1 (2400:8500:1300:1077::1)  0.814 ms  1.059 ms  1.072 ms
 6  ae-25.r00.tokyjp05.jp.bb.gin.ntt.net (2001:218:2000:5000::3c5)  1.475 ms  1.371 ms  1.374 ms
 7  ae-6.r31.tokyjp05.jp.bb.gin.ntt.net (2001:218:0:2000::351)  1.982 ms  0.935 ms  0.907 ms
 8  ae-6.r02.tokyjp03.jp.bb.gin.ntt.net (2001:218:0:2000::192)  1.714 ms  1.724 ms  1.575 ms
 9  2001:218:2000:5000::47e (2001:218:2000:5000::47e)  1.314 ms  1.325 ms  1.332 ms
10  2001:4860:0:1000::1 (2001:4860:0:1000::1)  1.774 ms  1.715 ms  1.539 ms
11  2001:4860:0:1::1b9b (2001:4860:0:1::1b9b)  1.598 ms  1.530 ms  1.495 ms
12  nrt12s15-in-x03.1e100.net (2404:6800:4004:807::2003)  1.576 ms  1.601 ms  1.551 ms

とりあえずこれでIPv6 onlyのMastodonインスタンスとの通信は出来ているのですが、ULA(fc00::/7)を使っているためRFC6724で定義されているpolicy tableに従って、IPv4/IPv6 dual stackのインスタンスとの通信ではIPv4が優先されるようでした。
Dockerfileを書いてコンテナ内に/etc/gai.confを置いてみたりしたんですが、相変わらずIPv4が優先されてしまいます。
Alpine Linuxはglibcではなくmusl libcを使っているので、そもそも/etc/gai.confを参照していませんでした。

コンテナにGUAを割り当てる場合

例えばホスト側のネットワークを2001:db8:1111:aaaa::/64と想定してコンテナに2001:db8:1111:aaaa:1::/80を割り当てる場合は次のように設定します。

# ip6tables -I FORWARD -i br+ -s 2001:db8:1111:aaaa:1::/80 -m conntrack --ctstate NEW -j ACCEPT
# ip6tables -I FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# ip6tables -L FORWARD
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all      anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all      2001:db8:1111:aaaa:1::/80  anywhere             ctstate NEW
REJECT     all      anywhere             anywhere             reject-with icmp6-adm-prohibited
$ docker network create --attachable --ipv6 --subnet=2001:db8:1111:aaaa:1::/80 mastodon_ipv6

$ ip -6 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500 qlen 1000
    inet6 2001:db8:1111:aaaa::4/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::9ea3:baff:fe31:e19e/64 scope link
       valid_lft forever preferred_lft forever
4: br-d797fe9ca63e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500
    inet6 2001:db8:1111:aaaa:1::1/80 scope global tentative
       valid_lft forever preferred_lft forever
    inet6 fe80::1/64 scope link tentative
       valid_lft forever preferred_lft forever

ホストとコンテナのルーティングテーブルは次のように設定されます。

$ ip -6 route
2001:db8:1111:aaaa:1::/80 dev br-d797fe9ca63e  proto kernel  metric 256
2001:db8:1111:aaaa:1::/80 dev br-d797fe9ca63e  metric 1024
2001:db8:1111:aaaa::/64 dev eth0  proto kernel  metric 256
fe80::/64 dev eth0  proto kernel  metric 256
fe80::/64 dev br-d797fe9ca63e  proto kernel  metric 256
default via fe80::1 dev eth0  metric 1024

$ docker exec -it mastodon_sidekiq_1 sh
/mastodon # ip -6 route
2001:db8:1111:aaaa:1::/80 dev eth0  metric 256
fe80::/64 dev eth0  metric 256
default via 2001:db8:1111:aaaa:1::1 dev eth0  metric 1024
unreachable default dev lo  metric -1  error -101
ff00::/8 dev eth0  metric 256
unreachable default dev lo  metric -1  error -101

NDP Proxyの使用

次の例では上流のルータからコンテナのIPv6アドレスに対してパケットを送信する際、マルチキャストアドレス宛てにMACアドレスを解決するためのNeighbor Solicitation(NS)が送信されていますが、このIPv6アドレスはホスト側のネットワークにいないためNeighbor Advertisement(NA)を応答せず通信に失敗しています。

14:44:37.764074 IP6 2001:db8:1111:aaaa:1::2 > nrt12s14-in-x0e.1e100.net: ICMP6, echo request, seq 0, length 64
14:44:37.771600 IP6 fe80::fac2:88ff:fe70:aebf > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:1111:aaaa:1::2, length 32
14:44:38.764905 IP6 2001:db8:1111:aaaa:1::2 > nrt12s14-in-x0e.1e100.net: ICMP6, echo request, seq 1, length 64
14:44:38.817944 IP6 fe80::fac2:88ff:fe70:aebf > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:1111:aaaa:1::2, length 32
14:44:39.765253 IP6 2001:db8:1111:aaaa:1::2 > nrt12s14-in-x0e.1e100.net: ICMP6, echo request, seq 2, length 64
14:44:39.905765 IP6 fe80::fac2:88ff:fe70:aebf > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:1111:aaaa:1::2, length 32

この場合は次のようにNDP Proxyを使用します。

/etc/sysctl.conf
net.ipv6.conf.eth0.proxy_ndp=1
$ docker network inspect mastodon_ipv6
        "Containers": {
            "085a4c201b795629e7447ac4fa019e2e36dbfd89a6166370b3ffba8e92f6f172": {
                "Name": "mastodon_web_1",
                "EndpointID": "6128ab4997af8ff3506ebe40f07fc44e86c19a30ad6ce32f24918ce3c2d1c681",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": "2001:db8:1111:aaaa:1::2/80"
            },
            "1f6e68fde888ef6b30be41667d9b4117227b466b5d31ba1f2a9d412698f1d803": {
                "Name": "mastodon_sidekiq_1",
                "EndpointID": "8d3a29b4c643d9841c4a6bbbb8f6bb259f8bb478a39d21b8f4f84135db9fdfee",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": "2001:db8:1111:aaaa:1::3/80"
            }
        },

# ip -6 neigh add proxy 2001:db8:1111:aaaa:1::2 dev eth0
# ip -6 neigh add proxy 2001:db8:1111:aaaa:1::3 dev eth0
# ip -6 neigh show proxy
2001:db8:1111:aaaa:1::2 dev eth0  proxy
2001:db8:1111:aaaa:1::3 dev eth0  proxy

2001:db8:1111:aaaa:1::2と2001:db8:1111:aaaa:1::3のNSに対する応答として、ホストのeth0のMACアドレスを格納したNAを送信し、このMACアドレスに対してパケットが送信されるようになります。

15:03:53.033531 IP6 2001:db8:1111:aaaa:1::2 > nrt12s14-in-x0e.1e100.net: ICMP6, echo request, seq 0, length 64
15:03:53.037057 IP6 fe80::fac2:88ff:fe70:aebf > ff02::1:ff00:2: ICMP6, neighbor solicitation, who has 2001:db8:1111:aaaa:1::2, length 32
15:03:53.542840 IP6 fe80::9ea3:baff:fe31:e19e > fe80::fac2:88ff:fe70:aebf: ICMP6, neighbor advertisement, tgt is 2001:db8:1111:aaaa:1::2, length 32
15:03:53.543872 IP6 nrt12s14-in-x0e.1e100.net > 2001:db8:1111:aaaa:1::2: ICMP6, echo reply, seq 0, length 64
15:03:53.542840 IP6 fe80::9ea3:baff:fe31:e19e > fe80::fac2:88ff:fe70:aebf: ICMP6, neighbor advertisement, tgt is 2001:db8:1111:aaaa:1::2, length 32
        0x0000:  6000 0000 0020 3aff fe80 0000 0000 0000  `.....:.........
        0x0010:  9ea3 baff fe31 e19e fe80 0000 0000 0000  .....1..........
        0x0020:  fac2 88ff fe70 aebf 8800 5644 4000 0000  .....p....VD@...
        0x0030:  2001 0db8 1111 aaaa 0001 0000 0000 0002  ................
        0x0040:  0201 9ca3 ba31 e19e                      .....1..
# ip link show dev eth0
2: eth0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 9c:a3:ba:31:e1:9e brd ff:ff:ff:ff:ff:ff

コンテナに割り当てている全てのアドレスでip -6 neigh add proxyコマンドを実行しましたが、docker-compose scaleを使う時に面倒なのでndppdを使った方がいいかもしれません。

# apt update
# apt install ndppd
# cat /etc/ndppd.conf
proxy eth0 {
   rule 2001:db8:1111:aaaa:1::/80 {
      static
   }
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away