跳到正文

多集群容器平台:接入、发布与网络 ​

一句话:多集群平台的价值不是把 kubectl 包一层,而是把「应用意图」和「某个集群里的 K8s 对象」分开:平台保存前者,适配层负责后者;换来一次建模、多处运行,代价是抽象会泄漏,且必须明确哪些差异可以屏蔽、哪些只能暴露给用户。

适用版本:Kubernetes 1.24+、ingress-nginx 1.9+;不同发行版与托管 K8s 的能力以集群侧实际声明为准。

为什么需要 ​

单集群时 kubectl apply 加一条流水线就够了。集群变成十几个、横跨自建机房和托管 K8s 后,问题不再是「怎么创建 Deployment」,而是:

  • 同一个应用在 A 集群用 nginx Ingress,B 集群用另一套控制器,C 集群的负载均衡要调云 API 而不是建 LoadBalancer Service;
  • 应用负责人只关心「我的服务、几个副本、什么镜像、什么域名」,不关心这些对象落在哪个 API 版本;
  • 出问题时必须回答「这个应用在哪些集群、什么版本、谁改的」,而 K8s 没有「应用」这个概念。

于是平台要做两件事:向上提供稳定的应用模型,向下用适配层吸收集群差异。难点在于第二件事只应该吸收「实现差异」,不能吸收「能力差异」——否则用户以为开了弹性伸缩,实际集群根本不支持。

核心机制 ​

接入层:屏蔽控制面,暴露能力面 ​

适配层适合屏蔽的是访问方式和对象生命周期:

屏蔽原因
认证信息、API Server 地址、证书业务侧不应该持有集群凭证
资源命名、命名空间、标签约定由平台统一生成,避免各集群各写一套
API 版本与字段差异同一份模型渲染成不同版本的 YAML
查询、删除、扩缩容的调用方式自建集群走 API,托管集群可能还要调云 API

不能屏蔽的是能力差异:Ingress 控制器类型、负载均衡实现、CNI/IPAM、节点架构、存储类、K8s 版本门槛、可观测组件。正确做法是让每个集群声明一份能力清单,平台在提交前检查;不支持就拒绝并说明原因,而不是静默降级。

比如「固定 IP + 自动伸缩」在多数集群上互斥:IP 池按副本数申请,扩容会超出池子。平台应该在创建时就报错,而不是等发布到一半才失败。自建集群的 LoadBalancer Service 往往没有实现,托管集群则可能创建一个真实云负载均衡并计费——这层差异必须让用户看见。

应用模型:三层对象 ​

平台侧通常拆成三层:应用(负责人、配额、冻结状态、审计)→ 应用集群(某应用在某集群的期望配置:镜像、副本、CPU/内存、端口、域名、发布策略)→ 运行实例(集群里实际的 Deployment/Service/Ingress)。平台数据库保存「意图」,K8s 保存「运行状态」,两者用定时对比发现漂移。入驻时先确认无状态化、健康检查、资源与网络需求,再生成应用集群记录;应用这层元数据正是停用、冻结、应急发布和审计的抓手。

冻结是平台策略,不是 K8s 能力。典型分三档:完全冻结(只读)、发布冻结(可扩缩容、不可发布或删除)、配置冻结(可发布、不可改配置)。冻结必须在统一的操作入口拦截,而不是在每个接口里各写一遍——否则总有一条路径能绕过。代价是平台成为唯一入口;有人用 kubectl 直接改,冻结就失效,所以还要配合 RBAC 和漂移检测。

应急发布不是「跳过检查」,而是「换一组检查」:保留镜像、资源、集群健康这类硬检查,放宽审批和耗时检查;但必须提前生成回滚预案,并在事后限时复盘。放宽的是流程,不是底线。

发布引擎:apply 成功只是开始 ​

平台发布是一个异步状态机,而不是一次 HTTP 调用:

图里最关键的两条边是 DEPLOYING → REVERT 和 ABORTED → 决策:前者说明发布成败由「副本是否全部 Ready」判定,不是由 kubectl apply 的返回码判定;后者说明中止不是杀线程,而是停在安全点后等人决定继续、回滚还是保持部分发布。

判定要看 Deployment 的 updatedReplicas == 期望、readyReplicas == 期望、unavailableReplicas == 0。只看 apply 成功,会把镜像拉取失败、探针失败、调度不上去都当成发布成功。

三个工程约束:

  • 异步与超时:发布放进任务队列,前端轮询状态;总超时按应用配置(常见 10 分钟,经验值)。超时不是失败的同义词,要先分清是调度慢、拉镜像慢还是探针一直不过。
  • 幂等:每次发布有唯一 releaseId 和目标版本,重试只查状态、不重复 apply;否则并发重试会互相覆盖副本数。
  • 回滚要有依据:发布前记录上一版的镜像、副本和关键配置;回滚是重新 apply 上一版,不是 rollout undo 的同义词——跨集群、跨对象类型的变更,K8s 的 revision 历史帮不上忙。

发布中止要区分三种结果:已发布部分保持运行(部分发布,需要告警和人工决策)、回滚到上一版、从中断点继续。中止时先发信号、等当前步骤收尾(常见 30 秒,经验值),再落状态;直接杀进程会留下「平台以为什么都没发生、集群里已经变了一半」的漂移。

检查链:先分层,再决定阻断还是告警 ​

检查项不应该是一个大 if,而是一组独立检查器,按动作类型注册、按顺序执行、汇总结果。责任链的关键不是「链」,而是每项检查的严重级别:

层级典型检查建议级别理由
配置镜像存在、架构匹配、端口/环境变量/配置集完整阻断确定性问题,放过去必然失败
资源配额、requests/limits、超卖比例、GPU阻断容量不足会在发布后才暴露
策略副本数、滚动步长、PDB、固定 IP 与伸缩互斥阻断参数组合本身非法
依赖下游地址可达、注册中心有实例告警网络抖动会误判,阻断会误伤
安全镜像漏洞等级、基线合规分级严重漏洞阻断,高危告警并留例外
容量节点余量能否承接 maxSurge阻断或告警能算准就阻断,只能估算就告警

执行顺序遵循「便宜且确定在前,昂贵且易误判在后」:先查配置和策略,再做镜像扫描和依赖探测。阻断项可以短路;告警项必须收集完再一起展示,否则用户改一个跑一次,体验很差。

还要注意检查项的环境差异:测试环境只查镜像、规格、配置;生产环境加上健康检查、配额、Ingress/SLB;首次部署要额外查域名冲突和证书。检查链配置应该跟着环境走,而不是一套规则打天下。

网络:三层各管一段 ​

  • Service 解决「一组 Pod 的稳定入口」:ClusterIP、DNS、四层转发、Endpoint 摘除。它不解析域名、不做 TLS。
  • Ingress 解决「七层路由」:host/path、TLS 终止、灰度注解,依赖具体控制器实现,换控制器注解就不通用。
  • SLB / 负载均衡 解决「集群外入口」:可以是云负载均衡,也可以由 LoadBalancer Service 触发,负责公网 IP、四层监听、健康检查。

平台代填参数时最容易埋的坑:

  • 端口四件套:容器端口、Service port、targetPort、NodePort 或 SLB 监听端口。任意一层对不上都是 connection refused;用命名端口比裸数字稳。
  • 健康检查:SLB 健康检查打的是节点或 Pod 端口,不是 K8s 的 readinessProbe;路径、返回码、间隔(如 5 秒、连续 3 次,经验值)要和应用真实健康接口一致,否则会出现「K8s 认为 Ready、SLB 认为不健康」。
  • 超时:Ingress 读超时、SLB 空闲超时、应用自身超时是三套配置;长请求和 WebSocket 必须显式调大,否则表现为 504 或连接被重置。
  • 证书:TLS Secret 在命名空间里,云负载均衡的证书在云账号里;两边不会自动同步,续期只更新一边就会在某个时间点集体报错。
  • 多控制器:集群里同时有 nginx 和其他 Ingress 控制器时,ingressClassName 必须显式指定;同一个 host 被两个控制器接管,行为取决于实现。

镜像治理:把「用了什么」变成可查询 ​

平台至少要管三类镜像:基础镜像(OS/JDK/中间件)、应用镜像、外购镜像。基础镜像目录的价值不在展示,而在反查:出现一个基础镜像 CVE 时,能立刻回答哪些应用、哪些集群受影响,并批量重建。

实践上:应用镜像由流水线构建,产物以镜像和 digest 交付;发布用 digest 或不可变 tag;扫描结果按严重级别入库;保留策略按「最后使用时间」而不是创建时间(如 90 天未使用转归档,经验值)。多架构镜像用 manifest list 打同一个 tag,迁移到 ARM 前先检查目标架构是否存在。

单元化的完整设计不在本文展开,见 13-单元化架构:平台侧的多集群模型只是它的基础设施,路由和跨单元一致性仍要在平台之外设计。

最小上手 ​

用原生 kubectl 复现平台发布引擎的判定逻辑:

bash
kubectl create deployment order --image=nginx:1.27 --replicas=3
kubectl rollout status deployment/order --timeout=600s
kubectl get deploy order -o jsonpath='{.status.updatedReplicas}/{.status.readyReplicas}/{.status.unavailableReplicas}{"\n"}'
kubectl get endpoints order   # Endpoint 为空时,Ready 也不代表能接流量
kubectl rollout undo deployment/order --to-revision=1

rollout status 成功只代表这次滚动完成;真正的验收还要看三个副本计数和 Endpoint。平台做的事,本质上是把这些判断固化成状态机,并补上跨集群、检查链和审计。

生产动作 ​

  • 每个集群登记能力清单,发布前校验;不支持的组合直接拒绝,并给出替代方案。
  • 发布超时、中止等待、观察窗口做成应用级配置;冻结走统一入口,配合 RBAC 和漂移检测。
  • 检查链按环境配置,阻断项和告警项分开;每次发布留存检查报告。
  • 网络参数用模板加校验(端口范围、证书有效期、域名冲突),不要只做表单。
  • 镜像固定 digest;基础镜像变更触发影响面查询和批量重建。
  • 灰度、蓝绿策略见 03-灰度发布与交付平台。

边界 ​

  • 抽象必然泄漏:托管 K8s 的负载均衡、存储、日志实现各不相同,平台只能声明能力,不能假装一致。
  • apply 成功不等于可用:调度、拉镜像、探针、Endpoint 任何一环失败,都可能在 apply 之后才发生。
  • 检查链挡不住业务错误:它能查配置和资源,查不了「这个版本逻辑对不对」。
  • 平台数据库不是真相,冻结也不是集群锁:有人绕过平台改集群时只有漂移检测能发现;冻结依赖 RBAC 和审计兜底。
  • 网络参数代填有上限:平台不知道应用协议、长连接比例和真实健康语义,默认值只能保守。
  • 镜像扫描有误报:漏洞是否可利用要看运行时和依赖路径,分级和例外机制比「零漏洞」更现实。

讲清楚 ​

多集群平台的本质是把应用意图与集群对象解耦:平台存期望配置,适配层渲染成各集群对象。适配层应屏蔽凭证、命名和 API 差异,但必须暴露能力差异——Ingress 类型、负载均衡实现、CNI、架构、版本门槛,不支持就拒绝,不能静默降级。

发布是异步状态机,成功标准是副本全部 Ready 且 Endpoint 有后端,不是 apply 成功;中止停在安全点再决策,回滚基于上一版记录。

检查链重在分级:配置、资源、策略阻断,依赖和容量估算告警。网络三层各管一段,端口、健康检查、超时、证书任一层错位都会变成 502 或 504。

把一门技术讲透到既懂也会用