全局视图
Docker 网络不是一坨代码,而是三层职责完全分离的设计。理解这三层的边界,后续看任何网络相关的代码都不会迷失。
scss
┌─────────────────────────────────────────────────────────────────────┐
│ REST API 层 POST /networks/create "driver=bridge&name=foo" │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 第 1 层 daemon/network (API 语义层,纯常量与轻量工具) │
│ - DefaultNetwork = "bridge" (Linux) / "nat" (Windows) │
│ - IsPredefined() 判断 none/host/bridge/default 等是否内置 │
│ - Filter 把 docker network ls --filter 'driver=bridge' 解析出来 │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 第 2 层 daemon.Daemon (wrapper,~1250 行,真正的"翻译官") │
│ - Daemon.netController 字段持有 *libnetwork.Controller │
│ - Daemon.CreateNetwork → daemon.createNetwork │
│ · 校验 IsPredefined │
│ · 注入 daemon.json 里的 DefaultNetworkOpts │
│ · 解析 enableIPv4/enableIPv6 等 bool 字段 │
│ · 把 CreateRequest 翻译成 []libnetwork.NetworkOption(变参) │
│ · 调用 c.NewNetwork(...) │
│ · pluginRefCount 维护插件引用计数 │
│ · 发 events.ActionCreate 事件 │
│ - Daemon.FindNetwork / GetNetworkByID / GetNetworkByName │
│ (支持 ID 全匹配 / ID 前缀 / Name 三种 term) │
│ - Daemon.DeleteNetwork / ConnectContainerToNetwork ... │
│ - buildNetworkResource / buildContainerAttachments 把 libnetwork │
│ 对象反向序列化成 API 返回的 networktypes.Network │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────────────┐
│ 第 3 层 libnetwork.Controller (本体,网络版 Kubernetes) │
│ - drvRegistry: 持有 bridge/host/ipvlan/macvlan/null/overlay 驱动 │
│ - ipamRegistry: 持有 IPAM 驱动(default/host-local/null) │
│ - pmRegistry: 端口映射器(nat / routed) │
│ - networks / endpoints / sandboxes 三张内存表 │
│ - store (*datastore.Store): 持久化网络与端点(bolt) │
│ - agent (*nwAgent): swarm 模式下的 gossip agent │
│ - ingressSandbox: 跨节点路由的特殊 sandbox │
│ - SetClusterProvider → 监听 cluster event → agentSetup/agentClose│
└─────────────────────────────────────────────────────────────────────┘
一次 docker network create -d bridge mynet 的完整旅程
- CLI/REST :
POST /networks/createbody{Name:"mynet", Driver:"bridge"}。
- router/server 路由到
daemon/server/daemon.go里注册的 handler。
- handler 调用
backend.CreateNetwork(ctx, req),即Daemon.CreateNetwork(daemon/network.go:290)。
Daemon.CreateNetwork包一层runInNetNS(rootless 模式下要切到容器 netns),然后调真正的daemon.createNetwork(daemon/network.go:300)。
createNetwork干 6 件事:
-
network.IsPredefined(name)拦掉 none/host/bridge 等保留字;
-
- 默认 driver 用
c.Config().DefaultDriver;
- 默认 driver 用
-
- overlay 但非 manager 直接 Forbidden(swarm-scoped 网络必须 manager 创建);
-
- 合并
cfg.DefaultNetworkOpts[driver](daemon.json 里 per-driver 默认参数);
- 合并
-
- 把
enableIPv4/IPv6、internal、attachable、ingress、configOnly、configFrom、IPAM 配置等翻译成一串libnetwork.NetworkOption(变参函数式选项);
- 把
-
- 调用
c.NewNetwork(ctx, driver, name, id, options...)进入 libnetwork 本体。
- 调用
- libnetwork.NewNetwork (
controller.go:503)锁住networkLocker(按 id 和 name 各加一把)、校验 scope、resolveDriver找 driver、nw.ipamAllocate()分配子网、c.addNetwork调 driver 的CreateNetwork、storeNetwork持久化、joinCluster(overlay 加入 gossip)、必要的话创建 LoadBalancer sandbox。
- 返回
*Network,wrapper 层调pluginRefCount给 bridge driver 引用计数 +1(内置 driver 跳过),LogNetworkEvent发 create 事件,API 返回{ID: ...}。
整条链读完,你就理解了 docker 网络为什么这么分:第 1 层只定义语义,第 2 层只翻译,第 3 层才真正干活。这种分层让你可以单独替换 driver(plugin)、单独测试 wrapper(用 mock controller)、单独演进 API(filter 不动核心)。
第 1 章 daemon/network 包:API 语义层
这是最小的一层,只有几个文件,但承担了"docker 网络在 API 层的语义定义"。
1.1 network_mode.go:平台无关入口
go
// daemon/network/network_mode.go
const DefaultNetwork = defaultNetwork // 平台相关,见 network_mode_unix.go / windows.go
func IsPredefined(network string) bool {
return isPreDefined(network)
}
只有两个 export:DefaultNetwork 常量和 IsPredefined 函数。真正的实现按平台拆开:
1.2 network_mode_unix.go vs network_mode_windows.go
go
// daemon/network/network_mode_unix.go
const defaultNetwork = network.NetworkBridge // "bridge"
func isPreDefined(network string) bool {
n := container.NetworkMode(network)
return n.IsBridge() || n.IsHost() || n.IsNone() || n.IsDefault()
}
go
// daemon/network/network_mode_windows.go
const defaultNetwork = network.NetworkNat // "nat"
func isPreDefined(network string) bool {
return !container.NetworkMode(network).IsUserDefined()
}
注意 Linux 是"白名单"(列 bridge/host/none/default),Windows 是"黑名单"(只要不是 user-defined 就是预定义)。两种思路源于不同平台对网络模式的不同抽象,注释里也有 TODO(thaJeztah): check if we can align the check for both platforms,说明开发者知道这不优雅,但兼容性包袱让其对齐困难。
1.3 settings.go:container-side 网络状态
Settings 和 EndpointSettings 是容器对象上挂载 的网络状态(Container.NetworkSettings 字段就是这个类型):
go
type Settings struct {
SandboxID string
SandboxKey string
Networks map[string]*EndpointSettings // network name → endpoint
Service *clustertypes.ServiceConfig // swarm service 信息
Ports networktypes.PortMap
HasSwarmEndpoint bool
}
type EndpointSettings struct {
*networktypes.EndpointSettings // API 暴露的部分
IPAMOperational bool // 不持久化、不暴露
DesiredMacAddress networktypes.HardwareAddr // 创建时从 MacAddress 拷贝
}
这是个包装模式 :networktypes.EndpointSettings 是 API 类型(出现在 inspect 输出和持久化文件里),但 daemon 还需要两个只在运行时存在 的字段,于是包一层 network.EndpointSettings 加上 IPAMOperational 和 DesiredMacAddress。注释明确说明:"needs to be persisted to disk but not exposed in the api"------但实际上 IPAMOperational 不持久化,只有 DesiredMacAddress 会持久化(从 API 的 MacAddress 拷贝过来,这样运行期改 mac 不会回写 API 输入)。
AttachmentStore 是 swarm 模式下节点 LoadBalancer IP 的映射,提供 ResetAttachments / GetIPForNetwork 等方法,在 daemon.createNetwork(... agent=true) 分支里用到(daemon/network.go:411)。
1.4 filter.go:docker network ls --filter 的实现
Filter 类型 + NewFilter / NewPruneFilter 工厂,支持 driver/name/id/label/scope/type/dangling/until 七种维度。注意它定义了一个 FilterNetwork interface:
scss
type FilterNetwork interface {
Driver() string
Name() string
ID() string
Labels() map[string]string
Scope() string
Created() time.Time
HasContainerAttachments() bool
HasServiceAttachments() bool
}
*libnetwork.Network 实现了这个 interface,所以 wrapper 层 daemon.GetNetworks 可以直接 filter.Matches(n)(daemon/network.go:691)。这就是第 1 层与第 3 层之间最隐秘的耦合点:filter 定义在第 1 层,但其 interface 形状由第 3 层的 Network 满足。如果第 3 层改了 Network 的方法签名,第 1 层 filter 会编译失败。
matchesType 把 type=builtin 翻译成 IsPredefined(nw.Name()),把 type=custom 翻译成 !IsPredefined(nw.Name())------又是一个第 1 层调用第 1 层自身常量的例子。
第 2 章 daemon wrapper:把 REST 翻译成 libnetwork 调用
入口:daemon/network.go(注意是 daemon 包,不是 daemon/network 子包)。这一层是整个网络子系统最厚的代码(~1250 行),但职责很纯粹:API 类型 ↔ libnetwork 类型 的双向翻译。
2.1 持有方式:Daemon.netController
go
// daemon/daemon.go:113
type Daemon struct {
...
netController *libnetwork.Controller
...
}
// daemon/network.go:56
func (daemon *Daemon) NetworkController() *libnetwork.Controller {
return daemon.netController
}
注意 netController 是私有字段,只有 NetworkController() 一个 getter。几乎所有外部访问都通过 Daemon 方法,不直接拿 controller ------这是 wrapper 模式的典型纪律。例外是 container_operations*.go 这种核心代码,需要直接调 SandboxByID / GetSandbox,绕过 wrapper。
2.2 创建控制器:初始化时机
go
// daemon/daemon_unix.go:843
func (daemon *Daemon) initNetworkController(cfg *config.Config, activeSandboxes map[string]any) error {
netOptions, err := daemon.networkOptions(cfg, daemon.PluginStore, daemon.id, activeSandboxes)
...
daemon.netController, err = libnetwork.New(ctx, netOptions...)
...
if len(activeSandboxes) > 0 {
// 有活容器 → 跳过 configureNetworking(用户改的 daemon.json 网络配置不生效)
} else {
daemon.runInNetNS(func() error {
return configureNetworking(ctx, daemon.netController, cfg)
})
}
setHostGatewayIP(daemon.netController, cfg)
return nil
}
activeSandboxes 是 restore 阶段从 libnetwork 视角收集的"还活着的 sandbox",透传给 libnetwork.New ------libnetwork 初始化时就知道这些 sandbox 还活着,可以选择重新接管而不是重建。Windows 版本在 daemon_windows.go:260,逻辑类似但 predefined networks 列表不同(nat/none/... )。
configureNetworking 创建预定义网络 none / host / bridge,只有 len(activeSandboxes)==0 才能调 ------因为这个函数会删了重建 bridge 网络,活容器都挂在 bridge 上,硬删会断网。用户在 daemon.json 里改网络配置后只有"无活容器重启 daemon"才能生效,根因就在这。
2.3 wrapper 的核心方法清单
| wrapper 方法 | libnetwork 调用 | 说明 |
|---|---|---|
NetworkController() |
(直接返回字段) | 唯一 getter |
FindNetwork(term) |
遍历 getAllNetworks |
支持 ID/Name/前缀三种 term |
GetNetworkByID(id) |
c.NetworkByID(id) |
全 ID 精确匹配 |
GetNetworkByName(name) |
c.NetworkByName(name) |
空 name 用 c.Config().DefaultNetwork |
GetNetworksByIDPrefix(p) |
c.WalkNetworks |
前缀匹配 |
getAllNetworks() |
c.Networks(ctx) |
不暴露 |
CreateNetwork(req) |
c.NewNetwork |
主创建路径 |
CreateManagedNetwork(req) |
同上但 agent=true |
swarm agent 网络 |
DeleteNetwork(id) |
n.Delete() |
拦掉预定义网络 |
DeleteManagedNetwork(id) |
同上 dynamic=true |
swarm 删除 |
ConnectContainerToNetwork |
daemon.ConnectToNetwork |
转给容器层 |
DisconnectContainerFromNetwork |
daemon.DisconnectFromNetwork |
同上 |
GetNetworkDriverList() |
c.BuiltinDrivers() + PluginStore |
列出可用 driver |
GetNetworks(filter, cfg) |
遍历 + filter.Matches | API ls 实现 |
SetNetworkBootstrapKeys |
c.SetKeys |
swarm 加密 key |
SetupIngress / ReleaseIngress |
异步 worker | 见 §6.3 |
UpdateAttachment / WaitForDetachment |
clusterProvider |
swarm 容器-网络附着 |
2.4 createNetwork 详解(50~430 行)
签名:
go
func (daemon *Daemon) createNetwork(
ctx context.Context,
cfg *config.Config,
create networktypes.CreateRequest,
id string, // 由 swarm manager 传入,普通创建为 ""
agent bool, // true 表示这是 swarm agent 路径
) (*networktypes.CreateResponse, error)
核心步骤(按行号) :
L301拦预定义网络 :network.IsPredefined(create.Name)直接返PredefinedNetworkError(实现Forbidden()接口,API 层会回 403)。
L306-309driver 默认值 :空 driver 用c.Config().DefaultDriver,Linux 是"bridge"。
L311-313swarm 拦截 :overlay 且!IsManager() && !agent直接 Forbidden,提示用户先docker swarm init/join。
L315-324daemon.json 默认参数 :从cfg.DefaultNetworkOpts[driver]取 per-driver 默认值合并进networkOptions,用户 CLI 显式传的优先 (if _, ok := networkOptions[k]; !ok)。
L326-350enableIPv4/IPv6 三态解析 :create.EnableIPv4是*bool,有三种状态(nil/true/false),加上 driver-opt 兜底。这是个经典三态字段陷阱,看注释能感觉到作者维护时的痛苦。
L352-408组装[]libnetwork.NetworkOption:把扁平的 CreateRequest 重新组装回函数式选项。看这一段能学到 Go 社区流行的 functional options 模式:NetworkOptionEnableIPv4(v)/NetworkOptionDriverOpts(m)等。
L410-417swarm agent LB IP :agent 模式且 driver=overlay,从AttachmentStore取节点 LB IP 设进去。
L419真正调用 libnetwork :c.NewNetwork(ctx, driver, create.Name, id, nwOptions...)。
L424-427插件引用计数 :pluginRefCount给 driver 和 IPAM driver 各加一次引用,内置 driver 用c.BuiltinDrivers()/c.BuiltinIPAMDrivers()跳过(避免内置 driver 误触发插件加载)。
L428事件 :LogNetworkEvent(n, events.ActionCreate)发 create 事件给docker events订阅者。
2.5 wrapper 反向序列化:buildNetworkResource
API 返回的不是 *libnetwork.Network,而是 networktypes.Network。daemon/network.go:734 的 buildNetworkResource 做反向翻译:
css
return networktypes.Network{
Name: nw.Name(),
ID: nw.ID(),
Created: nw.Created(),
Scope: nw.Scope(),
Driver: nw.Type(), // 注意:libnetwork 叫 Type,API 叫 Driver
EnableIPv4: nw.IPv4Enabled(),
EnableIPv6: nw.IPv6Enabled(),
IPAM: buildIPAMResources(nw),
Internal: nw.Internal(),
Attachable: nw.Attachable(),
Ingress: nw.Ingress(),
ConfigFrom: networktypes.ConfigReference{Network: nw.ConfigFrom()},
ConfigOnly: nw.ConfigOnly(),
Options: nw.DriverOptions(),
Labels: nw.Labels(),
Peers: buildPeerInfoResources(nw.Peers()),
}
注意 Driver: nw.Type() ------libnetwork 内部叫 networkType,API 暴露叫 Driver,因为对用户而言"driver"更直观。这种字段重命名在 wrapper 层随处可见,目的:内部命名遵循实现语义,外部命名遵循用户认知。
2.6 ingress worker:wrapper 里的异步模式
go
// daemon/network.go:147
type ingressJob struct {
create *clustertypes.NetworkCreateRequest // nil 表示释放
ip net.IP
jobDone chan struct{}
}
var (
ingressWorkerOnce sync.Once
ingressJobsChannel chan *ingressJob
ingressID string
)
func (daemon *Daemon) startIngressWorker() {
ingressJobsChannel = make(chan *ingressJob, 100)
go func() {
for r := range ingressJobsChannel {
if r.create != nil {
daemon.setupIngress(&daemon.config().Config, r.create, r.ip, ingressID)
ingressID = r.create.ID
} else {
daemon.releaseIngress(ingressID)
ingressID = ""
}
close(r.jobDone)
}
}
func (daemon *Daemon) enqueueIngressJob(job *ingressJob) {
ingressWorkerOnce.Do(daemon.startIngressWorker) // 懒启动
ingressJobsChannel <- job
}
这是一个典型的 single-flight + 串行化 worker 模式。为什么 ingress 需要异步?因为 swarm manager 同时下发多个 service 创建请求时,每个都可能触发 ingress 网络编程,但 ingress 网络本身是单例(整个 swarm 集群只有一个 ingress 网络),必须串行化。
SetupIngress 返回 <-chan struct{} 让调用方可以等待编程完成,这是 Go 里非常标准的 "future" 模式。ingressWorkerOnce 保证 worker 只启动一次,即使没人调用过 setup/release。
第 3 章 libnetwork.Controller:网络控制器本体
文件:daemon/libnetwork/controller.go(1138 行,libnetwork 的中枢)。文件顶部有 30+ 行的 package doc 注释,直接给出一段最小用法示例,这是阅读本文件的最佳起点。
3.1 Controller 结构体(L87-144)
go
type Controller struct {
id string // 随机生成的 controller ID
drvRegistry drvregistry.Networks // 网络驱动注册表
ipamRegistry drvregistry.IPAMs // IPAM 驱动注册表
pmRegistry drvregistry.PortMappers// 端口映射器注册表
sandboxes map[string]*Sandbox // sandbox 表(key 是 sandbox ID)
cfg *config.Config // 启动配置(daemon.json 编译结果)
store *datastore.Store // 持久化存储(bolt KV)
extKeyListener net.Listener // 外部 plugin 监听
svcRecords map[string]*svcInfo // DNS 服务发现记录
serviceBindings map[serviceKey]*service // swarm service 绑定
ingressSandbox *Sandbox // ingress 网络的 sandbox(全局唯一)
agent *nwAgent // swarm gossip agent
networkLocker *locker.Locker // 按 network name/id 加锁
agentInitDone chan struct{} // agent 初始化完成信号
agentStopDone chan struct{} // agent 停止完成信号
keys []*types.EncryptionKey // overlay 加密 key
diagnosticServer *diagnostic.Server // 调试 HTTP server
mu sync.Mutex // 主锁
networks map[string]*Network // 内存网络缓存(注意注释里的并发警告)
networksMu sync.Mutex
endpoints map[string]*Endpoint // 内存端点缓存
endpointsMu sync.Mutex
defOsSboxOnce sync.Once
defOsSbox *osl.Namespace // 默认 OS-level sandbox(用于 host 网络)
}
注意 networks 和 endpoints 字段上方的长注释 ------这是个非常重要且反直觉的设计:持久化层(datastore)每次读都会 new 一个新的 Network/Endpoint 对象,导致同一个网络可能有多个实例同时存在,所以 Network.mu 可能保护不了数据竞争 。注释要求使用这两张表时务必保证调用方代码线程安全,并维护 Sandbox/Network/Endpoint 之间一致的加锁顺序避免死锁。
这是个典型的"坑注释" ,任何想给 libnetwork 加并发代码的人都得先读这段。
3.2 New() 构造函数(L147-230)
go
func New(ctx context.Context, cfgOptions ...config.Option) (_ *Controller, retErr error) {
...
cfg := config.New(cfgOptions...)
store, err := datastore.New(cfg.DataDir, cfg.DatastoreBucket)
...
c := &Controller{
id: stringid.GenerateRandomID(),
cfg: cfg,
store: store,
sandboxes: map[string]*Sandbox{},
networks: map[string]*Network{},
endpoints: map[string]*Endpoint{},
svcRecords: make(map[string]*svcInfo),
serviceBindings: make(map[serviceKey]*service),
agentInitDone: make(chan struct{}),
networkLocker: locker.New(),
diagnosticServer: diagnostic.New(),
}
if err := c.selectFirewallBackend(); err != nil { return nil, err }
c.drvRegistry.Notify = c // 让 drvRegistry 能回调 controller
// 注册顺序很重要!
registerPortMappers(...) // 1. 端口映射器先注册(因为 driver init 时可能要恢复 sandbox 端口映射)
remotedriver.Register(...) // 2. 远程 driver(外部插件)注册
registerNetworkDrivers(...) // 3. 内置 driver(bridge/overlay/...)
ipams.Register(...) // 4. IPAM driver
c.WalkNetworks(func(nw *Network) bool { // 5. 恢复特殊 driver 的网络
if nw.hasSpecialDriver() && !nw.ConfigOnly() {
c.addNetwork(ctx, nw)
}
return false
})
c.reservePools() // 6. 重放 IPAM 池分配(防止重启后 IP 重复)
c.sandboxRestore(c.cfg.ActiveSandboxes) // 7. 恢复活 sandbox
c.cleanupLocalEndpoints() // 8. 清理孤儿 endpoint
c.networkCleanup() // 9. 清理 inDelete 网络
c.startExternalKeyListener() // 10. 启动 plugin 监听
c.setupPlatformFirewall() // 11. 平台防火墙初始化
return c, nil
}
11 个初始化步骤顺序极其严格 ,任何一个错位都会导致状态不一致。比如 reservePools 必须在 sandboxRestore 之前,否则恢复 sandbox 时会触发大量 IP 重复警告(注释 L209 明确说明)。
3.3 NewNetwork 详解(L503-739,236 行)
这是 libnetwork 最重要的方法,值得逐段拆解:
前置校验 (L504-526):
- 用
networkLocker.Lock(id)和networkLocker.Lock(name)分别加锁------两次不同 key 的锁,防止并发创建同名/同 ID 网络。
- 重名直接返
NetworkNameError。
构造 Network 对象 (L528-546):
yaml
nw := &Network{
name: name, networkType: networkType,
generic: map[string]any{netlabel.GenericData: make(map[string]string)},
ipamType: defaultIpamForNetworkType(networkType), // 比如 bridge→default
enableIPv4: true,
id: id, created: time.Now(),
ctrlr: c, persist: true,
drvOnce: &sync.Once{},
loadBalancerMode: loadBalancerModeDefault,
}
nw.processOptions(options...)
processOptions 把外部传入的 []NetworkOption 依次 apply 到 nw 上,这是 functional options 的标准实现。
ConfigOnly 短路 (L564-568):
ini
if nw.configOnly {
nw.scope = scope.Local
nw.networkType = "null"
goto addToStore // !!! goto
}
config-only 网络只是配置模板,强制 type=null + scope=Local ,然后跳到 addToStore 标签直接持久化。这里用了 goto,在 Go 里少见但合法------因为 defer 链太长,用 if-else 嵌套可读性更差。注释 L553-557 解释了为什么 caps 和 err 变量必须在 goto 之前声明。
Driver 能力检查 (L570-580):
go
_, caps, err := c.resolveDriver(nw.networkType, true)
...
if nw.scope == scope.Local && caps.DataScope == scope.Global {
return nil, types.ForbiddenErrorf("cannot downgrade network scope for %s networks", networkType)
}
if nw.ingress && caps.DataScope != scope.Global {
return nil, types.ForbiddenErrorf("Ingress network can only be global scope network")
}
scope 不能"降级"------比如 overlay 是 global,你不能把它当 local 用;ingress 必须是 global。
Swarm 重定向 (L595-609):
kotlin
if (caps.DataScope == scope.Global || nw.scope == scope.Swarm) && c.isSwarmNode() && !nw.dynamic {
if c.isManager() {
return nil, ManagerRedirectError(name) // 让 wrapper 转给 manager
}
return nil, types.ForbiddenErrorf("Cannot create a multi-host network from a worker node...")
}
这是 swarm 的核心重定向逻辑 :global-scope 网络(如 overlay)必须由 manager 创建,worker 节点收到请求会得到 ManagerRedirectError。wrapper 层捕获这个 error 后会通过 cluster provider 转给 manager,manager 创建完再下发到所有 agent。agent=true 路径(即 manager 下发到 agent)会用 nw.dynamic=true 跳过这个检查。
IPAM 分配 + addNetwork + 持久化 + joinCluster (L623-738):
scss
nw.ipamAllocate() // 让 IPAM driver 分配子网
defer { if retErr != nil { nw.ipamRelease() } } // 失败回滚
c.addNetwork(ctx, nw) // 调 driver.CreateNetwork(创建 bridge/overlay 数据面)
defer { if retErr != nil { nw.deleteNetwork() } }
// DSR 选项的特殊处理(L677-682)
if gval, ok := nw.generic[netlabel.GenericData]; ok && nw.networkType == "overlay" {
if _, ok := optMap[overlayDSROptionString]; ok {
nw.loadBalancerMode = loadBalancerModeDSR
}
}
// 注释 L669-676 坦白这是 hack:driver 应该自己改 Network 字段,但 libnetwork.Network
// 是核心类型,driver 改不了,只能在 controller 里特判 overlay 的 DSR 选项。
c.updateToStore(ctx, epCnt) // 持久化 endpoint count(兼容旧版本)
c.storeNetwork(ctx, nw) // 持久化 Network
joinCluster(nw) // overlay 加入 gossip cluster
if nw.hasLoadBalancerEndpoint() {
nw.createLoadBalancerSandbox()
}
注意 L685-705 那段"先存 epCnt 再存 network"的注释------这是为了 downgrade 兼容 :旧版本(<28.1.0)启动时如果发现 network 但没 epCnt 会报错,所以即使新版本不再需要 epCnt,也得创建一个空的存进去。这种"代码看着多余但有历史包袱"的注释非常宝贵,新人看到不要随手删。
3.4 query 方法(L819-870)
scss
func (c *Controller) Networks(ctx context.Context) []*Network {
var list []*Network
for _, n := range c.getNetworksFromStore(ctx) {
if n.inDelete { continue }
list = append(list, n)
}
return list
}
func (c *Controller) WalkNetworks(walker NetworkWalker) {
if slices.ContainsFunc(c.Networks(context.TODO()), walker) {
return
}
}
func (c *Controller) NetworkByName(name string) (*Network, error) { ... }
func (c *Controller) NetworkByID(id string) (*Network, error) {
return c.getNetworkFromStore(id)
}
注意 NetworkByID 走的是 store ,不是内存表------因为内存表可能不一致(注释 L107-119 解释过)。每次都从 store 重新读一遍以保证拿到的 Network 是最新副本。这有点反直觉(性能差),但保证了正确性。
NetworkWalker 是个 func 类型(L84):
go
type NetworkWalker func(nw *Network) bool // 返回 true 停止遍历
用 walker 模式而不是返回 slice,可以让调用方提前 break ------比如 FindNetwork 找到第一个匹配就停。
3.5 NewSandbox(L873-973)
Sandbox 是 libnetwork 里和容器 1:1 对应的对象(每个容器一个 sandbox,代表其网络命名空间)。NewSandbox 流程:
- stub 复用 (
L882-900):遍历c.sandboxes找同 containerID 的 stub sandbox。stub 是从 store 恢复但还没真正 setup 的 sandbox------复用它而不是新建,这样之前已经存储但没 attach 的 endpoint 不会丢。
- 创建新对象 (
L904-920):非 stub 路径,new 一个 Sandbox,默认sandboxID = containerID(Windows)或stringid.GenerateRandomID()(其他平台)。
- processOptions (
L922):应用[]SandboxOption。
- ingress 特殊处理 (
L925-938):ingress sandbox 全局唯一(c.ingressSandbox),id 固定"ingress_sbox",hostsPath 和 resolvConfPath 强制写到cfg.DataDir。LoadBalancer sandbox 的 id 是"lb_" + nid。
- setup 文件 + OSL (
L950-955):setupResolutionFiles准备 /etc/hosts /etc/resolv.conf,setupOSLSandbox真正创建 netns(Linux)或对应的 Windows 隔离。
- 注册到 sandboxes 表 (
L957-966):defer 失败时回滚删除。
- storeUpdate (
L968):持久化。
stub sandbox 设计 :这是一个两阶段提交 的体现------restore 时先把存储里的 sandbox 全部以 stub 形式加载进内存,真正需要时(对应的容器启动)再"激活"。激活过程把 isStub = false,这样新创建的 endpoint 就能挂上去。
3.6 driver 解析(L1036-1117)
go
func (c *Controller) resolveDriver(name string, load bool) (driverapi.Driver, driverapi.Capability, error) {
d, capabilities := c.drvRegistry.Driver(name)
if d != nil {
return d, capabilities, nil
}
if !load { return nil, driverapi.Capability{}, nil }
err := c.loadDriver(name) // 尝试加载外部 plugin
...
d, capabilities = c.drvRegistry.Driver(name) // 再查一次
if d == nil {
return nil, driverapi.Capability{}, fmt.Errorf("could not resolve driver %s in registry", name)
}
return d, capabilities, nil
}
func (c *Controller) loadDriver(networkType string) error {
if pg := c.GetPluginGetter(); pg != nil {
_, err = pg.Get(networkType, driverapi.NetworkPluginEndpointType, plugingetter.Lookup)
} else {
_, err = plugins.Get(networkType, driverapi.NetworkPluginEndpointType)
}
...
}
懒加载模式 :driver 注册时只注册内置的;外部 plugin driver 第一次被用到时才 loadDriver 触发 plugin manager 拉起 plugin 进程。load=false 的分支用来"探测",不强制加载。
3.7 Agent 生命周期(L232-386)
swarm 模式下 libnetwork 内嵌一个 gossip agent(基于 serf),用于跨节点同步网络状态。核心方法:
scss
func (c *Controller) SetClusterProvider(provider cluster.Provider) {
...
go c.clusterAgentInit()
}
func (c *Controller) clusterAgentInit() {
for {
eventType := <-clusterProvider.ListenClusterEvents()
switch eventType {
case cluster.EventNetworkKeysAvailable:
keysAvailable = (c.keys != nil)
fallthrough
case cluster.EventSocketChange, cluster.EventNodeReady:
if keysAvailable && c.isSwarmNode() {
c.agentOperationStart()
if err := c.agentSetup(clusterProvider); err != nil {
c.agentStopComplete()
} else {
c.agentInitComplete()
}
}
case cluster.EventNodeLeave:
c.agentOperationStart()
c.agentClose()
c.cleanupServiceDiscovery("")
c.cleanupServiceBindings("")
c.agentStopComplete()
return
}
}
}
三个 channel 同步:
agentInitDone:agent 初始化完成的信号,AgentInitWait()阻塞等它。
agentStopDone:agent 停止完成的信号,AgentStopWait()阻塞等它。
agentOperationStart/agentInitComplete/agentStopComplete:管理上面两个 channel 的开闭。
注意 fallthrough:EventNetworkKeysAvailable fallthrough 到下一个 case,但 keysAvailable 已被设好------这是个用 fallthrough 实现"或条件"的技巧 ,因为 keysAvailable 还要叠加 swarm node 状态才能决定是否启动 agent。
EventNodeLeave 里的注释 (L317-323):
// We are leaving the cluster. Make sure we close the gossip so that we stop all
// incoming gossip updates before cleaning up any remaining service bindings. But before
// deleting the networks since the networks should still be present when cleaning up service bindings
关闭顺序 :先 agentClose(停止 gossip)→ 再清理 service bindings → 网络.Delayed 删除。如果反过来,清理 service bindings 时 gossip 还在收更新,会产生竞态。
第 4 章 Network/Endpoint/Sandbox:libnetwork 的核心三件套
4.1 三件套关系
scss
Controller
└─ networks map[]*Network
└─ Network (一个逻辑网络,如 bridge mynet)
├─ endpoints (从 store 读取)
│ └─ Endpoint (一个网卡槽位,含 IP/MAC)
└─ driver (bridge/overlay/...)
└─ sandboxes map[]*Sandbox
└─ Sandbox (一个容器的网络命名空间)
└─ endpoints []*Endpoint (容器接入的所有网络)
多对多关系:一个 Endpoint 同时只属于一个 Network 和一个 Sandbox。一个 Sandbox 可以接多个 Network(通过多个 Endpoint)。一个 Network 可以被多个 Sandbox 接入。
4.2 Network 结构(network.go:203-240)
字段太多不一一列举,挑几个关键的:
ctrlr *Controller:反指 controller,常用n.ctrlr.storeXXX调存储。
drvOnce *sync.Once:driver 初始化只做一次。
generic options.Generic:driver 自定义选项(mapstringany)。
ipamV4Config/ipamV6Config/ipamV4Info/ipamV6Info:IPv4/v6 的 IPAM 配置和运行时信息。
inDelete bool:是否在删除中(用于 race 防护)。
configOnly/configFrom:配置模板和来源网络名。
loadBalancerIP/loadBalancerMode:swarm LB IP 和模式(NAT/DSR)。
platformNetwork:Windows 特有字段(embed 进来,Linux 是空 struct)。
mu sync.Mutex:对象级锁。
注意 drvOnce 为什么单独是指针 ?因为 Network 会被序列化/反序列化(存到 bolt),sync.Once 不能复制,所以必须用指针避免值拷贝丢失状态。
4.3 CreateEndpoint(network.go:1161-1260)
go
func (n *Network) CreateEndpoint(ctx context.Context, name string, options ...EndpointOption) (*Endpoint, error) {
if strings.TrimSpace(name) == "" { return nil, ... }
if n.ConfigOnly() { return nil, ... }
if _, err = n.EndpointByName(name); err == nil { return nil, ... } // 重名
n.ctrlr.networkLocker.Lock(n.id)
defer n.ctrlr.networkLocker.Unlock(n.id)
return n.createEndpoint(ctx, name, options...)
}
func (n *Network) createEndpoint(ctx context.Context, name string, options ...EndpointOption) (*Endpoint, error) {
ep := &Endpoint{name: name, generic: make(map[string]any), iface: &EndpointInterface{}}
ep.id = stringid.GenerateRandomID()
ep.network = n
ep.network, err = ep.getNetworkFromStore() // 拿最新副本
...
ep.processOptions(options...)
...
ipam, capability, err := n.getController().getIPAMDriver(n.ipamType)
...
if capability.RequiresMACAddress {
if ep.iface.mac == nil {
ep.iface.mac = netutils.GenerateRandomMAC()
}
ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String()
}
wantIPv6 := n.enableIPv6 && !ep.disableIPv6
if err = ep.assignAddress(ipam, n.enableIPv4, wantIPv6); err != nil { return nil, err }
defer { if err != nil { ep.releaseIPAddresses() } }
if err = n.addEndpoint(ctx, ep); err != nil { return nil, err } // 调 driver.CreateEndpoint
defer { if err != nil { ep.deleteEndpoint(false) } }
if err = n.getController().storeEndpoint(ctx, ep); err != nil { return nil, err }
defer { if err != nil { ... deleteStoredEndpoint ... } }
return ep, nil
}
典型的"defer 链式回滚" ------每一步成功后就 defer 一个回滚动作,任何一步失败,所有已执行的回滚动作按 LIFO 顺序执行。这种模式在 Go 里非常常见, libnetwork 用得到位。
capability.RequiresMACAddress:有些 IPAM driver(比如基于 MAC 的地址分配)需要先有 MAC 才能分 IP,这是 driver capability 协商的一部分。
4.4 Endpoint.Join(endpoint.go:461-545)
scss
func (ep *Endpoint) Join(ctx context.Context, sb *Sandbox, options ...EndpointOption) error {
if sb == nil || sb.ID() == "" || sb.Key() == "" { return ... }
sb.joinLeaveMu.Lock()
defer sb.joinLeaveMu.Unlock()
return ep.sbJoin(ctx, sb, options...)
}
func (ep *Endpoint) sbJoin(ctx context.Context, sb *Sandbox, options ...EndpointOption) (retErr error) {
n, err := ep.getNetworkFromStore()
ep, err = n.getEndpointFromStore(ep.ID()) // 拿最新副本
...
ep.mu.Lock()
if ep.sandboxID != "" {
ep.mu.Unlock()
return types.ForbiddenErrorf("another container is attached to the same network endpoint")
}
ep.network = n
ep.sandboxID = sb.ID()
ep.joinInfo = &endpointJoinInfo{}
ep.mu.Unlock()
defer { if retErr != nil { ep.sbLeave(ctx, sb, n, true) } } // 失败回滚
ep.processOptions(options...)
d, err := n.driver(true)
...
if err := d.Join(ctx, nid, epid, sb.Key(), ep, ep.generic, sb.Labels()); err != nil {
return err
}
defer { if retErr != nil { d.Leave(nid, epid) } }
...
}
双锁 :sb.joinLeaveMu 和 ep.mu。joinLeaveMu 是 sandbox 级别的"加入/离开"串行锁------同一个 sandbox 不能并发 join 多个 endpoint(否则 gateway 变更逻辑会乱)。ep.mu 是 endpoint 自身的字段锁。注释 L60-63 明确:加锁顺序是 joinLeaveMu → mu ,释放前要等 gateway 变更应用完。
ep.sandboxID 检查 :一个 endpoint 同一时间只能被一个 sandbox 持有。如果容器 A 已经 join 了 endpoint X,容器 B 再 join 同一个 X 会被拒绝。这防止了"网卡共享"模式的误用(用户期望的"共享"应该通过 --network container:A 走不同路径)。
4.5 Sandbox(sandbox.go:40-72)
go
type Sandbox struct {
id string
containerID string
config containerConfig // hostsPath/resolvConf/generic 等
extDNS []extDNSEntry // 额外 DNS
osSbx *osl.Namespace // OS-level netns
controller *Controller
resolver *Resolver // 内嵌 DNS resolver
resolverOnce sync.Once
dbIndex uint64
dbExists bool
isStub bool // 从 store 恢复但未激活
inDelete bool // 在删除中
ingress bool // 是否是 ingress sandbox
ndotsSet bool
oslTypes []osl.SandboxType
loadBalancerNID string // LB sandbox 关联的网络 ID
mu sync.Mutex
// joinLeaveMu 必须在 mu 之前获取,且至少持有到 gateway 变更应用完
joinLeaveMu sync.Mutex
endpoints []*Endpoint
epPriority map[string]int
populatedEndpoints map[string]struct{}
service sync.Mutex // 串行化 endpoint 的 service 操作
}
Sandbox 是 libnetwork 离 Linux 内核最近的对象 :osSbx *osl.Namespace 直接对应一个 netns(Linux)或 compartment(Windows)。resolver 是 docker 内嵌 DNS(127.0.0.11),负责服务发现和容器互访。
三把锁的职责:
mu:保护普通字段读写。
joinLeaveMu:保护 endpoints 列表的变更,以及 join/leave 期间 gateway 的应用。
service:保护 service-related 操作(每次 endpoint 的服务绑定)。
注释里专门强调了锁顺序------这是 libnetwork 死锁高发区,任何新增并发代码都得遵守。
第 5 章 Driver 与 IPAM 注册:可插拔能力的来源
5.1 内置 driver 列表(drivers_linux.go:22-45)
go
func registerNetworkDrivers(r driverapi.Registerer, cfg *config.Config, store *datastore.Store, pms *drvregistry.PortMappers) error {
for _, nr := range []struct {
ntype string
register func() error
}{
{ntype: bridge.NetworkType, register: func() error { return bridge.Register(r, store, pms, cfg.BridgeConfig) }},
{ntype: host.NetworkType, register: func() error { return host.Register(r) }},
{ntype: ipvlan.NetworkType, register: func() error { return ipvlan.Register(r, store) }},
{ntype: macvlan.NetworkType, register: func() error { return macvlan.Register(r, store) }},
{ntype: null.NetworkType, register: func() error { return null.Register(r) }},
{ntype: overlay.NetworkType, register: func() error { return overlay.Register(r) }},
} {
if err := nr.register(); err != nil {
return fmt.Errorf("failed to register %q driver: %w", nr.ntype, err)
}
}
return nil
}
Linux 内置 6 个 driver。Windows 在 drivers_windows.go 里注册 nat/overlay/null/transparent/l2bridge 等。drivers_unsupported.go 是其他平台的 stub。
为什么注册函数签名不一样:
bridge.Register需要store(持久化配置)、pms(端口映射器)、cfg.BridgeConfig(docker0 配置)。
host.Register/null.Register啥都不要(这俩 driver 是 no-op)。
overlay.Register不需要 store(状态走 gossip)。
签名差异反映了 driver 的复杂度------bridge 是最复杂的 driver(有 docker0 接口、iptables、NAT),host/null 是最简单的。
5.2 PortMapper 注册(drivers_linux.go:47-66)
go
func registerPortMappers(ctx context.Context, r *drvregistry.PortMappers, cfg *config.Config) error {
var pdc *rlkclient.PortDriverClient
if cfg.Rootless {
var err error
pdc, err = rlkclient.NewPortDriverClient(ctx)
...
}
if err := nat.Register(r, nat.Config{RlkClient: pdc}); err != nil { ... }
if err := routed.Register(r); err != nil { ... }
return nil
}
端口映射器有两个:
nat:传统的 iptables DNAT 模式。
routed:无 NAT 的路由模式(-p端口直接路由到容器)。
rootless 模式下额外创建 rlkclient.PortDriverClient(RootlessKit API 客户端),因为 rootless 模式没有 NET_ADMIN 权限,需要委托给 RootlessKit 进程做端口映射。
为什么 portmapper 要先于 driver 注册 (controller.go:179-184):
// Register portmappers before network drivers to make sure they can
// restore existing sandboxes (with port mappings) during their
// initialization, if the daemon is started in live restore mode.
Live restore 模式下,daemon 重启时容器不重启。bridge driver 初始化时会遍历现有 sandbox 恢复端口映射规则------这要求 portmapper 已经 ready。
5.3 driver 接口(driverapi.Driver)
driver 接口定义在 daemon/libnetwork/driverapi/driverapi.go(本系列不深入),关键方法:
go
type Driver interface {
GetCapabilities() *Capability
CreateNetwork(ctx, nid string, options map[string]any, ipv4Data, ipv6Data []*IPAMData) error
DeleteNetwork(nid string) error
CreateEndpoint(ctx, nid, eid string, key string, ep EndpointInterface, options map[string]any) error
DeleteEndpoint(nid, eid string) error
EndpointOperationalInfo(nid, eid string) (map[string]any, error)
Join(ctx, nid, eid string, key string, jinfo JoinInfo, options map[string]any) error
Leave(nid, eid string) error
Type() string
IsBuiltIn() bool
}
Driver 接口和 Network/Endpoint/Sandbox 三件套的对应关系:
CreateNetwork↔Network对象创建后调
CreateEndpoint↔Endpoint对象创建后调
Join↔Endpoint.Join内部调,真正把网卡塞进 sandbox
Leave↔Endpoint.Leave内部调
libnetwork 是"控制面",driver 是"数据面" :libnetwork 管存储、状态机、并发,driver 管具体的内核接口(iptables、veth、vxlan)。这种分离让你可以同一个 libnetwork 配不同 driver,逻辑代码完全复用。
第 6 章 Swarm 联动:Agent、Ingress 与 ManagerRedirect
6.1 ManagerRedirectError:worker 节点上的"假创建"
kotlin
// controller.go:595-609
if (caps.DataScope == scope.Global || nw.scope == scope.Swarm) && c.isSwarmNode() && !nw.dynamic {
if c.isManager() {
if !nw.enableIPv4 {
return nil, types.InvalidParameterErrorf("IPv4 cannot be disabled in a Swarm scoped network")
}
// 非分布式环境下 global-scope 网络重定向到 manager
return nil, ManagerRedirectError(name)
}
return nil, types.ForbiddenErrorf("Cannot create a multi-host network from a worker node...")
}
swarm 网络的本质 :global-scope 网络(如 overlay)的状态需要跨节点同步,只有 manager 节点能创建 。worker 节点上 docker network create -d overlay foo 会得到 ManagerRedirectError。
wrapper 层(daemon.CreateNetwork)捕获这个 error,通过 daemon.clusterProvider 把请求转发给 manager。Manager 上创建完后,通过 gossip 分发到所有 agent,agent 调用 CreateManagedNetwork(即 createNetwork 的 agent=true 分支),用 nw.dynamic=true 跳过 ManagerRedirect 检查,完成本地编程。
这就是为什么 daemon.createNetwork 第 5 个参数叫 agent:它区分了"用户主动创建"和"agent 接收 manager 下发"两条路径。
6.2 SetClusterProvider 与 Agent 生命周期
scss
// daemon.go:893
func (daemon *Daemon) setClusterProvider(clusterProvider cluster.Provider) {
daemon.clusterProvider = clusterProvider
daemon.netController.SetClusterProvider(clusterProvider)
daemon.attachableNetworkLock = locker.New()
}
daemon 启动 swarm cluster 时,把 cluster provider 同时塞给 daemon 自己和 libnetwork controller。controller 拿到后,启动 clusterAgentInit goroutine 监听 cluster 事件。
cluster.Provider 接口 (daemon/libnetwork/cluster/cluster.go):
scss
type Provider interface {
IsManager() bool
IsAgent() bool
ListenClusterEvents() chan ClusterEvent
UpdateAttachment(networkName, containerID string, config *networktypes.NetworkingConfig) error
WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error
}
实现这个接口的就是 daemon/cluster/cluster.go 的 Cluster struct(Swarm 入口学习文档的主角)。所以 libnetwork 和 swarmkit 通过这个接口解耦------libnetwork 不依赖 swarmkit 具体实现,只依赖接口。
6.3 Ingress 网络:全局唯一
Ingress 网络是 swarm 集群的路由网格后端,整个集群只有一个。它的生命周期:
docker swarm init时 manager 检查 ingress 网络是否存在,不存在则创建。
- manager 通过 gossip 把 ingress 网络下发到所有 agent。
- agent 上的 daemon 收到下发,调
daemon.SetupIngress(create, nodeIP)。
SetupIngress把请求塞进ingressJobsChannel(单例 worker,见 §2.6)。
- worker 串行处理:有 create 就
setupIngress(创建 ingress 网络 + ingress sandbox),无 create 就releaseIngress(删除)。
- worker 通过
r.jobDone通知调用方完成。
为什么用单例 worker?因为 ingress 网络是集群级单例,多个 service 创建并发触发 ingress 编程时必须串行化,否则两个 setup 抢同一个 ingress sandbox 会乱套。
setupIngress 还做了 stale 检测(daemon/network.go:206-208):
lua
if staleID != "" && staleID != create.ID {
daemon.releaseIngress(staleID)
}
如果上次的 ingress ID 和这次不一样(用户重建了 ingress 网络),先释放老的。这是 idempotent operator 的设计------同一个 setup 调用反复执行结果一致。
6.4 SetKeys:overlay 加密
go
// controller.go:255-279
func (c *Controller) SetKeys(keys []*types.EncryptionKey) error {
subsysKeys := make(map[string]int)
for _, key := range keys {
if key.Subsystem != subsysGossip && key.Subsystem != subsysIPSec {
return errors.New("key received for unrecognized subsystem")
}
subsysKeys[key.Subsystem]++
}
for s, count := range subsysKeys {
if count != keyringSize {
return fmt.Errorf("incorrect number of keys for subsystem %v", s)
}
}
if c.getAgent() == nil {
c.mu.Lock()
c.keys = keys
c.mu.Unlock()
return nil
}
return c.handleKeyChange(keys)
}
overlay 网络在加密模式下需要两组 key:
subsysGossip:gossip 协议加密(控制面)。
subsysIPSec:VXLAN 数据面加密。
每个 subsystem 必须 keyringSize 个 key(主 key + 备 key,用于轮转)。SetKeys 是 swarm manager 下发新 key 的入口,daemon 收到后转给 libnetwork。
daemon.SetNetworkBootstrapKeys(daemon/network.go:249)在设置完后还会 daemon.cluster.SendClusterEvent(lncluster.EventNetworkKeysAvailable),反向通知 swarm cluster ------这触发了 cluster 那边的 clusterAgentInit 里 EventNetworkKeysAvailable 分支,完成双向同步。
第 7 章 代码索引
7.1 第 1 层(daemon/network/ 子包)
| 文件 | 关键导出 | 行数 |
|---|---|---|
network_mode.go |
DefaultNetwork, IsPredefined |
13 |
network_mode_unix.go |
defaultNetwork = NetworkBridge, isPreDefined |
15 |
network_mode_windows.go |
defaultNetwork = NetworkNat, isPreDefined |
12 |
settings.go |
Settings, EndpointSettings, AttachmentStore |
77 |
filter.go |
Filter, NewFilter, NewPruneFilter, FilterNetwork interface |
190 |
7.2 第 2 层(daemon 主包)
| 文件 | 关键方法 | 行数 |
|---|---|---|
daemon/network.go |
NetworkController, FindNetwork, GetNetworkByID, GetNetworkByName, GetNetworksByIDPrefix, getAllNetworks, CreateNetwork, createNetwork, CreateManagedNetwork, DeleteNetwork, deleteNetwork, DeleteManagedNetwork, GetNetworks, GetNetworkSummaries, ConnectContainerToNetwork, DisconnectContainerFromNetwork, GetNetworkDriverList, SetupIngress/ReleaseIngress/setupIngress/releaseIngress/enqueueIngressJob/startIngressWorker, SetNetworkBootstrapKeys, UpdateAttachment, WaitForDetachment, UpdateContainerServiceConfig, clearAttachableNetworks, buildNetworkResource, buildContainerAttachments, buildServiceAttachments, buildIPAMResources, buildEndpointResource, buildCreateEndpointOptions, buildPortsRelatedCreateEndpointOptions, buildJoinOptions, buildEndpointInfo, getPortMapInfo, getEndpointPortMapInfo, pluginRefCount, validateIpamConfig, validateIPRange, validateAddress, getIpamConfig |
1249 |
daemon/daemon.go:113 |
Daemon.netController 字段 |
--- |
daemon/daemon.go:893-896 |
setClusterProvider(同时塞给 netController) |
--- |
daemon/daemon.go:1731-1733 |
shutdown 时调 netController.Stop() |
--- |
daemon/daemon_unix.go:843-868 |
initNetworkController |
--- |
daemon/daemon_unix.go:891+ |
configureNetworking(创建预定义网络) |
--- |
daemon/daemon_windows.go:239-260 |
Windows 版 initNetworkController | --- |
daemon/container_operations.go:535-610 |
setupNetworking→netController.NewSandbox |
--- |
daemon/container_operations_unix.go:130 |
netController.GetSandbox(parent 容器) |
--- |
daemon/info.go:299-302 |
netController.FirewallBackend()(docker info) |
--- |
7.3 第 3 层(daemon/libnetwork/)
| 文件 | 关键内容 | 行数 |
|---|---|---|
controller.go |
Controller struct(L87), New(L147), SetClusterProvider(L233), SetKeys(L255), clusterAgentInit(L287), AgentInitWait/AgentStopWait(L336/347), agentOperationStart/agentInitComplete/agentStopComplete(L357/369/379), ID/BuiltinDrivers/BuiltinIPAMDrivers(L389/394/406), processNodeDiscovery/pushNodeDiscovery(L417/426), Config(L452), isManager/isAgent/isSwarmNode(L461/470/479), RegisterDriver(L487), NewNetwork(L503), joinCluster(L741), reservePools(L752), doReplayPoolReserve(L794), addNetwork(L803), Networks/WalkNetworks/NetworkByName/NetworkByID(L820/834/842/865), NewSandbox(L873), GetSandbox/SandboxByID/SandboxDestroy(L979/1003/1017), resolveDriver/loadDriver/loadIPAMDriver/getIPAMDriver(L1038/1062/1082/1101), Stop(L1120), StartDiagnostic/StopDiagnostic/IsDiagnosticEnabled(L1126/1131/1136) |
1138 |
network.go |
Network struct(L203), Name/ID/Created/Type/Driver(L249+), Delete(L983), CreateEndpoint(L1161), createEndpoint(L1181), Endpoints(L1263), ipamAllocate(L1459), ipamAllocateVersion(L1505) |
2207 |
endpoint.go |
Endpoint struct(L75), MarshalJSON/UnmarshalJSON(L105/132), Join(L461), sbJoin(L483), Leave(L725), Delete(L917) |
1369 |
sandbox.go |
Sandbox struct(L40), containerConfig(L98) |
676 |
store.go |
storeNetwork/deleteStoredNetwork/cacheNetwork/findNetworks |
139 |
network_store.go |
getNetworkFromStore/getNetworks/getNetworksFromStore/getEndpointFromStore/getEndpointsFromStore/updateToStore/deleteFromStore/networkCleanup |
139 |
endpoint_store.go |
endpoint 持久化 | --- |
sandbox_store.go |
sandbox 持久化 | --- |
drivers_linux.go |
registerNetworkDrivers(L22), registerPortMappers(L47) |
66 |
drivers_windows.go |
Windows driver 注册 | --- |
drivers_unsupported.go |
stub | --- |
controller_linux.go |
Linux 特有方法(如 selectFirewallBackend) |
--- |
default_gateway.go + 平台变体 |
默认网关逻辑 | --- |
endpoint_info.go + 平台变体 |
EndpointInfo interface |
--- |
resolver.go + 平台变体 |
内嵌 DNS resolver(127.0.0.11) | --- |
agent.go + agent.pb.go |
swarm gossip agent 实现 | --- |
service.go + 平台变体 |
swarm service 后端 | --- |
service_common.go |
service 共享逻辑 | --- |
sandbox_options.go |
SandboxOption 函数式选项 |
--- |
firewall_linux.go |
iptables/nftables 后端选择 | --- |
error.go + errors.go |
ErrNoSuchNetwork, NetworkNameError, ManagerRedirectError |
--- |
7.4 关键依赖包
| 包 | 作用 |
|---|---|
daemon/libnetwork/config |
daemon.json 网络配置编译 |
daemon/libnetwork/datastore |
bolt KV 持久化 |
daemon/libnetwork/driverapi |
Driver/EndpointInterface/JoinInfo 接口 |
daemon/libnetwork/ipamapi |
Ipam driver 接口 |
daemon/libnetwork/drvregistry |
driver/ipam/portmapper 三联注册表 |
daemon/libnetwork/cluster |
Provider 接口(被 daemon.Cluster 实现) |
daemon/libnetwork/scope |
Local / Global / Swarm 常量 |
daemon/libnetwork/netlabel |
网络选项 key 常量(如 netlabel.GenericData) |
daemon/libnetwork/types |
TransportPort, PortBinding, EncryptionKey 等类型 |
daemon/libnetwork/osl |
OS-level sandbox(netns/compartment 抽象) |
daemon/libnetwork/networkdb |
swarm 跨节点状态同步(gossip 上的 KV) |
daemon/libnetwork/options |
Generic map 类型 |
daemon/libnetwork/drivers/{bridge,host,ipvlan,macvlan,null,overlay} |
6 个内置 driver |
daemon/libnetwork/portmappers/{nat,routed} |
2 个端口映射器 |
daemon/libnetwork/internal/rlkclient |
RootlessKit 端口驱动 client |
第 8 章 总结:七组核心抽象
读完整个网络子系统,需要记住的核心抽象:
- 三层结构 :API 语义层(
daemon/network/) → wrapper(daemon/network.go) → libnetwork 本体(daemon/libnetwork/)。每层职责纯粹,wrapper 层只做翻译。
- 三件套:Network / Endpoint / Sandbox。Network 是逻辑网络,Endpoint 是网卡槽位,Sandbox 是容器网络命名空间。一个 Sandbox 通过多个 Endpoint 接入多个 Network。
- 三联注册表 :
drvRegistry(网络驱动) +ipamRegistry(IPAM 驱动) +pmRegistry(端口映射器)。注册顺序固定(portmapper → remote → builtin driver → ipam),依赖关系决定。
- 三把锁 :
networkLocker(按 key 防并发) +joinLeaveMu(串行化 join/leave) +mu(对象字段)。加锁顺序固定:networkLocker>joinLeaveMu>mu>service。
- 三种网络 scope :
Local(单节点,如 bridge) /Global(跨节点,如 overlay) /Swarm(swarm-managed)。scope 决定是否需要重定向到 manager。
- 三种创建路径 :用户路径(
agent=false,严校验) / agent 路径(agent=true,松校验) / config-only(短路到持久化)。同一个createNetwork函数走不同分支。
- 三层 swarm 集成 :
SetClusterProvider(注入 cluster) →clusterAgentInit(监听事件) →agentSetup/agentClose(启动/停止 gossip)。配合ManagerRedirectError+SetupIngress单例 worker 完成 swarm 网络的全生命周期。
后续要深入的方向:
- bridge driver 内部 :
daemon/libnetwork/drivers/bridge/(docker0、iptables、NAT 的具体实现)。
- overlay driver 内部 :
daemon/libnetwork/drivers/overlay/(VXLAN、加密、跨节点)。
- OSL :
daemon/libnetwork/osl/(netns 操作的 Linux/Windows 抽象)。
- agent + networkdb :
daemon/libnetwork/agent.go+networkdb/(swarm gossip 协议)。
这四个方向任选一个深入,都能进一步串起整个 docker 网络的全貌。本文档建立的是"骨架认知",骨架立住了,具体 driver 和协议的肌肉和皮肤就好填了。