Skip to content

Banner image Banner image

Building self-service platforms with Crossplane v2.0

I liked this session because it skipped the fluff and went straight to the awkward bit most platform teams know too well: developers waiting days, sometimes weeks, for fairly standard infrastructure requests.

Quick takeaways

  • Expose a smaller platform API to developers, keep complexity behind compositions.
  • Use Crossplane Projects to keep API, function code, and tests versioned together.
  • Treat metrics as an operations product, not just raw counters.
  • Start with one self-service path, then expand once usage is stable.

What was getting in the way

At KubeCon EU 2026, Jared Watts and Adam Wolfe Gordon (Upbound) presented a universal challenge in platform engineering: developers often wait weeks to deploy services due to infrastructure complexity, compliance requirements, and DevOps bottlenecks. Creating a database, configuring networking, setting up monitoring - each step requires coordination across multiple teams and tools.

What we actually wanted

A control plane framework that extends Kubernetes to orchestrate everything beyond containers - enabling platform teams to expose curated, self-service APIs to developers while maintaining guardrails and organisational best practices.

CNCF graduation milestone

Crossplane achieved CNCF graduation status with over 3,000 community contributors, cementing its position as the foundational framework for platform engineering. This maturity brings:

  • Production-proven stability across enterprises
  • Broad ecosystem support (900+ AWS services as Kubernetes APIs)
  • Active governance and security practices
  • Multi-cloud abstraction layer built on Kubernetes patterns

Architecture: control plane for everything

Crossplane v2.0 container view Crossplane v2.0 container view

Core concepts

1. Composite Resource Definitions (XRDs)
Define the shape of your platform API - what developers see and interact with:

apiVersion: example.com/v1
kind: App
spec:
  image: my-container:v2.0
  database: postgres
  storage: 100Gi

Platform teams curate this experience, constraining options while maintaining flexibility.

2. Compositions
Implement the logic and transformation - how developer requests fan out into actual infrastructure: - Functions pipeline (gRPC-based, language-agnostic) - Python, Go, TypeScript, or simple Go templates - Transform XR → Deployment, Service, RDS instance, networking, scaling policies

3. Managed Resources
Represent cloud provider services as reconciled Kubernetes API objects: - S3 buckets, EKS clusters, RDS databases become kind: Bucket, kind: EKSCluster - Continuous reconciliation fixes drift automatically - Status conditions reflect real-world state

The Promise: From Weeks to Seconds

Before Crossplane:
Developer → DevOps ticket → Infrastructure team → Compliance review → Manual provisioning → Weeks elapsed

After Crossplane:
Developer applies simple App CR → Platform automatically provisions deployment + database + networking + monitoring → Minutes elapsed

Crossplane v2.0: developer experience improvements

The multi-repo problem

Traditional Crossplane development required juggling: - Repository for functions (Python/Go code) - Repository for configurations (XRDs, Compositions) - Dependencies spanning multiple repos - Manual synchronization on every update

Result: High cognitive load, brittle workflows, coordination overhead.

Crossplane Projects: unified development artifact

The v2.0 release introduces Projects - a single source repository containing: - API definitions (JSON Schema → XRDs) - Composition logic (functions) - Dependencies (providers, CRDs) - Tests (X-prin framework) - Versioning (unified releases)

Think of it like a modern application repository but for your platform APIs.

Live demo walkthrough

Adam demonstrated the new workflow at KubeCon:

1. Initialize Project

crossplane beta project init my-platform
cd my-platform

Creates structure:

crossplane.yaml  # Project metadata, OCI registry
apis/            # API definitions
functions/       # Function code
compositions/    # Composition templates

2. Define API with JSON Schema

{
  "type": "object",
  "properties": {
    "image": { "type": "string" },
    "port": { "type": "integer" },
    "database": { "type": "string", "enum": ["postgres", "mysql"] }
  }
}

Generate XRD:

crossplane beta project xrd generate api.json

3. Generate Composition and Function

crossplane beta project composition generate
crossplane beta project function add my-app-function

Creates Python function template with auto-ready baseline.

4. Write Function Logic

# functions/my-app-function/main.py
def compose(xr, observed, desired):
    # Extract values from XR
    image = xr.spec.image
    port = xr.spec.port
    db = xr.spec.database

    # Compose Kubernetes resources
    deployment = {
        "apiVersion": "apps/v1",
        "kind": "Deployment",
        "spec": {
            "template": {
                "spec": {
                    "containers": [{
                        "image": image,
                        "ports": [{"containerPort": port}]
                    }]
                }
            }
        }
    }

    service = {/* ... */}
    database_instance = {/* ... */}

    return [deployment, service, database_instance]

5. Test Locally

crossplane beta project render # Dry-run function pipeline
crossplane beta project run    # Spin up local kind cluster

Creates complete local environment with Crossplane + your project installed.

6. Validate with X-prin

# tests/app-test.yaml
xr:
  spec:
    image: nginx:1.21
    port: 80
    database: postgres

assertions:
  - deployment.spec.template.spec.containers[0].image == "nginx:1.21"
  - service.spec.ports[0].port == 80
  - database.spec.engine == "postgres"

Run tests:

xprin test tests/app-test.yaml

7. Deploy to Production

crossplane beta project build
crossplane beta project push

Packages everything into OCI artifact, pushes to registry.

Resource State Metrics: granular observability

The second major v2.0 feature addresses operational visibility at scale.

The old problem

Traditional Crossplane metrics: - "15 EKS clusters are unhealthy" - But which clusters? Which teams affected? What's the scope?

The new solution: Resource State Metrics

Built on upstream Resource State Metrics project with Cel expressions:

apiVersion: metrics.crossplane.io/v1alpha1
kind: ResourceMetricsMonitor
metadata:
  name: eks-cluster-health
spec:
  resources:
    - apiVersion: ec2.aws.crossplane.io/v1beta1
      kind: Cluster

  metrics:
    - name: cluster_health
      help: "EKS cluster health by team and environment"
      labels:
        team: 'object.metadata.labels["team"]'
        environment: 'object.metadata.labels["environment"]'
        xr_name: 'object.metadata.labels["crossplane.io/claim-name"]'

      cel: |
        object.status.conditions.exists(c, c.type == "Ready" && c.status == "True") ? 1 : 0

Result: Prometheus metrics with team/environment/XR labels for precise troubleshooting.

Cardinality Management

cardinalityLimit: 100  # Prevent Prometheus explosions

Status shows current usage:

status:
  observedCardinality: 12
  withinLimit: true

Grafana Dashboards

Query by team or environment:

cluster_health{team="platform", environment="prod"} == 0

Answers: "Show me unhealthy clusters for the platform team in production."

Implementation blueprint

For platform teams

  1. Adopt Crossplane Projects
  2. Migrate from multi-repo to unified project structure
  3. Version APIs + functions together
  4. Simplify developer onboarding

  5. Define Curated APIs

  6. Start with JSON Schema (familiar tooling)
  7. Constrain options (database sizes, instance types)
  8. Use familiar abstractions (App, Database, Queue)

  9. Write Functions in Preferred Language

  10. Python for data transformation
  11. Go for performance-critical logic
  12. TypeScript for web team expertise

  13. Deploy Metrics Monitoring

  14. Create ResourceMetricsMonitor for critical resources
  15. Extract team/environment labels
  16. Set cardinality limits per monitor

For developers

  1. Use Platform APIs

    apiVersion: example.com/v1
    kind: App
    metadata:
      name: my-service
    spec:
      image: my-org/my-service:v2.0
      database: postgres
      storage: 50Gi
    

  2. Self-Service Without Tickets

  3. No DevOps coordination
  4. No weeks-long waits
  5. Guardrails prevent misconfigurations

For organisations

  1. Measure Platform Success
  2. Track time-to-first-deployment
  3. Monitor ticket reduction
  4. Survey developer satisfaction

  5. Scale Incrementally

  6. Start with one team/use case
  7. Validate platform-market fit
  8. Iterate based on feedback

What changed in practice

Before: Infrastructure as code spread across Terraform, CloudFormation, Helm charts - manual coordination, weeks-long cycles
After: Unified Kubernetes API for everything - self-service with guardrails, minutes-to-deployment

Crossplane v2.0 shows platform engineering maturity: standardised patterns, better developer experience, and practical operational observability. With CNCF graduation and over 3,000 contributors, momentum in the ecosystem is strong.

The shift from "Platform as Code" to "Platform as API" fundamentally changes how organisations scale infrastructure operations.

References


Presented at KubeCon + CloudNativeCon Europe 2026 by Jared Watts & Adam Wolfe Gordon (Upbound)