群晖 Docker 开启 IPv6 完整方案(macvlan + 固定 ULA + OpenWrt NAT66)

技术·学习 · 昨天 · 19 人浏览

macvlan + 固定 ULA + OpenWrt NAT66

环境:群晖 DSM 7.4(Container Manager 24.0.2-1706)|主路由:京东云 RE-CS-02(ImmortalWrt SNAPSHOT,qualcommax,fw4/nftables)|宽带:电信,动态 IPv6 前缀(240e:xxxx:xxxx:xxxx::/64)

目标:容器拥有固定不变的 IPv6 地址,可出公网 IPv6、可被外网 IPv6 连接(BT 场景),且电信前缀变化后无需任何维护。

〇、方案选型(为什么走这条路)

踩坑:Docker 原生 IPv6 NAT 方案不可行

网上通用做法是修改 dockerd.json 开 ipv6 + ip6tables + fixed-cidr-v6,但在这台群晖上启动失败:

# 配置文件位置(DSM 7.2+ 套件名 ContainerManager)
vi /var/packages/ContainerManager/etc/dockerd.json

# 加入以下字段后 synopkg restart ContainerManager 报错:
"ipv6": true,
"fixed-cidr-v6": "fd00:db8:1::/64",
"ip6tables": true,
"experimental": true

错误日志(journalctl -b -u pkg-ContainerManager-dockerd.service 可查):

failed to start daemon: Error initializing network controller:
failed to create NAT chain DOCKER: iptables failed:
ip6tables --wait -t nat -N DOCKER: ip6tables v1.8.3 (legacy):
can't initialize ip6tables table `nat': Table does not exist (do you need to insmod?)

根因:DSM 内核裁剪掉了 IPv6 NAT 模块(/lib/modules/ 下只有 ip6table_filter.ko、ip6table_mangle.ko,没有 ip6table_nat.ko、nf_nat_ipv6.ko)。DSM 的内核不动为妙,故放弃此路。

最终架构

Internet(电信动态 IPv6 前缀,随时会变)
   │ WAN
┌──┴──────────────────┐
│  OpenWrt RE-CS-02   │ ① LAN 别名 fd00:db8:1::1/128(容器网关,固定)
│                     │ ② 静态路由 fd00:db8:1::/64 → 群晖 fe80(下一跳不变)
│                     │ ③ nat66-out:fd00:db8:1::/64 出 WAN 走 MASQUERADE
│                     │ ④ DNAT:5xxxx → fd00:db8:1::20(BT 入站)
└──┬──────────────────┘
   │ LAN(192.168.68.0/24)
┌──┴──────────────────┐
│  群晖 DSM 7.4       │ ⑤ IPv6 转发=1 + 宿主机 macvlan 垫片 mv-shim(::254)
│  eth0               │    开机计划任务自动恢复(见 三-3)
└──┬──────────────────┘
   │ macvlan lan_net(fd00:db8:1::/64)
qB / TR / iyuup → 192.168.68.20 + fd00:db8:1::20

核心思想:动态的电信前缀全部由路由器消化(NAT66 + DNAT),容器侧只用固定的 ULA 地址(fd 开头私有段),从此前缀随便变,配置永不动。


一、OpenWrt 侧(SSH 登录路由器)

1. LAN 上加一个固定 IPv6 别名(作为容器网关)

uci add_list network.lan.ip6addr='fd00:db8:1::1/128'
uci commit network
/etc/init.d/network restart

2. 静态路由:告诉 OpenWrt,fd00:db8:1::/64 的包都交给群晖

先拿到群晖的链路本地地址(在群晖上执行 ip -6 addr show eth0 | grep fe80,形如 fe80::xxxx:xxxx:xxxx:xxxx/64),然后在 OpenWrt 上:

uci add network route6
uci set network.@route6[-1].interface='lan'
uci set network.@route6[-1].target='fd00:db8:1::/64'
uci set network.@route6[-1].gateway='<群晖的fe80地址>'
uci commit network
/etc/init.d/network restart
用 fe80 地址做下一跳是刻意的:它只跟群晖的 MAC 绑定,不受电信前缀变化影响。

或者在路由 Web 页面配置(网络 → 路由 → 静态 IPv6 路由 → 添加):

字段填写内容
接口lan
路由类型unicast(单播) ← 关键!选成 local 是"本机地址"语义,必错
目标fd00:db8:1::/64
网关群晖的 fe80 链路本地地址,填纯地址不带 /64,如 fe80::2e0:22ff:fe29:919c
距离值/表留空

查找群晖的 IPv6 链路本地地址:

# 群晖 SSH 上执行
ip -6 addr show eth0 | grep fe80
# 输出示例:inet6 fe80::2e0:22ff:fe29:919c/64 scope link

配置结果:

2026-11-01T14:29:22.png

验证路由生效(OpenWrt SSH):

ip -6 route show | grep fd00
# 期望:fd00:db8:1::/64 via fe80::2e0:22ff:fe29:919c dev br-lan proto static

3. NAT66 出站(fw4 规则)

uci add firewall nat
uci set firewall.@nat[-1].name='nat66-out'
uci set firewall.@nat[-1].family='ipv6'
uci set firewall.@nat[-1].src='lan'
uci set firewall.@nat[-1].src_ip='fd00:db8:1::/64'
uci set firewall.@nat[-1].dest='wan'
uci set firewall.@nat[-1].target='MASQUERADE'
uci commit firewall
/etc/init.d/firewall restart
注意:如果你的 ImmortalWrt wan 区已经有 masq6='1'(uci show firewall | grep -i masq 查看),这步可以跳过。配置后验证:uci show firewall.@nat[0] 应能看到 name='nat66-out'。

4. BT 入站放行(IPv6 DNAT)

# qBittorrent 55678
uci add firewall redirect
uci set firewall.@redirect[-1].name='qb-bt-v6'
uci set firewall.@redirect[-1].family='ipv6'
uci set firewall.@redirect[-1].proto='tcp udp'
uci set firewall.@redirect[-1].src='wan'
uci set firewall.@redirect[-1].src_dport='55678'
uci set firewall.@redirect[-1].dest_ip='fd00:db8:1::20'
uci set firewall.@redirect[-1].target='DNAT'

# Transmission 51413
uci add firewall redirect
uci set firewall.@redirect[-1].name='tr-bt-v6'
uci set firewall.@redirect[-1].family='ipv6'
uci set firewall.@redirect[-1].proto='tcp udp'
uci set firewall.@redirect[-1].src='wan'
uci set firewall.@redirect[-1].src_dport='51413'
uci set firewall.@redirect[-1].dest_ip='fd00:db8:1::20'
uci set firewall.@redirect[-1].target='DNAT'

uci commit firewall
/etc/init.d/firewall restart

2026-11-01T14:29:50.png

二、群晖侧

1. 创建 macvlan 双栈网络(固定 ULA 段)

docker network rm lan_net   # 若之前建过先删
docker network create -d macvlan \
  --subnet=192.168.68.0/24 --gateway=192.168.68.1 \
  --ipv6 --subnet=fd00:db8:1::/64 --gateway=fd00:db8:1::1 \
  -o parent=eth0 lan_net
网关 fd00:db8:1::1 就是我们在 OpenWrt LAN 上加的别名地址。

验证网络与容器 IP:

docker network inspect lan_net --format '{{range .Containers}}{{.Name}}  {{.IPv4Address}}  {{.IPv6Address}}{{"\n"}}{{end}}'
# 期望:qbittorrent  192.168.68.20/24  fd00:db8:1::20/64

2. compose 改造(三容器共用一个 IP)

关键写法:只有 qbittorrent 挂 lan_net 持有 IP;transmission 和 iyuuplus 用 network_mode: "service:qbittorrent" 共享 qB 的网络栈(IPv4、IPv6 全部公用,靠端口区分)。macvlan 下 ports: 端口映射无效,全部删除。

services:
  qbittorrent:
    image: linuxserver/qbittorrent:4.5.2
    container_name: qbittorrent
    environment:
      - PUID=0
      - PGID=0
      - TZ=Asia/Shanghai
      - UMASK_SET=022
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent/config:/config
      - /volume5/temp:/temp
      - /volume3/media:/media
    networks:
      lan_net:
        ipv4_address: 192.168.68.20
        ipv6_address: fd00:db8:1::20
    restart: always

  transmission:
    image: chisbread/transmission:latest
    container_name: transmission
    depends_on:
      - qbittorrent
    environment:
      - PUID=0
      - USER=<你的用户名>
      - PASS=<你的密码>
      - RPCPORT=9091
    volumes:
      - ./transmission/config:/config
      - ./transmission/watch:/watch
      - ./transmission/transmission-web-control:/transmission-web-control
      - /volume5/temp:/temp
      - /volume3/media:/media
    network_mode: "service:qbittorrent"
    restart: always

  iyuuplus:
    image: iyuucn/iyuuplus-dev:latest
    container_name: iyuuplus-dev
    depends_on:
      - qbittorrent
    stdin_open: true
    tty: true
    volumes:
      - ./iyuuplus/iyuu:/iyuu
      - ./iyuuplus/data:/data
      - ./iyuuplus/log:/var/log/nginx
      - ./qbittorrent/config/qBittorrent/BT_backup:/BT_backup
      - ./transmission/config/torrents:/torrents
    network_mode: "service:qbittorrent"
    restart: always

networks:
  lan_net:
    external: true

改完后:

docker-compose down && docker-compose up -d

iyuuplus 里下载器地址改成 127.0.0.1:8080(qB)和 127.0.0.1:9091(TR)——三者共享网络栈,走 localhost 即可。

2026-11-01T14:30:27.png

3. IPv6 转发 + 宿主机垫片(⚠️ 最容易漏的一步)

配完上面后,容器能出公网(macvlan 二层直通 OpenWrt),但内网设备 ping 不通容器——检查会发现:

sysctl net.ipv6.conf.all.forwarding
# 输出 net.ipv6.conf.all.forwarding = 0  ← 转发没开

ip -6 route show | grep fd00
# 无输出 ← 宿主机不知道 fd00:db8:1::/64 该往哪转(macvlan 隔离,天生没有)

修复(两步缺一不可):

# 1. 开启 IPv6 转发
sysctl -w net.ipv6.conf.all.forwarding=1

# 2. 宿主机创建 macvlan 垫片接口,作为转发出口
ip link add mv-shim link eth0 type macvlan mode bridge
ip addr add fd00:db8:1::254/64 dev mv-shim   # ::254 避开 ::1(路由器) 和 ::20(容器)
ip link set mv-shim up

执行后验证:

ping -c3 fd00:db8:1::20    # 群晖宿主机 ping 容器,此前 macvlan 隔离时是不通的

4. 开机持久化(计划任务,防重启丢失)

sysctl 和 ip link 都是临时生效,群晖重启会丢。控制面板 → 任务计划 → 新增 → 触发的任务 → 用户定义的脚本:

  • 任务名称:docker-macvlan-ipv6
  • 用户账号:root(必须)
  • 事件:开机
  • 运行命令(幂等,重复执行不报错):
#!/bin/sh
# Docker macvlan IPv6 转发与垫片(fd00:db8:1::/64 方案)
sysctl -w net.ipv6.conf.all.forwarding=1

if ! ip link show mv-shim >/dev/null 2>&1; then
    ip link add mv-shim link eth0 type macvlan mode bridge
fi
ip addr replace fd00:db8:1::254/64 dev mv-shim
ip link set mv-shim up

保存后右键任务 → 运行,验证脚本无误。

2026-11-01T14:30:44.png


三、验证清单(自上而下逐级测)

层级命令/方法期望
容器 → OpenWrt 网关docker exec qbittorrent ping -c2 fd00:db8:1::1通
容器 → 公网 IPv6docker exec qbittorrent ping -6 -c4 2400:3200::1通(NAT66 生效)
路由器 → 容器OpenWrt 上 ping -6 -c3 fd00:db8:1::20通(转发+垫片生效)
内网电脑 → 容器Windows ping fd00:db8:1::20通
WebUI浏览器 http://192.168.68.20:8080 / :9091 / :8780正常打开
外网 BT 端口手机关 WiFi 走流量,ipv6.ping.pe 测 55678/51413端口开放

四、注意事项与常见问题

  1. IP 冲突:192.168.68.20~22 和 fd00:db8:1::20 必须避开路由器 DHCP 分配池,最稳做法是在路由器上做静态绑定(容器 MAC 用 docker inspect qbittorrent | grep MacAddress 查)。
  2. 电信前缀动态变化:GUA 前缀(240e:…)重拨会变,但本方案容器全走固定 ULA,无感;变化的只有外网访问时要用的"当前 WAN 地址"(BT tracker 上报的地址是被动告知的,无需配置)。
  3. macvlan 隔离:容器与群晖宿主机二层互相隔离,宿主机访问容器必须走 mv-shim 垫片路由,这是 macvlan 的内核机制,不是故障。
  4. ports: 映射无效:macvlan 下端口映射不工作,直接访问容器 IP:端口;群晖防火墙若开启,注意放行相应端口。
  5. Docker 套件重启/群晖升级后:计划任务会自动恢复转发与垫片;若手动 docker network rm 过 lan_net,需重新执行 二-1 的 create 命令再 up 容器。
  6. 排查工具箱:

    • dockerd 起不来:journalctl -b -u pkg-ContainerManager-dockerd.service | tail -50
    • 内核模块检查:ls /lib/modules/ | grep -i ip6
    • OpenWrt 路由/防火墙:ip -6 route show | grep fd00、uci show firewall | grep -iE "nat66|bt-v6"
群晖 Docker ipv6 macvlan
Theme Jasmine by Kent Liao