需求

最近尝(xian)试(de)把 nas 上的服务全部迁移到了 K3s 上,主要是觉得 Kubernetes 管理起来方便,而且 traefik 真的很好用,不需要我一个一个写 nginx conf。别的服务用起来都没有什么大问题,唯有用来下载的 qBittorrent,因为 K3s 默认不使用 IPv6,导致在下载的时候降低了很多速度。所以我尝试了在 K3s 上配置 IPv6。

内网 IPv6 (ULA)

肯定有人看到这一段会先骂为什么要在 IPv6 中使用 NAT,但是不要急,我们先从这里开始是为了保证在最极端的情况下(只有一个 /128)也能正常使用。

重装 K3s

根据 K3s 的文档,切换单栈/双栈网络只能在安装的时候配置,所以需要重装 K3s。

在重装 K3s 时,我们使用如下环境变量,可以看到,我们为 cluster cidrservice cidr 分配不同的 IP 段,并且设置了 flannel-ipv6-masq,这个选项是用来开启 IPv6 的地址转换,默认情况是关闭的。

export INSTALL_K3S_EXEC="--cluster-cidr=10.42.0.0/16,fd00::/64 --service-cidr=10.43.0.0/16,fd01::/112 --flannel-ipv6-masq"

安装好之后我们创建一个容器,kubectl run -it --rm --image=alpine alpine,在容器中可以看到容器被分配了一个内网的 IPv6,并且可以使用宿主机 IP 访问 IPv6 外网。

image.png

公网 IPv6 地址 (GUA)

众所周知,IPv6 不应该有 NAT,明明 IP 多的可以给地球上每一颗沙子都分配 IP,为什么还需要 NAT 这种无奈之举呢?

在家庭宽带中,运营商分给你的 IPv6 前缀不是一成不变的,和 IPv4 一样,可能重启光猫就会改变,而 K3s 目前是没有办法去修改 cluster cidr 的,和前面一样,是需要重装的。不过这个好像是 flannel 的锅,如果换成 calico 是可以解决的,但这个不在本文的讨论范围内。

划分 IPv6 网段

如果你对自家的 IPv6 前缀很有自信,可以手动划分一段 IP 给 K3s 使用,一般来说,ISP 提供的 IPv6 前缀一般是 /56 或者 /60,我这里是 /60,意味着有 16 个 /64 段可以用来划分。

划分网段意味着你需要在路由器上填写静态路由表,如果你是用的路由器是 openwrt 的,可以在 网络 -> 静态路由 这里找到配置。比如我这里将 240e:xxx:xxx:e304::/63 分配给宿主机,可以划分两块 /64 给 K3s 使用,需要注意的是这里的网关需要填写的是宿主机对应网卡的 link-local 地址。

但是如果你的路由器不支持直接填写静态路由表,可以尝试使用 DHCPv6-PD 的方式,看看路由器能不能直接分给你一段 IPv6 前缀并且自动修改好路由表,相关方式是使用命令 dhclient -d -6 -P eth0

image.png

重新安装 K3s,我们使用下面的环境变量。

export INSTALL_K3S_EXEC="--cluster-cidr=10.42.0.0/16,240e:xxx:xxx:e304::/64 --service-cidr=10.43.0.0/16,240e:xxx:xxx:e305::/112"

启动测试容器,可以看到我们拥有了一个 GUA Address,并且可以直接用来访问外网。

image.png

NPT (IPv6 前缀转换)

正如前面所说,家宽中的 IPv6 地址前缀经常变化,在家庭网络环境中,路由器除了会给你分配 GUA 地址之外,通常还会分配一个 ULA 地址,既然 IPv6 地址那么多,理论上我们可以实现 1:1 的 ULA 到 GLA 地址转换,而不用像 IPv4 那样,多个地址对应一个公网地址去做 NAT。这种方式被称作 NPT (Network Prefix Translation)。

首先仍然是划分地址段,openwrt 在 br-lan 上分配的 ULA 地址段为 fd9b:d5c7:75f::1/60,那么我在这里把 fd9b:d5c7:75f:4::1/63 分配给宿主机。

除此之外,我们还需要添加一条 default 路由,否则内网 ip 不会被转发,需要将接口设置成 WAN,目标设置成 ::/0, 网关这个需要使用命令行查看 pppoe-wan 接口的 peer 端的 link local。如果你不理解这是什么意思,可以直接输入 ip -6 route,默认应该有一条或者两条 default 路由,将 via 后面的地址抄上来即可。最后选择高级设置,将源地址设置成我们划分的子网。

image.png

image.png

image.png

和上面类似,用分配的网络段重建 K3s。

export INSTALL_K3S_EXEC="--cluster-cidr=10.42.0.0/16,fd9b:d5c7:75f:4::1/64 --service-cidr=10.43.0.0/16,fd9b:d5c7:75f:5::1/112"

最后还需要添加一条 IPv6 的地址转换。

# 如果你的 openwrt 版本 > 22.03, 那么应该已经默认使用 nftables 了
nft add rule inet fw4 srcnat oifname "pppoe-wan" ip6 saddr fd9b:d5c7:75f:4::/63 snat ip6 prefix to 240e:xxx:xxx:e304::/63
nft add rule inet fw4 dstnat iifname "pppoe-wan" ip6 daddr 240e:xxx:xxx:e304::/63 dnat ip6 prefix to fd9b:d5c7:75f:4::/63
# 不然的话应该使用 iptables
ip6tables -t nat -A POSTROUTING -o pppoe-wan -j NETMAP --to 240e:xxx:xxx:e304::/63 -s fd9b:d5c7:75f:4::/63
ip6tables -t nat -A PREROUTING -i pppoe-wan -j NETMAP -d 240e:xxx:xxx:e304::/63 --to fd9b:d5c7:75f:4::/63

image.png

可以看到我们内网 IPv6 地址的前缀被转成了公网的。不过这里还有一点小问题,当公网的 IPv6 地址变化时,我们需要手动的去调整 iptables/nftables,我看 openwrt 文档里给了一个 脚本,我看了一下还有一点小问题,如果要用的话最好自己固定 LAN Prefix,并且手动计算 WAN Prefix 的偏移。

结语

以上,就是我对家宽环境下 K3s 使用 IPv6 的探索,现在我的 qBittorrent 已经能正常使用 IPv6 了。