Kube-OVN 固定 IP 实现原理分析
摘要
Kube-OVN 的固定 IP 分配机制主要通过以下三个核心决策与设计实现:
- 持久化层引入名为 IP 的 CRD 资源,并借助 ipsLister 机制对其进行高效的缓存读取与状态同步。
- 在删除 Pod 阶段(Controller 协调),通过判断 Pod 是否归属于未被删除的 StatefulSet 或 VirtualMachine,选择保留其对应的 IP CRD 资源与内存 IPAM 状态,而不是像普通 Pod 一样将其释放。
- 在 Pod 重建阶段,由于 StatefulSet 的 Pod 名字具备唯一性,Controller 会基于一致的网口名称(nicName)向 IPAM 申请 IP。IPAM 内存状态检测到该 nicName 已有 IP 映射,从而实现对已有 IP 的复用。
有关 Kube-OVN 的背景和更详细说明,可以参考「Kube-OVN 官方文档」。
背景与挑战
在 Kubernetes 的默认设计中,Pod 的网络配置具备动态性与瞬时性。Pod 重启或重建通常意味着其旧有的 IP 会被回收,并由 CNI 插件从 IP 池中随机分发一个新的 IP。
然而,在诸如传统数据库集群、主从架构状态服务,以及虚拟化桥接等场景中,服务通常依赖于稳定的 IP 地址。这就要求容器网络方案能够提供「固定 IP」的能力。Kube-OVN 针对这一痛点,通过结合 Kubernetes 自定义资源、控制器调谐逻辑与内置的 IPAM(IP 地址管理)引擎,实现了两类固定 IP 分配能力:
- 静态 IP 分配:用户在 Pod 的注解(Annotations)中显式指定一个 IP。
- 状态保持的固定 IP(StatefulSet / VM):当 Pod 重构或重启时,无须指定注解,系统会自动保持原有的 IP 不变。
核心设计与辅助资源
Kube-OVN 通过一套底层的自定义资源(CRD)和内存缓存读取器来实现网络配置的持久化与状态同步。
1. IP 资源(CRD)
Kube-OVN 在设计上并没有只依靠 OVN 或内存状态来管理 IP,而是定义了专有的 CRD 资源,定义文件在 pkg/apis/kubeovn/v1/ip.go。
其中,IP struct 代表了一个虚拟网卡或者说 IP 分配的实例。其关键的属性如下:
- PodName: 该 IPCR 绑定的 Pod 名称。
- Namespace: 对应的命名空间。
- Subnet: 分配该 IP 的主子网。
- NodeName: 该 Pod 被调度并运行的节点。
- V4IPAddress / V6IPAddress: 已分配的 IPv4 和 IPv6 地址。
- MacAddress: 已分配的 MAC 地址。
- PodType: 记录 Pod 的所属工作负载类型(如 StatefulSet,VirtualMachine,或 Deployment)。
2. ipsLister 的作用
在 Kube-OVN 控制器(kube-ovn-controller)中,ipsLister 是只读的客户端缓存组件(类型为 kubeovnlisters.IPLister)。
- 它的作用是从 client-go 控制器维护的本地 Informer 缓存中,以极高的效率检索和获取集群中已存在的 IP 资源,避免频繁地向 Kubernetes API Server 发送网络请求。
- 在逻辑上,它是 IP 这一 CRD 资源在内存中的反射镜像窗口。
- 在控制器初始化时,它用于批量列出(List)所有 IP 实例来同步内存 IPAM 状态;在调谐与清理流程中,它用于单例查询(Get)特定 Pod 网络端口的 IP 信息。
固定 IP 工作流与机制
Kube-OVN 固定 IP 的生命周期涵盖了:准入控制、Controller 地址分配与持久化、Pod 删除时的保留决策、以及 CNI 配置。
流程 1:静态 IP 准入与校验
当用户显式配置了静态 IP 注解「ovn.kubernetes.io/ip_address」时,Kube-OVN 的 Mutating/Validating Webhook 会在 Pod 创建请求被处理前进行拦截。
图 1:Webhook 静态 IP 校验与准入流程
graph TD
A[用户创建/更新 Pod] --> B[Webhook 拦截请求]
B --> C{是否配置静态 IP/IPPool 注解?}
C -- 是 --> D[验证 IP CIDR 格式合法性]
D --> E[验证 IP 协议家族唯一性]
E --> F[从 cache 中检索已分配 IP 列表]
F --> G{是否存在 IP 地址冲突?}
G -- 是 --> H[拒绝请求: 返回 Denied 并阻断 Pod 创建]
G -- 否 --> I[放行请求: 允许 Pod 创建继续]
C -- 否 --> I
图 1 描述:此图展示了 Webhook 在 API 准入阶段的处理逻辑。通过解析 Pod 注解中的 IP 请求,如果存在冲突或非法的多网卡同协议 IP 地址(例如在一个注解里重复写两个 IPv4),则直接阻断 Pod 调度。
文字实现机制: 在 pkg/webhook/static_ip.go 中:
- ValidatingHook 的 PodCreateHook 方法被调用。
- validateIPConflict 函数调用 checkIPAddressFamilyUniqueness 检测指定 IP 是否存在同一家族的多地址异常(例如填入了两个 IPv4 地址)。
- 调用 checkIPConflict 比对现存的 IP CRD 列表(利用 cache 列表检索),防止已被占用的静态 IP 被二次分配。
流程 2:Controller 的 IP 分配与保存机制
Controller 在调谐 Pod 时,会分配网络。入口函数在 pkg/controller/pod.go 的 reconcileAllocateSubnets。
对于 StatefulSet 或 VM,Pod 随时可能因为升级、重启而被重建。如果重建时 IP 被释放,新建的 Pod 就会拿到新的随机 IP。因此其生命周期的保存是固定 IP 的核心逻辑。
图 2:Pod 生命周期协调与地址保存逻辑
graph TD
A[Pod 被删除] --> B{是否为 StatefulSet/VM?}
B -- 是 --> C{Owner 资源是否也被删除?}
B -- 否 --> D[普通清理: 删除 IP CRD 且释放 IPAM 地址]
C -- 否 --> E[保留模式: keepIPCR 为 true, 跳过 IP CRD 与 IPAM 清理]
C -- 是 --> D
图 2 描述:此图展示了 Pod 被销毁时,kube-ovn-controller 在 handleDeletePod 中的协调决策。如果是正常的 RollingUpdate 或手动删除 Pod 触发的重建,只要 Owner 资源(StatefulSet/VM)依然存在,IP CRD 状态将保持不变,IP 资源不会被释放回 IP 池。
文字实现机制:
- 调谐分配:reconcileAllocateSubnets 调用 acquireAddress。在 acquireAddress 内部,若识别到 Pod 带有静态 IP 注解,会分流至 acquireStaticAddressHelper。通过 acquireStaticAddress 进一步调用内存 IPAM(即 pkg/ipam/ipam.go 中的 GetStaticAddress)预留特定 IP,并调用 createOrUpdateIPCR 方法创建 IP CRD 实例。同时在 Pod 的注解上打上代表分配完成的「ovn.kubernetes.io/allocated: “true”」以及 IP、MAC、子网等参数。
- 销毁保护:在 pkg/controller/pod.go 的 handleDeletePod 逻辑中,识别 Pod 归属。如果只是 Pod 本身被删除,而 StatefulSet 资源没有被删,Controller 会判定 keepIPCR = true。若 keepIPCR 为 true,程序在 L1324-L1328 将执行 skip clean ip CR 逻辑,跳过对该 Pod IP CRD 资源的删除,也不从内存 IPAM 释放对应的 IP(不调用 ReleaseAddressByNic)。
流程 3:Controller 启动重建与内存 IPAM 初始化
一旦 Controller 重启,或者新主节点选举发生,Controller 需要保证内存 IPAM 数据的正确性。这由 pkg/controller/init.go 处的 Init 流程处理。
图 3:启动时从持久化存储恢复内存 IPAM 状态
graph TD
A[Controller 启动初始化] --> B[第一阶段: 扫描集群 IP CRD]
B --> C{是否为 StatefulSet 或 VM 类型的 IPCR?}
C -- 是 --> D[调用 GetStaticAddress 注册并锁定至内存 IPAM]
C -- 否 --> E[跳过,留待存活 Pod 扫描阶段处理]
D --> F[第二阶段: 扫描存活 Pod 列表]
E --> F
F --> G{Pod 是否运行且已分配网络?}
G -- 是 --> H[加载 Pod 注解中的 IP/MAC 至内存 IPAM]
H --> I[调用 createOrUpdateIPCR 确保同步]
G -- 否 --> J[跳过]
I --> K[初始化完成]
J --> K
图 3 描述:此图展示了 Controller 重启时加载已分配 IP 的过程。系统采用两阶段扫描,首先把被保留在集群中的 StatefulSet/VM IP CRD 信息同步至内存,随后扫描存活 Pod 补充加载普通 Pod 网络信息,从而防范了重启导致内存状态丢失引起 IP 被二次分配的风险。
文字实现机制:
- 首先,Init 调用 c.ipsLister.List(labels.Everything()) 扫描所有的 IP 资源。
- 检查 ip.Spec.PodType。对于工作负载类型为 StatefulSet 或 VM 的 IP CRD,无论关联的 Pod 此时是否存在,均将其对应的 IP、MAC 通过 c.ipam.GetStaticAddress 加载回内存 IPAM 中,将其锁定。
- 其次,它扫描存活的 Pod 列表,补充加载运行中的 Pod 占用的 IP 资源。
流程 4:Pod 重建时的 IP 复用
当相同的 StatefulSet Pod 重建时,其名称不变(例如 sts-0)。Controller 会重新调谐该 Pod。
图 4:重建 Pod 时的 IPAM 匹配与复用
graph TD
A[Pod 重建并被 Controller 调谐] --> B[调用 reconcileAllocateSubnets]
B --> C[向 IPAM 请求随机分配: 传入 nicName]
C --> D{IPAM 内存状态中是否存在该 nicName 映射?}
D -- 是 --> E[直接复用: 返回已绑定的原有 IP 和 MAC 地址]
D -- 否 --> F[从 IP 池中分配新地址并新建 IP CRD]
E --> G[Controller 更新 Pod annotations 标记 allocated 为 true]
F --> G
图 4 描述:此图展示了 Pod 重建时,IPAM 内部的复用路径。虽然 Pod 上没有指定静态 IP 的注解,但是 IPAM 通过比对与上一次相同的虚拟网口名称(nicName,即 podName.namespace.provider),在内存数据库中直接命中了之前未释放的缓存,从而达成 IP 地址的固定。
文字实现机制:
- Controller 重新调谐该 Pod 并调用 reconcileAllocateSubnets。由于没有静态 IP 注解,它会进入随机分配分支,调用 c.ipam.GetRandomAddress(key, portName, …). 这里的 portName 保持一致。
- 深入 pkg/ipam/subnet.go 的 getV4RandomAddress 方法中,IPAM 会首先检查缓存,如果 s.V4NicToIP[nicName] != nil,且 MAC 匹配,直接返回原有的 IP 和 MAC 记录,不再进行新的地址划分。
流程 5:CNI 插件与节点 Daemon 的协作执行
在 Controller 将 IP 配置完成并将「allocated: “true”」写入 Pod 之后,运行在 Pod 所在节点上的 kube-ovn-daemon 才会开始工作。
图 5:CNI 插件与节点 Daemon 配置网络接口
graph TD
A[Kubelet 触发 CNI ADD 请求] --> B[CNI Plugin 向本地 Daemon 发起 RESTful 请求]
B --> C[Daemon 读取缓存中的 Pod 实例]
C --> D{Allocated 注解是否为 true?}
D -- 否 --> E[未分配完成: 循环等待并重试]
E --> D
D -- 是 --> F[解析 Pod 注解中的 IP/MAC/子网/网卡类型等]
F --> G[在宿主机创建网络接口并打入容器 Namespace]
G --> H[设置容器内 IP 路由并启动网口]
H --> I[返回成功响应给 Kubelet]
图 5 描述:此图展示了从 kubelet 触发 CNI 请求到最终网卡在容器内建立配置的完整链路。CNI plugin 与节点上的 daemon 会循环轮询 Pod 的注解,同步等待 controller 在控制面完成 IP 分配并标记为 true 之后,再拉取地址数据配置容器网卡,确保网络状态的一致。
文字实现机制: 在 pkg/daemon/handler.go 的 handleAdd 方法中:
- 进程进入循环,等待并拉取 Pod annotations。
- 观察到 AllocatedAnnotationTemplate(“ovn.kubernetes.io/allocated”)的值被更新为 “true” 后,说明 Controller 分配动作已完成。
- 读取 Pod 注解中的 IP 路由等信息。在节点上进行网卡创建(如 veth pair)并将接口打入容器 Namespace,将已固定的 IP 地址和路由刷入容器内,完成整个配置。
设计总结
Kube-OVN 通过一套组合机制完美实现了 Pod IP 地址的固定:
- 数据面:使用独立的 IP 资源(CRD)作为分布式状态的物理存储,并利用 ipsLister 实现轻量高效的只读缓存查找。
- 控制面调谐:通过 Owner 关系智能决定在 Pod 删除时是否保持 IP 配置。
- IPAM 引擎:通过把唯一的端口标识(nicName)映射到 IP 地址,实现了“名称即地址”的复用链路。
这种基于 Controller 声明式管理与 IPAM 状态缓存的架构,保证了固定 IP 在云原生弹性的应用生命周期中表现稳定。