为 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
2
3
4
5
6
7
容器 eth0 (fd00::2/64)
↕ TUN 设备(pasta 管理)
pasta 进程(用户态,支持 IPv6)

宿主机 ens3(公网 IPv6)

公网

前置条件

  • Docker rootless 已安装并运行
  • 宿主机可以访问 IPv6 公网(curl -6 https://6.ipw.cn 有返回)
  • 系统已开启 IPv6 转发

确认 IPv6 转发已开启:

1
2
sysctl net.ipv6.conf.all.forwarding
# 应返回 net.ipv6.conf.all.forwarding = 1

如果未开启:

1
2
echo "net.ipv6.conf.all.forwarding=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

安装 pasta

pastapasst 软件包提供:

1
2
sudo apt update
sudo apt install passt -y

验证安装:

1
2
pasta --version
which pasta

注意:部分发行版仓库中的 passt 版本较旧(如 0.0~git20230309),可能存在端口转发失效的 bug。如遇问题,参考文末的升级方法。

配置 rootlesskit 使用 pasta

Rootless Docker 通过 rootlesskit 管理网络后端,需要通过 systemd override 文件指定使用 pasta。

创建 override 目录和配置文件:

1
2
mkdir -p ~/.config/systemd/user/docker.service.d/
vim ~/.config/systemd/user/docker.service.d/override.conf

写入以下内容:

1
2
3
4
5
[Service]
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--ipv6 --copy-up=/etc --disable-host-loopback"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=1500"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_NET=pasta"
Environment="DOCKERD_ROOTLESS_ROOTLESSKIT_PORT_DRIVER=implicit"

各参数说明:

参数 说明
--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
2
3
4
5
6
7
{
"experimental": true,
"ipv6": true,
"fixed-cidr-v6": "fd00::/64",
"iptables": true,
"ip6tables": true
}

各字段说明:

字段 说明
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
2
systemctl --user daemon-reload
systemctl --user restart docker

重启现有容器:

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
2
3
4
5
# ping 公网 DNS
docker exec <容器名> ping6 -c 3 2606:4700:4700::1111

# 访问 IPv6 网站
docker exec <容器名> curl -6 https://ipv6.icanhazip.com

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
2
# Debian/Ubuntu
sudo apt install -t $(lsb_release -cs)-backports passt

或者从源码编译安装最新版本,参考 passt 官网

容器内 DNS 解析失败

检查容器内的 DNS 配置:

1
docker exec <容器名> cat /etc/resolv.conf

pasta 默认会设置 --dns-forward=10.0.2.3,如果解析异常可在 daemon.json 中手动指定 DNS:

1
2
3
{
"dns": ["2606:4700:4700::1111", "8.8.8.8"]
}

参考资料

  • passt 项目主页
  • Docker rootless 官方文档
  • rootlesskit 文档