The Universal Control Plane – Crossplane 101

In this next series of posts, I’m going to cover Crossplane in increasing detail.

The most basic/simplified description I can think of for Crossplane is: A platform that leverages the Kubernetes api/controller model to deliver continuously reconciled infrastructure as code. It is really much more than that, but let’s start there for the sake of simplicity.

Some K8s principles upfront. By now, most in our industry know what Kubernetes is; and minimally, that it orchestrates containers. But K8s offers much more than just container orchestration these days,

K8s offers us an extensible API, allowing anyone to add functionality. The basic pattern K8s employs is a ‘control loop’ pattern to create/read/update/delete ‘things’.  It is essentially an API server with RBAC and a loosely opinionated model for interacting with it.

Let’s do a quick review of what comprises a K8s cluster. There are control plane nodes. These nodes maintain our api server and a handful of controllers. Controllers are responsible for doing something when we add or modify values stored in the api server’s key/value pair store.

Controllers are programmed to poll the api server for the values they’re responsible for. They use these values to check those things and reconcile any differences between the stored value (intended state) and the actual things. (This is the controller pattern.)

We can extend the K8s API schema so it understands and stores new types of key/vale pairs. In K8s, these key/value pairs are grouped to represent ‘Resource Definitions’. We can create ‘Custom Resource Definitions’ and a custom controller that uses those CRDs to perform some responsibility. So we can essentially have a controller that does anything a piece of software can do, that is directed by values we place in the K8s API key/value store.

There are a lot of moving parts involved that would potentially confuse things here, so I won’t go deeper than that. Just think of a controller as a piece of running software, that routinely polls the K8s API server for things it’s responsible for, then takes those values, checks the things in the world that those represent, and performs CRUD operations on them accordingly.

Controllers can perform operations directly, like creating a container and plumbing its virtual networking, or they can serve as a liaison between the K8s API server and another API server.

The latter is what Crossplane generally does. Crossplane extends the schema of a K8s cluster with CRDs that represent virtual infrastructure, implements controllers (Providers in this case) that know how to take a CRD’s values and create things in various cloud providers via the cloud provider’s API, then reconcile and maintain desired state of those things. It even enables us to define our own CRDs (Thus allowing us to create new API definitions in K8s that we can consume to create composed resources).

Let’s look at the basic components of the Crossplane machine. They are: Core Crossplane, Providers, Managed Resources, Compositions, Composite Resources, and Composite Resource Claims.

Core Crossplane components are responsible for making the Crossplane machine work. They orchestrate and managed the following components (Not a complete list).

A Provider is a K8s controller that knows how to create things in other clouds and even other platforms (e.g. AWS, GCP, Azure, Helm, Github,  etc.). You can check out some of the community contributed Providers here : https://github.com/crossplane-contrib.

Managed Resources are CRDs that are installed when we install a Provider in our Crossplane cluster. A Managed Resource is a representation of the values required for a Provider to create/manage something. You can review these CRD schemas here API Documentation. For example, if you’re interested in creating an Azure ResourceGroup, you can review this Azure Resource Group API definition.

The two above components are the bare minimum to make Crossplane useful. After installing the core Crossplane components, then adding/configuring the Azure Provider, we can create/reconcile in Azure by creating Managed Resources that fall into its purview.

Before long, creating individual Managed Resources that represent a larger set of related constructs becomes unwieldy. This is where Compositions come in,

Composition is another CRD that is created with core Crossplane components. It enables us to define multiple Managed Resources at once, perform transformations on values, and define relationships between them. A Composition is where we define all the things Crossplane needs to create corresponding Managed Resources (Most often, multiple related Managed Resources at once.).

Next comes the Composite Resource. Composite Resource is a powerful little thing that lets us define and create new CRDs /APIs in our cluster. We get to create our own API paths in the K8s API server and store things we want to use to feed into a Composition.

We don’t create Managed Resources by creating a Composition instance directly. We create a Composite Resource that instantiates the Composition. Composite Resources enable us to provide different values to be used in the Composition. For example, I might define a ‘region’ field in my Composite Resource so that I can pass it to a Composition with different values, thus creating resources in different regions.

So, to level set what we’ve covered so far, a Composite Resource is something we use to provide values to a Composition, a Composition takes those values, potentially performs some transforms,  and then creates Managed Resources, then Providers take the values from those Managed Resources to create/reconcile things.

I’ll wrap this post up at that level. My hope is to describe the components at a high enough level as to provide basic understanding, without getting into the weeds of how they’re all configured just yet. In coming posts, I’ll dive deeper on the creation and use of the components.

For more information on Composite Resources, check out the Crossplane docs here.