為 Docker 開啟 IPv6

前言

繼之前的 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。

~$ nano /etc/docker/daemon.json
{
  "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 系的同學可以跳過這步 (?)

#!/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

~# 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 設定

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

# /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).
   }
}

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

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

3. 將 Container 加入網路

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

感謝

謝謝 勝勝 幫我做 thumbnail yay

按讚

發佈留言

電子郵件地址不會被公開。必填項已用 * 標註