Cilium Node IPAM LB:用节点 IP 直接充当 LoadBalancer 入口的免云厂商外部负载均衡方案
【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium
Node IPAM LB(Node-IPAM LoadBalancer)是 Cilium 实现的一种 KubernetesLoadBalancerService IPAM 方案:它由 cilium-operator 内的控制器监听 Service、EndpointSlice 与 Node 三类资源,将满足条件的节点 IP 直接写入Service.status.loadBalancer.ingress,从而"把节点 IP 广播为负载均衡入口"。该特性适用于无法控制底层网络、既不能用 Cilium L2 通告也不能用 BGP 通告 VIP 的托管环境。读完本文,你将理解其节点候选与 IP 选择规则、掌握通过 Helm 启用nodeIPAM.enabled与defaultLBServiceIPAM的完整配置方法,并能看懂externalTrafficPolicy为Cluster与Local时的行为差异及其边界限制。
什么是 Node IPAM LB
Node IPAM LoadBalancer 的设计灵感来自 k3s 的 "ServiceLB" 特性。它的核心思想非常直接:不申请任何云上负载均衡资源,不创建 VIP,而是直接把集群节点的 IP 地址作为 LoadBalancer Service 的 ingress 地址对外暴露。工作方式是获取被选中节点的地址(status.addresses),然后将其"广播"(advertise)到 Service 的status.loadBalancer.ingress字段中。
这一设计在以下场景中尤其有用:
- 运行环境是托管 Kubernetes 服务,你不控制底层网络,无法在外部交换机上配置 L2 MAC 通告;
- 环境没有可用的 BGP 对等关系,Cilium 的 BGP 控制器无从通告 VIP;
- 你希望避免依赖云厂商的 LB API(Cilium 的
lbipam方案需要调用云 API 创建 NLB/ELB),转而仅用集群内已有信息完成"外部入口"的语义。
从源码结构看,Node IPAM 的全部运行时逻辑位于 cilium-operator 中,核心实现集中在 operator/pkg/nodeipam 目录:
- nodesvclb.go:
nodeSvcLBReconciler控制器,负责 Service 的调和(reconcile)与 IP 广播; - node_predicates.go:节点是否纳入负载均衡候选的判定谓词;
- cell.go:Cilium hive 框架的模块注册与启用开关;
- pkg/nodeipamconfig/cell.go:配置项
--enable-node-ipam与loadBalancerClass常量定义。
控制器通过 operator/cmd/root.go 中的nodeipam.Cell注册进 operator 主流程,因此Node IPAM 是 operator 侧能力,不是 agent 侧能力——启用开关下发给 operator 进程。
工作机制:从 Service 到节点 IP 的完整链路
控制器监听哪些对象
nodeSvcLBReconciler.SetupWithManager注册了三类 watch(见 nodesvclb.go#L48-L63):
- Service:只处理"受支持"的 Service(见下文判定逻辑),作为 reconcile 的主对象;
- EndpointSlice:当某个 EndpointSlice 更新时,通过其
kubernetes.io/service-name标签反查所属 Service 并触发该 Service 重新调和(enqueueRequestForEndpointSlice,nodesvclb.go#L65-L86)。这是为了在externalTrafficPolicy: Local模式下及时反映 Pod 分布变化; - Node:当一个通过基础谓词检查的节点发生变化时,列出全部受支持的 Service 并逐一入队(
enqueueRequestForNode,nodesvclb.go#L88-L122),保证节点增删改时 ingress 列表随之刷新。
哪些 Service 会被处理
isServiceSupported(nodesvclb.go#L151-L163)的判定条件是:
- Service 未被标记删除;
spec.type必须为LoadBalancer;spec.loadBalancerClass等于io.cilium/node(常量NodeSvcLBClass,定义于 pkg/nodeipamconfig/cell.go#L38);- 若 Service未设置
loadBalancerClass,则只有当defaultLBServiceIPAM配置为nodeipam时才会被接管(该标志在 pkg/lbipamconfig/cell.go#L57 中定义为DefaultLBClassNodeIPAM = "nodeipam")。
这一设计让 Node IPAM 能与其他 LB 方案(如云厂商lbipam)按loadBalancerClass共存,互不干扰;测试用例Test_nodeIPAM_Reconcile中的 "unsupported service reset" 子用例(nodesvclb_test.go#L361-L382)专门验证了:对于不受支持的 Service,控制器不会去改动它已有的status.loadBalancer.ingress。
候选节点的筛选规则
节点筛选分三层,全部实现于getRelevantNodes与shouldIncludeNode:
第一层:基础排除谓词(KEP-3458 对齐)。node_predicates.go 中的shouldIncludeNode显式声明其语义与 kubernetes/cloud-provider 项目中的stableNodeSetPredicates保持一致,包含三条:
- 节点无
deletionTimestamp(正在删除的节点排除); - 节点没有标签
node.kubernetes.io/exclude-from-external-load-balancers(v1.LabelNodeExcludeBalancers); - 节点没有污点
ToBeDeletedByClusterAutoscaler(Cluster Autoscaler 在删除节点前会打上该污点)。
被排除的节点会被记录到日志("Skipping Nodes that are being deleted or are excluded from load balancers"),便于排查为什么某个节点 IP 没有出现在 ingress 中。
第二层:externalTrafficPolicy语义。对应文档中最重要的行为分支(nodesvclb.go#L165-L198):
externalTrafficPolicy: Cluster:不对节点做任何基于 Pod 的过滤,所有通过第一层的节点都是候选;externalTrafficPolicy: Local:列出该 Service 在对应 namespace 下的全部 EndpointSlice,只收集conditions.ready不为 false 且带有nodeName的 endpoint,得到"实际运行该 Service 后端 Pod 的节点集合",只保留这些节点。
这正是文档警告项的源码依据:当externalTrafficPolicy: Local却没有(或只有空的/假数据的)EndpointSlice 关联到该 Service 时,候选节点集合为空,Node IPAM 无法广播任何地址。文档特别提醒:Cilium 自研的 GatewayAPI / Ingress 实现使用的是 dummy Endpoint,其 LoadBalancer Service 的externalTrafficPolicy不能设为Local(相关模板见 install/kubernetes/cilium/templates/cilium-ingress-service.yaml);其他多数 GatewayAPI 实现预计不受影响,若行为异常应检查其 EndpointSlice 或改回Cluster。
第三层:按 Service 注解限定节点范围。若 Service 带有注解io.cilium.nodeipam/match-node-labels(常量NodeSvcLBMatchLabelsAnnotation,pkg/nodeipamconfig/cell.go#L39),其值按 KubernetesLabel Selector语法解析后作为 List Nodes 的 label selector,把候选范围进一步收窄——适合"只有部分节点能接收外部入站流量"的拓扑(例如仅特定 zone/az 的节点挂了公网 EIP)。解析失败会报错并终止本次调和,空结果会输出告警日志"No Nodes found with configured label selector"。
IP 选择与广播
最终 IP 集合由getNodeLoadBalancerIngresses(nodesvclb.go#L265-L308)计算,规则如下:
- 只读取节点
status.addresses中类型为ExternalIP与InternalIP的地址,其他类型忽略; - 按 Service 的
spec.ipFamilies过滤地址族——只有当 ipFamilies 包含IPv4时 IPv4 地址才被计入,IPv6同理,因此单栈集群只广播单栈地址,双栈集群两种都广播; - 优先级:只要存在任何节点 ExternalIP,就全部使用 ExternalIP;否则回退到 InternalIP(注意是"整体二选一",不是逐节点回退);
- 去重后按字典序排序,保证输出稳定、diff 友好;
- 每个地址生成一个
LoadBalancerIngress{IP: ...},整体写回svc.Status.LoadBalancer.Ingress(nodesvclb.go#L143-L148)。
单元测试覆盖了上述各分支:单栈下 IPv4/IPv6 × internal/external 四种单地址组合、双栈广播(nodesvclb_test.go#L384-L420)、defaultLBServiceIPAM模式(Test_nodeIPAM_defaultIPAM_Reconcile)以及 Cilium 自定义资源联动场景(Test_nodeIPAM_CiliumResources_Reconcile),可作为行为核对的权威依据。
启用与使用
通过 Helm 启用
Cilium 的 Node IPAM默认禁用。Helm 值定义在 install/kubernetes/cilium/values.yaml#L2402-L2405:
nodeIPAM: # -- Configure Node IPAM # ref: https://docs.cilium.io/en/stable/network/node-ipam/ enabled: false安装时启用(全新部署):
helm install cilium cilium/cilium --namespace kube-system --set nodeIPAM.enabled=true在已有部署上启用:
helm upgrade cilium cilium/cilium --namespace kube-system --reuse-values --set nodeIPAM.enabled=true kubectl -n kube-system rollout restart deployment/cilium-operator启用后,Helm 模板 install/kubernetes/cilium/templates/cilium-configmap.yaml#L1279-L1283 会在配置中写入:
enable-node-ipam: "true" default-lb-service-ipam: "lbipam"这两个键分别对应 operator 的--enable-node-ipam布尔开关(pkg/nodeipamconfig/cell.go#L33-L35)与defaultLBServiceIPAM。从源码结构看,operator/pkg/nodeipam/cell.go 中的registerNodeSvcLBReconciler会在NodeIPAMConfig.IsEnabled()为 false 或 Kubernetes clientset 未启用时直接跳过控制器注册——这也解释了为什么改动该值后需要重启 cilium-operator 才能生效。
声明一个 Node IPAM 管理的 Service
最小可用的 Service 需要满足两个条件:type: LoadBalancer且loadBalancerClass: io.cilium/node:
apiVersion: v1 kind: Service metadata: name: node-ipam-svc namespace: default spec: type: LoadBalancer loadBalancerClass: io.cilium/node selector: app: myapp ports: - port: 80 targetPort: 8080调谐完成后,kubectl get svc node-ipam-svc的 EXTERNAL-IP 列会列出被选中节点的 IP(按前述 ExternalIP 优先规则)。
若希望未声明loadBalancerClass的 LoadBalancer Service 也默认走 Node IPAM,把defaultLBServiceIPAM设为nodeipam(Helm 值defaultLBServiceIPAM,取值范围lbipam/nodeipam/none,默认lbipam)。
用注解限定承载节点
要在多个节点中只让部分节点接收外部流量,给 Service 加注解io.cilium.nodeipam/match-node-labels,值为标准 Label Selector:
apiVersion: v1 kind: Service metadata: name: node-ipam-svc annotations: # 只选择带 role=ingress 标签的节点广播 IP io.cilium.nodeipam/match-node-labels: role=ingress spec: type: LoadBalancer loadBalancerClass: io.cilium/node ...节点排除机制速查
| 节点状态 | 是否参与广播 | 依据 |
|---|---|---|
带标签node.kubernetes.io/exclude-from-external-load-balancers | 否 | node_predicates.gonodeIncludedPredicate |
带污点ToBeDeletedByClusterAutoscaler | 否 | node_predicates.gonodeUnTaintedPredicate |
deletionTimestamp非零 | 否 | nodeNotDeletedPredicate |
被match-node-labelsselector 排除 | 否 | nodesvclb.go#L210-L221 |
| eTP=Local 且无 ready 后端 Pod 落在此节点 | 否 | nodesvclb.go#L184-L195 |
已知限制与排查
- eTP=Local 依赖真实 EndpointSlice。文档明确警告:
externalTrafficPolicy: Local但没有对应(或只有 dummy)EndpointSlice 的 Service 无法工作。因此 Cilium 自研的 GatewayAPI/Ingress 实现(其 LoadBalancer Service 使用 dummy Endpoint,见 install/kubernetes/cilium/templates/cilium-ingress-service.yaml)不能设Local,只能使用Cluster。 - ExternalIP 是"全有或全无"。IP 选择逻辑是集合级回退:只要任一节点有 ExternalIP,ingress 就只包含 ExternalIP 集合,InternalIP 不会被混合进来。在异构网络(部分节点有公网 IP、部分只有内网 IP)中这一点需要特别注意。
- 广播的是节点地址,不是 VIP。客户端会看到多个入口 IP,端口转发依赖节点上内核/endpoint 对 Service 的处理路径;若外部防火墙只放行单一 VIP,该方案不适用。
- 排查入口:operator 日志(控制器名为
service-nodeipam,nodesvclb.go#L61)会输出 "Reconciling Service"、"Skipping Nodes..."、"No Nodes found after filtering..." 等关键事件;operator 侧还有 feature 指标(pkg/metrics/features/operator中的 NodeIPAM enabled 指标)可用于确认特性处于启用状态。
小结
Node IPAM LB 用"广播节点 IP"这一最朴素的手段补齐了 Cilium 在无 L2/BGP 能力环境下的 LoadBalancer 语义缺口:
- 入口:
type: LoadBalancer+loadBalancerClass: io.cilium/node,或通过defaultLBServiceIPAM: nodeipam使其成为默认方案; - 启用:Helm 值
nodeIPAM.enabled=true,映射为 operator 的enable-node-ipam开关,改动后需重启cilium-operator; - 行为:eTP=Cluster 选全部合格节点,eTP=Local 选运行后端 Pod 的节点;
io.cilium.nodeipam/match-node-labels注解进一步收窄范围;IP 广播遵循ipFamilies过滤、ExternalIP 优先、去重排序规则; - 边界:节点排除谓词与 KEP-3458 的 cloud-provider 行为对齐,Cluster Autoscaler 缩容节点不会残留在 ingress 中。
核心实现、开关与测试分别位于 operator/pkg/nodeipam、pkg/nodeipamconfig 与 operator/pkg/nodeipam/nodesvclb_test.go,可作为进一步阅读源码与核对行为的起点。
【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考