Infrastructure as code is solved — Terraform, Pulumi, and CDK all work. What is not solved is the question of how developers consume infrastructure. Handing every team a Terraform repo with cloud admin permissions does not scale and is rarely safe. The control-plane pattern is the answer the platform community has converged on.
The Idea
Build an in-cluster API that exposes higher-level resource types to developers — PostgresInstance, S3Bucket, KafkaTopic — implemented by a controller that talks to the cloud on their behalf. Developers kubectl apply a manifest; the controller reconciles. Same loop as Pods and Deployments, but for cloud resources.
This is what AWS, GCP, and Azure do internally for their own engineers. The tooling exists now to build it yourself.
Crossplane
Crossplane (CNCF Incubating, originally from Upbound) is the most widely adopted control plane framework. It works in two layers:
1. Providers and Managed Resources
A Provider is a controller that knows how to talk to a cloud. Install the AWS Provider and you get hundreds of CRDs like RDSInstance, S3Bucket, VPC:
apiVersion: rds.aws.crossplane.io/v1alpha1
kind: DBInstance
metadata:
name: orders-db
spec:
forProvider:
region: eu-west-1
dbInstanceClass: db.t4g.micro
engine: postgres
engineVersion: "16"
allocatedStorage: 20
masterUsername: orders
providerConfigRef: { name: aws-default }
This is a one-to-one wrapper around the cloud API. Useful, but still exposes too much surface to developers.
2. Compositions and XRDs
The killer feature. Define your own abstraction:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: postgresinstances.platform.acme.io
spec:
group: platform.acme.io
names:
kind: PostgresInstance
plural: postgresinstances
claimNames:
kind: Postgres
plural: postgreses
versions:
- name: v1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size: { type: string, enum: [small, medium, large] }
region: { type: string }
Then write a Composition that says "when someone asks for a Postgres of size 'small', create an RDSInstance with these parameters, a subnet group, a security group, and a Secret with credentials." The developer's manifest becomes:
apiVersion: platform.acme.io/v1
kind: Postgres
metadata:
name: orders-db
spec:
size: small
region: eu-west-1
Three lines. Behind the scenes Crossplane creates a dozen cloud resources. The developer sees only the abstraction the platform chose to expose.
Kratix and the Promise Pattern
Kratix (Syntasso) is younger and takes a different tack. Its abstraction is a Promise — a bundle that declares "I deliver a Postgres" and ships the controllers, RBAC, and templates needed to fulfil it. Promises can compose; one Promise can require another.
Kratix is multi-cluster from the ground up: a single platform cluster ("Kratix") schedules workloads onto destination clusters based on labels. Useful if your IDP serves many clusters across many regions or clouds.
Cluster API
If your control plane goal is clusters themselves, look at Cluster API (CAPI). It is a SIG-cluster-lifecycle project that exposes Cluster, MachineDeployment, MachineSet as Kubernetes resources, with providers for AWS, Azure, GCP, vSphere, bare metal. The standard way to declaratively provision and lifecycle fleets of Kubernetes clusters.
Why Bother? Trade-offs vs Terraform
| Concern | Terraform / Pulumi | Crossplane-style control plane |
|---|---|---|
| Mental model | Run a CLI to plan/apply | kubectl apply, reconcile loop |
| Drift detection | Periodic terraform plan | Continuous |
| Self-service | Developers run TF (risky) or open PRs | Developers apply a typed claim |
| State | External backend (S3, TF Cloud) | Kubernetes API + cloud reality |
| RBAC | Cloud IAM, TF Cloud teams | Kubernetes RBAC + namespaces |
| Operational cost | Low | Higher — you run controllers |
| Abstraction power | Modules (still exposes most surface) | XRDs (genuinely typed APIs) |
Hybrid is common: Terraform for bootstrap (the platform cluster itself, the VPC, the IAM baseline) and Crossplane for everything developers consume.
The GitOps Combination
Crossplane CRDs are Kubernetes resources, which means they fit perfectly into Argo CD or Flux. A team's repo contains their Postgres claim; Argo CD applies it; Crossplane reconciles to AWS; the secret with credentials is written into the team's namespace; the application Pod consumes it. End to end declarative, no human "click here."
When to Reach for This
- You serve many teams who currently provision via tickets or Terraform PRs that bottleneck on the platform team
- You have repeated abstractions (every team's "small Postgres" looks the same)
- You already have Kubernetes operators in production and the operational model is comfortable
- You want a single pane (kubectl + portal) for application and infrastructure
When Not to
- You are a small org with one cluster — overkill
- You lack Kubernetes operational depth — running controllers that touch cloud at scale is non-trivial
- Your cloud surface is exotic and unsupported by the Provider (compose with Terraform for the gap)
Operational Realities
- Crossplane Providers have their own release cadence; track them like dependencies
- Cloud-credentials handling is critical — use IRSA / workload identity, not static keys
- Plan for backup of the Crossplane state — losing the control-plane Kubernetes cluster loses your IaC reality of record
- For multi-region resilience, run multiple control planes — Kratix does this natively, Crossplane via federation patterns
Control planes are a powerful pattern but only if your platform team has the runway to operate them well. Used responsibly they collapse the gap between "deploy an app" and "provision a database" into a single mental model — which is the long-standing platform-engineering dream.