Moby macvlan 网络驱动详解:容器直连物理网络、802.1Q VLAN Trunk 与双栈实战
2026/9/5 23:17:45 网站建设 项目流程

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

【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby

本文基于 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 驱动概述:无 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-L25:parent-o parent)与macvlan_mode。parseNetworkOptions 中,macvlan_mode为空时默认置为bridge;合法取值为bridgeprivatepassthruvepa四种,否则报错。此外lo(回环接口)被显式拒绝为父接口;若-o parent为空,网络会被标记为internal(无外部连通性)。
  • 父接口不存在时的自动创建:createNetwork 中,若父接口不存在:父接口是dm-前缀的 dummy 名时,调用 createDummyLink 创建 dummy 接口;否则调用 createVlanLink 按iface.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 模式:一个网卡跑多个虚拟网络

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 ID50。等价的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 id60标签的父接口(-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)是隔离租户流量的第一步。

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 -fdocker ps -qa`` 可移除宿主机上所有容器(运行中和停止的)。

七、小结

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

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

【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询