It is long overdue to continue writing this series on building platforms with Crossplane. Where the previous post mentioned why we’re throwing out a bunch of Terraform code it also just namedrops Crossplane, mic drops, and leaves everyone confused.
So it’s time to do some explaining
The very basic introduction
In short, and to play an association game, Crossplane is a piece of software you can run in a Kubernetes cluster. This piece of software act as a control plane to manage your infrastructure resources. Or as the official website puts it in their more polished tagline:
Crossplane Is the Cloud-Native Framework for Platform Engineering
Your first reaction might be “so a bit like opentofu/terraform/pulumi/ansible/<foo>, but with more YAML?” But (thank the great Spaghetti monster) the answer to that question is “no. not at all”. Most toolchains for Infrastructure as Code-tools differ in syntax, but all follow the same loop of plan → apply → destroy either by installing something on your local machine or through CI/CD. No one runs crossplane apply to apply changes to infrastructure, there is no plan to inspect to verify changes before applying and there is no refreshing status of a pipeline and read through the job output to see what happened.
The mental model
You tell the cluster what you want to exist, and a controller in the cluster creates it, and spends the rest of the lifecycle for that resource making sure the description matches what actually exists.
This loop has a name: reconciliation. In Kubernetes this happens continuously, not just when you run terraform apply or destroy, but for the entire lifecycle of a resource the controller wakes up, compares what you have asked for against what actually exists, corrects the difference, and goes back to sleep.
What you as a Platform Engineer get out of it is a set of building blocks to build user friendly APIs that abstracts away (or at least reduces) the need for your developers to know or care about the underlying infrastructure. No matter if it’s Azure or a different cloud vendor, a bunch of bare metal with a wildly expensive VMWare, or something else entirely.
What the users get is a familiar interface. Just like their container deployments, infrastructure components are now also kubernetes resources. They can use kubectl, all the GitOps tools like Flux or ArgoCD, and every other tool a developer usually have very strong opinions on.
An example
- A developer creates a
claimthat describes some infrastructure they need - A GitOps tool picks up the change
- The control plane is made aware of the changes
- The claim is resolved as managed resources
- Resources are created, updated or deleted
- The control plane monitors changes in actual infrastructure and corrects drift
Hybrid
This also makes the concept of a “hybrid platform” interesting. If the developers don’t care, or need to care, about finer details on the infrastructure, we can build interfaces where the user does not have to deal with different resource models, permission models, workflows or authentication. The control plane takes care of it.*
(* yes. you could say that modules in OpenTofu/Terraform can solve the same thing. I partly disagree. It can solve most of the problem with enough duct tape, but not as well as a control plane approach is able to)
Explaining the building blocks
Basicly. Crossplane has two halves:
- The bottom half talks to the underlying infrastructure layers through
providersandmanaged resources. - The top half is where the platform team and developers interact. The land of
XRDs,Compositions, andFunctions.
You might not need both. “Just” doing the first half can still result in something useful for your IT-team, but it’s in the second half the “platform engineering” (and imho, fun) happens.
So, to follow along in a bottom up approach, there are ~4 concepts that are important to understand:
- Providers
A package you install in your cluster to bringcontrollersandcustom resource definitionsto communicate with external APIs.provider-azure-familyknows how to talk with Azure,provider-upjet-githubtalks with GitHub etc. The name and the concept is very similar to people familiar with OpenTofu/Terraform, and often, Crossplane usecrossplane/upjetto generate the provider from existing OpenTofu/Terraform code. - Managed Resources
A 1:1 mapping with a real resource. ALinuxVirtualMachinemanaged resource (MR) represents a Virtual Machine running Linux in Azure. The schema is generated from the vendor’s own API schema and usually feels familiar if you’re migrating from other tools to deploy resources.
I find using the anatomy of the MRs to be a nice mental note when working with compositions: Everything underspec.forProvideris what I want to deploy, and everything understatus.atProvidercontains what actually exists. The controller’s whole job is to make sure these two match. - Compositions and XRDs
In short, this what your users care about. The API contract between a developer and the platform.
AComposite Resource Definition(XRD) defines the API. Whatkindof resource it is, whichgroupit belongs to, the inputfieldswith optionaldefaultsandvalidationrules to make sure inputs are sanitized.
ACompositionis the implementation of the API and provides the coveted abstraction of infrastructure-concept I keep mentioning =). It might deploy a single resource, or ten different resources that gives the user a complete runtime environment.
(ps: as a mental model, the comparison with a OpenTofu/Terraformmoduleis close enough on the most important bits:variables.tfis your XRD, andmain.tfis your composition). - Functions
Finally we havefunctions. A quick history lesson is needed to explain why these are great:
In the beginning, Compositions usedpatch-and-transformto map input fields from the XRD. This YAML-dialect works fairly well and is easy to understand, until you need conditionals, looping over a list of unknown length, or more creative logic, and it turns ugly or straight up impossible.
Thus,functionswas introduced to provide more ways to describe resources in a Composition.function-go-templatinglet you run wild with (sometimes unreadable) template tags,function-kclgives you the option KCL, and withfunction-kroyou can have the same YAML+CEL experience as in Kro. And if none of them fits your purpose, you can (just) create your own bespoke functions.
All wired together, these four components are what allow you to create nice interfaces your users will actually love using!
No tool is perfect for everything
You might be sold already and think this is better than a posh hotel breakfast, but every technology comes with a set of caveats. Nothing is perfect for everything. We move away from Terraform to Crossplane because we see that the amount of duct tape and plumbing needed to make the plan → apply → destroy tools are not a good fit for our requirements to build a true hybrid platform, with a great self-service experience, and continuous reconciliation. Your shop is probably different, and you should take that into account before you climb up to the diving board and make a big decision.
- The initial setup
Crossplane runs on Kubernetes. That means you now have a cluster it’s really important to take good care of. If you’re already running a bunch of container workloads you should already have all the processes in place for upgrades, monitoring, backup etc. in place. If not, you have big work coming before you’re ready. - There is no dry run
Noterraform plan, no-whatif, no approval gate in your CI/CD. Tools likecrossplane renderhelp, and while the tools are good they are not amazing and like everytime you want to test something against a Cloud API they have gaps. You need to design a process for how you do dev→test→release to build the confidence that you’re not breaking something important. - Provider coverage
The ecosystem is not as mature as others. Though many providers are generated by usingcrossplane/upjetand this strategy is good for attaining broad coverage fast, it also means providers are trailing upstream and the further away from the most mainstream resources you get, the gaps becomes larger and more annoying. (If you’re willing and allowed, you can contribute to make this situation better though!) - Migrations are time-consuming
You can adopt current infrastructure into Crossplane by usingmanagement-policiestoobserveresources that already exist, but it’s time-consuming, careful, and (imho) boring work. - Learning tax
Getting started with tools like OpenTofu/Terraform is easy. You download a binary, create a small bit of code, run apply, and resources are created. Getting everything together to run Crossplane requires more investment, and because the mental model is different, the knowledge transfer for experiences infrastructure engineers are not as clean as you might think. Especially debugging errors is less transparent in the beginning and there are definitively well camouflaged pit-falls that separates working code from good code.