Moby libnetwork IPAM Driver:远程 IPAM 插件契约、生命周期流程与协议详解
本文以 Moby 仓库中 libnetwork 的 IPAM(IP Address Management)驱动文档为核心,系统讲解 CNM 模型下 IP 地址分配的控制机制:内置 IPAM 驱动与第三方远程 IPAM 驱动的注册方式、IpamDriver.* 各 HTTP 接口的请求/响应契约、地址空间(Address Space)的语义、网络与端点创建/删除时 libnetwork 与 IPAM 驱动的完整交互流程,以及 RequiresMACAddress、RequiresRequestReplay 两项能力声明。读完后,你将具备实现一个可被 libnetwork 动态加载的远程 IPAM 插件所需的完整 API 契约知识,并能从源码层面理解默认 IPAM 驱动的分配策略。
如上所示的 CNM(Container Network Model)中,每个容器拥有独立的 Network Sandbox,其中可包含一个或多个 Endpoint,Endpoint 再挂接到具体的 Network 上。正是在 Network 与 Endpoint 的整个生命周期中,CNM 模型通过 IPAM 驱动控制网络与端点接口的 IP 地址分配。libnetwork 内置了一个默认 IPAM 驱动,同时允许第三方 IPAM 驱动以插件方式动态插入。创建网络时,用户可以指定 libnetwork 使用哪个 IPAM 驱动来管理该网络的地址。
IPAM 驱动的注册机制
libnetwork 将 IPAM 分为内置驱动与远程驱动两类:
- 内置驱动:随 libnetwork 初始化即注册,如
ipams/defaultipam包提供的默认驱动(实现见 defaultipam),以及不做任何地址分配的ipams/null空驱动; - 远程驱动:通过 Docker 插件机制动态加载,
ipams.remote包负责为其提供代理进程。
从源码结构看,所有 IPAM 驱动(无论内置还是远程)都必须实现 Ipam 接口:
type Ipam interface {
// GetDefaultAddressSpaces returns the default local and global address spaces for this ipam
GetDefaultAddressSpaces() (string, string, error)
// RequestPool allocate an address pool either statically or dynamically
RequestPool(req PoolRequest) (AllocatedPool, error)
// ReleasePool releases the address pool identified by the passed id
ReleasePool(poolID string) error
// RequestAddress request an address from the specified pool ID
RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)
// ReleaseAddress releases the address from the specified pool ID
ReleaseAddress(string, net.IP) error
// IsBuiltIn returns true if it is a built-in driver.
IsBuiltIn() bool
}
远程 IPAM 驱动的注册流程
与远程网络驱动的注册机制完全一致(详见 remote 驱动文档),libnetwork 通过 Init() 函数初始化 ipams.remote 包(实现见 remote.go)。Init() 接收一个 ipamapi.Callback 参数,该回调实现了 RegisterIpamDriver() 方法(接口定义见 Registerer):
type Registerer interface {
RegisterIpamDriver(name string, driver Ipam) error
RegisterIpamDriverWithCapabilities(name string, driver Ipam, capability *Capability) error
}
远程驱动包利用该接口,通过 plugins.Handle 回调把自己的远程驱动注册到 libnetwork 的 NetworkController 上。远程驱动经由 Docker 插件包与 libnetwork 完成注册和通信,ipams.remote 则充当远程驱动进程的代理(proxy),把 libnetwork 侧的接口调用转成 HTTPS 请求发给插件进程。
协议与握手
- 协议:远程 IPAM 驱动的通信协议与远程网络驱动完全相同(基于 Docker 插件框架的 HTTPS 双向 RPC)。
- 握手(Handshake):驱动注册期间,libnetwork 会向远程驱动查询其默认的 local / global 地址空间名称以及驱动能力(capabilities),对应下文的
GetDefaultAddressSpaces与GetCapabilities两个接口。 - 数据持久化要求:数据库(即池与地址分配记录)的管理是远程驱动自己的责任,libnetwork 不代为持久化;如果驱动选择不持久化本地作用域网络的池分配,可依赖
RequiresRequestReplay能力在守护进程重载时重建。
IPAM 契约:远程驱动必须实现的 5+1 个接口
远程 IPAM 驱动必须处理以下请求:
- GetDefaultAddressSpaces — 返回默认 local/global 地址空间;
- RequestPool — 注册(申请)一个地址池;
- ReleasePool — 释放一个已注册的地址池;
- RequestAddress — 预留一个 IP 地址;
- ReleaseAddress — 释放一个 IP 地址;
- GetCapabilities — 查询驱动能力(可选,但注册握手会尝试调用)。
这些接口与 Go 侧 ipamapi.Ipam 一一对应,其 HTTPS 请求/响应结构体完整定义在 ipams/remote/api/api.go 中,下文逐个说明。
GetDefaultAddressSpaces
GetDefaultAddressSpaces 返回该 IPAM 默认的 local 与 global 地址空间名称。地址空间是一组与"其他地址空间中的池"相互隔离的、互不重叠的地址池集合——同一个池可以同时存在于 N 个不同的地址空间中。地址空间天然地映射到租户(tenant)概念。
在 libnetwork 中,local 与 global 地址空间的含义是:local 地址空间不需要在集群内各节点间同步,而 global 地址空间需要同步。除非 IPAM 配置中另行指定,libnetwork 会根据所创建网络的作用域选择从默认 local 或默认 global 地址空间中申请地址池。例如:bridge 网络(本地作用域)默认从 default local 地址空间取池,overlay 网络(全局作用域)默认从 default global 地址空间取池。
注册期间,远程驱动会收到一个 POST /IpamDriver.GetDefaultAddressSpaces 请求,无 payload。驱动响应格式(对应源码 GetAddressSpacesResponse):
{
"LocalDefaultAddressSpace": "string",
"GlobalDefaultAddressSpace": "string"
}
RequestPool
该 API 用于向 IPAM 驱动注册一个地址池。多次相同参数的调用必须返回相同结果(幂等),池的引用计数(reference count)由 IPAM 驱动自己维护——这也是 ReleasePool 成对出现的原因。
Go 侧签名(文档中的原始形式,当前源码已演进为以 PoolRequest 结构体传参,见 PoolRequest):
RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error)
远程驱动将收到 POST /IpamDriver.RequestPool,payload 如下(对应 RequestPoolRequest):
{
"AddressSpace": "string",
"Pool": "string",
"SubPool": "string",
"Options": {"k": "v"},
"V6": false
}
字段说明:
AddressSpace:IP 地址空间,表示一组互不重叠的池。这是唯一必填字段;Pool:IPv4 或 IPv6 地址池,CIDR 格式;SubPool:地址池的一个可选子集,CIDR 格式的 IP 范围;Options:IPAM 驱动专有的选项 map;V6:当希望 IPAM 自选择一个池时,指示该池是否为 IPv6。
校验规则:若未指定 Pool,IPAM 驱动可以返回一个自选择的池;此情况下若调用方想要 IPv6 池必须置位 V6 标志。空 Pool 且非空 SubPool 的请求应被视为非法而拒绝;未指定 Pool 时,IPAM 将分配其默认池之一,此时如果网络需要分配 IPv6 地址,V6 标志应当置位。
成功响应格式(对应 RequestPoolResponse):
{
"PoolID": "string",
"Pool": "CIDR string",
"Data": {"k": "v"}
}
PoolID:池的标识符。相同的池必须返回相同的 pool id;Pool:池本身,CIDR 格式;Data:IPAM 驱动为该池提供的元数据。
ReleasePool
释放一个先前注册的地址池。远程驱动收到 POST /IpamDriver.ReleasePool,payload(对应 ReleasePoolRequest):
{
"PoolID": "string"
}
PoolID 即池标识符。成功响应为空对象 {}。
RequestAddress
预留一个 IP 地址。Go 侧签名:
RequestAddress(string, net.IP, map[string]string) (*net.IPNet, map[string]string, error)
远程驱动收到 POST /IpamDriver.RequestAddress,payload(对应 RequestAddressRequest):
{
"PoolID": "string",
"Address": "string",
"Options": {"k": "v"}
}
PoolID:池标识符;Address:期望地址,常规 IP 形式(A.B.C.D)。若无法满足该指定地址,请求失败;若为空,则由 IPAM 驱动在池内任选一个可用地址;Options:IPAM 驱动专有的选项(如携带端点 MAC 地址,见后文能力说明)。
成功响应(对应 RequestAddressResponse):
{
"Address": "A.B.C.D/MM (CIDR)",
"Data": {"k": "v"}
}
Address:分配出的地址,CIDR 格式;Data:IPAM 驱动专有的元数据。
ReleaseAddress
释放一个 IP 地址。远程驱动收到 POST /IpamDriver.ReleaseAddress,payload(对应 ReleaseAddressRequest):
{
"PoolID": "string",
"Address": "string"
}
PoolID:池标识符;Address:要释放的 IP 地址。
GetCapabilities
驱动注册期间,libnetwork 会查询驱动的能力。该 URL 端点并非强制要求:若驱动不支持它,注册会自动成功,并以内置空能力附加到内部驱动句柄上。
注册期间,远程驱动收到 POST /IpamDriver.GetCapabilities,无 payload。响应格式(对应 GetCapabilityResponse):
{
"RequiresMACAddress": false,
"RequiresRequestReplay": false
}
能力(Capabilities)详解
能力是远程 IPAM 驱动在注册时向 libnetwork 表达的需求/特性声明,结构体定义见 ipamapi.Capability。目前 libnetwork 接受以下两种:
RequiresMACAddress
布尔值,告知 libnetwork 该 IPAM 驱动是否需要知道接口 MAC 地址才能正确处理 RequestAddress() 调用。若为 true,则在 CreateEndpoint() 时,libnetwork 会为端点生成一个随机 MAC 地址(除非用户已显式提供 MAC),并在请求 IP 时通过 options map 传给 RequestAddress(),使用的 key 是 netlabel.MacAddress 常量:"com.docker.network.endpoint.macaddress"。这类驱动典型场景是需要按 MAC 做地址绑定或 ARP 代理的设备。
RequiresRequestReplay
布尔值,告知 libnetwork 该驱动是否需要在守护进程重载(daemon reload)时收到 RequestPool() 与 RequestAddress() 请求的重放。libnetwork 控制器初始化时会从本地存储中取出当前本地作用域网络的列表;若此能力标志已置位,它会重放本地网络所属各池的 RequestPool() 请求以及各本地网络网关地址的 RequestAddress() 请求,使 IPAM 驱动得以重建其池数据库。这对那些选择不持久化本地作用域网络池分配的 IPAM 驱动非常有用。
IPAM 配置与完整生命周期流程
通过 NetworkOptionIpam 传入 IPAM 配置
libnetwork 用户在创建网络时,可以通过 NetworkOptionIpam 设置函数提供 IPAM 相关配置,定义见 network.go:
func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption
调用方必须提供 IPAM 驱动名,并可提供地址空间以及 IPv4 的 IpamConf 列表和 IPv6 的 IpamConf 列表。IPAM 驱动名是唯一必填字段,若未提供,网络创建将失败。这一点在集成测试中有直接验证,libnetwork_linux_test.go 中明确断言:
_, err := lnb.CreateNetwork("test",
libnetwork.NetworkOptionIpam("my-ipam", "", nil, nil, nil), // 合法
libnetwork.NetworkOptionIpam("", "", ipamV4ConfList, nil, nil), // 缺驱动名,应失败
libnetwork.NetworkOptionIpam("", "", nil, ipamV6ConfList, nil), // 缺驱动名,应失败
)
列表中每个 IpamConf 元素具有如下形式:
// IpamConf contains all the ipam related configurations for a network
type IpamConf struct {
// The master address pool for containers and network interfaces
PreferredPool string
// A subset of the master pool. If specified,
// this becomes the container pool
SubPool string
// Input options for IPAM Driver (optional)
Options map[string]string
// Preferred Network Gateway address (optional)
Gateway string
// Auxiliary addresses for network driver. Must be within the master pool.
// libnetwork will reserve them if they fall into the container pool
AuxAddresses map[string]string
}
各字段语义:PreferredPool 是容器与网络接口用的主地址池;SubPool 是主池的子集,若指定则成为容器地址池(容器地址只从该子池分配);Options 是传给 IPAM 驱动的选项;Gateway 是期望的网络网关地址;AuxAddresses 是网络驱动用的辅助地址,必须位于主池内,若落在容器池内 libnetwork 会为其预留。
网络创建时的 IPAM 请求序列
网络创建时,libnetwork 遍历配置列表,向 IPAM 驱动依次执行:
- RequestPool:通过
RequestPool()申请地址池,并顺带传递Options; - 网关地址:若指定了
Gateway则申请该特定地址;否则从池中申请任意地址用作网络网关。均通过RequestAddress()完成; - 辅助地址:通过
RequestAddress()依次申请每个指定的AuxAddresses地址。
关于配置列表为空的默认行为:
- IPv4 列表为空:libnetwork 会自动追加一个空
IpamConf结构。这会触发 libnetwork 向 IPAM 驱动申请一个"驱动自选"的 IPv4 地址池——在已配置地址空间上(若指定了)或在 IPAM 驱动默认地址空间上(否则)。若 IPAM 驱动无法提供地址池,网络创建失败; - IPv6 列表为空:libnetwork 不做任何动作(IPv6 属于完全可选)。
执行第 1)~3) 步期间从 IPAM 驱动获取的数据会存储在 network 结构中,分别为 IPv4 与 IPv6 保存为 IpamInfo 结构列表。
端点创建时的地址分配
端点(endpoint)创建时,libnetwork 遍历配置列表并执行:
- 从 IPv4 池申请一个 IPv4 地址,并赋给端点接口的 IPv4 地址。成功则停止遍历;
- 从 IPv6 池(若存在)申请一个 IPv6 地址,并赋给端点接口的 IPv6 地址。成功则停止遍历。
上述任一操作失败,端点创建即失败。 这里也体现了 RequiresMACAddress 能力的生效点:若驱动声明该能力,libnetwork 会在步骤 1/2 的 RequestAddress() options 中携带 com.docker.network.endpoint.macaddress。
端点删除时的地址释放
端点删除时:
- 释放端点接口的 IPv4 地址;
- 若存在,释放端点接口的 IPv6 地址。
网络删除时的池释放序列
网络删除时,libnetwork 遍历 IpamData 结构列表,向 IPAM 驱动执行:
- 通过
ReleaseAddress()释放网络网关地址; - 通过
ReleaseAddress()释放每个辅助地址; - 通过
ReleasePool()释放地址池。
该顺序与创建时严格相反(先地址后池),配合 RequestPool 的幂等语义与驱动侧引用计数,保证了网络对象在存储中重建后池状态可以正确收敛。
内置默认 IPAM 驱动的源码印证
作为对照,内置默认驱动(ipams/defaultipam)在进程内实现同一契约。从源码结构看,address_space.go 中的 addrSpace 结构维护了地址空间内"已分配子网"的有序列表、按前缀索引的子网池数据,以及按前缀排序、去掉重叠项后的 predefined 预定义池列表——排序与去重是为了保证动态子网分配时不会因为长短前缀重叠而选错池。其预定义池正是 docker network create --subnet 系列场景的落点,与远程驱动契约中 Pool/SubPool 的语义(静态分配 vs 动态分配)一一对应。
此外,ipamapi 集中定义了 IPAM 的通用错误集(如 ErrNoAvailableIPs、ErrIPAlreadyAllocated、ErrIPOutOfRange、ErrPoolOverlap 等),远程驱动返回的错误经由 api.Response.Error 字段(见 api.go,Error == "" 即视为成功)传递回 libnetwork,实现与文档所列契约在错误语义上保持一致。
在测试层面,libnetwork_internal_test.go 展示了典型用法:NetworkOptionIpam(defaultipam.DriverName, "", []*IpamConf{{PreferredPool: "10.35.0.0/16", Gateway: "10.35.255.253"}}, nil, nil),即显式主池 + 显式网关的组合,验证了上文配置流程中"指定 Gateway 时申请特定地址"的分支。
小结
- 契约核心:远程 IPAM 驱动必须实现
GetDefaultAddressSpaces、RequestPool、ReleasePool、RequestAddress、ReleaseAddress五个 POST 端点,GetCapabilities可选;所有 payload 结构在 ipams/remote/api/api.go 中有权威定义。 - 关键约束:
AddressSpace是RequestPool唯一必填字段;相同RequestPool调用必须幂等且返回相同PoolID;引用计数由驱动维护;数据库持久化是驱动自己的责任。 - 生命周期对应关系:网络创建 = RequestPool + 网关/辅助地址申请;端点创建 = 按 v4、v6 顺序请求地址(任一失败即失败);端点/网络删除 = 严格逆序的 Release 序列。
- 能力协商:
RequiresMACAddress决定地址请求 options 中是否携带端点 MAC(key 为com.docker.network.endpoint.macaddress);RequiresRequestReplay决定守护进程重载时是否重放池与网关地址请求,以支撑不持久化本地池分配的驱动实现。 - 实现入口:Go 侧统一接口为 ipamapi.Ipam,远程接入参考 remote 驱动文档 与 ipams/remote 的代理实现;IPAM 配置入口为 NetworkOptionIpam。
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
