主题
多集群容器平台:接入、发布与网络
一句话:多集群平台的价值不是把 kubectl 包一层,而是把「应用意图」和「某个集群里的 K8s 对象」分开:平台保存前者,适配层负责后者;换来一次建模、多处运行,代价是抽象会泄漏,且必须明确哪些差异可以屏蔽、哪些只能暴露给用户。
适用版本:Kubernetes 1.24+、ingress-nginx 1.9+;不同发行版与托管 K8s 的能力以集群侧实际声明为准。
为什么需要
单集群时 kubectl apply 加一条流水线就够了。集群变成十几个、横跨自建机房和托管 K8s 后,问题不再是「怎么创建 Deployment」,而是:
- 同一个应用在 A 集群用 nginx Ingress,B 集群用另一套控制器,C 集群的负载均衡要调云 API 而不是建
LoadBalancerService; - 应用负责人只关心「我的服务、几个副本、什么镜像、什么域名」,不关心这些对象落在哪个 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 / 负载均衡 解决「集群外入口」:可以是云负载均衡,也可以由
LoadBalancerService 触发,负责公网 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=1rollout status 成功只代表这次滚动完成;真正的验收还要看三个副本计数和 Endpoint。平台做的事,本质上是把这些判断固化成状态机,并补上跨集群、检查链和审计。
生产动作
- 每个集群登记能力清单,发布前校验;不支持的组合直接拒绝,并给出替代方案。
- 发布超时、中止等待、观察窗口做成应用级配置;冻结走统一入口,配合 RBAC 和漂移检测。
- 检查链按环境配置,阻断项和告警项分开;每次发布留存检查报告。
- 网络参数用模板加校验(端口范围、证书有效期、域名冲突),不要只做表单。
- 镜像固定 digest;基础镜像变更触发影响面查询和批量重建。
- 灰度、蓝绿策略见 03-灰度发布与交付平台。
边界
- 抽象必然泄漏:托管 K8s 的负载均衡、存储、日志实现各不相同,平台只能声明能力,不能假装一致。
- apply 成功不等于可用:调度、拉镜像、探针、Endpoint 任何一环失败,都可能在 apply 之后才发生。
- 检查链挡不住业务错误:它能查配置和资源,查不了「这个版本逻辑对不对」。
- 平台数据库不是真相,冻结也不是集群锁:有人绕过平台改集群时只有漂移检测能发现;冻结依赖 RBAC 和审计兜底。
- 网络参数代填有上限:平台不知道应用协议、长连接比例和真实健康语义,默认值只能保守。
- 镜像扫描有误报:漏洞是否可利用要看运行时和依赖路径,分级和例外机制比「零漏洞」更现实。
讲清楚
多集群平台的本质是把应用意图与集群对象解耦:平台存期望配置,适配层渲染成各集群对象。适配层应屏蔽凭证、命名和 API 差异,但必须暴露能力差异——Ingress 类型、负载均衡实现、CNI、架构、版本门槛,不支持就拒绝,不能静默降级。
发布是异步状态机,成功标准是副本全部 Ready 且 Endpoint 有后端,不是 apply 成功;中止停在安全点再决策,回滚基于上一版记录。
检查链重在分级:配置、资源、策略阻断,依赖和容量估算告警。网络三层各管一段,端口、健康检查、超时、证书任一层错位都会变成 502 或 504。