最近,我家的杭州电信宽带能够自动分配到 IPv6 地址了,于是折腾一下自己的博客,使 Docker 中运行的 WordPress 也能够通过 IPv6 访问。
获取 IPv6 地址
通过 IPv6 隧道设置获得 IPv6 地址
我的博客搭建在 BandwagonHost (aff) 的 VPS 上。BandwagonHost 提供有 OpenVZ 和 KVM 两种虚拟化架构的 VPS,其中我选择了 KVM 架构,因为 KVM 实现了更加完整的虚拟化,能够自定义内核参数、使用 TCP BBR、安装 Docker 等。
但由于技术原因,BandwagonHost 的 IPv6 仅支持 OpenVZ 架构。对于 KVM VPS,就需要通过隧道等方式自行解决。
我使用的是 Hurricane Electric 的 tunnelbroker.net 免费 IPv6 隧道。使用方法比较简单,直接输入 VPS 的公网 IP,即可创建隧道,并能自动生成各个平台的配置脚本/配置文件。
添加防火墙规则
设置完 IPv6 地址,第二天早上,发现似乎无法通过 IPv6 地址访问 VPS 了。但在 VPS 中,对应的 IPv6 隧道接口仍然是 UP 状态,而且在 VPS 中执行 ping6 ipv6.google.com
后,又重新恢复正常。
这时候首先想到的是通过 crontab,定时 ping 一个 IPv6 地址,来实现对隧道的 keep-alive 操作,如下:
*/2 * * * * ping6 -c 3 ipv6.google.com > /dev/null
不过经过查找资料,其根本原因防火墙屏蔽了协议号 41 的入站流量,导致 IPv6 隧道的服务器无法访问到 VPS,所以根本的解决方法是添加对应的防火墙规则。如果使用 iptables 做为防火墙,可按照这个帖子中的方法来设置。而我使用的是 UFW,可通过如下命令添加规则:
ufw allow from <IPv6 隧道服务器地址> proto ipv6
IPv6 + Docker
如果不使用 Docker,直接在 VPS 上搭建 WordPress 博客,在获取到 IP 地址后,只需要经过简单设置 Web 服务器,并在 DNS 中添加 AAAA 记录,即可使博客支持 IPv6.
但我的博客基于 Docker 搭建,使用了 PHP、数据库、Nginx 三个 Docker 容器。想在 Docker 环境里面支持 IPv6,事情变得麻烦起来……
方案一:直接使用 Docker 自带的 IPv6 支持
Docker 官方文档中有如下两篇文章:
通过指定 --ipv6
参数,即可打开 Docker 自带的 IPv6 支持。但是,通过这种方式,每个容器都会获得一个独立的公网 IPv6 地址,所有端口暴露在公网,安全性较低。
方案二:使用 Docker 的 userland proxy
Docker 默认打开了 userland-proxy,在用户态实现了一个代理服务器,同时监听 IPv4 和 IPv6 端口,并将流量转发至对应的 Docker 容器。这种方式有一定的局限性,例如:
- 在用户态实现代理服务器,而不是通过 iptables 实现流量转发,性能相对较低
- 用户可通过 IPv6 地址访问 Docker 容器提供的服务,但 Docker 容器内没有 IPv6 地址,容器内的进程无法直接通过 IPv6 与外界通信
- 通过代理服务器之后,报文源地址发生了改变。导致 WordPress 无法获得用户的真实地址。而 WordPress 评论系统,以及部分防止恶意登录的插件,都需要用到用户的真实 IP 地址。
如下图所示,在使用 Docker userland proxy 的情况下,使用 IPv6 地址发表评论,WordPress 中显示的是本地 IPv4 地址:
方案三:使用 nftables 手动实现 IPv6 NAT
这篇文章介绍了 Angry Bytes 在生产环境中使用 Docker IPv6 的方式:
该方案的基本思路,是为 Docker 容器指定本地 IPv6 地址,而不是公网 IPv6 地址。然后通过手动添加 nftables 规则,实现 IPv6 NAT.
该方案使用 nftables 代替 iptables. nftables 是一种可以取代 iptables 的 Linux 防火墙框架,与 iptables 相比拥有更多新特性。但在我的 VPS 上,使用 nftables 代替 iptables 和 ufw,需要破坏原有环境。而且这种方案需要在创建容器时手动指定容器的 IPv6 地址,并在创建容器后手动添加 nftables 规则,操作较为繁琐。
最终方案:使用 robbertkl/docker-ipv6nat 自动实现 IPv6 NAT
robbertkl/docker-ipv6nat 项目,实现了 Docker 环境下的 IPv6 NAT,而且无需用户进行更加复杂的配置。所以,我最终选择了这种方案。
由于我使用 jwilder/nginx-proxy 做为反向代理服务器,而该镜像默认没有打开 IPv6 支持,所以需要删除原有的容器,然后添加 IPv6 参数,重新创建一个支持 IPv6 的反向代理。
完整的操作步骤如下:
# 创建 IPv6 NAT 容器
docker run -d --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--privileged \
--net=host \
robbertkl/ipv6nat
# 创建一个 IPv6 Docker 网络
docker network create --ipv6 --subnet=fd00:dead:beef::/48 ipv6_bridge
# 创建支持 IPv6 的 nginx 反向代理
docker run -d --name nginx-proxy \
--restart=always \
-p 80:80 \
-p 443:443 \
-e ENABLE_IPV6=true \
-v /var/run/docker.sock:/tmp/docker.sock:ro \
-v /srv/docker/certs:/etc/nginx/certs:ro \
-v /srv/docker/nginx/vhost.d:/etc/nginx/vhost.d \
-v /srv/docker/nginx/html:/usr/share/nginx/html \
--label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy \
jwilder/nginx-proxy
# 将 nginx 反向代理连接到 IPv6 Docker 网络
docker network connect ipv6_bridge nginx-proxy
# 通过 Let's Encrypt 实现 SSL
docker run -d --name nginx-proxy-ssl-support \
--restart=always \
-v /srv/docker/certs:/etc/nginx/certs:rw \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--volumes-from nginx-proxy \
jrcs/letsencrypt-nginx-proxy-companion
此时,使用 IPv6 地址在博客上发表评论,已经能够显示出用户的真实 IP 地址了:
速度测试
经过上面的一系列配置,我的博客已经能够正常支持 IPv6 了。顺便在杭州电信 100M 宽带的环境下进行了一次测速,结果如下:
wget 单线程下载:
aria2 多线程下载(10 个线程):
ping 延迟:
同在杭州,不知道博主是怎么拨号的?
直接 PPPoE 拨号,IPv6 相关配置参考这条微博截图后两张:https://www.weibo.com/1650858902/GrC7IsBIi 没有其他特殊配置
非常感谢博主,v6 这个问题困扰了我几个小时了,刚刚遇到您这篇文章总算是有了明确的解决方向