前言

繼之前的 Docker Nginx,這次雷雷想要讓這個 Nginx container 拿到 IPv6,這樣就不用過路由器跟 Docker 的兩個 NAT,可以改善延遲 yay

條件

  • 跑 Docker 的主機 / Container / VM 有 RA
  • 一段可配置的 v6 Prefix (/80 以上)

實作

我們以 Alpine Linux 為 Host 作為例子。

1. Docker 開啟 IPv6

為了安全,我們預設讓 bridge 發放 fd00 開頭的 Private IP 地址,而不是預計之後要發的 Public IP。

1
~$ nano /etc/docker/daemon.json
1
2
3
4
{
"ipv6": true,
"fixed-cidr-v6": "fd00::/80"
}

可以自己換成自己喜歡的 Private IP Range。

完成後 service docker restart,確認 Bridge network 上的 Container 已經可以拿到 Private IPv6 了。

2. NDP Proxy

雷雷的 SLAAC 是廣播在 eth0 上的。
為了讓 Docker 建立的其他網卡也能收到路由資訊,我們要建立一個 NDP Proxy。

ip -6 neigh 的方法要一個一個 IP 指定,太麻煩了,所以我們要用 ndppd 來解決。

在後面我們用 {HOST_V6_IFACE} 來代指這個 eth0


2.a 編譯安裝 ndppd

因為 Alpine Linux 上沒有 ndppd,所以我們要先編譯他。

使用 Ubuntu / Debian 系的同學可以跳過這步 (?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/sh

export NDPPD_VERSION=master
apk --no-cache add --virtual .build-dependencies make g++ linux-headers patch wget ca-certificates libnl3-dev glib-dev \
&& mkdir -p /usr/src \
&& wget -qO- https://github.com/lekoOwO/ndppd/archive/${NDPPD_VERSION}.tar.gz | tar -xzC /usr/src \
&& cd /usr/src/ndppd-${NDPPD_VERSION} \
&& sed 's/\(${LIBS}\).*\(${OBJS}\)/\2 \1/' -i Makefile
&& sed -i 's/return strerror_r(errno, buf, sizeof(buf));/\/\/ return strerror_r(errno, buf, sizeof(buf));\n return "errno: "+errno;/g' src/logger.cc

# 編譯安裝
CXXFLAGS=" -I/usr/include/libnl3 " LDFLAGS=" -w " make
CXXFLAGS=" -I/usr/include/libnl3 " LDFLAGS=" -w " make install

# 安裝執行時期依賴
apk add --no-cache libnl3 iproute2

# 新增 service 檔
cat << EOF > /etc/init.d/ndppd
#!/sbin/openrc-run

depend() {
need net
}

command="/usr/local/sbin/ndppd"
command_args="-d -p /run/${RC_SVCNAME}.pid -c /etc/ndppd.conf"
pidfile="/run/${RC_SVCNAME}.pid"
EOF

2.b 在 Docker 裡新增用來發放 IPv6 的 network

1
~# docker network create --ipv6 --subnet={MY_SUBNET}::/{MY_CIDR} {MY_NETWORK_NAME}

注意到 CIDR 至少要在 80 以上,太小會沒辦法發。

將回傳的 hash 記錄下來,用 ip a 檢查新增的網卡名字 (postfix 應該會跟 hash 前幾位一樣)

例如我的是 br-179d885229e7,在後面稱作 {MY_DOCKER_IFACE}


2.c ndppd 設定

將設定檔內的花括號變數填入你自己的參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# /etc/ndppd.conf

# route-ttl <integer> (NEW)
# This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route.
# Default value is '30000' (30 seconds).

route-ttl 30000

# address-ttl <integer> (NEW)
# This tells 'ndppd' how often to reload the IP address file /proc/net/if_inet6
# Default value is '30000' (30 seconds).

address-ttl 30000

# proxy <interface>
# This sets up a listener, that will listen for any Neighbor Solicitation
# messages, and respond to them according to a set of rules (see below).
# <interface> is required. You may have several 'proxy' sections.

proxy {HOST_V6_IFACE} {

# router <yes|no|true|false>
# This option turns on or off the router flag for Neighbor Advertisement
# messages. Default value is 'true'.

router yes

# timeout <integer>
# Controls how long to wait for a Neighbor Advertisment message before
# invalidating the entry, in milliseconds. Default value is '500'.

timeout 500

# autowire <yes|no|true|false>
# Controls whether ndppd will automatically create host entries
# in the routing tables when it receives Neighbor Advertisements on a
# listening interface. The the default value is no.
# Note: Autowire will ignore all rules with 'auto' or 'static' given it
# is expected that the routes are already defined for these paths

autowire no

# keepalive <yes|no|true|false>
# Controls whether ndppd will automatically attempt to keep routing
# sessions alive by actively sending out NDP Solicitations before the the
# session is expired. The the default value is yes.

keepalive yes

# retries <integer>
# Number of times a NDP Solicitation will be sent out before the daemon
# considers a route unreachable. The default value is 3

retries 3

# promiscuous <yes|no|true|false>
# Controls whether ndppd will put the proxy listening interface into promiscuous
# mode and hence will react to inbound and outbound NDP commands. This is
# required for machines behind the gateway to talk to each other in more
# complex topology scenarios. The the default value is no.

promiscuous no

# ttl <integer>
# Controls how long a valid or invalid entry remains in the cache, in
# milliseconds. Default value is '30000' (30 seconds).

ttl 30000

# rule <ip>[/<mask>]
# This is a rule that the target address is to match against. If no netmask
# is provided, /128 is assumed. You may have several rule sections, and the
# addresses may or may not overlap.

rule {MY_SUBNET}::/{MY_CIDR} {
# Only one of 'static', 'auto' and 'interface' may be specified. Please
# read 'ndppd.conf' manpage for details about the methods below.

# 'auto' should work in most cases.

# static (NEW)
# 'ndppd' will immediately answer any Neighbor Solicitation Messages
# (if they match the IP rule).

# iface <interface>
# 'ndppd' will forward the Neighbor Solicitation Message through the
# specified interface - and only respond if a matching Neighbor
# Advertisement Message is received.

# auto (NEW)
# Same as above, but instead of manually specifying the outgoing
# interface, 'ndppd' will check for a matching route in /proc/net/ipv6_route.

# auto

iface {MY_DOCKER_IFACE}

# autovia <yes|no|true|false>
# Any addresses updated using NDP advertisments will use a gateway to
# route traffic on this particular interface (only works wiith the iface
# rule type). Default is no

autovia yes

# Note that before version 0.2.2 of 'ndppd', if you didn't choose a
# method, it defaulted to 'static'. For compatibility reasons we choose
# to keep this behavior - for now (it may be removed in a future version).
}
}

然後我們啟用它,並讓它開機自動啟動:

1
2
~# rc-update add ndppd default
~# service ndppd start

3. 將 Container 加入網路

最後將你想 expose 的 Container 加入 {MY_NETWORK_NAME} 就大功告成啦!