首页
/ Moby macvlan 网络驱动详解:容器直连物理网络、802.1Q VLAN Trunk 与双栈实战

Moby macvlan 网络驱动详解:容器直连物理网络、802.1Q VLAN Trunk 与双栈实战

2026-09-04 22:49:53作者:郜逊炳

本文基于 Moby 仓库中 macvlan 驱动官方文档,系统讲解 macvlan 内建网络驱动如何绕过 Linux 网桥与 NAT,把容器接口直接挂接到宿主机物理网卡(或其 VLAN 子接口)上,实现与数据中心现有 L2 网络无缝集成。读完后你将掌握:docker network create -d macvlan 的完整参数体系(--subnet--gateway-o parent-o macvlan_mode--ip-range--aux-address)、bridge 模式、802.1Q Trunking、多子网与 IPv4/IPv6 双栈的实操方法,并能对照 驱动源码 理解子接口自动创建/回收、网关注入与内核级过滤行为的底层机制。

Macvlan bridge mode topology: containers attach directly to the parent interface eth0 in the same broadcast domain

一、macvlan 驱动概述:无 NAT、无网桥的直连架构

macvlan 驱动让运维人员能够以简单、轻量级的方式把容器网络融入底层物理网络。macvlan 是 Linux 内核原生支持的成熟网络类型,Moby 的内建 macvlan 驱动不需要任何端口映射,并且支持 VLAN trunking(虚拟局域网)。VLAN 是传统的网络虚拟化与二层数据路径隔离手段,在各类数据中心中普遍存在。

与 bridge 驱动相比,macvlan 的核心差异在于数据路径:

  • 不经过宿主机网桥。传统 bridge 方案要把容器接口接到 Docker 宿主机的 Linux 网桥上,再通过两种方式打通外部网络:一是用 iptables 规则对网桥做 NAT 转换(好处是网卡无需混杂模式);二是把宿主机外部以太网口直接移入网桥——这种方式风险较高,配置失误可能把宿主机自己"踢"出网络,甚至造成桥接环路(bridging loop),在整个数据中心的 VLAN 范围内瘫痪流量。
  • 容器接口直接挂到宿主机以太网接口(或其子接口)上。每个网络(network)绑定一个唯一的父接口(parent interface)。同一网络内的容器共享同一个广播域,网络内部互通是允许的;两个不同网络各自绑定不同的父接口,父接口就构成了两个网络之间的数据路径隔离。跨网络通信需要 Docker 宿主机之外的 IP 路由器在两个网络之间做路由,流量"hairpin"(绕回)到物理网络后再回到宿主机。hairpin 流量效率虽不如留在宿主机本地的东西向流量,但许多用户正是希望复用数据中心中现成的网络服务(防火墙、负载均衡器等),这种取舍在实践上是合理的。
  • 性能与可观测性收益。绕过 Linux 网桥带来正向的性能影响,同时减少活动部件、简化架构。macvlan 容器很容易排障:容器的真实 MAC 地址和 IP 地址直接"桥接"进了上游网络,出问题的应用可以被网络侧工具直接从链路上追踪;现有的底层网络管理和监控工具保持可用。

该驱动要求对底层宿主机有完全访问权限,因此适合拥有宿主机管理员权限的企业级数据中心场景。

前置条件

  • 文档中所有示例均为单主机场景,要求 Linux 上的 Docker v1.12 或更高版本(当前 Moby 仓库实现远超过此要求)。
  • 任何使用 eth0.10 这类子接口(sub-interface)的示例,都可以替换为 eth0 或宿主机上任意有效的父接口。带 . 的子接口是动态创建的;父接口参数 -o parent 也可以完全省略,此时驱动会创建一个 dummy 类型的 Linux 接口,仅支持宿主机本地(容器之间)的连通性,用于完成示例练习。
  • 内核要求:可用 uname -r 查看当前内核版本,macvlan 要求 Linux 内核 v3.9–3.19 及 4.0+
  • 源码层面,macvlan 驱动仅编译于 Linux 平台,入口文件 macvlan.go 首行即为 //go:build linux,驱动在 Register 函数 中注册,声明 DataScope: scope.LocalConnectivityScope: scope.Global——即网络数据是节点本地的,但连通性面向全局。

二、Bridge 模式基础用法

macvlan 驱动网络必须挂接到一个父接口上,父接口可以是:

  • 物理接口,如 eth0
  • 802.1q VLAN 打标签的子接口,如 eth0.10.10 即 VLAN 10);
  • 甚至绑定接口 bond0——把两个以太网口捆绑成单一逻辑接口,提供链路冗余。

关键行为约定:

  • 网关是宿主机外部的,预期由网络基础设施提供。若未用 --gateway 指定,Libnetwork 会推断子网的第一个可用地址作为网关:--subnet 10.1.100.0/24 对应网关 10.1.100.1--subnet 10.1.100.128/25 对应网关 10.1.100.129。这个推断行为在源码中得到印证:GetSkipGwAlloc 返回 true, true,注释明确说明"网关必须位于 Docker macvlan 网络之外,驱动不把它分配给任何东西"。
  • 不同网络上的容器之间没有外部路由介入时无法互相到达。
  • 每个 macvlan bridge 模式网络彼此隔离,一个父接口同一时刻只允许挂一个 macvlan 网络(passthru 模式有更严格的独占检查,见下文源码分析)。每个宿主机网卡理论上最多可挂 4,094 个子接口(VLAN ID 1–4094)。
  • 驱动限制"每个父接口一个网络",但允许在同一个 Docker 网络里分配多个子网(multi-subnet 需求),由上游路由器在两个子网之间做 proxy-arping。
  • 同一子网内的容器可以直接通过 IP 通信,无需网关。注意:只要有容器挂到父接口上,父接口就会进入混杂模式(promiscuous mode),因为每个容器拥有独立的 MAC 地址。作为对照,目前仍处于实验阶段的 ipvlan 驱动复用父接口的 MAC 地址,因此不需要父接口开启混杂模式。

完整示例:父接口 eth0,网段 172.16.86.0/24

在下面的示例中,Docker 宿主机 eth0 位于 172.16.86.0/24 网络,默认网关 172.16.86.1(一个外部路由器)。bridge 模式下 eth0 上并不要求有 IP 地址,它只需处于正确的上游网络,能被交换机或路由器转发即可。

注意:Docker 网络指定的子网必须与宿主机父接口所在网络匹配(对外通信时)。请使用与 -o parent= 指定的宿主机以太网接口相同的子网和网关。父接口本身不要求分配 IP 地址,因为这只是 L2 泛洪(flooding)与学习(learning)。

父接口 eth0 配置如下:

ip addr show eth0
3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 172.16.86.250/24 brd 172.16.86.255 scope global eth0

创建 macvlan 网络并运行两个容器:

# Macvlan  (-o macvlan_mode= 未指定时默认为 bridge 模式)
docker network create -d macvlan \
    --subnet=172.16.86.0/24 \
    --gateway=172.16.86.1  \
    -o parent=eth0 pub_net

# 在新网络上运行容器并显式指定 --ip 地址
docker run --net=pub_net --ip=172.16.86.10 -itd alpine /bin/sh

# 启动第二个容器并 ping 第一个容器
docker run --net=pub_net -it --rm alpine /bin/sh
ping -c 4 172.16.86.10

查看容器内的 IP 与路由表:

ip a show eth0
    eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 46:b2:6b:26:2f:69 brd ff:ff:ff:ff:ff:ff
    inet 172.16.86.2/24 scope global eth0

ip route
    default via 172.16.86.1 dev eth0
    172.16.86.0/24 dev eth0  src 172.16.86.2

# 注意:容器无法 ping 底层宿主机的接口地址,
# 因为 Linux 内核有意过滤这类流量以实现额外隔离。
# 本例中容器无法 ping -o parent 的 172.16.86.250

用户可以用 -o macvlan_mode=bridge 显式指定 bridge 模式,也可以省略该选项——bridge 是驱动默认值,也是最常见的模式。

IPAM 相关参数:--aux-address 与 --ip-range

虽然 eth0 接口不必须有 IP 地址,但实际环境中给父接口分配 IP 很常见。可以用 --aux-address=x.x.x.x 把指定地址从内建 IPAM 的分配中"拉黑",防止它被分给容器:

# 与上面相同的网络示例,但把 -o parent=eth0 的地址从可分配池中排除
docker network create -d macvlan \
    --subnet=172.16.86.0/24 \
    --gateway=172.16.86.1  \
    --aux-address="exclude_host=172.16.86.250" \
    -o parent=eth0 pub_net

另一种控制可用地址子池/区间的方式是 --ip-range=:指示驱动从该特定区间分配容器地址,而不是从 --subnet= 指定的更大范围分配。下面示例中地址从 192.168.32.128 开始按 n+1 递增分配:

docker network create -d macvlan  \
    --subnet=192.168.32.0/24  \
    --ip-range=192.168.32.128/25 \
    --gateway=192.168.32.254  \
    -o parent=eth0 macnet32

# 启动容器并验证地址为 192.168.32.128
docker run --net=macnet32 -it --rm alpine /bin/sh

网络删除:

docker network rm <network_name or id>

常见坑(gotcha):Linux macvlan 接口类型无法 ping 或访问默认命名空间中的 IP 地址。例如创建容器后去 ping Docker 宿主机的 eth0,是不会成功的——该流量被内核显式过滤,用于提供额外的提供方隔离与安全。这是用户初次接触这类 Linux 接口时最常见的困惑,因为测试时自然会先 ping 本地地址。

源码视角:驱动如何解析选项与处理父接口

结合 macvlan_network.go 的实现,文档中的行为都有明确的代码依据:

  • 选项解析:驱动只识别两个通用选项,常量定义在 macvlan.go#L14-L25parent-o parent)与 macvlan_modeparseNetworkOptions 中,macvlan_mode 为空时默认置为 bridge;合法取值为 bridgeprivatepassthruvepa 四种,否则报错。此外 lo(回环接口)被显式拒绝为父接口;若 -o parent 为空,网络会被标记为 internal(无外部连通性)。
  • 父接口不存在时的自动创建createNetwork 中,若父接口不存在:父接口是 dm- 前缀的 dummy 名时,调用 createDummyLink 创建 dummy 接口;否则调用 createVlanLinkiface.vlan_id 格式解析并创建 802.1Q 子接口——VLAN ID 校验为 1–4094(12 位字段),创建后自动 LinkSetUp 置起接口。驱动创建的从属链路会记录 CreatedSlaveLink = true,为后续回收做准备。
  • 删除网络时的链路回收DeleteNetwork 只在"驱动自己创建的从属接口 + 该网络是唯一使用者"(parentHasSingleUser 检查)时才删除 VLAN 子接口或 dummy 接口,否则保留;对 macvlan 和 ipvlan 同挂一个父接口的情形,注释说明了"同一父接口上只能有一个从属接口处于激活状态"的限制。
  • passthru 模式独占性createNetwork 中,若已有网络占用同一父接口,且新旧网络任一方是 passthru 模式,直接报错拒绝——这就是"每个父接口一个网络"限制在 passthru 下的严格执行。

三、容器接入流程:Join 阶段的 macvlan 接口创建与网关注入

文档中"容器获得真实 MAC/IP 直通上游网络"的现象,发生在端点 Join 阶段。Join 方法(macvlan_joinleave.go)的完整流程是:

  1. veth 前缀生成一个宿主机侧临时接口名(如 vethxxxxxxxx);
  2. 调用 createMacVlan,通过 netlink 的 LinkAdd 在父接口上创建 macvlan 从属接口,模式由 setMacVlanMode 映射到 netlink.MACVLAN_MODE_BRIDGE/PRIVATE/VEPA/PASSTHRU
  3. 将端点的 IPv4/IPv6 地址与网络子网做匹配(getSubnetForIP,要求掩码长度一致且 IP 被包含):子网配置了 GwIP 就调用 jinfo.SetGateway / SetGatewayIPv6 写入默认路由——这就是容器内 ip routedefault via <gateway> dev eth0 的来源;未配置网关时走 ForceGw4/ForceGw6 保留旧行为;
  4. 调用 jinfo.DisableGatewayService() 关闭网桥类驱动才需要的网关服务;
  5. 把宿主机侧的 macvlan 接口重命名映射为容器内的 eth0(前缀 eth),与文档示例中 eth0@if3 的命名一致。

MAC 地址方面,CreateEndpoint(macvlan_endpoint.go)在接口信息未带 MAC 时调用 netutils.GenerateRandomMAC() 生成随机 MAC——这正是"每个容器拥有唯一 MAC 地址、父接口进入混杂模式"的实现根源。同一文件还显式拒绝端口映射(-p)与端口暴露(--expose):检测到相关选项时仅记录警告"macvlan driver does not support port mappings"——与文档概述"内建 macvlan 驱动不需要任何端口映射"相呼应,说明容器流量本身就是以真实地址出现在物理网络上,无需转换。

四、802.1Q Trunk Bridge 模式:一个网卡跑多个虚拟网络

Multi-tenant 802.1Q VLANs: each Docker macvlan network maps to a VLAN-tagged sub-interface on the host

VLAN 长期以来一直是虚拟化数据中心网络的主要手段,在几乎所有现有网络中仍在使用。VLAN 通过在二层隔离域上打一个 **12 位标识符(1–4094)**的标签工作。VLAN 标签插入报文头部,实现单个子网或多个 IPv4/IPv6 子网的逻辑分组。网络运维常见做法是按子网功能或安全画像(如 webdb 等隔离需求)用 VLAN 分隔流量。

计算宿主机上同时运行多个虚拟网络是常见需求。Linux 网络长期支持 VLAN 打标签(标准 802.1Q),用于维护网络之间的数据路径隔离。连接 Docker 宿主机的以太网链路可以通过创建 Linux 子接口来支持 802.1q VLAN ID,每个子接口分配一个唯一的 VLAN ID。

把 802.1q Trunk 接到 Linux 宿主机对运维来说 notoriously 痛苦:需要修改配置文件才能重启后持久化;若涉及网桥,物理网卡需要移入网桥、网桥再获取 IP 地址。由于切断访问或配置失误的风险较高,导致许多服务器"搁浅"(stranded)。

和其他 Docker 网络驱动一样,总目标是减轻网络资源管理的运维之痛。为此:

  • 当网络收到一个尚不存在的子接口作为父接口时,驱动会在创建网络的同时创建 VLAN 打标签的接口;子接口若已存在则直接使用。
  • 宿主机重启时,无需修改往往复杂的网络配置文件,驱动会在 Docker 守护进程重启时重建所有网络链路。驱动会跟踪该 VLAN 子接口最初是否由网络创建时由驱动生成,并且只有在驱动最初创建过该链路时才会在重启后重建。
  • docker network rm 删除网络时同理:若子接口是驱动用 docker network create 创建的,会替运维删除该子接口链路。
  • 若用户不希望 Docker 创建/删除 -o parent 子接口,只需传入一个已存在的接口作为父链路。eth0 这类父接口不会被删除,只有从属(slave)链路才会。
  • 驱动增删 VLAN 子接口要求 -o parent interface_name.vlan_tag 的格式。例如 -o parent eth0.50 表示父接口 eth0、从属接口 eth0.50、VLAN ID 50。等价的 ip link 命令是 ip link add link eth0 name eth0.50 type vlan id 50
  • -d 驱动参数中的 macvlan 替换为 ipvlan,即可创建 ipvlan 的 802.1q trunk。

该行为与源码完全对应:createVlanLink 中按 strings.Contains(parentName, ".") 判定子接口,parseVlan 强制"名字.vlan_id"两段格式、校验 VLAN ID 范围并确认主接口存在;而 delVlanLink不能解析成 iface.vlan_id 的命名(即用户自定义名称)一律保留不删——即文档所说"手工创建的链路无论如何命名都不会被删除"。

示例一:VLAN ID 50

网络在 Docker 宿主机上打标签并隔离,eth0.50 作为父接口把以太网流量打上 vlan id 50 标签(由 -o parent=eth0.50 的父接口命名约定指定)。其他命名格式也可以使用,但那样的链路需要手工用 ip link 或 Linux 配置文件增删。只要 -o parent 存在且符合 Linux netlink 规范,用什么名字都行。

# 像往常一样添加网络和主机,挂到已打标签的 master (sub)interface 上
docker network create -d macvlan \
    --subnet=192.168.50.0/24 \
    --gateway=192.168.50.1 \
    -o parent=eth0.50 macvlan50

# 在两个终端中各启动一个容器,容器之间现在可以互 ping
docker run --net=macvlan50 -it --name macvlan_test5 --rm alpine /bin/sh
docker run --net=macvlan50 -it --name macvlan_test6 --rm alpine /bin/sh

示例二:VLAN ID 60,显式指定 macvlan_mode

第二个网络同样在宿主机上打标签隔离,eth0.60 是打 vlan id 60 标签的父接口(-o parent=eth0.60)。macvlan_mode= 默认是 macvlan_mode=bridge,显式设置结果相同,如下例所示:

# 像往常一样添加网络和主机,挂到已打标签的 master (sub)interface 上
docker network create -d macvlan \
    --subnet=192.168.60.0/24 \
    --gateway=192.168.60.1 \
    -o parent=eth0.60 \
    -o macvlan_mode=bridge macvlan60

# 在两个终端中各启动一个容器,容器之间现在可以互 ping
docker run --net=macvlan60 -it --name macvlan_test7 --rm alpine /bin/sh
docker run --net=macvlan60 -it --name macvlan_test8 --rm alpine /bin/sh

示例三:多子网 macvlan 802.1Q Trunking

与前一示例相同,但网络绑定了额外的子网,用户可以选择在上面供给容器。macvlan/bridge 模式下,容器只有在同一子网/广播域内才能互 ping,除非有外部路由器在两个子网之间做路由(回应 ARP 等)。为网络分配多个子网需要一个宿主机外部、落在子网范围内的网关,把流量 hairpin 回宿主机。

docker network create -d macvlan \
    --subnet=10.1.20.0/24 --subnet=10.1.10.0/24 \
    --gateway=10.1.20.1 --gateway=10.1.10.1 \
    -o parent=eth0.101 mcv101

# 网络创建后查看链路 ip link
$ ip link

# 测试 10.1.20.0/24 的连通性
docker run --net=mcv101 --ip=10.1.20.9 -itd alpine /bin/sh
docker run --net=mcv101 --ip=10.1.20.10 -it --rm alpine ping -c 4 10.1.20.9

# 测试 10.1.10.0/24 的连通性
docker run --net=mcv101 --ip=10.1.10.10 -itd alpine /bin/sh
docker run --net=mcv101 --ip=10.1.10.9 -it --rm alpine ping -c 4 10.1.10.10

# 删除所有容器
docker rm -f `docker ps -qa`

# 删除所有网络
docker network rm $(docker network ls -q)

# 再次运行 ip link,验证链路已清理干净
ip link

同一 VLAN 上的主机通常位于同一子网,且几乎总是按安全策略分组。多数场景下,多层应用的不同层位于不同子网,因为每个进程的安全画像要求某种形式的隔离。例如,把信用卡处理与前端的 web 服务器放在同一个虚拟网络上会成为合规问题,也违背纵深防御的分层架构最佳实践。VLAN(或使用内建 Overlay 驱动时等价的 VNI,Virtual Network Identifier)是隔离租户流量的第一步。

VLANs in depth: tenant traffic isolation via VLAN/VNI

NetOps 交付值 → docker network create 的映射范式

关键收获:运维人员可以把物理网络映射到虚拟网络,把容器集成进现有环境而无需运营层面的大改造。NetOps 只需把一根 802.1q trunk 接入 Docker 宿主机,这条虚拟链路就是网络创建时传入的 -o parent=。对于无标签(非 VLAN)链路,简单到 -o parent=eth0;对于带 VLAN ID 的 802.1q trunk,每个网络映射到对应的 VLAN/子网即可。

例如,NetOps 提供接入 Docker 宿主机以太网链路的 VLAN ID 及对应子网。这些值直接填入 docker network create 命令即可。这些是持久化配置,每次 Docker 引擎启动都会重新应用,从而免去管理往往复杂的配置文件。网络接口也可以预先手工创建来管理——Docker 网络永远不修改它们,只是把它们当父接口使用。从 NetOps 到 Docker 网络命令的映射示例如下:

  • VLAN: 10,子网: 172.16.80.0/24,网关: 172.16.80.1
    • --subnet=172.16.80.0/24 --gateway=172.16.80.1 -o parent=eth0.10
  • VLAN: 20,子网: 172.16.50.0/22,网关: 172.16.50.1
    • --subnet=172.16.50.0/22 --gateway=172.16.50.1 -o parent=eth0.20
  • VLAN: 30,子网: 10.1.100.0/16,网关: 10.1.100.1
    • --subnet=10.1.100.0/16 --gateway=10.1.100.1 -o parent=eth0.30

五、手工创建 802.1Q 链路

如果用户不希望驱动创建 VLAN 子接口,只需在 docker network create 之前让该接口存在即可。若子接口命名不是 interface.vlan_id 形式,-o parent= 同样会尊重它——前提是接口存在且已 up。

手工创建的链路可以随便命名,只要网络创建时它们存在就足够了。网络用 docker network rm 删除时,手工创建的链路无论叫什么名字都不会被删除。

# 创建一个绑定 dot1q vlan 40 的新子接口
ip link add link eth0 name eth0.40 type vlan id 40

# 启用新子接口
ip link set eth0.40 up

# 像往常一样添加网络和主机,挂到已打标签的 master (sub)interface 上
docker network create -d macvlan \
   --subnet=192.168.40.0/24 \
   --gateway=192.168.40.1 \
   -o parent=eth0.40 macvlan40

# 在两个终端中各启动一个容器,容器之间现在可以互 ping
docker run --net=macvlan40 -it --name mcv_test5 --rm alpine /bin/sh
docker run --net=macvlan40 -it --name mcv_test6 --rm alpine /bin/sh

示例:以任意名称手工创建 VLAN 子接口

# 创建一个绑定 dot1q vlan 40 的新子接口
ip link add link eth0 name foo type vlan id 40

# 启用新子接口
ip link set foo up

# 像往常一样添加网络和主机,挂到已打标签的 master (sub)interface 上
docker network create -d macvlan \
    --subnet=192.168.40.0/24 --gateway=192.168.40.1 \
    -o parent=foo macvlan40

# 在两个终端中各启动一个容器,容器之间现在可以互 ping
docker run --net=macvlan40 -it --name mcv_test5 --rm alpine /bin/sh
docker run --net=macvlan40 -it --name mcv_test6 --rm alpine /bin/sh

手工创建的链路清理:

ip link del foo

与其他 Libnetwork 驱动一样,不同类型的网络可以混用混搭。这也适用于第三方生态驱动——它们可以与内建驱动并行运行,为用户提供最大灵活性。

六、Dual Stack:IPv4 + IPv6 macvlan bridge 模式

下面创建同时指定 v4 和 v6 地址的网络,每个容器会从两个地址族各分配一个地址。你可以显式指定某个地址族,也可以让 Libnetwork IPAM 从子网池中自动分配。

关于 IPv6 的说明docker network create 声明 v6 子网时,必须带 --ipv6 标志以及子网(下例为 --subnet=2001:db8:abc8::/64)。与 IPv4 功能类似,若未指定 IPv6 --gateway,则推断 v6 子网中第一个可用地址作为该广播域的网关。

以下示例创建一个含多个 IPv4 与 IPv6 子网的网络,网络挂到 eth0.218 子接口上。指定 eth0.218 作为父接口后,驱动会创建该子接口(若尚不存在),并为网络内所有容器的流量打上 VLAN ID 218 的标签。ToR(top of rack)交换机端口必须启用 802.1Q Trunking,宿主机进出通信才能工作。

# 创建双栈多子网网络:
docker network create -d macvlan \
    --subnet=192.168.216.0/24 --subnet=192.168.218.0/24 \
    --gateway=192.168.216.1  --gateway=192.168.218.1 \
    --ipv6 --subnet=2001:db8:abc8::/64 --gateway=2001:db8:abc8::10 \
     -o parent=eth0.218 \
     -o macvlan_mode=bridge macvlan216

# 在第一个子网 192.168.216.0/24 上启动容器
docker run --net=macvlan216 --name=macnet216_test --ip=192.168.216.10 -itd alpine /bin/sh

# 在第二个子网 192.168.218.0/24 上启动容器
docker run --net=macvlan216 --name=macnet218_test --ip=192.168.218.10 -itd alpine /bin/sh

# ping 第一个子网 192.168.216.0/24 上先启动的容器
docker run --net=macvlan216 --ip=192.168.216.11 -it --rm alpine /bin/sh

# 在容器 shell 里 ping 同子网的另一台主机,然后退出
$ ping -c4 192.168.216.10
$ exit

# ping 第二个子网 192.168.218.0/24 上先启动的容器
docker run --net=macvlan216 --ip=192.168.218.11 -it --rm alpine /bin/sh

# 在容器 shell 里 ping 同子网的另一台主机,然后退出
$ ping -c4 192.168.218.10
$ exit

# 显式声明 v6 地址启动后台容器
docker run --net=macvlan216 --ip6=2001:db8:abc8::20 -itd alpine /bin/sh

# 再启动一个容器,ping 前一个容器的 v6 地址
docker run --net=macvlan216 -it --rm alpine /bin/sh
$ ping6 -c4 2001:db8:abc8::20
$ exit
# 或者把 ping 作为显式进程运行
docker run --net=macvlan216 -it --rm alpine ping6 -c4 2001:db8:abc8::20

查看其中一个容器的详情:

docker run --net=macvlan216 --ip=192.168.216.11 -it --rm alpine /bin/sh

root@526f3060d759:/# ip a show eth0
25: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 02:42:c0:a8:d8:0b brd ff:ff:ff:ff:ff:ff
    inet 192.168.216.11/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:abc8::1/64 scope link
       valid_lft forever preferred_lft forever

# 默认网关是宿主机外部的网络网关
$ ip route
default via 192.168.216.1 dev eth0
192.168.216.0/24 dev eth0  src 192.168.216.11

# 指定的 v6 网关 2001:db8:abc8::10
$ ip -6 route
  2001:db8:abc4::/64 dev eth0  proto kernel  metric 256
  2001:db8:abc8::/64 dev eth0  proto kernel  metric 256
  default via 2001:db8:abc8::10 dev eth0  metric 1024

# 容器接口可以同时分配 v4 和 v6 地址
docker run --net=macvlan216 --ip=192.168.216.50  --ip6=2001:db8:abc8::50 -it --rm alpine /bin/sh

# 在容器内查看双栈 eth0 接口详情
$ ip a show eth0
95: eth0@if91: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 02:42:c0:a8:d8:32 brd ff:ff:ff:ff:ff:ff
    inet 192.168.216.50/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:abc8::50/64 scope global flags 02
       valid_lft forever preferred_lft forever

源码层面,双栈网关行为由 Join 阶段的双段逻辑实现:macvlan_joinleave.go#L51-L111 分别处理 IPv4 与 IPv6——ep.addr/ep.addrv6 各自匹配子网,配置了网关则 SetGateway/SetGatewayIPv6,未配置则 ForceGw4/ForceGw6,两条路由表各自独立写入,与上面 ip routeip -6 route 的输出一致。

网关推断 + 子池保留综合示例

下面示例演示当 docker network create 中某个子网未指定 --gateway 时,默认网关如何被推断——选取子网中第一个可用地址。同时演示 --ip-range--aux-address 联用,用于排除网络内的地址分配、在网络子网内保留可用地址子池。由于使用 eth0 而非子接口,所有流量均无 VLAN 标签。

docker network create -d macvlan \
  --subnet=192.168.136.0/24 \
  --subnet=192.168.138.0/24 \
  --ipv6 --subnet=fd11::/64 \
  --ip-range=192.168.136.0/25 \
  --ip-range=192.168.138.0/25 \
  --aux-address="reserved1=fd11::2" \
  --aux-address="reserved2=192.168.136.2" \
  --aux-address="reserved3=192.168.138.2" \
  -o parent=eth0 mcv0

docker run --net=mcv0 -it --rm alpine /bin/sh

在该示例网络 mcv0 上供给的容器输出如下(v4 网关推断为 192.168.136.1192.168.138.1,v6 网关推断为 fd11::1):

# 容器 eth0 输出(fe80::42:c0ff:fea8:8803/64 是本地链路地址)
ip address show eth0
100: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether 02:42:c0:a8:88:03 brd ff:ff:ff:ff:ff:ff
    inet 192.168.136.3/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fd11::3/64 scope global flags 02
       valid_lft forever preferred_lft forever
    inet6 fe80::42:c0ff:fea8:8803/64 scope link
       valid_lft forever preferred_lft forever

# 容器内 IPv4 路由表
$ ip route
default via 192.168.136.1 dev eth0
192.168.136.0/24 dev eth0  src 192.168.136.3

# 容器内 IPv6 路由表(第二个 v6 地址是本地链路地址)
$ ip -6 route
fd11::/64 dev eth0  metric 256
fe80::/64 dev eth0  metric 256
default via fd11::1 dev eth0  metric 1024

示例完成后的清理:docker rm -f docker ps -qa`` 可移除宿主机上所有容器(运行中和停止的)。

七、小结

macvlan 驱动的设计哲学可以概括为:让 NetOps 把物理网络原样映射进虚拟网络。一根 802.1q trunk 接入宿主机,-o parent=eth0.x 即完成一个网络与一个 VLAN/子网的映射;无标签链路则 -o parent=eth0 即可。这些是持久化配置,每次 Docker 引擎启动自动生效,替代了往往复杂易错的接口配置文件。同时,驱动对"谁创建、谁回收"的边界处理得非常明确(驱动创建的从属链路才自动重建/删除,父接口与手工链路永不触碰),配合内核级对宿主机本地地址的过滤,使容器网络在获得物理网络级可观测性的同时保持租户隔离。

延伸阅读(仓库内文档):bridge 驱动overlay/IPAM 文档网络设计文档;驱动实现位于 macvlan 驱动目录,含 单元测试setup 逻辑测试

登录后查看全文
热门项目推荐
相关项目推荐