Gentoo Linux 使用 xtables-nft-multi 之后,docker wg-easy 不能正常使用
上一篇记录了 Gentoo Linux 在新内核里把 iptables 从 legacy backend 切换到 xtables-nft-multi 的过程。宿主机切完之后,大部分自己写的防火墙脚本都能继续工作,因为命令还是 iptables,只是背后翻译成了 nftables 规则。
不过还有一个容易忽略的地方:Docker 容器里的程序也可能会调用 iptables。这次出问题的就是 Docker 版 wg-easy。
背景
从 Linux Kernel 6.17 之后,iptables legacy 这条路已经不适合继续依赖了。对于自己编译内核的 Gentoo 用户来说,如果没有再打开 legacy 相关配置,系统里继续使用旧的 iptables backend 就会遇到各种规则写入失败的问题。
比较合适的做法是把 iptables 命令切到 nft backend:
1eselect iptables list
2eselect iptables set 2
切换后确认一下:
1iptables --version
正常应该看到类似:
1iptables v1.8.x (nf_tables)
这表示现在执行 iptables 命令时,用户态工具会把规则翻译成 nftables 规则再写入内核。
wg-easy 为什么会出问题
wg-easy 本身是一个很好用的 WireGuard Web 管理工具。它的问题不在 Web UI,而在容器启动和 WireGuard peer 生效时,仍然会执行一些 iptables 命令,例如:
1iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
2iptables -A FORWARD -i wg0 -j ACCEPT
3iptables -A FORWARD -o wg0 -j ACCEPT
这些规则的作用很直接:
- 允许
wg0接口转发流量 - 给 WireGuard 客户端做
MASQUERADE - 让客户端能通过服务器访问外部网络
问题在于:容器内看到的是自己的文件系统,但防火墙规则最终写入的是宿主机内核的 netfilter。也就是说,容器里的 iptables 如果还是 legacy 版本,它并不会因为宿主机已经切到 xtables-nft-multi 就自动变好。
于是就会出现一种很别扭的现象:
- 宿主机执行
iptables正常 - 宿主机的 Docker 网络基本正常
wg-easy容器能启动- 但 WireGuard 客户端连上后不能正常转发流量
本质原因是容器里的 iptables backend 和宿主机内核实际支持的 backend 不一致。
处理思路
最稳妥的方案不是改 wg-easy 的历史数据,也不是重建 WireGuard 配置,而是只改镜像里的 iptables 命令入口。
也就是说,让容器内的这些命令:
1iptables
2ip6tables
3iptables-save
4iptables-restore
都指向 xtables-nft-multi。
这样 wg-easy 原来生成的配置、已有的 peer、历史数据都不需要动。它仍然以为自己在执行 iptables,但实际写入内核时已经走 nf_tables backend。
自定义 Dockerfile
可以在本地创建一个很薄的派生镜像,只替换 iptables 相关命令:
1FROM ghcr.io/wg-easy/wg-easy:latest
2
3RUN set -eux; \
4 if command -v xtables-nft-multi >/dev/null 2>&1; then \
5 ln -sf "$(command -v xtables-nft-multi)" /usr/sbin/iptables; \
6 ln -sf "$(command -v xtables-nft-multi)" /usr/sbin/ip6tables; \
7 ln -sf "$(command -v xtables-nft-multi)" /usr/sbin/iptables-save; \
8 ln -sf "$(command -v xtables-nft-multi)" /usr/sbin/ip6tables-save; \
9 ln -sf "$(command -v xtables-nft-multi)" /usr/sbin/iptables-restore; \
10 ln -sf "$(command -v xtables-nft-multi)" /usr/sbin/ip6tables-restore; \
11 else \
12 echo "xtables-nft-multi not found in image" >&2; \
13 exit 1; \
14 fi
如果基础镜像里没有 xtables-nft-multi,需要先安装对应的 iptables 包。不同基础镜像的包管理器不一样,可以先进入容器确认:
1docker run --rm -it ghcr.io/wg-easy/wg-easy:latest sh
2command -v xtables-nft-multi
3cat /etc/os-release
如果是 Alpine 系镜像,可以改成:
1FROM ghcr.io/wg-easy/wg-easy:latest
2
3RUN set -eux; \
4 apk add --no-cache iptables; \
5 ln -sf /usr/sbin/xtables-nft-multi /usr/sbin/iptables; \
6 ln -sf /usr/sbin/xtables-nft-multi /usr/sbin/ip6tables; \
7 ln -sf /usr/sbin/xtables-nft-multi /usr/sbin/iptables-save; \
8 ln -sf /usr/sbin/xtables-nft-multi /usr/sbin/ip6tables-save; \
9 ln -sf /usr/sbin/xtables-nft-multi /usr/sbin/iptables-restore; \
10 ln -sf /usr/sbin/xtables-nft-multi /usr/sbin/ip6tables-restore
构建镜像:
1docker build -t wg-easy:nft .
然后在 docker compose 里把原来的镜像替换掉:
1services:
2 wg-easy:
3 image: wg-easy:nft
4 container_name: wg-easy
5 cap_add:
6 - NET_ADMIN
7 - SYS_MODULE
8 sysctls:
9 - net.ipv4.ip_forward=1
10 - net.ipv4.conf.all.src_valid_mark=1
11 volumes:
12 - ./data:/etc/wireguard
13 ports:
14 - "51820:51820/udp"
15 - "51821:51821/tcp"
这里的关键点只有一个:./data:/etc/wireguard 继续使用原来的数据目录。这样已有的 WireGuard 配置和 peer 信息都保留下来,不需要重新生成客户端配置。
验证
容器启动后,先确认容器内的 iptables backend:
1docker exec -it wg-easy iptables --version
期望输出里包含:
1nf_tables
再看宿主机上的规则是否已经写入:
1iptables -t nat -S
2iptables -S
如果能看到 wg-easy 或 wg0 相关的 MASQUERADE、FORWARD 规则,再用客户端连接测试外网访问,基本就可以确认问题解决。
小结
这类问题容易误判成 Docker、WireGuard 或 wg-easy 的配置错误,但核心其实还是 iptables backend 不一致。
宿主机切到 xtables-nft-multi 之后,容器内凡是会直接调用 iptables 的程序也要一起处理。对 wg-easy 来说,最小改动就是做一个派生镜像,把容器里的 iptables 命令替换成 xtables-nft-multi。
这样做的好处是很明确的:
- 不需要迁移
wg-easy历史数据 - 不需要重建 WireGuard peer
- 不需要改已有客户端配置
- 继续兼容原来基于
iptables的启动逻辑 - 实际规则写入时走新的
nftablesbackend
从长期看,原生 nftables 当然是更干净的方向。但对于这类已经封装好 iptables 调用的 Docker 应用,先用 xtables-nft-multi 做兼容翻译,是一个成本低、风险小、维护起来也比较舒服的过渡方案。
Posts in this series
- Gentoo Linux 使用 xtables-nft-multi 之后,docker wg-easy 不能正常使用
- Gentoo Linux Kernel 从 6.12 升级至 6.18
- Gentoo Linux 手搓路由器 —— 使用 odhcpd 支持 IPv6
- 使用Gentoo Linux手搓路由器之四 -- 支持 IPv6
- 使用 Gentoo Linux 手搓路由器之三 -- 国内访问加速
- 使用 Gentoo Linux 手搓路由器之二 -- 透明代理
- IPTV单线复用——在Linux系统上配置
- 使用 Gentoo Linux 搭建简单路由器并支持IPTV功能