Kubernetes 缺少的不是又一个平台,而是组合原语
AWS EKS 工程师、kro 项目贡献者 Adam Crowder 论证,Kubernetes 平台团队反复构建相同的资源组合逻辑,而 kro 将组合本身视为共享原语:依赖顺序从 CEL 引用中推导而非单独声明,从而消除声明与引用之间的漂移。
问一个平台团队去年建了什么,答案大概率是一个 Kubernetes 控制器:按顺序创建几个对象,在它们之间复制字段,没有业务逻辑,只有接线。一个 Deployment,前面放一个 Service,再加一个 ConfigMap,其中唯一有意思的值是 Service 状态里报告的 hostname。Kubebuilder 把第一个控制器变成愉快的下午,第十个则带来自己的 reconcile bug、升级路径和 on-call 轮值。它是同一个控制器,每次略有不同,存在于成千上万个组织中。
AWS 工程师、kro 项目贡献者 Adam Crowder 在 CNCF 赞助的这篇技术随笔中提出了一个架构判断:Kubernetes 有所有原料——CRD 定义新类型,控制器赋予行为,server-side apply 追踪字段所有权,CEL 求值表达式,OpenAPI 给每个字段类型——但没有提供'食谱'。没有任何东西说几个资源构成一个整体,或者一个资源的值来自另一个资源的状态并且必须等待它。
这个缺失不是假设。Helm 在渲染时做组合,Helm 4 加了 server-side apply 和等待资源状态。Argo CD 有 sync waves、hooks 和 health checks,因为清单本身说不出需要什么顺序或何时完成。Crossplane 有 compositions 和 functions,v2 直接组合原生 Kubernetes 资源。Operator 用 Go 手写。把这些工具剥开,内核相同:一组对象、对象间的依赖、沿边流动的值、以及一个持续重新检查集群是否仍然匹配的东西。每个近期新增功能都在伸手够一个该工具原本缺少的部分:顺序、就绪、逐字段所有权、集群内求值。
Crowder 认为,既然每个人都在构建同一块,问题就变成了它是否应该被共享。这正是 kro 的立场,也是它于 2025 年 9 月移入 Kubernetes SIG Cloud Provider 的原因——作为其他东西在其上构建的原语,而非又一个端到端平台。
kro 的做法是把组合写成数据。ResourceGraphDefinition 持有两个可分离的部分:组合(每个实例背后的资源模板和引用它们之间的 CEL 表达式)和 API(schema、由它生成的 CRD、以及应用团队创建的实例)。关键机制在于:依赖图从 CEL 表达式中的引用推导,顺序从不显式声明。ConfigMap 模板引用了 Service 的 hostname 字段,这个引用本身就是整个排序规则——ConfigMap 从 Service 读取,所以 Service 先创建,没有人写下顺序。显式声明的顺序是对清单已经包含的关系的第二份描述,重命名或重排后两者可能不一致。推导的顺序不会漂移,因为它是派生的而非独立定义的。依赖就是引用。
Crowder 进一步指出,kro 0.10 的 Graph 原语揭示了更根本的分离:API 从来不是输入,只是一种输入。一个组合针对集群状态做 reconcile,自定义资源的新实例只是它能反应的众多状态变化之一。Graph 是组合部分单独存在——有命名空间,没有 schema,没有 CRD,没有实例。给 Service 打个标签,一条 Ingress 规则出现;去掉标签,它消失。没有人创建实例。
他用了一个 Kubernetes 自身的先例来类比:滚动更新曾经是 kubectl 子命令,直到 Deployment 控制器接管了它,上面的一切都变得更简单。组合看起来到了同样的迁移时刻。
需要指出的是,Crowder 本人是 kro 的贡献者,这篇论证带有项目立场。最强的反驳是:碎片化可能是组织问题而非架构缺陷——团队不共享是因为没有动力或信任,不是因为原语不存在。Helm、Argo CD 和 Crossplane 各自已经解决了组合问题,用户各取所需。将组合抽为共享原语是否真正减少总复杂度,还是只是把复杂度从写代码转移到定义数据和拥有 API,Crowder 自己给出了部分答案:十套代码库没了,十个 API 留下了。成本从写抽象移到了拥有抽象。团队应该数 API,不是数控制器。