Crossplane itself doesn’t actually do anything with your cloud resources; it’s the providers that do the heavy lifting, and those providers are just regular Kubernetes controllers.
Let’s see a provider in action. Imagine you want to create an AWS S3 bucket using Crossplane. You’d typically have a Composition that defines how to provision an S3Bucket managed resource, and then you’d create an S3Bucket instance in your Kubernetes cluster.
apiVersion: s3.aws.crossplane.io/v1beta1
kind: Bucket
metadata:
name: my-crossplane-bucket
spec:
forProvider:
region: us-east-1
bucketName: my-unique-crossplane-bucket-name-12345
acl: private
providerConfigRef:
name: default-aws
When you apply this, the s3.aws.crossplane.io provider controller, specifically the Bucket reconciler within it, watches for Bucket objects. It sees my-crossplane-bucket, checks if an S3 bucket with that name already exists in AWS, and if not, it calls the AWS API to create it. It then continuously monitors the actual AWS resource and updates the Bucket object’s status to reflect the real-world state.
The core problem Crossplane solves is abstracting away cloud provider specifics. Instead of learning AWS SDKs or Terraform, you interact with a consistent Kubernetes API. The providers translate these Kubernetes API calls into the native API calls of the underlying cloud. This enables GitOps workflows for infrastructure, allowing you to manage your cloud resources declaratively using familiar Kubernetes tools.
Internally, each provider is a set of Kubernetes controllers, one for each managed resource type it supports. For the AWS provider, there’s a Bucket controller, an RDSInstance controller, a VPC controller, and so on. Each controller has a reconciler loop that continuously compares the desired state (defined in the Kubernetes object) with the actual state in the cloud provider. If there’s a drift, it takes action to reconcile them.
The spec.forProvider block is where you define the cloud-specific configuration. For an S3 bucket, this includes things like region, bucketName, acl, versioningConfiguration, etc. The providerConfigRef points to a ProviderConfig resource, which holds the credentials and configuration needed for the provider to authenticate with the cloud.
You control how these provider controllers run by configuring their respective Deployment objects within the Crossplane installation. These deployments are standard Kubernetes deployments, meaning you can set resource requests and limits for CPU and memory, just like any other Kubernetes workload. This is crucial for managing the performance and stability of your Crossplane installation, especially when dealing with a large number of managed resources or complex compositions.
For example, to set resource limits for the AWS provider’s controller manager, you’d locate its deployment (often named something like provider-aws-controller-manager) and edit its resources section:
apiVersion: apps/v1
kind: Deployment
metadata:
name: provider-aws-controller-manager
namespace: crossplane-system
spec:
template:
spec:
containers:
- name: manager
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Setting appropriate requests and limits prevents a single provider controller from consuming excessive resources and impacting other controllers or the Kubernetes control plane. It also ensures that if a controller does start misbehaving or consuming too many resources, Kubernetes can step in and potentially restart the pod, or the cloud provider can be notified of the resource constraint.
The crucial detail often overlooked is that the ProviderConfig itself doesn’t contain the credentials directly for most cloud providers. Instead, it references Kubernetes Secret objects that hold the actual cloud credentials. This allows you to manage sensitive credentials securely using Kubernetes Secrets and grant Crossplane access to them via RBAC, rather than embedding them directly in the ProviderConfig YAML.
The next step after managing provider resource limits is understanding how to configure specific provider capabilities, like custom endpoints or region-specific features.