K3s ServiceLB 云负载均衡控制器迁入云控制器管理器(CCM):基于 cloudprovider.LoadBalancer 接口的架构演进深度解析 📅 发布时间:2026/9/10 4:25:48 👁 浏览次数: K3s ServiceLB 云负载均衡控制器迁入云控制器管理器CCM基于 cloudprovider.LoadBalancer 接口的架构演进深度解析【免费下载链接】k3sLightweight Kubernetes项目地址: https://gitcode.com/GitHub_Trending/k3/k3s导读本文基于 K3s 官方架构决策记录ADRdocs/adrs/servicelb-ccm.md完整还原 K3s 将 ServiceLB 负载均衡控制器从独立接入 Wrangler 核心控制器的私有实现重构为内嵌云控制器管理器CCM中 cloudprovider.LoadBalancer 接口后端的演进全过程。你将理解 K3s 为何放弃自建 Service 监听与 finalizer 管理逻辑、如何通过 Kubernetes 官方云提供商接口复用核心代码以及--disable-cloud-controller、--disableservicelb两个开关在重构前后的语义变化与资源收益。一、背景一个只做节点生命周期的桩云提供商K3s 一直内置一个桩stub云提供商它只实现了cloudprovider.Instances接口中与节点生命周期相关的极少部分功能正确设置节点的地址node addresses清除节点首次加入集群时被添加的Uninitialized污点taint使节点能正常参与调度。这一点可以在源码中得到印证pkg/cloudprovider/instances.go 中的k3s结构体实现了cloudprovider.InstancesV2接口InstanceExists恒返回trueK3s 节点始终存在、InstanceShutdown恒返回falseK3s 节点从不被云侧关闭而InstanceMetadata则负责从节点注解annotation与标签label中解析内网 IP、外网 IP、DNS 与主机名。Kubernetes 的云提供商接口cloudprovider.Interface本身为负载均衡控制器预留了扩展点cloudprovider.LoadBalancer但 K3s 当时并未实现它。相反K3s 选择运行一个独立standalone的 ServiceLB 控制器直接挂接在核心 Wrangler 控制器体系上。ADR 记录的时间点为 2022-09-29状态为Accepted已采纳。二、问题所在重复造轮子的独立控制器ADR 明确指出由于没有走 Kubernetes 官方的负载均衡接口独立 ServiceLB 控制器被迫从零实现大量本应由核心 Kubernetes 代码代劳的逻辑监听 Service 资源需要自己注册 informer 并编写变更回调类型与状态过滤需要自行判断一个 Service 是否是LoadBalancer类型、当前状态是否符合处理条件管理 finalizer需要在 Service 上手动添加/移除 finalizer以控制资源的清理顺序与所有权转移。这些逻辑如果实现了cloudprovider.LoadBalancer接口全部由 Kubernetes 核心的 service 控制器统一调度处理属于典型的重复造轮子。同时独立控制器与云提供商机制并存也让 K3s 的组件边界变得模糊。三、决策把 ServiceLB 搬进云控制器管理器ADR 的决策只有一句话但信息量很大将 ServiceLB 代码迁移到云控制器cloud-controller中作为LoadBalancer接口实现的后端。同时明确了两条必须保留的既有行为保留禁用节点生命周期功能的既有行为用户仍然可以一边使用 ServiceLB一边挂载其他负责节点生命周期的 cloud-controller-manager即 ServiceLB 不再与 CCM 强绑定保留通过节点标签node labels定制 ServiceLB 行为的支持。这一决策在源码中的直接体现是 pkg/cloudprovider/cloudprovider.gok3s结构体通过var _ cloudprovider.Interface k3s{}声明完整实现 Kubernetes 云提供商接口并在 init() 中调用cloudprovider.RegisterCloudProvider(version.Program, ...)完成注册。3.1 可外部配置的 Config 结构pkg/cloudprovider/cloudprovider.go 定义了 JSON 反序列化的配置结构这是理解哪些功能可以被开关控制的关键配置字段JSON 键说明默认值LBDefaultPriorityClassNamelbDefaultPriorityClassNameServiceLB Pod 默认 PriorityClasssystem-node-criticalLBEnabledlbEnabled是否启用负载均衡控制器trueLBImagelbImageServiceLB 使用的镜像rancher/klipper-lb:v0.4.17LBNamespacelbNamespaceServiceLB DaemonSet 所在命名空间kube-systemNodeEnablednodeEnabled是否启用节点生命周期功能trueRootlessrootless是否以 rootless 模式运行false值得注意的是默认值的硬编码位置pkg/cloudprovider/servicelb.go 中定义了DefaultLBNS meta.NamespaceSystem即kube-system、DefaultLBPriorityClassName system-node-critical与DefaultLBImage。若传入的 config 同时关闭了 LB 与节点功能init()会直接返回错误all cloud-provider functionality disabled by config。3.2 接口分派LB 与节点功能可独立开关pkg/cloudprovider/cloudprovider.go 中接口方法的返回值直接决定了功能开关的语义LoadBalancer()返回k, k.LBEnabled仅当LBEnabled为真时K3s 的 CCM 才向 Kubernetes 暴露负载均衡能力InstancesV2()返回k, k.NodeEnabled仅当NodeEnabled为真时才暴露节点实例能力Zones()、Clusters()、Routes()均返回nil, false即 K3s 云提供商不支持这些扩展。这正对应 ADR 中ServiceLB 可与其他处理节点生命周期的 CCM 并存的承诺LBEnabled与NodeEnabled是正交的。四、ServiceLB 控制器的工作原理源码级拆解迁移后的 ServiceLB 全部实现位于 pkg/cloudprovider/servicelb.go下面按职责拆解其核心链路。4.1 标签与 finalizer 常量pkg/cloudprovider/servicelb.go 定义了控制器全生命周期使用的标识符finalizerName svccontroller.k3s.cattle.io/daemonset旧实现遗留在 Service 上的 finalizersvcNameLabel/svcNamespaceLabelDaemonSet 与 Pod 上标记所属 Service 的标签daemonsetNodeLabel svccontroller.k3s.cattle.io/enablelb节点参与 ServiceLB 的开关标签daemonsetNodePoolLabel svccontroller.k3s.cattle.io/lbpool节点池标签nodeSelectorLabel、priorityAnnotation、tolerationsAnnotation分别用于 DaemonSet 自身标记、优先级类与容忍度定制controllerName names.ServiceLBController使用 Kubernetes 官方定义的控制器名。4.2 注册与事件驱动Register()servicelb.go是控制器的心脏它把三类资源变化接入处理Node 变化onChangeNode当节点带enablelb标签时触发updateDaemonSets()刷新所有 ServiceLB DaemonSet 的节点选择器Pod 变化onChangePod当带 svc 标签的 Pod 获得 IP 后向工作队列投递对应 ServiceEndpointSlice 变化onChangeEndpointSlice用于在externalTrafficPolicy: Local场景下确保负载均衡地址只列出有就绪 Pod 的节点。同时Register()还会做三件启动期工作确保命名空间存在ensureServiceLBNamespace、确保名为svclb的 ServiceAccount 存在ensureServiceLBServiceAccount、以及清除旧实现遗留的 Service finalizerremoveServiceFinalizers从而把所有权平稳移交给 CCM 实现。4.3 轻量工作队列而非完整 Service 控制器一个很关键的设计细节ServiceLB 并没有启用完整的 Wrangler Service 控制器而是使用workqueue.RateLimitingInterface搭建了一个轻量工作队列servicelb.go。代码注释明确说明这是为了在 Pod 频繁更新时降低抖动thrashing并注明参考了 rancher/lasso 的 controller 实现。队列项经processSingleItem解析为 namespace/name 后调用updateStatus完成状态回写。4.4 DaemonSet 的生成、部署与回收生成newDaemonSetservicelb.go根据 Service 的端口生成名为lb-proto-port的容器注入SRC_PORT、SRC_RANGES、DEST_PROTO、DEST_PORT、DEST_IPS等环境变量并设置NET_ADMIN能力与 IPv4/IPv6 转发 sysctl。命名规则见generateNamesvclb-service-uid前8位并对超长名称做了 48 字符截断与尾连字符保护有对应单测部署deployDaemonSet通过 Wrangler 的apply处理器以 Service 为 Owner 应用对象集回收deleteDaemonSet在 Service 删除时清理对应 DaemonSetdeleteAllDaemonsets则在负载均衡功能被禁用时按标签批量清理全部托管 DaemonSet见 Initialize 的 else 分支节点选择nodeHasDaemonSetLabel检测是否存在任意带enablelb标签的节点一旦存在DaemonSet 就带enablelbtrue节点选择器若 Service 带lbpool标签则进一步限定节点池。4.5 负载均衡接口的四件套pkg/cloudprovider/loadbalancer.go 实现cloudprovider.LoadBalancer接口方法行为GetLoadBalancer查询对应 DaemonSet 是否存在并返回状态GetLoadBalancerName返回generateName(svc)生成的负载均衡名称EnsureLoadBalancer部署 DaemonSet随后返回cloudprovider.ImplementedElsewhere状态回写交由别处完成UpdateLoadBalancer直接返回cloudprovider.ImplementedElsewhere注释解释了原因Kubernetes 核心对节点更新的过滤条件与 K3s DaemonSet 的节点选择逻辑不兼容EnsureLoadBalancerDeleted删除对应 DaemonSetEnsureLoadBalancer返回ImplementedElsewhere是一个关键细节K3s 的 ServiceLB 选择部署完 DaemonSet 后由自己的轻量工作队列回写 Service 状态而非依赖核心 service 控制器——这既复用了接口带来的类型过滤与生命周期管理又保留了 K3s 对节点选择、状态计算的完全控制。4.6 状态计算IP 族过滤与 Local 流量策略getStatusservicelb.go与podIPsservicelb.go实现了地址计算优先使用节点 ExternalIP若无则回退 InternalIPfilterByIPFamily按 Service 的IPFamilies对地址排序过滤支持 IPv4 单栈、IPv6 单栈与双栈详见单测 servicelb_test.goexternalTrafficPolicy: Local时通过 EndpointSlice 的就绪状态筛出有就绪 Pod 的节点Rootless 模式下统一回退为127.0.0.1。4.7 行为定制注解与标签ADR 承诺保留的节点标签定制之外ServiceLB 还支持两种 Service 注解servicelb.gosvccontroller.k3s.cattle.io/priorityclassname覆盖 DaemonSet Pod 的 PriorityClasssvccontroller.k3s.cattle.io/tolerations以 JSON/YAML 追加 Pod 容忍度且validateToleration会对操作符与键值做合法性校验。五、后果开关语义与资源收益ADR 列出了四条后果均能在 pkg/daemons/control/server.go 中得到直接验证资源节约ServiceLB 被禁用时K3s 不再无条件启动若干核心控制器资源占用更低。从Initialize的实现看LBEnabledfalse时甚至不会创建 Wrangler factory 与各类缓存cloudprovider.go--disable-cloud-controller语义变化该开关现在只禁用 CCM 的cloud-node与cloud-node-lifecycle两个控制器——这正是历史上 K3s CCM 唯一支持的控制器。见 cloudControllerManager 的参数拼接controllers *,-route,-cloud-node,-cloud-node-lifecycle且secure-port0--disableservicelb语义变化该开关现在禁用 CCM 的service控制器server.gocontrollers ,-serviceservicelb从独立组件名变为 CCM 内部 controller 名完全禁用时 CCM 不运行Server 启动逻辑 中if !cfg.DisableCCM || !cfg.DisableServiceLB才会调用cloudControllerManager两者同时禁用时整个 cloud-controller-manager 进程都不再启动。反过来当 CCM 启用时kube-controller-manager 也会相应让位在 controllerManager 中会追加controllers *,-service,-route,-cloud-node-lifecycle并设置configure-cloud-routesfalse避免与 CCM 抢活。命令行层面--disable-cloud-controller定义在 pkg/cli/cmds/server.go绑定到ServerConfig.DisableCCMserver.go。六、测试保障迁移并非一刀切重写而是在复用核心代码的同时保留了原有行为这从测试可以窥见一斑servicelb_test.go 的Test_UnitFilterByIPFamily覆盖无 IPFamily、IPv4 单栈、IPv6 单栈、双栈四种组合Test_UnitFilterByIPFamily_Ordering验证地址排序稳定性Test_UnitGenerateName验证 DaemonSet 命名规则包括短名、长名、以及含连续连字符的长名的截断行为。七、总结这份 ADR 记录了一次典型的向 Kubernetes 标准看齐的重构ServiceLB 从挂接在 Wrangler 上的私有大轮子收敛为云控制器管理器内 LoadBalancer 接口的后端实现。收益清晰可见——Service 监听、类型过滤、finalizer 等繁重逻辑交由 Kubernetes 核心代码处理K3s 得以把精力集中在节点选择、IP 族过滤、Local 流量策略等真正体现差异化价值的实现上同时通过--disable-cloud-controller与--disableservicelb两个正交开关让用户自由组合负载均衡与节点生命周期两种能力。【免费下载链接】k3sLightweight Kubernetes项目地址: https://gitcode.com/GitHub_Trending/k3/k3s创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考