---
title: "Crossplane for Platform Abstractions"
description: "Building platform abstractions with Crossplane — Compositions, CompositeResourceDefinitions (XRDs), claims, provider packages, and a practical database claim that provisions RDS, CloudSQL, or Azure DB based on environment."
url: https://agent-zone.ai/knowledge/platform-engineering/crossplane-platform-abstractions/
section: knowledge
date: 2026-02-22
categories: ["platform-engineering"]
tags: ["crossplane","platform-abstractions","xrd","compositions","kubernetes","cloud-provisioning","infrastructure-as-code"]
skills: ["crossplane-authoring","xrd-design","composition-development","provider-configuration"]
tools: ["crossplane","kubernetes","aws","gcp","azure","helm","kubectl"]
levels: ["intermediate","advanced"]
word_count: 1030
formats:
  json: https://agent-zone.ai/knowledge/platform-engineering/crossplane-platform-abstractions/index.json
  html: https://agent-zone.ai/knowledge/platform-engineering/crossplane-platform-abstractions/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Crossplane+for+Platform+Abstractions
---


## What Crossplane Does

Crossplane extends Kubernetes to provision and manage cloud infrastructure using the Kubernetes API. Instead of writing Terraform and running `apply`, you write Kubernetes manifests and `kubectl apply` them. Crossplane controllers reconcile the desired state with the actual cloud resources.

The real value is not replacing Terraform — it is building abstractions. Platform teams define custom resource types (like `DatabaseClaim`) that developers consume without knowing whether they are getting RDS, CloudSQL, or Azure Database. The composition layer maps the simple claim to the actual cloud resources.

## Architecture: Four Layers

**Providers** are the lowest layer. Each provider talks to one cloud API. `provider-aws` manages AWS resources, `provider-gcp` manages GCP, `provider-azure` manages Azure. Install them as Kubernetes packages:

```yaml
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-aws
spec:
  package: xpkg.upbound.io/upbound/provider-aws-rds:v1.16.0
```

Providers install CRDs for each cloud resource. After installing `provider-aws-rds`, you can create `RDSInstance` resources directly. But developers should never interact with provider resources directly — that defeats the purpose.

**Managed Resources** are the provider-level representations of cloud resources. A managed resource for an RDS instance:

```yaml
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
metadata:
  name: my-database
spec:
  forProvider:
    region: us-east-1
    engine: postgres
    engineVersion: "15"
    instanceClass: db.t3.medium
    allocatedStorage: 20
    masterUsername: admin
    masterPasswordSecretRef:
      name: db-password
      namespace: crossplane-system
      key: password
```

This works but exposes every cloud-specific parameter. Developers should not need to know about `instanceClass` or `allocatedStorage`.

**Composite Resources (XRs)** are the platform team's abstractions. An XR defines a high-level resource type — `XDatabase`, `XBucket`, `XCache` — with a simplified schema. The platform team writes Compositions that map XR fields to managed resources.

**Claims** are the developer-facing interface. A claim is a namespaced request for a composite resource. Developers create claims; the platform resolves them into cloud resources.

## Building a Database Claim: Step by Step

### Step 1: Define the XRD

The CompositeResourceDefinition (XRD) defines the schema for your abstraction. This is what developers will see as the API contract.

```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: xdatabases.platform.example.com
spec:
  group: platform.example.com
  names:
    kind: XDatabase
    plural: xdatabases
  claimNames:
    kind: DatabaseClaim
    plural: databaseclaims
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                engine:
                  type: string
                  enum: ["postgres", "mysql"]
                  description: "Database engine"
                version:
                  type: string
                  description: "Engine version"
                size:
                  type: string
                  enum: ["small", "medium", "large"]
                  description: "T-shirt size for compute and storage"
                region:
                  type: string
                  description: "Cloud region"
              required: ["engine", "size"]
            status:
              type: object
              properties:
                connectionHost:
                  type: string
                connectionPort:
                  type: string
                databaseName:
                  type: string
```

The XRD creates two CRDs: `XDatabase` (cluster-scoped composite) and `DatabaseClaim` (namespace-scoped claim). Developers only interact with `DatabaseClaim`.

### Step 2: Write Compositions

Each Composition maps the XRD to a specific cloud provider's resources. You write one Composition per provider.

**AWS Composition:**

```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: xdatabase-aws
  labels:
    provider: aws
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
  mode: Pipeline
  pipeline:
    - step: patch-and-transform
      functionRef:
        name: function-patch-and-transform
      input:
        apiVersion: pt.fn.crossplane.io/v1beta1
        kind: Resources
        resources:
          - name: rds-instance
            base:
              apiVersion: rds.aws.upbound.io/v1beta2
              kind: Instance
              spec:
                forProvider:
                  engine: postgres
                  publiclyAccessible: false
                  skipFinalSnapshot: false
                  storageEncrypted: true
                  autoMinorVersionUpgrade: true
            patches:
              - type: FromCompositeFieldPath
                fromFieldPath: spec.engine
                toFieldPath: spec.forProvider.engine
              - type: FromCompositeFieldPath
                fromFieldPath: spec.version
                toFieldPath: spec.forProvider.engineVersion
              - type: FromCompositeFieldPath
                fromFieldPath: spec.region
                toFieldPath: spec.forProvider.region
              - type: FromCompositeFieldPath
                fromFieldPath: spec.size
                toFieldPath: spec.forProvider.instanceClass
                transforms:
                  - type: map
                    map:
                      small: db.t3.micro
                      medium: db.t3.medium
                      large: db.r6g.xlarge
              - type: FromCompositeFieldPath
                fromFieldPath: spec.size
                toFieldPath: spec.forProvider.allocatedStorage
                transforms:
                  - type: map
                    map:
                      small: 20
                      medium: 100
                      large: 500
              - type: ToCompositeFieldPath
                fromFieldPath: status.atProvider.endpoint
                toFieldPath: status.connectionHost
```

**GCP Composition:**

```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: xdatabase-gcp
  labels:
    provider: gcp
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
  mode: Pipeline
  pipeline:
    - step: patch-and-transform
      functionRef:
        name: function-patch-and-transform
      input:
        apiVersion: pt.fn.crossplane.io/v1beta1
        kind: Resources
        resources:
          - name: cloudsql-instance
            base:
              apiVersion: sql.gcp.upbound.io/v1beta2
              kind: DatabaseInstance
              spec:
                forProvider:
                  deletionProtection: true
                  settings:
                    - tier: db-f1-micro
                      ipConfiguration:
                        - ipv4Enabled: false
                          privateNetworkRef:
                            name: platform-vpc
            patches:
              - type: FromCompositeFieldPath
                fromFieldPath: spec.engine
                toFieldPath: spec.forProvider.databaseVersion
                transforms:
                  - type: map
                    map:
                      postgres: POSTGRES_15
                      mysql: MYSQL_8_0
              - type: FromCompositeFieldPath
                fromFieldPath: spec.size
                toFieldPath: spec.forProvider.settings[0].tier
                transforms:
                  - type: map
                    map:
                      small: db-f1-micro
                      medium: db-n1-standard-2
                      large: db-n1-standard-8
```

### Step 3: Select Composition by Environment

Use a `CompositionSelector` to pick the right Composition based on labels. The platform team labels claims with the target provider:

```yaml
spec:
  compositeTypeRef:
    apiVersion: platform.example.com/v1alpha1
    kind: XDatabase
  compositionSelector:
    matchLabels:
      provider: aws    # or gcp, azure
```

Or use `compositionRef` to hardcode the selection per environment. A common pattern is setting the provider label via a namespace annotation that the platform team configures per cluster.

### Step 4: Developer Creates a Claim

The developer's experience is simple:

```yaml
apiVersion: platform.example.com/v1alpha1
kind: DatabaseClaim
metadata:
  name: orders-db
  namespace: orders-team
spec:
  engine: postgres
  version: "15"
  size: medium
  compositionSelector:
    matchLabels:
      provider: aws
```

Apply it: `kubectl apply -f database-claim.yaml`. Crossplane provisions the cloud resource, creates a connection secret, and updates the claim status with connection details.

Check status:

```bash
kubectl get databaseclaim orders-db -n orders-team
kubectl describe databaseclaim orders-db -n orders-team
```

The connection secret appears in the same namespace:

```bash
kubectl get secret orders-db-connection -n orders-team -o yaml
```

## Provider Configuration

Providers need cloud credentials. Use `ProviderConfig` to reference a Kubernetes secret or IRSA (IAM Roles for Service Accounts):

```yaml
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  credentials:
    source: IRSA
```

For GCP, use Workload Identity. For Azure, use Workload Identity Federation. Avoid long-lived credential secrets in production.

## Composition Functions

Crossplane v1.14+ supports Composition Functions — custom logic written in Go, Python, or any language that implements the function protocol. Use functions when patch-and-transform is not expressive enough:

```yaml
pipeline:
  - step: validate-size
    functionRef:
      name: function-custom-validator
  - step: patch-and-transform
    functionRef:
      name: function-patch-and-transform
    input: ...
```

Functions enable conditional resource creation, complex transformations, and external lookups that pure patching cannot handle.

## Practical Considerations

**Debugging.** When a claim is not provisioning, check three places: the claim status (`kubectl describe databaseclaim`), the composite resource (`kubectl get xdatabase`), and the managed resource (`kubectl get instance.rds`). Errors bubble up but slowly — check the managed resource first for fast feedback.

**Drift detection.** Crossplane continuously reconciles. If someone modifies the cloud resource manually, Crossplane reverts it. This is a feature, not a bug — but it surprises teams accustomed to Terraform's plan/apply workflow.

**Deletion.** Deleting a claim deletes the composite, which deletes the managed resources, which deletes the cloud resources. Set `deletionPolicy: Orphan` on managed resources if you want to keep the cloud resource after claim deletion.

**Testing.** Use `crossplane beta render` to preview what a Composition will produce without applying it. This is essential for CI validation of Composition changes.

