为 Rootless Docker 配置 IPv6(使用 pasta 网络后端)
本来 docker 对 ipv6 的支持就不好,使用 rootless 模式更是雪上加霜。
也许有人问为啥不用 host
网络模式,答案就在于“rootless”,此时使用 host
网络模式可能无法正确监听端口。当然,一种可行的方法是,使用 root 身份启动
docker 进程,然后手动指定 user PID 和 GID 来降级运行(此时可以使用 host
模式)。
考虑安全原则,本文我们来讨论下如何在 rootless 下配置 ipv6。
背景与原理
测试环境:Ubuntu 22.04/24.04,Docker rootless 模式,VPS 具有公网 IPv6
Rootless Docker 默认使用 slirp4netns
作为网络后端,该方案对 IPv6 支持极差,容器内的 IPv6
流量会被用户态网络栈丢弃,无法到达宿主机的公网接口。
pasta(来自 passt
项目)是更现代的替代方案,能够正确转发容器内的 IPv6 流量。
1 | 容器 eth0 (fd00::2/64) |
前置条件
- Docker rootless 已安装并运行
- 宿主机可以访问 IPv6 公网(
curl -6 https://6.ipw.cn有返回) - 系统已开启 IPv6 转发
确认 IPv6 转发已开启:
1 | sysctl net.ipv6.conf.all.forwarding |
如果未开启:
1 | echo "net.ipv6.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf |
安装 pasta
pasta 由 passt 软件包提供:
1 | sudo apt update |
验证安装:
1 | pasta --version |
注意:部分发行版仓库中的
passt版本较旧(如0.0~git20230309),可能存在端口转发失效的 bug。如遇问题,参考文末的升级方法。
配置 rootlesskit 使用 pasta
Rootless Docker 通过 rootlesskit 管理网络后端,需要通过
systemd override 文件指定使用 pasta。
创建 override 目录和配置文件:
1 | mkdir -p ~/.config/systemd/user/docker.service.d/ |
写入以下内容:
1 | [Service] |
各参数说明:
| 参数 | 说明 |
|---|---|
--net=pasta |
使用 pasta 替代 slirp4netns |
--ipv6 |
启用 IPv6 支持 |
--disable-host-loopback |
安全隔离,防止容器访问宿主机 loopback |
--copy-up=/etc |
允许容器内修改 /etc(DNS 解析需要) |
--port-driver=implicit |
pasta 模式下正确暴露端口所必需 |
--mtu=1500 |
设置 MTU,与宿主机网卡保持一致 |
配置 Docker daemon
编辑(或创建)Docker daemon 配置文件:
1 | vim ~/.config/docker/daemon.json |
写入:
1 | { |
各字段说明:
| 字段 | 说明 |
|---|---|
ipv6: true |
为容器启用 IPv6 |
fixed-cidr-v6 |
容器使用的 IPv6 子网,使用 ULA 私有地址段 |
ip6tables: true |
允许 Docker 管理 ip6tables 规则 |
experimental: true |
部分 IPv6 功能需要实验模式 |
为什么用
fd00::/64而不是公网地址?不要将宿主机的公网
/64子网直接分配给 Docker(如2666:f123::/64)。这会导致路由冲突——内核认为容器子网在本地二层,直接走 NDP 邻居发现而不走路由,导致容器无法访问子网外的公网地址。使用 ULA 私有地址(
fd00::/64),由 pasta 负责 NAT 转发,容器访问公网时源地址会 SNAT 为宿主机的公网 IPv6。
重载配置并重启 Docker
1 | systemctl --user daemon-reload |
重启现有容器:
1 | docker restart <容器名> |
验证
确认 pasta 进程正在运行
1 | systemctl --user status docker | grep -i pasta |
应看到类似输出:
1 | ├─ pasta --stderr --ns-ifname=tap0 --mtu=1500 --config-net ... |
确认容器获得了 IPv6 地址
1 | docker exec <容器名> ip -6 addr show eth0 |
应看到 fd00:: 开头的地址:
1 | inet6 fd00::2/64 scope global flags 02 |
确认容器内默认路由存在
1 | docker exec <容器名> ip -6 route show |
应包含:
1 | default via fd00::1 dev eth0 metric 1024 |
测试 IPv6 连通性
1 | # ping 公网 DNS |
curl 返回的应该是宿主机的公网 IPv6 地址(SNAT 后)。
常见问题
IPv6 通了但端口无法从外部访问
检查 UFW 是否放行了相关端口:
1 | sudo ufw status |
重启系统后 Docker 不自动启动
确认 rootless Docker 的 linger 已启用:
1 | loginctl enable-linger $USER |
pasta 版本过旧导致端口转发失效
查看当前版本:
1 | apt show passt |
如果版本为 0.0~git20230309 等旧版本,尝试从 backports
升级:
1 | # Debian/Ubuntu |
或者从源码编译安装最新版本,参考 passt 官网。
容器内 DNS 解析失败
检查容器内的 DNS 配置:
1 | docker exec <容器名> cat /etc/resolv.conf |
pasta 默认会设置
--dns-forward=10.0.2.3,如果解析异常可在
daemon.json 中手动指定 DNS:
1 | { |
参考资料
- passt 项目主页
- Docker rootless 官方文档
- rootlesskit 文档