Cilium 设计 Identifier(识别符)系统的初衷

4 阅读4分钟

toolName: view_files

status: success

filePath: /root/f/cilium/pkg/endpoint/id/id.go

toolName: view_files

status: success

filePath: /root/f/cilium/pkg/endpointmanager/manager.go

好的,我将从代码层面仔细梳理 Cilium 设计 Identifier(识别符)系统的初衷。

核心初衷:多维度的 Endpoint 索引与查找

通过分析 pkg/endpoint/id/id.go(定义识别符)和 pkg/endpointmanager/manager.go(使用识别符),我们可以清晰地看到,Cilium 设计 Identifier 系统的核心初衷是:为了满足不同组件在不同场景下,通过仅有的信息快速定位到同一个 Endpoint 对象的需求。

Cilium 是一个复杂的系统,不同的子系统(Subsystems)持有 Endpoint 的不同属性作为“把手”(Handle)。Identifier 系统就是为了将这些“把手”统一映射到内存中的 Endpoint 结构体。

1. 满足不同组件的查找需求

我们来看看 pkg/endpoint/id/id.go 中定义的各种 PrefixType,它们分别对应了哪些组件的查找需求:

  • CNI 插件 (CNIAttachmentIdPrefix)

    • 场景: 当 Kubernetes 调用 CNI 插件(Add/Del)时,CNI 插件只知道 Container IDInterface Name
    • 需求: CNI 需要根据这两个信息找到对应的 Endpoint 进行配置或删除。
    • 代码: NewID(CNIAttachmentIdPrefix, containerID + ":" + ifName)
  • 容器运行时/Docker (ContainerIdPrefix, DockerEndpointPrefix)

    • 场景: 与 Docker 或其他容器运行时交互时,通常使用 Container IDDocker Endpoint ID
    • 需求: 通过容器 ID 关联到 Cilium Endpoint。
  • Kubernetes 控制平面 (PodNamePrefix, CEPNamePrefix)

    • 场景: Cilium Agent 监听 K8s API Server 的事件(如 Pod 更新、CiliumEndpoint 更新)。
    • 需求: 当收到一个 Pod 事件时,Controller 需要通过 Namespace/PodName 快速找到本地对应的 Endpoint,以更新 Labels 或 Policy。
  • 数据平面/网络栈 (IPv4Prefix, IPv6Prefix)

    • 场景: 当网络包进入 Datapath,或者接收到 ARP/NDP 请求时,系统只知道 IP 地址。
    • 需求: 根据 IP 地址快速查找 Endpoint,进行路由决策、策略执行或回复 ARP。
  • 内部管理 (CiliumLocalIdPrefix)

    • 场景: Cilium 内部逻辑,如 BPF Map 中的 Key(通常是数字 ID)。
    • 需求: 使用一个紧凑的整数 ID (uint16) 来代表 Endpoint,优化 BPF Map 的存储和查找效率。

2. 统一的索引实现 (endpointsAux)

pkg/endpointmanager/manager.go 中,Cilium 并没有为每种查找方式维护单独的 Map(例如没有 map[string]*Endpoint for containerID, map[string]*Endpoint for IP...)。

相反,它设计了一个统一的辅助索引 Map

// pkg/endpointmanager/manager.go

type endpointManager struct {
    // 主索引:数字 ID -> Endpoint
    endpoints    map[uint16]*endpoint.Endpoint

    // 辅助索引:通用字符串标识符 -> Endpoint
    endpointsAux map[string]*endpoint.Endpoint
    // ...
}

所有的查找逻辑(LookupCNIAttachmentID, LookupIPv4, LookupPodNameLocked 等)最终都是调用同一个 Map:

func (mgr *endpointManager) lookupIPv4(ipv4 string) *endpoint.Endpoint {
    // 构造 Key: "ipv4:" + ipv4
    if ep, ok := mgr.endpointsAux[endpointid.NewID(endpointid.IPv4Prefix, ipv4)]; ok {
        return ep
    }
    return nil
}

设计初衷总结

  1. 统一性: 将所有维度的查找统一到一个 Key-Value 存储中,简化了状态管理。
  2. 灵活性: 只需要定义新的 Prefix,就可以轻松支持新的查找维度(如我们刚刚添加的 vni-ipv4),而不需要修改底层数据结构。
  3. 一致性: 确保任何维度的 Key 在系统中是唯一的(Map 的特性)。

IP 冲突检测:只是“索引唯一性”的副作用

你提到的“IP 冲突检测”,实际上并不是 Identifier 系统设计的目的,而是它作为索引系统的一个自然属性

  • 因为 endpointsAux 是一个 Map。
  • 因为 Map 的 Key 必须唯一。
  • 所以,当你试图插入一个已存在的 Key(如 ipv4:10.0.0.1)时,就必须失败。
  • 这个“插入失败”在业务逻辑上就被解释为“IP 冲突”。

所以,Cilium 设计 Identifier 主要是为了“能找到”(Lookup),而“防冲突”(Conflict Detection)是“能找到”的前提条件(索引必须唯一)。

Native-VPC 的扩展逻辑

在 Native-VPC 模式下,我们引入 vni-ipv4,其初衷也是为了扩展索引维度

  • 问题: 在多 VPC 场景下,仅靠 IP (ipv4:10.0.0.1) 已经不足以唯一标识一个 Endpoint 了(因为不同 VPC 可能有相同的 IP)。
  • 解决: 我们需要更多的信息来定位 Endpoint,即 VNI
  • 实现: 定义新的索引 Key vni-ipv4:<vni>:<ip>
    • 这使得 Cilium 可以通过 VNI + IP 这对组合信息,在 endpointsAux唯一地索引查找到一个 Endpoint。
    • 同时也利用 Map 的唯一性,防止了同一 VNI 下的 IP 冲突。

希望这次梳理能更清晰地解答关于 Identifier 系统设计初衷的疑问。