Docker 基础体系 · 第 48/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker IPv6、Macvlan 与 Host 网络:场景、隔离和路由边界
Docker 网络的关键不是“容器有没有 IP”,而是要回答三个问题:
- 这个 IP 属于哪个网络命名空间和地址域?
- 数据包经过哪些内核设备、路由表和防火墙规则?
- 隔离边界位于二层、三层、网络命名空间,还是根本没有建立?
IPv6、Macvlan 和 Host 网络分别改变了这些问题的不同部分:
- Docker IPv6主要改变地址分配、路由和邻居发现,通常仍然运行在 Linux 网络命名空间隔离模型中。
- Macvlan把容器接入宿主机的二层网络,使容器拥有独立的 MAC 和同一物理网络上的地址。
- Host 网络直接移除容器自己的网络命名空间隔离,让容器进程使用宿主机的网络栈。
这三者不是互相替代的“网络模式”,也不能只用“性能更好”或“隔离更强”概括。下面从 Linux 容器边界、数据流和失败路径分别展开。
一、先建立 Docker 网络的 Linux 基础模型
1. 网络命名空间决定“谁看得到什么”
Linux 网络命名空间(network namespace)拥有一组独立的网络资源,包括:
- 网卡和虚拟网卡;
- IPv4、IPv6 地址;
- 路由表;
- 邻居表,例如 ARP 和 IPv6 NDP;
- iptables/nftables 相关规则;
- socket 端口空间;
lo回环接口。
普通 Docker bridge 网络大致包含如下结构:
flowchart LR
C[容器网络命名空间<br/>eth0: 172.20.0.2]
V1[veth pair]
B[宿主机 docker bridge<br/>docker0 或 br-app]
F[宿主机 netfilter<br/>iptables/nftables]
R[宿主机路由表]
E[外部网络]
C --- V1 --- B
B --> F
F --> R
R --> E
容器中的 eth0 和宿主机中的 veth 端点是一对虚拟以太网设备。数据包离开容器后进入宿主机 bridge,再由宿主机路由和过滤。
因此,容器中的:
ip addr
ip route
ip -6 route
看到的并不是宿主机的状态,而是容器网络命名空间的状态。
2. 路由选择先于大多数转发行为
对一个目的地址,Linux 通常先在当前网络命名空间中进行路由查找:
目的地址
↓
最长前缀匹配
↓
选择出口接口和下一跳
↓
邻居解析
↓
发送到链路
例如容器拥有:
IPv6 地址:2001:db8:10::2/64
默认路由:default via 2001:db8:10::1 dev eth0
当它访问 2001:db8:20::5 时:
2001:db8:20::5不属于2001:db8:10::/64;- 因此使用默认路由;
- 下一跳是
2001:db8:10::1; - 容器需要通过 IPv6 Neighbor Discovery Protocol(NDP)解析下一跳的链路层地址;
- 数据包进入宿主机 bridge 或其他网络设备。
如果没有默认路由,或者下一跳不可通过 NDP 解析,问题出在路由或邻居发现阶段,而不是 Docker DNS。
3. IPv6 与 IPv4 的一个重要差异
IPv4 中常见的 Docker 出站流程是:
容器私有 IPv4
→ bridge
→ 宿主机 IPv4
→ MASQUERADE
→ 外部网络
IPv6 通常不应简单套用这个模型。Docker 可以为 bridge 网络分配 IPv6 地址并配置路由,但一个可用的 IPv6 出站网络还必须满足外部网络能够把该 IPv6 前缀路由回宿主机。
例如:
容器地址:2001:db8:100::2/64
Docker bridge:2001:db8:100::1/64
外部路由器必须知道:
2001:db8:100::/64 via 宿主机的 IPv6 地址
否则容器虽然能够把请求发出去,返回包却无法到达宿主机。
这里的核心条件是:
只满足前半部分,连接仍然失败。
二、Docker IPv6:地址分配、路由和端口发布
1. Docker IPv6 功能的组成
Docker IPv6 支持不是一个单独的开关就能解释完整的功能,它至少涉及:
- Docker daemon 是否启用 IPv6;
- 网络是否启用 IPv6;
- Docker 为网络分配的 IPv6 前缀;
- Linux 内核是否启用 IPv6 转发;
- 宿主机和上游路由器是否能路由该前缀;
- 防火墙是否允许转发和入站流量;
- 应用是否监听 IPv6 地址。
常见 daemon 配置示例:
{
"ipv6": true,
"fixed-cidr-v6": "2001:db8:100::/64"
}
通常保存于 Linux 上的 /etc/docker/daemon.json。修改后需要重启 Docker daemon:
sudo systemctl restart docker
2001:db8::/32 是文档示例专用前缀,不可作为真实公网地址使用。生产环境应使用运营商或云平台实际委派的 IPv6 前缀;内部网络也可以使用 ULA,例如 fd00::/8,但 ULA 本身不会自动获得公网可达性。
验证 daemon 和网络状态:
docker info
docker network ls
docker network inspect bridge
如果 daemon 尚未启用 IPv6,直接在网络配置中写 enable_ipv6: true 可能失败,或者网络能够创建但无法获得预期的全局 IPv6 路由。具体行为取决于 Docker Engine 版本和网络驱动配置,不能只看 Compose 文件判断能力是否完整。
2. 用户定义 bridge 网络中的 IPv6
一个可读性较好的 Compose 示例:
services:
web:
image: nginx:alpine
networks:
app:
ipv4_address: 172.30.0.10
ipv6_address: 2001:db8:100::10
ports:
- "8080:80"
networks:
app:
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: 172.30.0.0/24
gateway: 172.30.0.1
- subnet: 2001:db8:100::/64
gateway: 2001:db8:100::1
启动:
docker compose up -d
检查地址和路由:
docker compose exec web ip -6 addr show dev eth0
docker compose exec web ip -6 route
docker network inspect app
典型结果应包含:
inet6 2001:db8:100::10/64 scope global
default via 2001:db8:100::1 dev eth0
每一步成立的原因是:
enable_ipv6: true允许该 bridge 网络配置 IPv6;subnet定义容器地址所在的 IPv6 前缀;gateway指定容器的下一跳;ipv6_address请求静态地址;ports配置的是宿主机发布端口,不等于取消容器地址本身;ip -6 route可以确认地址配置之后是否真正拥有默认路由。
静态 IPv6 地址必须位于该网络声明的前缀内,且不能和其他容器或网关冲突。Docker 的 IPAM 负责分配动态地址,但静态地址冲突仍然可能由手工配置造成。
3. IPv6 地址不等于公网可达
下面是一个常见反例:
容器:2001:db8:100::10
宿主机:2001:db8:1::20
上游路由器:知道 2001:db8:1::/64
上游路由器:不知道 2001:db8:100::/64
容器向外发送请求时,宿主机可能成功转发:
容器 → 宿主机 → 上游路由器 → 互联网
但返回包按照目标地址 2001:db8:100::10 查找路由时,没有任何设备知道该前缀位于哪台宿主机后面,于是丢失。
这和以下错误不同:
- 没有默认路由:容器一开始就无法发出包;
- NDP 失败:找不到同链路下一跳的 MAC;
- 上游无回程路由:请求发出但响应回不来;
- 防火墙丢包:路由存在,但转发或入站规则拒绝;
- 应用未监听 IPv6:网络层可达,但 TCP 连接没有监听者。
生产中使用全局 IPv6 前缀时,应明确前缀的来源和回程路由方式。云环境可能要求为子网添加路由、绑定委派前缀,或者使用平台提供的 IPv6 地址分配机制。不能假设 Docker 会替你向物理路由器发布 BGP、静态路由或 NDP 代理。
4. ULA、NAT66 与路由
使用 ULA 的示例:
networks:
private6:
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: fd42:42:42::/64
这种网络适合内部服务之间通信,但不能直接访问公网 IPv6,除非存在以下之一:
- 上游网络知道
fd42:42:42::/64的回程路由; - 宿主机或边界设备配置了 IPv6 转换机制;
- 容器通过代理或其他应用层出口访问外部。
IPv6 的设计倾向于端到端路由,而不是默认依赖 IPv4 式 NAT。Docker 的 IPv6 bridge 配置不应被理解为“自动提供完整 NAT66 出口”。如果业务确实需要 NAT66,应由明确的边界网络设备或 Linux 防火墙规则实现,并接受其可观测性、地址语义和排障复杂度。
5. IPv6 端口发布的边界
services:
api:
image: example/api:latest
ports:
- "8080:8080"
端口发布的含义是:Docker 在宿主机上接收访问 8080 的流量,并根据规则转发到容器的 8080。
它不等于:
容器获得宿主机的 IPv6 地址
也不等于:
容器自动获得一个独立公网监听入口
验证发布状态:
docker ps
docker port <container>
ss -lnt
sudo nft list ruleset
实际规则可能由 iptables-legacy、iptables-nft 或 Docker 当前使用的防火墙后端呈现,不能假设一定存在某个固定链名。重点是验证三层状态:
- Docker 是否记录了端口映射;
- 宿主机是否有对应监听或转发路径;
- 容器进程是否监听目标端口。
应用如果只监听:
127.0.0.1:8080
那么即使 Docker 端口发布正确,来自容器网络接口的转发流量也可能无法访问。要同时提供 IPv4 和 IPv6 服务,应用通常需要监听 0.0.0.0 和 ::,但具体行为受应用框架和 IPV6_V6ONLY 等系统设置影响,不能只根据配置字符串推断。
三、IPv6 中的 NDP:为什么 ARP 经验不完全适用
IPv4 使用 ARP 将 IP 地址解析为 MAC 地址。IPv6 使用 ICMPv6 Neighbor Discovery Protocol,包括:
- Neighbor Solicitation(NS);
- Neighbor Advertisement(NA);
- Router Solicitation(RS);
- Router Advertisement(RA)。
例如容器向网关 2001:db8:100::1 发送包时,内核可能先发出 NS:
谁拥有 2001:db8:100::1?
请把你的 MAC 告诉 2001:db8:100::10
如果 ICMPv6 被错误防火墙规则过滤,表现可能不是“IPv6 完全没有地址”,而是:
ping -6 2001:db8:100::1
# Destination unreachable
# 或长时间超时
诊断时应同时检查:
docker compose exec web ip -6 neigh
docker compose exec web ping -6 -c 3 2001:db8:100::1
sudo tcpdump -ni any icmp6
ip -6 neigh 可能显示:
2001:db8:100::1 dev eth0 FAILED
这表示邻居解析失败。此时继续修改 Docker DNS 或应用配置没有意义。
在 Macvlan 中,NDP 更接近真实物理网络:容器的 IPv6 地址可能直接通过宿主机物理接口对外发送 NDP。交换机、云平台的端口安全策略、虚拟化平台的 MAC/ND 限制,都可能影响结果。
四、Macvlan:把容器放入二层网络
1. Macvlan 的工作模型
Macvlan 是 Linux 内核提供的虚拟网络设备类型。Docker 使用它时,宿主机在指定父接口上创建逻辑网络,使每个容器拥有一个独立的虚拟接口和 MAC 地址。
典型结构:
flowchart LR
C1[容器 A<br/>192.168.50.10<br/>MAC A]
C2[容器 B<br/>192.168.50.11<br/>MAC B]
M[Macvlan 子接口/内核转发]
P[宿主机物理接口 eth0]
S[交换机]
G[局域网网关]
C1 --- M
C2 --- M
M --- P
P --- S
S --- G
与 bridge 的主要区别是:
Bridge:
容器通常连接私有子网,宿主机承担三层网关、NAT 或端口发布。
Macvlan:
容器可以直接使用物理网络中的地址和默认网关,通常不需要 Docker bridge NAT。
这里的“直接”不是指容器绕过 Linux 内核,而是指容器的二层帧通过 macvlan 逻辑设备和父接口进入外部 LAN,不再先进入 Docker 的私有 bridge 地址域。
2. 创建一个 Macvlan 网络
假设:
物理接口:eth0
局域网:192.168.50.0/24
局域网网关:192.168.50.1
Docker 可用地址:192.168.50.192/27
创建网络:
docker network create -d macvlan \
--subnet=192.168.50.0/24 \
--gateway=192.168.50.1 \
--ip-range=192.168.50.192/27 \
-o parent=eth0 \
lan_macvlan
启动容器:
docker run -d \
--name lan-nginx \
--network lan_macvlan \
--ip 192.168.50.200 \
nginx:alpine
检查:
docker network inspect lan_macvlan
docker exec lan-nginx ip addr
docker exec lan-nginx ip route
预期:
eth0: 192.168.50.200/24
default via 192.168.50.1
这些配置成立的前提是:
eth0确实是连接目标 LAN 的父接口;192.168.50.200没有被其他设备使用;- 网关允许该地址通过;
- 交换机和虚拟化平台允许额外的源 MAC;
- 物理网络对该容器地址存在正常的 ARP 或 NDP 行为。
--ip-range 只影响 Docker 从哪个地址池分配地址,不会自动在 DHCP 服务器上登记租约,也不会替你修改交换机或路由器配置。
3. 802.1Q trunk 模式
如果父接口写成:
-o parent=eth0.50
表示使用已存在的 VLAN 子接口。Docker 也支持通过带点号的 parent 名称使用 802.1Q VLAN 场景,例如:
docker network create -d macvlan \
--subnet=192.168.50.0/24 \
--gateway=192.168.50.1 \
-o parent=eth0.50 \
lan_vlan50
这要求:
- 宿主机已经能够正确处理 VLAN 50;
- 交换机端口配置与 VLAN 模式一致;
- 网关位于同一个 VLAN;
- 不存在把 access 端口和 trunk 配置混用的情况。
如果 VLAN 配置错误,容器通常会表现为地址存在但网关不可达。此时可以检查:
ip -d link show eth0.50
tcpdump -eni eth0 vlan
4. Macvlan 的模式和真实隔离能力
常见 Macvlan 模式包括:
bridge:同一父接口上的 Macvlan 端点可以直接互通,并可与外部网络通信;private:限制同一父接口上的端点互通,但仍可按配置访问外部网络;vepa:依赖外部交换机回流处理同一端口上的流量;passthru:通常用于单个端点的特殊场景,约束更多。
Docker 最常见的是 bridge 模式。它提供的是二层接入方式,不是自动的安全分区。若多个容器处于同一个 Macvlan 子网:
容器 A 192.168.50.10
容器 B 192.168.50.11
它们通常位于同一个广播域,可以直接进行二层或三层通信。要实现安全隔离,需要使用不同 VLAN、不同子网、外部防火墙或其他网络策略。
5. Macvlan 的宿主机不可达陷阱
Macvlan 最容易误解的行为是:
Macvlan 容器通常不能直接访问创建它的宿主机父接口地址,
宿主机也通常不能直接通过父接口访问 Macvlan 容器。
例如:
宿主机 eth0:192.168.50.20
容器:192.168.50.200
从局域网其他机器访问 192.168.50.200 可能成功,但在宿主机上执行:
ping 192.168.50.200
却可能失败。
原因不是 Docker 防火墙拒绝,而是 Linux Macvlan 的端点隔离语义:父接口本身不能像普通交换机端口一样直接与其 Macvlan 子端点互通。
常见解决方式是在宿主机创建一个同一 Macvlan 网络中的宿主端点:
sudo ip link add macvlan-host link eth0 type macvlan mode bridge
sudo ip addr add 192.168.50.201/32 dev macvlan-host
sudo ip link set macvlan-host up
sudo ip route add 192.168.50.192/27 dev macvlan-host
之后宿主机访问容器:
ping 192.168.50.200
数据路径变为:
宿主机进程
→ macvlan-host
→ eth0 上的 macvlan 逻辑转发
→ 容器
这里使用独立的 192.168.50.201,不能把宿主机原有 eth0 地址复制到新接口上,否则会产生地址重复和路由歧义。
该配置通常不会自动持久化。重启宿主机、网络服务重载或父接口变化后,接口和路由可能消失。生产系统应通过 NetworkManager、systemd-networkd 或发行版对应的网络配置持久化,并在恢复脚本中验证接口、地址和路由。
6. Macvlan 的外部网络约束
每个 Macvlan 容器通常拥有不同的 MAC 地址。这带来几个边界:
- 交换机可能启用了端口安全,只允许一个或有限数量的 MAC;
- 云厂商可能禁止虚拟机发送未登记的源 MAC;
- 虚拟化平台可能要求开启混杂模式、Promiscuous Mode 或 Forged Transmit;
- 无线网卡通常不适合作为承载多个独立 MAC 的 Macvlan 父接口;
- 大量容器会增加交换机 MAC 表、邻居表和广播处理压力。
因此,Macvlan 在裸机或受控物理网络中比较自然;在公有云虚拟机中,是否可用取决于云平台的二层虚拟化策略,而不是 Docker 命令本身。
如果目标只是让容器拥有独立的 IP,同时减少额外 MAC,通常应评估 Ipvlan。Ipvlan 可以让多个端点共享父接口的 MAC,在某些网络环境中更容易通过端口安全限制。但 Ipvlan 仍然受上游路由、VLAN、地址规划和宿主机可达性等约束,不能把它当作 Macvlan 的无条件替代。
五、Macvlan 与 IPv6 一起使用时的边界
Macvlan 和 IPv6 可以组合使用:
docker network create -d macvlan \
--ipv6 \
--subnet=2001:db8:50::/64 \
--gateway=2001:db8:50::1 \
-o parent=eth0 \
lan6_macvlan
容器可以获得:
2001:db8:50::10/64
但这会把问题直接推入真实二层网络:
- 容器需要正常发送 NDP;
- 网关需要接受并响应容器的 NDP;
- 交换机和虚拟化平台需要允许对应的 MAC 和 ICMPv6;
- 外部路由器必须知道该地址或前缀的归属;
- 防火墙不能错误阻断 ICMPv6 的必要消息。
IPv6 中的 ICMPv6 不是普通“可选 ping 协议”。NDP、路径 MTU 发现等机制依赖 ICMPv6。粗暴使用:
ip6tables -A FORWARD -p ipv6-icmp -j DROP
可能导致地址看似配置完成,但邻居发现或大报文传输异常。
诊断 Macvlan IPv6 时,应从容器逐层测试:
docker exec <container> ip -6 addr
docker exec <container> ip -6 route
docker exec <container> ip -6 neigh
docker exec <container> ping -6 -c 3 <gateway>
docker exec <container> ping -6 -c 3 <external-ipv6>
判断顺序是:
- 没有地址:Docker 网络或 IPAM 配置问题;
- 没有默认路由:网关配置问题;
- 网关邻居为
FAILED:NDP、VLAN、链路或防火墙问题; - 网关可达但外部不可达:转发或上游路由问题;
- 外部 IP 可达但域名失败:DNS 或应用配置问题;
- TCP 失败但 ICMPv6 成功:监听、端口或防火墙问题。
六、Host 网络:共享宿主机网络命名空间
1. Host 模式的定义
Host 网络模式不创建独立的容器网络命名空间,而是让容器进程使用宿主机的网络命名空间。
docker run -d \
--name host-nginx \
--network host \
nginx:alpine
此时容器内执行:
ip addr
ip route
ss -lnt
看到的基本上是宿主机的网络状态。容器不会获得一个独立的 eth0,也不存在 Docker bridge 到容器的 veth 路径。
数据流可以简化为:
flowchart LR
P[外部客户端]
H[宿主机网络接口<br/>eth0 / lo / IPv4 / IPv6]
S[共享网络命名空间中的 socket]
C[Host 模式容器进程]
P --> H --> S --> C
这意味着 Host 模式改变的不是“容器使用哪段地址”,而是直接移除了网络命名空间隔离。
2. -p 在 Host 模式下没有作用
下面的命令虽然语法可能被接受:
docker run -d \
--network host \
-p 8080:80 \
nginx:alpine
但 -p 8080:80 不会建立普通 bridge 网络中的端口映射。容器内 Nginx 监听哪个端口,宿主机就直接暴露哪个端口。
如果 Nginx 监听:
0.0.0.0:80
[::]:80
外部访问宿主机的 80 端口即可到达容器进程。如果应用监听:
127.0.0.1:8080
则它只绑定宿主机网络命名空间中的回环地址,不会自动出现在宿主机物理接口的 8080 上。
检查方式:
docker inspect host-nginx \
--format '{{.HostConfig.NetworkMode}}'
docker exec host-nginx ss -lnt
ss -lnt
两处看到的监听 socket 会属于同一网络命名空间。端口冲突也发生在同一端口空间中:
docker run --rm --network host nginx:alpine
docker run --rm --network host nginx:alpine
如果两个进程都尝试监听宿主机的 80,后启动者通常会收到:
bind: address already in use
这不是 Docker bridge 端口发布冲突,而是普通 Linux socket bind 冲突。
3. Host 模式中的 IPv6
Host 模式下,容器使用宿主机已经拥有的 IPv6 地址和路由:
宿主机:
eth0 = 2001:db8:1::20/64
default via 2001:db8:1::1
Host 容器:
不获得独立 IPv6
直接使用上述网络栈
因此,Host 容器访问外部 IPv6 时,不需要 Docker 为它分配新的 IPv6 子网。它的连通性等同于宿主机进程的连通性。
但这也意味着:
- 无法为容器单独分配一个 Docker 管理的 IPv6 地址;
- 无法依赖 Docker bridge 的容器级地址边界;
- 容器中的进程可能监听宿主机的所有接口;
- 容器访问
localhost时,看到的是宿主机网络命名空间上的服务; - 宿主机其他服务和 Host 容器共享端口资源。
一个重要的安全后果是:
Host 模式不是“绕过 Docker 端口映射”,而是“直接使用宿主机网络栈”。
因此,应用本身的监听地址、宿主机防火墙和进程权限变得更加重要。
4. Host 模式与 Compose
Compose 中可以这样声明:
services:
metrics-agent:
image: example/metrics-agent:latest
network_mode: host
此时不要同时写:
ports:
- "9100:9100"
因为 Host 模式不存在需要发布的容器私有端口。也不能把 network_mode: host 与普通 networks: 配置混用来期待容器同时连接 bridge、Macvlan 等网络。network_mode 选择的是容器使用的网络命名空间,和加入一个或多个 Docker 网络是两种不同机制。
此外,Compose 中的服务名 DNS 只对加入 Docker 用户定义网络的服务成立。Host 模式服务不应被假设拥有普通 Compose 网络中的容器别名解析行为。
5. Linux 边界与平台差异
本文讨论的是 Linux 容器边界。Docker Desktop 在 macOS 和 Windows 上通常运行一个 Linux VM,容器实际位于该 VM 的 Linux 网络环境中。宿主操作系统看到的网络路径还会经过 Desktop 的虚拟化和转发层,因此不能把 Linux 裸机上的:
--network host
直接等同于“共享 macOS 或 Windows 主机的物理网络命名空间”。
Host 网络支持还可能受 Docker Engine、rootless 模式和平台实现影响。部署前应使用目标环境实际验证:
docker version
docker info
docker run --rm --network host alpine ip addr
尤其在 rootless Docker 中,网络由用户态组件和权限边界参与实现,Host 模式的语义和性能不能假设与 rootful Engine 完全一致。
七、三种模式的隔离边界对比
| 维度 | IPv6 bridge | Macvlan | Host |
|---|---|---|---|
| 是否有独立网络命名空间 | 是 | 通常是 | 否 |
| 是否有独立容器接口 | 通常有 eth0 |
有 Macvlan 接口 | 没有独立接口 |
| 地址来源 | Docker IPAM 分配 IPv6 前缀 | 外部 LAN 或 Docker 配置的地址池 | 宿主机地址 |
| 是否经过 Docker bridge | 是 | 否,经过 Macvlan/父接口 | 否 |
| 是否通常需要端口发布 | 对外访问通常需要 | 通常不需要,可直接访问容器地址 | 不需要,直接使用宿主机端口 |
| 容器间隔离 | 网络命名空间和 bridge 规则 | 网络命名空间,但同一二层网络可互通 | 无网络命名空间隔离 |
| 宿主机直接访问容器 | 通常可通过 bridge 地址访问 | 父接口默认不可直接访问,需额外 Macvlan 端点 | 通过共享网络栈直接访问 |
| IPv6 回程路由要求 | 使用全局前缀时需要 | 使用真实 LAN 地址时需要 | 使用宿主机已有路由 |
| 主要故障域 | 路由、NDP、Docker 防火墙、上游路由 | VLAN、MAC 限制、NDP、地址冲突 | 端口冲突、宿主机暴露、应用绑定 |
表中的“隔离”需要谨慎理解:
- bridge 的网络命名空间隔离不等于应用安全隔离;
- Macvlan 的独立 MAC 不等于安全隔离;
- Host 模式基本没有网络命名空间隔离,但仍可能有容器进程、文件系统和权限方面的其他隔离;
- IPv6 地址是全局地址还是 ULA,不决定容器是否拥有独立网络命名空间。
八、数据流算例:从容器访问外部 IPv6
假设使用 IPv6 bridge:
容器 eth0:
2001:db8:100::10/64
Docker bridge:
2001:db8:100::1/64
容器默认路由:
default via 2001:db8:100::1
容器访问:
2001:db8:200::20:443
完整过程如下:
第一步:容器路由查找
目标 2001:db8:200::20 不属于本地 2001:db8:100::/64,选择默认路由:
下一跳:2001:db8:100::1
出口:eth0
第二步:NDP 解析网关
容器发出 ICMPv6 Neighbor Solicitation,获得网关的链路层地址。若邻居表进入 FAILED,数据包不会有效离开容器。
第三步:进入宿主机 bridge
容器的 veth 对端收到帧,并进入 Docker bridge。宿主机内核根据转发规则判断是否允许从 bridge 转向外部接口。
第四步:宿主机 IPv6 路由
宿主机查找 2001:db8:200::20 的路由。可能经由 eth0 和上游网关转发。
第五步:外部返回
外部设备必须知道:
2001:db8:100::/64 位于该宿主机后方
否则响应无法返回容器。
因此,连接失败的位置可以表示为:
容器路由失败
→ NDP 失败
→ Docker/宿主机转发失败
→ 宿主机上游路由失败
→ 外部回程路由失败
→ 应用端口或防火墙失败
使用 tcpdump 可以定位方向:
sudo tcpdump -ni any 'icmp6 or tcp port 443'
解释抓包位置时要注意:-i any 适合快速观察,但在复杂环境中会重复显示或隐藏接口细节。需要确认具体路径时,应分别抓:
sudo tcpdump -ni docker0 icmp6
sudo tcpdump -ni eth0 icmp6
九、常见误解与反例
误解一:给容器启用 IPv6 就自动获得公网访问
错误。启用 IPv6 只解决 Docker 内部地址配置的一部分。公网可达还需要:
地址唯一
+ 容器默认路由
+ 宿主机 IPv6 转发
+ 上游回程路由
+ ICMPv6/NDP 正常
+ 防火墙允许
+ 应用监听 IPv6
缺少任一项,都可能失败。
误解二:Macvlan 容器一定能访问宿主机
错误。Macvlan 的父接口与子端点之间存在特殊隔离。宿主机访问容器需要额外创建 Macvlan host 端点,或者让流量经由外部网络绕行。
误解三:Macvlan 比 bridge 更安全
错误。Macvlan 主要改变二层接入和地址呈现方式。处于同一 Macvlan 网络的容器通常共享同一广播域,安全策略仍然需要由 VLAN、路由器、防火墙或应用自身提供。
误解四:Host 网络性能好,所以应该默认使用
错误。Host 模式减少了网络虚拟化层,但同时放弃了容器级网络命名空间隔离和独立端口空间。它适合确实需要观察宿主机接口、绑定大量端口或使用某些低层网络能力的程序,不适合作为普遍默认值。
误解五:端口发布和容器地址是同一件事
例如:
ports:
- "8080:80"
表达的是:
宿主机 8080 → 容器 80
而 Macvlan 中直接访问:
192.168.50.200:80
不需要 Docker 端口发布。Host 模式中访问:
宿主机地址:80
实际连接的是共享网络命名空间中的监听 socket。三者的数据路径不同,排障方式也不同。
十、按故障层次进行诊断
1. 先确认模式和地址
docker inspect <container> \
--format 'mode={{.HostConfig.NetworkMode}} networks={{json .NetworkSettings.Networks}}'
然后检查:
docker exec <container> ip addr
docker exec <container> ip -6 addr
docker exec <container> ip route
docker exec <container> ip -6 route
需要确认:
- 容器到底使用 bridge、Macvlan 还是 Host;
- IPv6 地址是否存在;
- 默认路由是否存在;
- 地址是否位于预期前缀;
- Host 模式下是否误以为应该存在独立
eth0。
2. 再测试链路和网关
IPv4:
docker exec <container> ping -c 3 <ipv4-gateway>
IPv6:
docker exec <container> ping -6 -c 3 <ipv6-gateway>
docker exec <container> ip -6 neigh
如果网关不可达,先不要测试 DNS 和应用端口。网关不可达说明问题位于容器接口、VLAN、bridge、Macvlan、ARP/NDP 或防火墙层。
3. 区分 DNS 与路由问题
docker exec <container> getent hosts example.com
docker exec <container> getent ahosts example.com
如果域名解析出 IPv6 地址但连接失败,可以直接测试字面量地址:
docker exec <container> wget -6 -O- http://[2001:db8:200::20]/
IPv6 字面量放在 URL 中时必须使用方括号,否则冒号会与端口分隔符混淆。
4. 检查端口监听与发布
Bridge 或 Macvlan:
docker exec <container> ss -lntp
docker port <container>
Host:
docker exec <container> ss -lntp
ss -lntp
如果 Host 模式下端口冲突,直接查看宿主机监听进程;如果 bridge 模式下端口发布异常,还要检查 Docker 防火墙规则和宿主机转发策略。
5. 观察实际数据包
Bridge:
sudo tcpdump -ni docker0 'ip6 or arp'
sudo tcpdump -ni any 'icmp6 or tcp'
Macvlan:
sudo tcpdump -eni eth0 'arp or icmp6'
重点观察:
- 是否发出 NS/NA;
- 是否看到网关响应;
- 请求是否从父接口离开;
- 是否收到返回包;
- 返回包是否到达容器接口。
如果请求能在 eth0 上看到、响应也到达 eth0,但容器仍收不到,问题可能位于宿主机过滤、Macvlan 模式或邻居状态,而不是上游路由。
十一、如何选择网络模式
可以用网络边界来选择,而不是按“功能多少”选择。
选择 IPv6 bridge
适合:
- 容器之间需要 Docker 网络隔离;
- 需要 Compose 服务发现;
- 希望由 Docker 管理地址和网络生命周期;
- 可以接受端口发布或已规划 IPv6 路由;
- 不要求容器直接作为物理 LAN 节点出现。
它的关键工作是规划:
Docker IPv6 前缀
宿主机转发
上游回程路由
防火墙
NDP
选择 Macvlan
适合:
- 容器必须直接出现在现有二层 LAN;
- 网络设备需要看到容器独立地址;
- 不希望通过宿主机端口映射访问服务;
- 可以控制 VLAN、交换机和虚拟化平台;
- 能接受宿主机访问限制和额外 MAC 的运维成本。
不适合:
- 父接口是普通 Wi-Fi;
- 云平台禁止额外 MAC;
- 需要大量容器但交换机端口安全严格;
- 把“独立 MAC”误当成安全隔离。
选择 Host
适合:
- 程序需要宿主机所有接口和路由;
- 端口范围动态或数量较多,难以逐个发布;
- 应用确实需要低层网络观察能力;
- 可以接受宿主机端口空间和网络隔离的消失。
不适合:
- 需要多个实例监听同一端口;
- 需要容器级网络策略;
- 需要每个容器独立 IPv6 地址;
- 希望通过 Docker 网络提供清晰的服务边界。
十二、最终边界
三种模式的本质可以压缩为三句话:
- IPv6 bridge:容器仍然处在独立网络命名空间中,Docker 管理地址和虚拟链路,但真实 IPv6 可达性最终取决于路由、NDP、转发和防火墙。
- Macvlan:容器被接入宿主机的二层网络,拥有独立 MAC 和通常独立的 LAN 地址,但会受到交换机、VLAN、云平台和宿主机不可直达语义的约束。
- Host:容器进程直接使用宿主机网络命名空间,没有独立容器网络地址和端口空间,换来的简单路径同时意味着更弱的网络隔离。
因此,判断一个 Docker 网络方案是否正确,不能只检查:
容器是否拿到了 IP
还必须检查:
地址属于哪个网络
默认路由指向哪里
邻居解析是否成功
数据包经过哪些接口
返回路径是否存在
端口由谁监听
防火墙在哪一层生效
宿主机与容器之间是否存在预期的可达性
只有把这些边界逐层验证,IPv6、Macvlan 和 Host 网络才不会从配置选项变成难以解释的故障来源。
系列导航与关联阅读
- 系列入口:Docker 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker 容器 DNS:嵌入式解析、Service Name、Search Domain 和故障
- 下一篇:Docker GPU 容器:NVIDIA Runtime、设备、驱动、资源和可观测
- 延伸:Docker 网络完整指南:Bridge、端口映射、DNS、Overlay 和排障
- 延伸:Docker Bridge 与 iptables/nftables:NAT、端口发布、转发和冲突
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论