前言
多台 Docker 服务器上的容器互通是一个不好解决的问题。如果自建一个 Overlay 网络,就需要在一台服务器上建立 etcd 之类的服务。但如果 etcd 所在的服务器挂了,整个网络就 GG 了。我用的便宜 VPS 有偶尔网络中断的情况,我自己搞崩也服务器是常有的事,所以我不能采取这种方式。
Docker 也有其它的基于 Overlay 的商业化组网方案,例如 Weave,但是对于个人用户来说这些方案的价格太高了(我只是搞来玩玩),所以也不考虑。
在这些网络结构上,etcd 或者 Weave 之类的中心服务器记录了每个容器所在的服务器和内部 IP,所以在任何容器上都可以直接 DNS 解析到其它容器。也就是说,假如我设置了 lantian-nginx 和 lantian-phpfpm 两个容器,在 nginx 的配置文件里我可以直接把 php-fpm 的地址填成 lantian-phpfpm:[端口号],方便配置。
但我好像可以放弃这个功能啊?我的容器数量并不多,而且只有几个 MariaDB 需要跨服务器连接,做数据库主从备份,手动指定一下 IP 并不麻烦。
那么我就可以直接使用传统的 VPN 方案来做互通了。
问题又来了:由于自己的服务器不稳定,我不希望某台服务器挂掉导致网络互通挂掉,所以 Open^_^VPN 之类需要架设中心 VPN 服务器的也算了。Tinc 这类 P2P VPN 符合我的要求,但是我的服务器常有增减,难道我每次都要一台台上去改配置吗?
有中央管理面板的 P2P VPN,我或许可以用 LogMeIn Hamachi。这款免费软件通过和 LogMeIn 公司的中心服务器连接,获取网络内其它计算机的实际 IP,并分别建立 P2P 连接。我的服务器上倒是有 Hamachi,但是它只给每台计算机一个 IPv4 和一个 IPv6,对于 Docker 组网来说不够用啊。而且它每个网络只能让 5 台计算机互联,否则就要加钱。
到现在为止,我的需求如下:
- 任何一台服务器 GG 不能影响其它服务器
- 需要一个统一的管理面板可以快速增减服务器
- 不需要 Docker 的 DNS 解析之类功能
- 每台服务器可以获得多于一个的 IPv4 和 IPv6(可以是内网 ULA 网段),最好有一个内网网段
经过一番搜索,我选定了 ZeroTier One 这款软件。它类似于 Hamachi,但是相比之下它有如下优点:
- 我可以指定哪些 IPv4 网段被路由到 ZeroTier One,并且可以任意设置分配地址池。
- 我可以让每台计算机分配到一个 IPv6 ULA 下的 /80 网段,足够 Docker 使用。
- 每个用户可以免费添加 100 台计算机。
第一点尤其重要,例如,我可以设置给每台计算机在 172.27.0.0/16 下自动分配 IP,但是我又可以统一指定把 172.28.0.0/24 路由到某台 Docker 服务器,172.28.1.0/24 路由到另一台,以此类推。
安装与配置
ZeroTier One 官方提供一键安装脚本。在你的服务器上运行如下命令即可。
curl -s 'https://pgp.mit.edu/pks/lookup?op=get&search=0x1657198823E52A61' | gpg --import && \ if z=$(curl -s 'https://install.zerotier.com/' | gpg); then echo "$z" | sudo bash; fi
然后在 ZeroTier One 官方注册一个账号。这里需要你的手机号。我测试中国电信手机号可以收到验证短信,但是隔了 7 个小时……最后我用的是 Google Voice 的号码。
注册登录后你就能看到如下界面:
点击上方的 Network(网络),再点击 Create New Network(创建新网络),你会看到列表上多了一个网络。
如图,第二个是我已有的网络,第一个是新创建的。点击进去。
左上角是 Network ID,稍后你要输入到你的 ZeroTier 客户端去。
Short Name 是网络名称,你可以自定义以使得它更好辨别。
右上角 Managed Routes 是路由表设置,稍后再改。
IPv4 Auto-Assign 是 IPv4 地址的自动分配,我们要把它打开。
打开后,在列出的网段中选一个看得顺眼的。这个网段不能与你服务器实际在用的网段有重合。例如,如果有一台 NAT VPS,其 IPv4 所在网段是 172.17.0.0/16,那么你就不能选它,否则它有可能会断网。选择后,会自动给你添加好路由表设置,如图:
IPv6 Auto-Assign 是 IPv6 的自动分配,下有两个选项。
RFC4193 选项会为你随机生成一个 ULA 网段(在 fd00::/8 下),每台服务器获得一个 IPv6(/128);6PLANE 在另一个 ULA 网段(fc00::/8 下),每台服务器获得一个 /80 段。为了 Docker 组网,我们需要的就是 6PLANE 网段。
接下来,我们把服务器加进这个网络。在服务器上输入命令:
zerotier-cli join [网络ID]
然后在设置页面的下方,勾上对应服务器的 Auth 选项:
页面上会显示服务器对应的 IPv4 和 IPv6,各台服务器也能互相 ping 通了。
配置 Docker - 原生 Docker
在这个例子中,假定这台 Docker 服务器分配到了 10.147.17.233 和 fc23:3333:3333:3333:3333::1/80,我们希望添加一个 172.28.233.x/24 供它使用,系统环境是 Debian 8。
我们需要指定 Docker 使用一些启动命令参数。在 Debian 8 这类 systemd 发行版上,我们需要修改一下 systemd 的配置。输入以下命令:
cd /etc/systemd/system
mkdir docker.service.d
cd docker.service.d
nano docker.conf
向 docker.conf 输入如下内容:
[Service]
EnvironmentFile=-/etc/default/docker
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// $DOCKER_OPTS
Ctrl+X,Y,回车保存退出,再运行:
systemctl daemon-reload
cd /etc/default
nano docker
经过如上修改,此处显示的 DOCKER_OPTS 环境变量就能生效了。将对应的这一行修改成:
DOCKER_OPTS="--fixed-cidr=172.28.233.0/24 --ipv6 --fixed-cidr-v6=fc23:3333:3333:3333:3333::/80"
重启 Docker:
service docker restart
然后你的 Docker 容器就有了在这两个网段上的 IP。
问题又来了,由于容器获得的是 ULA 网段,无法访问 IPv6 公网,怎么办?我们需要借助 docker-ipv6nat。这个软件(容器)可以根据你的容器设置自动配置 IPv6 NAT,就像 Docker 在 IPv4 上做的一样。
等等,为什么要用 IPv6 NAT?我认为它的优点如下:
- 某些主机商只提供一个 IPv6 地址
- 更方便的防火墙配置(外网无法直接访问到容器)
- 它在 ULA 网段上运行,刚好符合这次的需求
启动这个容器:
docker run -d --restart=always -v /var/run/docker.sock:/var/run/docker.sock:ro --privileged --net=host robbertkl/ipv6nat
你可能需要重新启动一下其它容器以使配置生效。这样你的容器就能访问 IPv6 公网了。
如果你愿意,可以在 DOCKER_OPTS 里加一句 --userland-proxy=false
,禁用 Docker 的应用层代理,可以节省内存。
配置 Docker - Compose
如果你用 Docker-Compose,那么事情就方便了许多,不需要修改 systemd 配置了。将你的 docker-compose.yml 里修改成如下内容:
version: "2.1"
services:
docker-ipv6nat:
image: robbertkl/ipv6nat
container_name: docker-ipv6nat
restart: always
privileged: true
network_mode: host
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
你原有的容器:
[...]
depends_on:
- docker-ipv6nat
[你原有的容器们...]
networks:
default:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 172.28.233.0/24
gateway: 172.28.233.1
- subnet: fc23:3333:3333:3333:3333::/80
gateway: fc23:3333:3333:3333:3333::1
注意要给原有的每个容器添加依赖 docker-ipv6nat。然后重新 docker-compose up -d
即可。
配置系统转发
在 /etc/sysctl.conf 里添加以下内容:
net.ipv4.ip_forward = 1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.proxy_ndp=1
执行 sysctl -p
开启转发。如果你有防火墙,记得给相应的网段放行。
配置 ZeroTier 路由表
在右上角 Managed Routes 里添加「172.28.233.1/24,10.147.17.233」,代表把 172.28.233.1/24 网段的请求全部交由 10.147.17.233 处理。
然后,你在其它服务器就能 ping 通 10.147.17.233 上的 Docker 容器了。在其它服务器上如法炮制(记得 IP 段不能重合),就能在所有 Docker 服务器和容器之间建成一张双栈局域网了。