Moby libnetwork 容器网络模型(CNM)设计解析:Sandbox、Endpoint、Network 与 Driver 扩展机制
本文以 Moby 仓库中 daemon/libnetwork/docs/design.md 设计文档为主体,系统讲解 libnetwork 如何基于容器网络模型(Container Network Model, CNM)实现容器网络抽象:Sandbox、Endpoint、Network 三要素的定义与关系、NetworkController 与各 Driver 的职责划分、完整的对象生命周期,以及当前仓库中驱动接口、Linux Sandbox 实现的源码级对应关系。读完本文,你可以理解 Docker 引擎背后容器网络的抽象层是如何组织的,并能在阅读 daemon/libnetwork 源码时快速定位关键对象与调用链。
设计目标与背景
设计文档开宗明义:libnetwork 遵循 Docker 与 Linux 的哲学——开发小型、高度模块化且可组合、并且能独立良好工作的组件(small, highly modular and composable tools)。libnetwork 的目标就是满足容器网络场景下的这种可组合需求,即提供一个稳定的抽象层,让具体的网络实现(网桥、VLAN、VXLAN 等)以驱动的形式可插拔地接入。
文档同时指出,libnetwork 的许多设计决策源自 Docker v1.6 时代网络设计的经验教训;更早的设计可以在 Docker v1.6 网络设计文档 中查阅。理解这一背景有助于把握 CNM 各概念为何被这样划分。
容器网络模型(CNM):Sandbox、Endpoint、Network
libnetwork 实现的核心是 Container Network Model(CNM):它把“为容器提供网络”这件事形式化为几个固定步骤,并给出一套抽象,使得同一套上层 API 可以支撑多种不同的网络驱动。CNM 建立在 3 个主要组件之上(如文首图片所示):
Sandbox(沙箱)
- 包含一个容器网络栈的完整配置:容器接口的管理、路由表、DNS 设置;
- 一种典型的实现是 Linux Network Namespace,也可以是 FreeBSD Jail 或其他类似概念;
- 一个 Sandbox 可以包含来自多个网络的多个 Endpoint。
Endpoint(端点)
- 负责把一个 Sandbox 连接到某个 Network;
- 一种典型的实现是
veth对、Open vSwitch 内部端口或类似机制; - 约束很明确:一个 Endpoint 只能属于一个网络,且在已连接的情况下只能属于一个 Sandbox。
Network(网络)
- 是一组可以直接互相通信的 Endpoint 的集合;
- 实现可以是 Linux 网桥、VLAN 等;
- 一个网络包含多个 Endpoint。
三者组合起来表达了 CNM 的核心不变量:容器(Sandbox)多网卡、多网络接入的能力——一个容器可以同时接入 bridge、overlay 等多个网络,每个接入关系由一个独立的 Endpoint 表示。
CNM 对象:Controller、Driver、Network、Endpoint、Sandbox
这一节逐一对应文档中“CNM Objects”部分定义的五个对象及其语义边界。
NetworkController
NetworkController 是进入 libnetwork 的入口点,向 Docker Engine 等上层使用者暴露简单 API,用于分配和管理网络。libnetwork 支持多个同时活动的驱动(内建的与远程的),NetworkController 允许使用者把特定的驱动绑定到特定的网络。
在源码中,Controller 由 libnetwork.New() 创建,入口在 controller.go#L147;创建网络的核心 API 是 Controller.NewNetwork(),位于 controller.go#L503,其签名接收 networkType 与 name,并支持变参的 NetworkOption 传入驱动级选项。
Driver(驱动)
Driver 不是面向使用者的可见对象,但驱动提供了实际的网络实现。NetworkController 提供 API 让用户以驱动特定的选项/标签(对 libnetwork 透明、由驱动直接处理)来配置驱动。驱动既可以是内建的(Bridge、Host、None、overlay 等),也可以是远程的(由插件提供者实现),以覆盖各种用例与部署场景。文档特别强调:在这一阶段,驱动拥有一个网络,并负责该网络的全部管理(包括 IPAM 等);未来可以由多个驱动分别参与不同的网络管理功能。
当前仓库中,Linux 平台下内建驱动的注册逻辑集中在 drivers_linux.go#L22-L45 的 registerNetworkDrivers():依次注册 bridge、host、ipvlan、macvlan、null、overlay 六个网络类型。可以看出,相对设计文档最初列出的 null/bridge/overlay/remote,仓库后来把 host、macvlan、ipvlan 也纳入了内建驱动集合,这正是“驱动可插拔注册”这一设计的直接体现。
Network
Network 对象是前文 CNM : Network 的实现。NetworkController 提供 API 创建和管理 Network;每当网络被创建或更新,对应的 Driver 都会收到该事件的通知。libnetwork 以抽象层面对待 Network 对象——提供“属于同一网络的一组端点之间的连通性”以及“与其余部分的隔离”;而实际提供连通性与隔离的工作由 Driver 完成。连通性既可以在同一主机内,也可以跨多主机,因此 Network 在集群内具有全局作用域。
Endpoint
Endpoint 代表一个服务端点(Service Endpoint),为容器在某个网络中暴露的服务、与网络中其他容器提供的服务之间提供连通。Network 对象提供 API 创建和管理 Endpoint。一个 Endpoint 只能附加到一个网络。Endpoint 的创建会调用到对应网络的 Driver,由驱动负责为该 Sandbox 分配所需资源。由于 Endpoint 表达的是服务而非某个特定容器,它同样具有集群内全局作用域。
Sandbox
Sandbox 对象表达容器的网络配置:IP 地址、MAC 地址、路由、DNS 条目。当用户请求在某网络创建 Endpoint 时,会创建 Sandbox 对象。处理该网络的 Driver 负责分配所需网络资源(如 IP 地址),并把结果(SandboxInfo)传回 libnetwork;libnetwork 再利用操作系统特定机制(Linux 上即 netns)把网络配置写入容器。一个 Sandbox 可以附加多个连接到不同网络的 Endpoint。由于 Sandbox 关联的是某台主机上的某个容器,它具有本地作用域——即容器所在的那台 Host。
Options 与 Labels
这两个机制是驱动与用户之间的配置通道:
- Options:向
Driver直接传递驱动特定配置选项的通用、灵活机制。Options就是键值对:key是字符串,value是通用对象(如 Go 的interface{})。libnetwork 只有在key命中net-labels包中定义的“知名标签”(well-known Labels)时才会对Options进行操作;Options在语义上包含下面介绍的Labels。Options通常不面向最终用户(不在 UI 中展示)。 - Labels:与
Options非常相似,实际上就是Options的子集。Labels通常面向最终用户可见,在 UI 中通过--labels选项显式表达,从 UI 传递到Driver,供驱动执行驱动特定操作(例如指定从哪个子网分配 IP 地址)。
在当前仓库中,知名标签的定义位于 daemon/libnetwork/netlabel 包(文档中的 net-labels 即其前身命名),IPAM 相关子网配置的完整设计另见 IPAM 设计文档。
CNM 生命周期:从驱动注册到网络删除
CNM 的使用者(如 Docker)通过 CNM 对象及其 API 来为所管理的容器配置网络。文档给出的生命周期共 9 个步骤,这里完整保留并结合当前源码逐条说明:
- 驱动注册:
Drivers向NetworkController注册。内建驱动在 libnetwork 内部注册(对应 drivers_linux.go 中的registerNetworkDrivers),远程驱动则通过插件机制注册(文档标注 plugin-mechanism 当时仍在完善中)。每个driver处理一种networkType。 - 创建控制器:使用
libnetwork.New()API 创建NetworkController对象,管理网络的分配,并可任选地以驱动特定Options配置驱动。源码入口见 controller.go#L147。 - 创建网络:使用控制器的
NewNetwork()API,提供name与networkType创建Network。networkType用于选择对应的Driver,并把创建的Network绑定到该驱动;从此,对该Network的任何操作都由该驱动处理。 - 驱动选项:
controller.NewNetwork()还接受可选的options参数,携带驱动特定选项与Labels,供驱动自行使用。 - 创建端点:
network.CreateEndpoint()在指定网络中创建新 Endpoint,可附带可选options(其中既可能包含知名标签,也可能包含驱动特定标签)。驱动随后被以driver.CreateEndpoint调用,它可以在 Endpoint 创建时预留 IPv4/IPv6 地址。驱动通过driverapi中定义的InterfaceInfo接口给端点分配地址——这些地址是完成端点作为“服务定义”所必需的,因为服务端点本质上就是“网络地址 + 应用容器监听的端口号”。 在源码中,InterfaceInfo的当前定义见 driverapi.go#L167-L193,包含SetMacAddress、SetIPAddress、Address()、NetnsPath()、SetCreatedInContainer等方法;以 bridge 驱动为例,其CreateEndpoint实现在 bridge_linux.go#L1061。 - 加入端点:
endpoint.Join()用于把容器附加到 Endpoint。若该容器的Sandbox尚不存在,Join 操作会创建一个。驱动可以利用 Sandbox Key 来识别附加到同一容器上的多个端点。该 API 同样接受可选options。 文档特别给出两点工程建议与 FAQ:- 强烈建议 Docker 之类的上层在容器
Start()生命周期中、容器进入运行状态之前调用endpoint.Join(),Docker 集成会负责这一点; - 为什么需要“创建 Endpoint”和“加入 Endpoint”两个 API? 答案是:Endpoint 表达的是服务,服务可能有、也可能没有容器作为载体。Endpoint 创建时资源即被预留,任何容器之后都可以附加到该端点并获得一致的网络行为。
bridge 驱动的
Join实现可参考 bridge_linux.go#L1420。
- 强烈建议 Docker 之类的上层在容器
- 离开端点:容器停止时可调用
endpoint.Leave(),驱动借此清理Join()期间分配的状态。当最后一个引用某 Sandbox 的端点离开网络时,libnetwork 会删除该Sandbox;但只要端点仍然存在,libnetwork 会保留 IP 地址,并在容器(或任何容器)再次加入时复用——这保证了容器 Stop/Start 后资源被复用而非重新分配。 - 删除端点:
endpoint.Delete()从网络中删除端点,同时清理缓存的sandbox.Info。 - 删除网络:
network.Delete()用于删除网络;若网络上还存在任何已附加的端点,libnetwork 不会允许删除继续执行。
实现细节:Network/Endpoint 的委托与 Sandbox 的 Linux 实现
Networks & Endpoints:libnetwork 的 Network 与 Endpoint API 主要用于管理对应对象并做簿记(book-keeping),以提供 CNM 所需的抽象层;实际实现被委托给驱动,由驱动兑现 CNM 承诺的功能。
Sandbox:libnetwork 提供了一个跨多操作系统实现 Sandbox 的框架。设计文档写作时,Linux 上的 Sandbox 实现在 sandbox 包中的 namespace_linux.go 与 configure_linux.go;每个 Sandbox 对应一个网络命名空间(Network Namespace),由宿主文件系统上的唯一路径标识。接口从全局 namespace 移入 Sandbox namespace、以及命名空间内路由表的管理,都通过 Netlink 调用完成。
需要指出的是,这一描述对应的是文档写作时期的代码布局:从当前源码结构看,Sandbox 相关实现已经上移到 daemon/libnetwork 顶层的 sandbox.go、sandbox_linux.go、sandbox_store.go 等文件中,netns 的创建与接口迁移逻辑仍基于 netlink 封装(见 daemon/libnetwork/nlwrap 包)。核心机制——“一个 Sandbox = 一个以宿主机路径标识的 netns,接口与路由经 netlink 配置”——与文档描述一致。
Drivers:面向驱动的 API 与语义
驱动本质上是 libnetwork 的扩展,为上文所有 LibNetwork API 提供实际实现。因此对每一个 Network/Endpoint API 都存在 1-1 对应的驱动侧 API。
Driver 接口
文档列举的核心驱动 API 为:driver.Config、driver.CreateNetwork、driver.CreateNetwork、driver.DeleteNetwork、driver.CreateEndpoint、driver.DeleteEndpoint、driver.Join、driver.Leave。这些面向驱动的 API 使用唯一标识符(networkid、endpointid 等)而非名字(用户侧 API 使用名字)。文档也如实说明:这些 API 在当时仍在完善中,尤其是在多主机网络需求下可能还会变化。
当前仓库中,驱动必须实现的核心接口定义在 driverapi.go#L15-L55 的 Driver 接口,包含:
CreateNetwork(ctx, nid, options, nInfo, ipV4Data, ipV6Data)DeleteNetwork(nid)CreateEndpoint(ctx, nid, eid, ifInfo, options)DeleteEndpoint(nid, eid)EndpointOperInfo(nid, eid)Join(ctx, nid, eid, sboxKey, jinfo, epOpts, sbOpts)Leave(nid, eid)Type()与IsBuiltIn()
对比可见,相对文档中的清单,接口演进了 EndpointOperInfo、IsBuiltIn 等方法,且 Join 增加了 sboxKey 与选项参数——与生命周期第 6 步中“驱动用 Sandbox Key 识别同一容器的多个端点”的描述相吻合。
除核心接口外,driverapi 还定义了一组可选接口(见 driverapi.go#L74-L150):TableWatcher(向 gossip 层注册表并接收 CRUD 通知,仅对 global scope 驱动有效)、ExtConner(外部连通性/默认网关的编程)、PortManager(动态增删已加入端点的发布端口)、IPv6Releaser、GwAllocChecker 等。从源码结构看,这些可选接口正是文档中“未来可由多个驱动参与不同网络管理功能”这一演进方向的具体落地。
Driver 语义:CreateEndpoint 的 InterfaceInfo 约定
文档对 Driver.CreateEndpoint 给出了严格的资源分配语义:
- 该方法接收一个
EndpointInfo接口(当前源码中对应driverapi.InterfaceInfo),其中提供Interface(读取)与AddInterface(写入)两类方法; - 若
Interface返回非 nil 值:驱动必须使用其中的接口信息(例如把地址视为静态提供的),若无法使用必须返回错误; - 若返回
nil:驱动应当恰好分配一个全新的接口,并用AddInterface记录下来,无法做到则返回错误; - 当
Interface非 nil 时,禁止使用AddInterface。
这段约定明确了“静态地址注入”与“动态地址分配”两条路径的互斥性,避免驱动与上层 IPAM 分配结果相互覆盖。
内置驱动实现
设计文档列出 libnetwork 包含以下驱动包:null、bridge、overlay、remote:
- Null:驱动 API 的
noop(空操作)实现,仅用于不希望有任何网络的情形,用以向后兼容 Docker 的--net=none选项。 - Bridge:基于 Linux Bridge 的 Linux 特定网桥实现,详见 Bridge 驱动文档。
- Overlay:实现可跨多主机、使用 VXLAN 等 overlay 封装的网络。
- Remote:
remote包本身不提供驱动,而是提供通过远程传输层支持驱动的手段,允许用任意语言编写驱动,详见 Remote 驱动设计文档。
在当前仓库中,这些驱动位于 daemon/libnetwork/drivers/ 目录下(null、bridge、overlay、remote,以及后加入的 host、macvlan、ipvlan),Linux 下的注册顺序与入口见 drivers_linux.go#L22-L45。此外,macvlan 驱动另有专门文档 macvlan.md,网络数据库(全局作用域网络在多主机间同步状态的基础)设计见 networkdb.md。
小结
libnetwork 的设计可以用三句话概括:其一,用 Sandbox—Endpoint—Network 三元组把“容器如何接入网络”形式化为 CNM,其中 Sandbox 是主机本地作用域的容器网络栈,Endpoint 是全局作用域的服务端点,Network 是端点集合的连通域;其二,用 NetworkController + Driver 分离“抽象管理”与“具体实现”,Options/Labels 作为两层配置通道把用户意图透传给驱动;其三,用一条九步生命周期(注册驱动 → New → NewNetwork → 选项 → CreateEndpoint → Join → Leave → DeleteEndpoint → DeleteNetwork)约束上层(如 Docker Engine)的调用顺序,使容器 Stop/Start 时 IP 等网络资源可以被稳定复用。理解了这套模型,就能沿着本文列出的 controller.go、driverapi.go、drivers_linux.go 与 drivers/ 各子包,完整追溯 Moby 中容器网络从 API 到内核 netlink 调用的全部路径。
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 StartedRust0622
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
