Crossplane Composite Resources are opinionated Kubernetes Custom Resources that are composed of Managed Resources. We often call them XRs for short.
Composite Resources are designed to let you build your own platform with your own opinionated concepts and APIs without needing to write a Kubernetes controller from scratch. Instead, you define the schema of your XR and teach Crossplane which Managed Resources it should compose (i.e. create) when someone creates the XR you defined.
If you’re already familiar with Composite Resources and looking for a detailed configuration reference or some tips, tricks, and troubleshooting information, try the Composition Reference.
Below is an example of a Composite Resource:
apiVersion: database.example.org/v1alpha1
kind: XPostgreSQLInstance
metadata:
name: my-db
spec:
parameters:
storageGB: 20
compositionRef:
name: production
writeConnectionSecretToRef:
namespace: crossplane-system
name: my-db-connection-details
You define your own XRs, so they can be of whatever API version and kind you like, and contain whatever spec and status fields you need.
The first step towards using Composite Resources is configuring Crossplane so
that it knows what XRs you’d like to exist, and what to do when someone creates
one of those XRs. This is done using a CompositeResourceDefinition
(XRD)
resource and one or more Composition
resources.
Once you’ve configured Crossplane with the details of your new XR you can either create one directly, or use a claim. Typically only the folks responsible for configuring Crossplane (often a platform or SRE team) have permission to create XRs directly. Everyone else manages XRs via a lightweight proxy resource called a Composite Resource Claim (or claim for short). More on that later.
If you’re coming from the Terraform world you can think of an XRD as similar to the
variable
blocks of a Terraform module, while theComposition
is the rest of the module’s HCL code that describes how to use those variables to create a bunch of resources. In this analogy the XR or claim is a little like atfvars
file providing inputs to the module.
A CompositeResourceDefinition
(or XRD) defines the type and schema of your XR.
It lets Crossplane know that you want a particular kind of XR to exist, and what
fields that XR should have. An XRD is a little like a CustomResourceDefinition
(CRD), but slightly more opinionated. Writing an XRD is mostly a matter of
specifying an OpenAPI “structural schema”.
The XRD that defines the XPostgreSQLInstance
XR above would look like this:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.database.example.org
spec:
group: database.example.org
names:
kind: XPostgreSQLInstance
plural: xpostgresqlinstances
claimNames:
kind: PostgreSQLInstance
plural: postgresqlinstances
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
storageGB:
type: integer
required:
- storageGB
required:
- parameters
You might notice that the XPostgreSQLInstance
example above has some fields
that don’t appear in the XRD, like the writeConnectionSecretToRef
and
compositionRef
fields. This is because Crossplane automatically injects some
standard Crossplane Resource Model (XRM) fields into all XRs.
A Composition
lets Crossplane know what to do when someone creates a Composite
Resource. Each Composition
creates a link between an XR and a set of one or
more Managed Resources - when the XR is created, updated, or deleted the set of
Managed Resources are created, updated or deleted accordingly.
You can add multiple Compositions for each XRD, and choose which should be used when XRs are created. This allows a Composition to act like a class of service - for example you could configure one Composition for each environment you support, such as production, staging, and development.
A basic Composition
for the above XPostgreSQLInstance
might look like this:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: example
labels:
crossplane.io/xrd: xpostgresqlinstances.database.example.org
provider: gcp
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: database.example.org/v1alpha1
kind: XPostgreSQLInstance
resources:
- name: cloudsqlinstance
base:
apiVersion: database.gcp.crossplane.io/v1beta1
kind: CloudSQLInstance
spec:
forProvider:
databaseVersion: POSTGRES_12
region: us-central1
settings:
tier: db-custom-1-3840
dataDiskType: PD_SSD
ipConfiguration:
ipv4Enabled: true
authorizedNetworks:
- value: "0.0.0.0/0"
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.storageGB
toFieldPath: spec.forProvider.settings.dataDiskSizeGb
The above Composition
tells Crossplane that when someone creates an
XPostgreSQLInstance
XR Crossplane should create a CloudSQLInstance
in
response. The storageGB
field of the XPostgreSQLInstance
should be used to
configure the dataDiskSizeGb
field of the CloudSQLInstance
. This is only a
small subset of the functionality a Composition
enables - take a look at the
reference page to learn more.
We almost always talk about XRs composing Managed Resources, but actually an XR can also compose other XRs to allow nested layers of abstraction. XRs don’t support composing arbitrary Kubernetes resources (e.g. Deployments, operators, etc) directly but you can do so using our Kubernetes and Helm providers.
Crossplane uses Composite Resource Claims (or just claims, for short) to allow application operators to provision and manage XRs. When we talk about using XRs it’s typically implied that the XR is being used via a claim. Claims are almost identical to their corresponding XRs. It helps to think of a claim as an application team’s interface to an XR. You could also think of claims as the public (app team) facing part of the opinionated platform API, while XRs are the private (platform team) facing part.
A claim for the XPostgreSQLInstance
XR above would look like this:
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstance
metadata:
namespace: default
name: my-db
spec:
parameters:
storageGB: 20
compositionRef:
name: production
writeConnectionSecretToRef:
name: my-db-connection-details
There are three key differences between an XR and a claim:
kind
than the XR - by convention the XR’s kind
without the proceeding X
. For example a PostgreSQLInstance
claims an
XPostgreSQLInstance
.Not all XRs offer a claim - doing so is optional. See the XRD section of the Composition reference to learn how to offer a claim.
Claims may seem a little superfluous at first, but they enable some handy scenarios, including:
Private XRs. Sometimes a platform team might not want a type of XR to be
directly consumed by their application teams. For example because the XR
represents ‘supporting’ infrastructure - consider the above VPC XNetwork
XR. App
teams might create PostgreSQLInstance
claims that reference (i.e. consume)
an XNetwork
, but they shouldn’t be creating their own. Similarly, some
kinds of XR might be intended only for ’nested’ use - intended only to be
composed by other XRs.
Global XRs. Not all infrastructure is conceptually namespaced. Say your
organisation uses team scoped namespaces. A PostgreSQLInstance
that belongs
to Team A should probably be part of the team-a
namespace - you’d represent
this by creating a PostgreSQLInstance
claim in that namespace. On the other
hand the XNetwork
XR we mentioned previously could be referenced (i.e. used)
by XRs from many different namespaces - it doesn’t exist to serve a particular
team.
Pre-provisioned XRs. Finally, separating claims from XRs allows a platform team to pre-provision certain kinds of XR. Typically an XR is created on-demand in response to the creation of a claim, but it’s also possible for a claim to instead request an existing XR. This can allow application teams to instantly claim infrastructure like database instances that would otherwise take minutes to provision on-demand.