ReadingResearchRadarInvestment frameworkAI Company Primary Sources
Sign in / Sign up
← Back to reading
NewsMaterial4 min readDisconfirmAI Editorial

What Kubernetes Is Missing Is Not Another Platform, But Composition Primitives

Adam Crowder, an AWS EKS engineer and contributor to the kro project, argues that Kubernetes platform teams repeatedly build the same resource-composition logic. The kro project treats composition itself as a shared primitive: dependency order is derived from references in Common Expression Language (CEL) rather than declared separately, thereby eliminating drift between declarations and references.

2026-10-29 21:00 GMT+8

Ask a platform team what they built last year, and the answer is likely a Kubernetes controller: it creates several objects in sequence, copies fields between them, contains no business logic, only wiring. A Deployment, preceded by a Service, plus a ConfigMap where the only interesting value is the hostname reported in the Service's status. Kubebuilder turns building the first controller into a pleasant afternoon; the tenth brings its own reconciliation bugs, upgrade path, and on-call rotation. It is the same controller, slightly different each time, existing in thousands of organizations.

In this CNCF-sponsored technical essay, Adam Crowder, an AWS engineer and contributor to the kro project, presents an architectural judgment: Kubernetes has all the ingredients—CRDs define new types, controllers give behavior, server-side apply tracks field ownership, CEL evaluates expressions, OpenAPI types every field—but provides no 'recipe.' Nothing says that several resources form one thing, or that a value in one comes from the status of another and must wait for it.

This missing piece is not hypothetical. Helm does composition at render time; Helm 4 added server-side apply and waiting for resource status. Argo CD has sync waves, hooks, and health checks because manifests themselves cannot say what order they need or when they are done. Crossplane has compositions and functions, with v2 composing native Kubernetes resources directly. Operators do it by hand in Go. Strip these tools down and the kernel is the same: a set of objects, dependencies between them, values flowing along edges, and something that keeps re-checking whether the cluster still matches. Each recent addition reaches for a part the tool originally lacked: order, readiness, per-field ownership, evaluation inside the cluster.

Sign in / Sign up
ReadingResearchRadarInvestment frameworkAI Company Primary Sources

Crowder argues that since everyone is building the same piece, the question becomes whether it should be shared. This is exactly kro's position, which is why it moved into the Kubernetes SIG Cloud Provider in September 2025—as a primitive for other things to build on, rather than yet another end-to-end platform.

kro's approach is to write composition as data. A ResourceGraphDefinition holds two separable parts: the composition (the resource template behind each instance and the CEL expressions referencing between them) and the API (the schema, the generated CRD, and the instances created by application teams). The key mechanism lies in deriving the dependency graph from references within CEL expressions, meaning order is never explicitly declared. The ConfigMap template references the Service's hostname field; this reference itself constitutes the entire ordering rule—the ConfigMap reads from the Service, so the Service is created first, and nobody writes down the order. Explicitly declared order is a second description of a relationship already contained in the manifest, and the two can become inconsistent after renaming or rearranging. Derived order does not drift because it is derived rather than independently defined. Dependency is reference.

Crowder further points out that kro 0.10's Graph primitive reveals a more fundamental separation: the API was never the input, only one kind of input. A composition reconciles against cluster state, and a new instance of a custom resource is just one of many state changes it can react to. The Graph exists as the composition part alone—with namespaces, but no schema, no CRD, no instances. Label a Service, and an Ingress rule appears; remove the label, and it disappears. Nobody creates an instance.

He uses a precedent from Kubernetes itself to draw an analogy: rolling updates used to be a kubectl subcommand until the Deployment controller took over, making everything above it simpler. Composition looks like it is due for the same migration moment.

It is worth noting that Crowder himself is a contributor to kro, so this argument carries a project bias. The strongest counterargument is that fragmentation may be an organizational problem rather than an architectural defect—teams do not share because there is no motivation or trust, not because the primitive does not exist. Helm, Argo CD, and Crossplane have each already solved the composition problem, and users take what they need. Whether extracting composition into a shared primitive truly reduces total complexity, or merely shifts complexity from writing code to defining data and owning APIs, Crowder offers a partial answer: ten codebases are gone, but ten APIs remain. The cost has moved from writing abstractions to owning them. Teams should count APIs, not controllers.

Sources

  • thenewstack.ioThe New Stack↗
Continue researching →