docker网络学习

全局视图

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 的完整旅程

  1. CLI/REST :POST /networks/create body {Name:"mynet", Driver:"bridge"}
  1. router/server 路由到 daemon/server/daemon.go 里注册的 handler。
  1. handler 调用 backend.CreateNetwork(ctx, req),即 Daemon.CreateNetwork(daemon/network.go:290)。
  1. Daemon.CreateNetwork 包一层 runInNetNS(rootless 模式下要切到容器 netns),然后调真正的 daemon.createNetwork(daemon/network.go:300)。
  1. createNetwork 干 6 件事:
    1. network.IsPredefined(name) 拦掉 none/host/bridge 等保留字;
    1. 默认 driver 用 c.Config().DefaultDriver;
    1. overlay 但非 manager 直接 Forbidden(swarm-scoped 网络必须 manager 创建);
    1. 合并 cfg.DefaultNetworkOpts[driver](daemon.json 里 per-driver 默认参数);
    1. enableIPv4/IPv6internalattachableingressconfigOnlyconfigFrom、IPAM 配置等翻译成一串 libnetwork.NetworkOption(变参函数式选项);
    1. 调用 c.NewNetwork(ctx, driver, name, id, options...) 进入 libnetwork 本体。
  1. libnetwork.NewNetwork (controller.go:503)锁住 networkLocker(按 id 和 name 各加一把)、校验 scope、resolveDriver 找 driver、nw.ipamAllocate() 分配子网、c.addNetwork 调 driver 的 CreateNetworkstoreNetwork 持久化、joinCluster(overlay 加入 gossip)、必要的话创建 LoadBalancer sandbox。
  1. 返回 *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 网络状态

SettingsEndpointSettings容器对象上挂载 的网络状态(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 加上 IPAMOperationalDesiredMacAddress。注释明确说明:"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 会编译失败。

matchesTypetype=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)

核心步骤(按行号) :

  1. L301 拦预定义网络 :network.IsPredefined(create.Name) 直接返 PredefinedNetworkError(实现 Forbidden() 接口,API 层会回 403)。
  1. L306-309 driver 默认值 :空 driver 用 c.Config().DefaultDriver,Linux 是 "bridge"
  1. L311-313 swarm 拦截 :overlay 且 !IsManager() && !agent 直接 Forbidden,提示用户先 docker swarm init/join
  1. L315-324 daemon.json 默认参数 :从 cfg.DefaultNetworkOpts[driver] 取 per-driver 默认值合并进 networkOptions,用户 CLI 显式传的优先 (if _, ok := networkOptions[k]; !ok)。
  1. L326-350 enableIPv4/IPv6 三态解析 :create.EnableIPv4*bool,有三种状态(nil/true/false),加上 driver-opt 兜底。这是个经典三态字段陷阱,看注释能感觉到作者维护时的痛苦。
  1. L352-408 组装 []libnetwork.NetworkOption:把扁平的 CreateRequest 重新组装回函数式选项。看这一段能学到 Go 社区流行的 functional options 模式:NetworkOptionEnableIPv4(v) / NetworkOptionDriverOpts(m) 等。
  1. L410-417 swarm agent LB IP :agent 模式且 driver=overlay,从 AttachmentStore 取节点 LB IP 设进去。
  1. L419 真正调用 libnetwork :c.NewNetwork(ctx, driver, create.Name, id, nwOptions...)
  1. L424-427 插件引用计数 :pluginRefCount 给 driver 和 IPAM driver 各加一次引用,内置 driver 用 c.BuiltinDrivers() / c.BuiltinIPAMDrivers() 跳过(避免内置 driver 误触发插件加载)。
  1. L428 事件 :LogNetworkEvent(n, events.ActionCreate) 发 create 事件给 docker events 订阅者。

2.5 wrapper 反向序列化:buildNetworkResource

API 返回的不是 *libnetwork.Network,而是 networktypes.Networkdaemon/network.go:734buildNetworkResource 做反向翻译:

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 流程:

  1. stub 复用 (L882-900):遍历 c.sandboxes 找同 containerID 的 stub sandbox。stub 是从 store 恢复但还没真正 setup 的 sandbox------复用它而不是新建,这样之前已经存储但没 attach 的 endpoint 不会丢。
  1. 创建新对象 (L904-920):非 stub 路径,new 一个 Sandbox,默认 sandboxID = containerID(Windows)或 stringid.GenerateRandomID()(其他平台)。
  1. processOptions (L922):应用 []SandboxOption
  1. ingress 特殊处理 (L925-938):ingress sandbox 全局唯一(c.ingressSandbox),id 固定 "ingress_sbox",hostsPath 和 resolvConfPath 强制写到 cfg.DataDir。LoadBalancer sandbox 的 id 是 "lb_" + nid
  1. setup 文件 + OSL (L950-955):setupResolutionFiles 准备 /etc/hosts /etc/resolv.conf,setupOSLSandbox 真正创建 netns(Linux)或对应的 Windows 隔离。
  1. 注册到 sandboxes 表 (L957-966):defer 失败时回滚删除。
  1. 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.joinLeaveMuep.mujoinLeaveMu 是 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 三件套的对应关系:

  • CreateNetworkNetwork 对象创建后调
  • CreateEndpointEndpoint 对象创建后调
  • JoinEndpoint.Join 内部调,真正把网卡塞进 sandbox
  • LeaveEndpoint.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(即 createNetworkagent=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 集群的路由网格后端,整个集群只有一个。它的生命周期:

  1. docker swarm init 时 manager 检查 ingress 网络是否存在,不存在则创建。
  1. manager 通过 gossip 把 ingress 网络下发到所有 agent。
  1. agent 上的 daemon 收到下发,调 daemon.SetupIngress(create, nodeIP)
  1. SetupIngress 把请求塞进 ingressJobsChannel(单例 worker,见 §2.6)。
  1. worker 串行处理:有 create 就 setupIngress(创建 ingress 网络 + ingress sandbox),无 create 就 releaseIngress(删除)。
  1. 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 那边的 clusterAgentInitEventNetworkKeysAvailable 分支,完成双向同步。

第 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 setupNetworkingnetController.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 章 总结:七组核心抽象

读完整个网络子系统,需要记住的核心抽象:

  1. 三层结构 :API 语义层(daemon/network/) → wrapper(daemon/network.go) → libnetwork 本体(daemon/libnetwork/)。每层职责纯粹,wrapper 层只做翻译。
  1. 三件套:Network / Endpoint / Sandbox。Network 是逻辑网络,Endpoint 是网卡槽位,Sandbox 是容器网络命名空间。一个 Sandbox 通过多个 Endpoint 接入多个 Network。
  1. 三联注册表 :drvRegistry(网络驱动) + ipamRegistry(IPAM 驱动) + pmRegistry(端口映射器)。注册顺序固定(portmapper → remote → builtin driver → ipam),依赖关系决定。
  1. 三把锁 :networkLocker(按 key 防并发) + joinLeaveMu(串行化 join/leave) + mu(对象字段)。加锁顺序固定:networkLocker > joinLeaveMu > mu > service
  1. 三种网络 scope :Local(单节点,如 bridge) / Global(跨节点,如 overlay) / Swarm(swarm-managed)。scope 决定是否需要重定向到 manager。
  1. 三种创建路径 :用户路径(agent=false,严校验) / agent 路径(agent=true,松校验) / config-only(短路到持久化)。同一个 createNetwork 函数走不同分支。
  1. 三层 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 和协议的肌肉和皮肤就好填了。

相关推荐
达达尼昂1 小时前
在 Claude Cowork 中用好 Claude Fable 5
android·人工智能·后端
HONG````2 小时前
HarmonyOS ArkUI Button 组件深度解析:类型、状态与自定义样式
后端
ThanksGive2 小时前
TurboLock:从 v1 Redis 分布式锁到 v2 Go 并发控制中间件
分布式·后端
贺国亚2 小时前
特征平台-Feature-Store设计
后端
杨运交2 小时前
[049][Crypto模块]前后端混合加密API实战:基于Spring Boot的AES+RSA安全传输方案
spring boot·后端·安全
geovindu2 小时前
CSharp: Recursion Algorithm
开发语言·后端·算法·c#·递归算法
Eofer2 小时前
零成本内网自定义域名 + Nginx 建站完整教程|家用路由 + Debian 本地搭建
后端
自进化Agent智能体2 小时前
Hermes Agent 消息网关——连接 20+ 聊天平台
后端
你为她披上外套时我正站在窗外2 小时前
subtitler v2.0:从稳健到极致
后端