为博客增加 IPv6 支持

in #cn6 years ago (edited)

文章同时发表于我的博客,原文链接:https://blanboom.org/2018/ipv6-support-on-my-blog/

最近,我家的杭州电信宽带能够自动分配到 IPv6 地址了,于是打算折腾下自己的博客,使自己的博客也能够通过 IPv6 访问。

blog-ipv6-traceroute.png

获取 IPv6 地址

通过 IPv6 隧道设置获得 IPv6 地址

我的博客搭建在 BandwagonHost (aff) 的 VPS 上。BandwagonHost 提供有 OpenVZKVM 两种虚拟化架构的 VPS,其中我选择了 KVM 架构,因为其实现了更加完整的虚拟化,能够自定义内核参数、使用 TCP BBR、安装 Docker 等。

但由于技术原因,BandwagonHost 的 IPv6 仅支持 OpenVZ 架构。对于 KVM VPS,就需要通过隧道等方式自行解决。

我使用的是 Hurricane Electrictunnelbroker.net 免费 IPv6 隧道。使用方法比较简单,直接输入 VPS 的公网 IP,即可创建隧道,并能自动生成各个平台的配置脚本/配置文件。

blog-ipv6-tunnel-conf.png

添加防火墙规则

设置完 IPv6 地址,第二天早上,发现似乎无法通过 IPv6 地址访问 VPS 了。但在 VPS 中,对应的 IPv6 隧道接口仍然是 UP 状态,而且在 VPS 中执行 ping6 ipv6.google.com 后,发现 VPS 又能通过 IPv6 地址来访问了。

这时候首先想到的是通过 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 地址:

blog-ipv6-comments-address.png

方案三:使用 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 地址了:

blog-ipv6-comment-with-ipv6-address.png

速度测试

经过上面的一系列配置,我的博客已经能够正常支持 IPv6 了。顺便在杭州电信 100M 宽带的环境下进行了一次测速,结果如下:

wget 单线程下载:

blog-ipv6-speed-wget.png

aria2 多线程下载(10 个线程):

blog-ipv6-speed-aria.png

ping 延迟:

blog-ipv6-ping.png

Sort:  

This user is on the @buildawhale blacklist for one or more of the following reasons:

  • Spam
  • Plagiarism
  • Scam or Fraud

Congratulations @blanboom! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Do not miss the last post from @steemitboard:

The Steem community has lost an epic member! Farewell @woflhart!
SteemitBoard - Witness Update
Do not miss the coming Rocky Mountain Steem Meetup and get a new community badge!
Vote for @Steemitboard as a witness to get one more award and increased upvotes!