Crossplane’s Composite Resource Definitions (XRDs) let you define your own cloud primitives, effectively building your own internal PaaS on top of existing cloud providers.

Let’s see this in action. Imagine you want a "ManagedDatabase" that’s always a PostgreSQL instance with specific configurations, regardless of whether it’s running on AWS RDS or GCP Cloud SQL.

First, we define the Composite Resource Definition (XRD):

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: manageddatabases.mycompany.com
spec:
  group: mycompany.com
  names:
    kind: ManagedDatabase
    plural: manageddatabases
  versions:
    - name: v1alpha1
      served: true
      referenceable: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                storageGB:
                  type: integer
                version:
                  type: string
              required:
                - storageGB
                - version

This tells Crossplane about a new kind of resource: ManagedDatabase. It has two spec fields: storageGB and version.

Next, we define a Composite Resource (XResource) of this new kind. This is where we specify the actual cloud resources that will be provisioned. We’ll use a CompositeToManaged composition.

apiVersion: mycompany.com/v1alpha1
kind: ManagedDatabase
metadata:
  name: my-prod-db
spec:
  parameters:
    storageGB: 100
    version: "13.4"
  compositionSelector:
    matchLabels:
      composition: aws-rds-postgres

This ManagedDatabase resource, my-prod-db, requests 100GB storage and PostgreSQL version 13.4. It also uses a compositionSelector to find a specific composition.

Now, the composition itself. This is the "glue" that maps our abstract ManagedDatabase to concrete cloud resources.

apiVersion: core.crossplane.io/v1
kind: Composition
metadata:
  name: aws-rds-postgres
  labels:
    composition: aws-rds-postgres
spec:
  compositeTypeRef:
    apiVersion: mycompany.com/v1alpha1
    kind: ManagedDatabase
  writeConnectionSecretToFieldPaths:
    - connectionSecretRef.name
    - connectionSecretRef.namespace
  patchSets:
    - name: common-patches
      patches:
        - fromFieldPath: spec.parameters.version
          toFieldPath: spec.forProvider.engineVersion
        - fromFieldPath: spec.parameters.storageGB
          toFieldPath: spec.forProvider.allocatedStorage
  resources:
    - name: rds-instance
      base:
        apiVersion: rds.aws.upbound.io/v1beta1
        kind: Instance
        spec:
          region: us-east-1
          skipFinalSnapshot: true
          dbSubnetGroupName: default-rds-subnet-group
          vpcSecurityGroupIds:
            - sg-0123456789abcdef0
          publiclyAccessible: false
      patches:
        - patchSetRef:
            name: common-patches
        - fromFieldPath: spec.parameters.version
          toFieldPath: spec.forProvider.engine
          transforms:
            - type: map
              map:
                "13.4": postgres
                "14.2": postgres
        - fromFieldPath: metadata.name
          toFieldPath: spec.writeConnectionSecretToRef.name
          transforms:
            - type: prepend
              string: managed-db-
        - fromFieldPath: metadata.namespace
          toFieldPath: spec.writeConnectionSecretToRef.namespace

This Composition named aws-rds-postgres describes how to fulfill a ManagedDatabase. It uses patchSets to copy version and storageGB from our ManagedDatabase spec to the underlying AWS RDS Instance spec. It also maps the version to the correct engine type (e.g., 13.4 becomes postgres). Crucially, it defines the actual AWS RDS Instance resource that will be created.

When you apply these, Crossplane’s engine orchestrates the creation of an AWS RDS instance. The ManagedDatabase resource becomes a single pane of glass for your developers, hiding the underlying cloud specifics. They don’t need to know about RDS instance classes, storage types, or regions; they just ask for a ManagedDatabase with their desired specs.

The writeConnectionSecretToFieldPaths and the patching for spec.writeConnectionSecretToRef are key for making connection details (like endpoints and credentials) available to your applications. Crossplane automatically populates a Kubernetes secret with this information.

What most people miss is how you can use transforms within patches to perform complex data manipulation. For example, you might need to convert a simple version string into a specific database engine identifier, or conditionally set a field based on a parameter value. This allows your abstract types to remain clean while the composition handles the intricate mapping to provider-specific APIs.

The next step is to define a composition for GCP Cloud SQL, allowing my-prod-db to be provisioned there with a simple change in its compositionSelector.

Want structured learning?

Take the full Crossplane course →