Moby macvlan 网络驱动详解:容器直连物理网络、802.1Q VLAN Trunk 与双栈实战
本文基于 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.Local、ConnectivityScope: 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;合法取值为bridge、private、passthru、vepa四种,否则报错。此外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)的完整流程是:
- 用
veth前缀生成一个宿主机侧临时接口名(如vethxxxxxxxx); - 调用 createMacVlan,通过 netlink 的
LinkAdd在父接口上创建 macvlan 从属接口,模式由 setMacVlanMode 映射到netlink.MACVLAN_MODE_BRIDGE/PRIVATE/VEPA/PASSTHRU; - 将端点的 IPv4/IPv6 地址与网络子网做匹配(
getSubnetForIP,要求掩码长度一致且 IP 被包含):子网配置了GwIP就调用jinfo.SetGateway / SetGatewayIPv6写入默认路由——这就是容器内ip route中default via <gateway> dev eth0的来源;未配置网关时走ForceGw4/ForceGw6保留旧行为; - 调用
jinfo.DisableGatewayService()关闭网桥类驱动才需要的网关服务; - 把宿主机侧的 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 子网的逻辑分组。网络运维常见做法是按子网功能或安全画像(如 web、db 等隔离需求)用 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 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)是隔离租户流量的第一步。
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 route 与 ip -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.1、192.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 逻辑测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00


