Today I’m going to give you a sneak peek into an upcoming OCI provider for Crossplane. Normally I’m not into spoiling surprises, however this is so exciting I’m breaking my normal form to give a pre-release view of what’s coming!
WARNING: What you’re about to see is pre-release. It’s subject to change. By the time the OCI Crossplane Provider is released, it’s possible that it’ll look vastly different than what you see here. The concepts will hopefully still be relevant, but your mileage may vary.
What is Crossplane?
Crossplane is a new (relatively speaking) entrant in the infrastructure-as-code (IaC) space. Like most IaC tools, it uses a declarative format for defining resources. It is a CNCF project, which means it has a healthy and active community behind it. Since the IaC tool space is fairly crowded (and has some very mature members), let’s look at a few of the capabilities that make Crossplane attractive.
Why Crossplane?
Crossplane is designed from the ground-up to deliver a “K8s-native” experience. It uses built-in K8s constructs to provide a management solution for different kinds of resources. Crossplane (like many other IaC tools) supports many different cloud providers, allowing it to integrate nicely into a multi-cloud strategy.
You can use traditional K8s role-based access control (RBAC) concepts and functionality to secure and control the interface used by Crossplane to manage resources. K8s data storage is used in the storage of resource definitions (both the Custom Resource Definition (CRD) and managed resource instantiations).
The “K8s-native” experience Crossplane delivers another inherent benefit: broad visibility into the end-to-end environment of an application. Crossplane delivers a single pane of glass, allowing you to view your entire environment from the OCI resources themselves up to the pods running in a Deployment.
This gives full visibility into the app environment, all available via the familiar K8s API (and tools like kubectl, k9s, etc.). OCI resources are defined in K8s manifests (and/or resources created via the API/kubectl), yielding a consistent management experience (and controls) as you get from deploying and managing applications in K8s (Deployments, Services, etc.).
Another Crossplane benefit is the “always on” functionality, meaning in a typical installation the Crossplane control-plane will be running constantly in the background, looking for changes that it might need to make, based on the resources that have been defined. Changes might be creating new resources, deleting previously resources or updating existing resources (often called “drift detection and correction”). The “always on” experience is not typical for open-source IaC tools, such as Terraform, Ansible and others. Because few open-source tools are always (or regularly) executing, drift detection/correction is also not common. I would be remiss if I didn’t point out that there are exceptions! OCI Resource Manager (ORM) provides drift detection and correction, but again, this isn’t typical in the standalone open-source tool space.
Some Important Crossplane Concepts
Before diving into some examples, you need to understand some basic Crossplane concepts. Each Crossplane provider includes Custom Resource Definitions (CRDs) (which is a native K8s construct) to extend the K8s API into recognizing resources that are unique to the specific Crossplane provider. This is how Crossplane is able to extend K8s to recognize new cloud resources (such as OCI resources). Here’s a snippet from looking at the output of kubectl api-resources on a cluster that has the OCI Crossplane provider:
$ kubectl api-resources
NAME SHORTNAMES APIVERSION NAMESPACED KIND
<snip>
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
persistentvolumes pv v1 false PersistentVolume
pods po v1 true Pod
<snip>
routetables core.oci.jet.crossplane.io/v1alpha1 false RouteTable
securitylists core.oci.jet.crossplane.io/v1alpha1 false SecurityList
subnets core.oci.jet.crossplane.io/v1alpha1 false Subnet
vcns core.oci.jet.crossplane.io/v1alpha1 false Vcn
vlans core.oci.jet.crossplane.io/v1alpha1 false Vlan
vnicattachments core.oci.jet.crossplane.io/v1alpha1 false VnicAttachment
volumeattachments core.oci.jet.crossplane.io/v1alpha1 false VolumeAttachment
volumebackuppolicies core.oci.jet.crossplane.io/v1alpha1 false VolumeBackupPolicy
volumebackuppolicyassignments core.oci.jet.crossplane.io/v1alpha1 false VolumeBackupPolicyAssignment
volumebackups core.oci.jet.crossplane.io/v1alpha1 false VolumeBackup
volumegroupbackups core.oci.jet.crossplane.io/v1alpha1 false VolumeGroupBackup
volumegroups core.oci.jet.crossplane.io/v1alpha1 false VolumeGroup
volumes core.oci.jet.crossplane.io/v1alpha1 false Volume
<snip>
The above snippet shows many different API resources that are part of the OCI Crossplane Provider, including (this is not an exhaustive list): Subnet, Vcn, RouteTable, etc. These aren’t typically in a plain vanilla OCI Container Engine for Kubernetes (OKE) or any other K8s implementation, but are installed by the OCI Crossplane Provider. The net result is that we’re able to manage these new types of OCI resources using familiar K8s manifests (typically in YAML format).
The user interface (UI) – the inputs and outputs – of a configuration is effectively a K8s CRD which Crossplane calls a Composite Resource Definition (abbreviated as XRD). The part of a configuration that defines the resources is called a Composition. A Composition is paired with an XRD. The name used in the XRD and Composition are identical, effectively creating a new kind of resource, which can be used by a Claim. To deploy the resource(s), an instantiation of a Composition is created, which is called a Claim. There can be many Claims (many instantiations of a given XRD/Composition). Here’s a visual representation of what this looks like:

For those familiar with Terraform, the inputs (variables) and outputs are defined in the XRD. The different resources themselves are defined in the Composition. Unlike Terraform where you can define resources in an adhoc manner or optionally create modules, Crossplane effectively takes a modules-only approach. It’s up to you whether a Composition contains one or many resources.
What about the kind? It is a bit confusing… an XRD is a kind of CompositeResourceDefinition, a Composition is a kind of Composition and a Claim is a kind of a given XRD/Composition. A claim’s kind is set to the name given to the desired XRD/Composition.
Each Crossplane provider defines resources which can be managed – it’s up to you to assemble these into a configuration that fits your needs. This is no different than how other IaC tools work. The Crossplane configuration format is a bit different than many other IaC tools.
Process for using Crossplane to manage OCI resources
Let’s assume that the OCI provider is already installed in Crossplane. The following high-level steps are what you might use to create an XRD, Composition and Claim:
- Create XRD (text file)
- Apply XRD
- kubectl apply -f <xrd_file_name.yaml>
- kubectl describe -f <xrd_file_name.yaml>
- Create Composition (text file)
- Apply Composition
- kubectl apply -f <composition_file_name.yaml>
- kubectl describe -f <composition_file_name.yaml>
- Create Claim (text file)
- Apply Claim
- kubectl apply -f <claim_file_name.yaml>
- kubectl describe -f <claim_file_name.yaml>
- Look at the resources defined to see info on the deployment status, etc. (kubectl get managed is a useful command)
That’s it! Because the Crossplane control plane is “always on”, it’ll detect when there are XRDs, Compositions and Claims that are created, deleted and/or updated.
Example of managing OCI resources
Now that we’ve covered some of the basic concepts and understand the practicalities of how to work with Crossplane, let’s look at a few examples of what this might look like. Remember, this is pre-release, so it might look different at release. Let’s go with a really basic example where we’ll setup a single VCN. Because we can make the XRD as simple or complex as we’d like, I’ll show a couple of potential examples. Here’s a very fixed (rigid) XRD, where the only thing customizable is the Compartment OCID and the VCN name:
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: simplevcns1.oci.platformref.crossplane.io
spec:
group: oci.platformref.crossplane.io
names:
kind: SimpleVCN1
plural: simplevcns1
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
compartmentId:
type: string
description: The Compartment OCID to deploy resources into.
vcn:
type: object
properties:
name:
type: string
description: The name to use for the VCN.
required:
- name
required:
- compartmentId
- vcn
status:
type: object
properties:
vcnId:
description: VCN OCID
type: string
The above XRD has a name (which is the kind we’ll be using in Claims) of SimpleVCN1, which can be instantiated with a Claim (we’ll see a Claim example shortly). What we’ve done above is defined two required input attributes: compartmentId and vcn.name. The status.vcnId attribute is used to store the OCID of the VCN that’s created (it’s used as an output, effectively).
Here’s a Composition that goes with it:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: simplevcn1
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: oci.platformref.crossplane.io/v1alpha1
kind: SimpleVCN1
resources:
# VCN
- name: vcn
base:
apiVersion: core.oci.jet.crossplane.io/v1alpha1
kind: Vcn
spec:
forProvider:
cidrBlocks:
- "10.10.0.0/16"
dnsLabel: "test"
patches:
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.id
toFieldPath: status.vcnId
- type: FromCompositeFieldPath
fromFieldPath: spec.compartmentId
toFieldPath: spec.forProvider.compartmentId
- type: FromCompositeFieldPath
fromFieldPath: spec.vcn.name
toFieldPath: spec.forProvider.displayName
There’s a lot going on here… and we don’t have time to delve into all the specifics, but this example creates a VCN (it points to apiVersion core.oci.jet.crossplane.io/v1alpha1) with a kind of Vcn. The kind effectively tells Crossplane what kind of resource that is to be managed. The VCN has a hardcoded IPv4 CIDR block and DNS label. The patches section is how to get data into and out of the resource as attributes (from and to). We’re storing the VCN OCID in the status.vcnId attribute (of type ToCompositeFieldPath). Likewise we’re pulling in the compartmentId and vcn.name fields from the provided spec and writing them to the resource attributes (patch type FromCompositeFieldPath).
Here’s a sample Claim that instantiates a SimpelVCN1:
apiVersion: oci.platformref.crossplane.io/v1alpha1
kind: SimpleVCN1
metadata:
name: crossplane-demo
spec:
compartmentId: ocid1.compartment.<redacted>
vcn:
name: crossplane_demo
This claim will create a VCN that has a name of crossplane_demo, placed in the Compartment given (redacted in the above sample). Note how the kind field is set to SimpleVCN1 (the same kind as used in the XRD and Composition above).
Now let’s look at a new XRD/Composition that’s more extensible. Here’s the XRD (the UI):
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: vcnexamples1.oci.platformref.crossplane.io
spec:
group: oci.platformref.crossplane.io
names:
kind: VcnExample1
plural: vcnexamples1
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
compartmentId:
type: string
description: The Compartment OCID to deploy resources into.
vcn:
type: object
properties:
name:
type: string
description: The name to use for the environment.
cidrBlocks:
type: array
description: Array of CIDR blocks to use for the VCN (one or more).
items:
type: string
dnsLabel:
type: string
description: DNS label to use.
required:
- name
- cidrBlocks
- dnsLabel
required:
- compartmentId
- vcn
status:
type: object
properties:
vcnId:
description: VCN OCID
type: string
The above example defines a UI where we require the VCN name, CIDR blocks and DNS label, along with the Compartment OCID (Compartment ID) where it is to be created. Here’s the Composition that goes with it:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: vcnexample1
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: oci.platformref.crossplane.io/v1alpha1
kind: VcnExample1
resources:
- name: vcn
base:
apiVersion: core.oci.jet.crossplane.io/v1alpha1
kind: Vcn
patches:
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.id
toFieldPath: status.vcnId
- type: FromCompositeFieldPath
fromFieldPath: spec.compartmentId
toFieldPath: spec.forProvider.compartmentId
- type: FromCompositeFieldPath
fromFieldPath: spec.vcn.dnsLabel
toFieldPath: spec.forProvider.dnsLabel
- type: FromCompositeFieldPath
fromFieldPath: spec.vcn.name
toFieldPath: spec.forProvider.displayName
- type: FromCompositeFieldPath
fromFieldPath: spec.vcn.cidrBlocks
toFieldPath: spec.forProvider.cidrBlocks
There’s a few more patches here, which is how we’re able to pull in more of the user-provided data (such as the CIDR blocks, DNS label, etc.). Now let’s look at a sample Claim, which is how we’re instantiating this:
apiVersion: oci.platformref.crossplane.io/v1alpha1
kind: VcnExample1
metadata:
name: vcnexample1
spec:
compartmentId: ocid1.compartment.<redacted>
vcn:
name: vcnexample1
cidrBlocks:
- 192.168.0.0/22
dnsLabel: vcn1
Wrapping It Up
This has been a long article, but hopefully you’ve been able to see a brief glimpse into what Crossplane is and why you should consider it in your IaC toolbox. Crossplane is not for everyone… it has some rough edges and limitations (as of the time of this writing, it is not as mature as many other its IaC counterparts). For those who are wanting to be on the bleeding edge, really like K8s and would benefit from a tool that gives a broad, end-to-end view of your application environment, take a look at Crossplane.
