OCI CCM v1.34:不存在的 annotation 和让 LB 瘫痪的 Token Collision
发布于 2026年4月21日

背景
在将一个 Next.js 应用迁移到 OKE(Oracle Kubernetes Engine)时,我们使用 LoadBalancer 类型的 Service,让 Cloud Controller Manager(CCM)自动创建和管理 OCI Load Balancer。CCM 版本为 v1.34,与 OKE 控制平面配套使用。
原本应该很简单的事情——在 Service 上声明 annotation 让 CCM 执行——变成了一系列静默失败、409 错误和孤立 LB 不断累积的过程。本文记录了真正可用的内容、被忽略的内容,以及最危险的陷阱:Token Collision。
OCI CCM v1.34 中不存在的 annotation
官方 OCI CCM 文档列出了一组支持的 annotation。但在运行真实工作负载时,一些在论坛、社区示例或旧版本中找到的 annotation 根本不起作用——CCM 不会发出任何警告,而是静默忽略并继续运行。
1. 复用已有 LB
尝试:将 CCM 指向一个账户中已存在的、在集群创建前手动创建的 Load Balancer。
# 失败 — OCI CCM v1.34 中不存在此 annotation
service.beta.kubernetes.io/oci-load-balancer-id: "<lb-ocid>"结果:CCM 完全忽略了该 annotation,创建了一个使用自动生成 UUID 名称的新 LB。手动 LB 继续运行并计费,没有任何流量。
2. Reserved IP(固定 IP)
尝试:使用 OCI Reserved IP 固定 Load Balancer 的公网 IP,以避免 Service 被重建时 IP 发生变化。
# 失败 — annotation 被忽略,CCM 创建自己的 IP
service.beta.kubernetes.io/oci-load-balancer-reserved-ip-id: "<publicip-ocid>"
# 同样失败
oci.oraclecloud.com/load-balancer-ip: "192.0.2.4"结果:CCM 忽略了两者,使用临时 IP 创建了 LB(但只要 Service 存在,IP 就保持稳定)。Reserved IP 保持 AVAILABLE 状态,产生费用但没有使用(约 1.80 美元/月)。
实际上,只要 Kubernetes Service 不被删除,CCM 管理的 LB IP 就不会改变。对于生产 DNS,这个 IP 足够稳定——不需要 Reserved IP。
3. 通过 NSG 进行安全规则管理
尝试:使用 NSG 模式让 CCM 通过 Network Security Group 而非 Security List 管理流量规则。
# 失败 — 需要基本 OKE 未配置的 IAM 策略
service.beta.kubernetes.io/oci-load-balancer-security-rule-management-mode: "NSG"
# 同样失败 — 没有 VirtualNetwork Manage 权限,返回 404
service.beta.kubernetes.io/oci-network-security-groups: "<nsg-ocid>"结果:VirtualNetwork API 返回 404 错误。基本 OKE 中的 CCM 服务账号没有创建或修改 NSG 所需的 IAM 策略。可行的解决方案是使用 `security-list-management-mode: None` 并手动管理网络规则。
OCI CCM v1.34 中经过确认可用的 annotation
排除有问题的 annotation 后,以下是经过生产验证的一致可用配置:
annotations:
# LB 将创建在哪个子网
service.beta.kubernetes.io/oci-load-balancer-subnet1: "<公网子网-ocid>"
# 弹性 shape(推荐,优于固定 shape)
service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "100"
# CCM 不修改 Security List(手动管理)
service.beta.kubernetes.io/oci-load-balancer-security-list-management-mode: "None"
# 健康检查(通过 kube-proxy healthz,端口 10256)
service.beta.kubernetes.io/oci-load-balancer-health-check-protocol: "HTTP"
service.beta.kubernetes.io/oci-load-balancer-health-check-path: "/healthz"
service.beta.kubernetes.io/oci-load-balancer-health-check-retries: "3"
service.beta.kubernetes.io/oci-load-balancer-health-check-interval: "10000"
service.beta.kubernetes.io/oci-load-balancer-health-check-timeout: "5000"
# 后端协议
service.beta.kubernetes.io/oci-load-balancer-backend-protocol: "HTTP"
# HTTPS(当集群中已创建 TLS secret 时)
service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
service.beta.kubernetes.io/oci-load-balancer-tls-secret: "myapp/myapp-tls"Token Collision:最危险的问题
Token Collision 是 OCI CCM 中最隐蔽的陷阱。它表现为 OCI API 返回 HTTP 409——当它发生时,CCM 停止协调 LB,却不会在集群中发出任何明确的告警。
幂等性 token 的工作原理
OCI CCM 为每个 Load Balancer 创建操作生成一个幂等性 token。该 token 由以下内容派生:
token = hash("cluster~createLoadBalancer~{serviceUID}")Service UID 在 Service 存在期间是固定的。问题在于:对 Service spec 的任何更改(添加或删除 annotation、修改端口、调整 shape)不会改变 UID——但会改变发送给 OCI API 的请求内容。OCI 返回 409,因为该 token 已经以不同的 payload 使用过了。
症状
CCM 进入协调循环,无法更新 LB。Service 事件显示重复错误,但集群中没有高级别日志指示发生了什么。OCI 中的 LB 保持之前的状态,不反映 Service 的变更。
唯一出路:删除并重建 Service
没有办法在不重建 Service 的情况下解决 Token Collision。新的 Service 生成新的 UID → 新 token → 对 OCI 的干净操作。正确的步骤是:
# 1. 移除 finalizer 以解除删除阻塞
kubectl patch service myapp-svc -n myapp -p '{"metadata":{"finalizers":[]}}' --type=merge
# 2. 删除 Service(CCM 自动删除 LB)
kubectl delete service myapp-svc -n myapp
# 3. 使用正确的 annotation 重建 Service
kubectl apply -f service.yaml
# 4. 在新 LB 上重新激活 WAF(如果有关联的 WAF policy)
# LB 的 OCID 会变更——WAF instance 需要重建关键:在生产环境删除 Service 之前,必须规划好 WAF 的重新注册。CCM 删除 Service 时会删除旧 LB——关联的 WAF instance 会失去引用。
避免 Token Collision 的黄金法则
在第一次 apply 之前确定好 Service 的最终 spec。一旦以某个 UID 创建,任何结构性更改(端口、shape annotation、子网)都需要完整的 delete + recreate 流程。不要在生产环境中用 `kubectl edit` 或 `kubectl patch` 修改功能性 annotation——这会触发 409。
文档中未重点说明的必要端口
两个网络要求是关键的,在官方文档中只作为次要说明出现,但如果缺失会导致静默失败。
端口 12250 — 节点注册
工作节点通过 TCP 端口 12250 与 OKE 管理端点通信。如果端点的 NSG 中没有此入站规则,节点永远无法完成注册。
# OKE 端点 NSG
方向:INGRESS
协议:TCP
来源:工作节点 NSG(或节点子网 CIDR)
目标端口:12250
描述:Worker 节点 -> OKE 管理端点没有端口 12250 的症状:所有节点保持 `UnknownNodeError` 状态,显示消息"has not been seen for more than 20 minutes"——即使是节点池刚刚创建的节点也是如此。
端口 10256 — Load Balancer 健康检查
CCM 将 LB 健康检查配置为指向节点上的端口 10256,即 kube-proxy 的 `/healthz` 端点。如果没有允许从 LB 子网到节点该端口的规则,所有后端都会被标记为不健康,流量无法被路由。
# 工作节点 NSG(或 Security List)
方向:INGRESS
协议:TCP
来源:LB 子网 CIDR(如 10.1.0.0/24)
目标端口:10256
描述:OCI LB 健康检查 -> kube-proxy healthz注意:CCM 使用 10256 作为默认健康检查端口,无论配置了什么 health-check-path annotation——该端口在控制器行为中是硬编码的。
由 CCM 管理的 HTTPS:在 Service 中声明,而非在 LB 中
一个常见陷阱是直接通过 CLI 或控制台将 HTTPS 监听器添加到 OCI Load Balancer。CCM 会定期协调 LB(在每次节点或 Pod 变更时),并删除所有未在 Kubernetes Service 中声明的内容。
让 CCM 管理 HTTPS 的正确方式:
# 1. 在集群中创建 TLS secret
kubectl create secret tls myapp-tls -n myapp \
--cert=myapp.crt \
--key=myapp.key
# 2. 在 Service spec 中声明端口 443
spec:
ports:
- name: http
port: 80
targetPort: 3000
protocol: TCP
- name: https
port: 443
targetPort: 3000
protocol: TCP
# 3. 添加 TLS annotation
annotations:
service.beta.kubernetes.io/oci-load-balancer-ssl-ports: "443"
service.beta.kubernetes.io/oci-load-balancer-tls-secret: "myapp/myapp-tls"使用此配置,CCM 会创建两个监听器和两个后端集。当节点变化时,CCM 会自动协调并重建监听器——无需手动干预。
CCM 后端自动更新:约 2 分钟内确认
解决所有 annotation 和端口问题后,我们验证了 CCM 的自动更新行为:我们通过 OCI 控制台手动移除了一个节点。节点池创建了一个替代节点,CCM 在大约 2 分钟内检测到新节点并更新了 LB 后端,无需干预。
# 节点替换期间的 Service 事件
Warning UnAvailableLoadBalancer 11m service-controller There are no available provisioned nodes for LoadBalancer
Normal UpdatedLoadBalancer 9m21s (x4 over 13m) service-controller Updated load balancer with new hosts真实的停机时间不是由 CCM 造成的——而是由容器镜像拉取时间决定的(2.1 GB 在每个新节点上需要 1 分 34 秒)。CCM 做好了它的工作;瓶颈在于镜像大小。
为减少节点替换时的停机时间:使用 Next.js standalone 输出、多阶段构建和适当的 .dockerignore。2.1 GB 的镜像可以降至约 500 MB——拉取时间随之降低。
陷阱总结
对于第一次配置 OKE + CCM v1.34 的人,按影响程度排列的主要陷阱:
1. Token Collision 409 — 创建后永远不要编辑 Service spec。任何结构性更改都需要移除 finalizer 后 delete + recreate。
2. 缺少端口 12250 — 节点永远处于 UnknownNodeError。没有这个端口,集群无法正常工作。
3. 缺少端口 10256 — 所有 LB 后端都不健康。没有这个端口,流量永远无法到达应用程序。
4. 不存在的 annotation — oci-load-balancer-id、reserved-ip-id 和 security-rule-management-mode: NSG 被静默忽略。不要使用。
5. 在 Service 外配置 HTTPS — 直接添加到 OCI LB 的监听器会在 CCM 下次协调时被删除。


