Crossplane doesn’t just provision cloud resources; it fundamentally changes how you think about infrastructure by treating it as code managed by Kubernetes.
Let’s see Crossplane in action. Imagine you want to provision an AWS S3 bucket.
First, you need to install Crossplane into your Kubernetes cluster. This is typically done via Helm:
helm install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace
This command deploys Crossplane’s core components, including its controller manager and API server, into a dedicated crossplane-system namespace. These components watch for Crossplane-specific Custom Resource Definitions (CRDs) that you’ll define.
Next, you’ll install a Crossplane provider. Providers are the plugins that allow Crossplane to interact with specific cloud services. For AWS, you’d install the AWS provider:
kubectl crossplane install provider xpkg.upbound.io/upbound/provider-aws:v0.38.0
This command fetches the provider’s package and installs its CRDs and controller into your cluster. The provider controller is now responsible for translating Crossplane’s generic resource claims into AWS API calls.
Now, you need to configure the provider with your cloud credentials. For AWS, this means creating a ProviderConfig resource:
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-credentials
key: credentials
And a Kubernetes secret named aws-credentials in the crossplane-system namespace containing your AWS access key ID and secret access key, typically in a ~/.aws/credentials format:
[default]
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET_ACCESS_KEY
With the provider installed and configured, you can define infrastructure. Instead of using AWS’s native tools, you declare an S3Bucket resource using Crossplane’s generic API:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: my-crossplane-test-bucket
spec:
forProvider:
region: us-east-1
acl: private
providerConfigRef:
name: default
When you apply this manifest (kubectl apply -f bucket.yaml), Crossplane’s AWS provider controller notices the Bucket resource. It uses the credentials from the default ProviderConfig to call the AWS API and create an S3 bucket named my-crossplane-test-bucket in us-east-1 with private ACL. The controller continuously watches the actual AWS resource, reconciling any drift back to the desired state defined in your Kubernetes manifest.
The core problem Crossplane solves is the operational burden of managing diverse cloud infrastructure alongside application workloads. By extending the Kubernetes API, it allows you to manage cloud resources with the same familiar tools and workflows (like kubectl, GitOps, CI/CD pipelines) used for applications. This unification reduces context switching and simplifies infrastructure management. Internally, Crossplane operates on a control loop: it watches for Managed resources (like our Bucket), compares their desired state to the actual state in the cloud provider, and reconciles any differences. Providers are the key; they abstract the specifics of each cloud API, presenting a consistent Crossplane API.
What’s often overlooked is how Crossplane’s Composition and CompositeResourceDefinition (XRD) work together to create higher-level abstractions. You can define a CompositeResourceDefinition that represents a "managed database" — a concept that might involve an RDS instance, a security group, and a subnet group. Then, a Composition defines how to translate instances of this "managed database" composite resource into the specific cloud provider resources (like RDSInstance, SecurityGroup, DBSubnetGroup for AWS). This allows you to define opinionated infrastructure patterns that your developers can consume without needing to know the underlying cloud specifics.
The next step is to explore how to use Compositions to create your own custom infrastructure abstractions.