- Authors
- Name
- Introduction
- Crossplane vs Terraform
- Installing Crossplane
- AWS Provider Configuration
- Managed Resources — Direct Cloud Resource Management
- Composition — The Core of Abstraction
- Functions — Advanced Composition Logic
- GitOps Integration with ArgoCD
- Usages — Resource Dependency Protection
- Troubleshooting
- Summary
Introduction
Terraform has been the de facto standard for IaC, but it has limitations in Kubernetes-centric environments. State file management, manual Plan/Apply execution, and difficulty with drift detection are common challenges. Crossplane solves these problems using Kubernetes' declarative model and controller pattern.
Having graduated to CNCF Graduated project status in October 2025, Crossplane is now thoroughly validated for production environments.
Crossplane vs Terraform
| Category | Terraform | Crossplane |
|---|---|---|
| Execution Model | CLI (Plan/Apply) | Kubernetes Controller (Continuous Reconciliation) |
| State Management | tfstate file (external backend needed) | etcd (Kubernetes native) |
| Drift Detection | Manual plan required | Automatic (reconciliation loop) |
| Abstraction | Module | Composition |
| Access Control | Separate IAM configuration | RBAC integration |
| GitOps | Separate pipeline needed | Native ArgoCD/Flux integration |
Installing Crossplane
# Install with Helm
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace \
--set args='{"--enable-usages"}'
# Verify installation
kubectl get pods -n crossplane-system
kubectl api-resources | grep crossplane
AWS Provider Configuration
# Install AWS Provider
cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-s3
spec:
package: xpkg.upbound.io/upbound/provider-aws-s3:v1.18.0
EOF
# Verify Provider installation
kubectl get providers
Authentication Configuration
# Create AWS credentials Secret
kubectl create secret generic aws-creds \
-n crossplane-system \
--from-file=credentials=$HOME/.aws/credentials
# Create ProviderConfig
cat <<EOF | kubectl apply -f -
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: credentials
EOF
Managed Resources — Direct Cloud Resource Management
# s3-bucket.yaml
apiVersion: s3.aws.upbound.io/v1beta2
kind: Bucket
metadata:
name: my-crossplane-bucket
spec:
forProvider:
region: ap-northeast-2
tags:
Environment: production
ManagedBy: crossplane
providerConfigRef:
name: default
kubectl apply -f s3-bucket.yaml
# Check status
kubectl get bucket my-crossplane-bucket
# NAME READY SYNCED EXTERNAL-NAME AGE
# my-crossplane-bucket True True my-crossplane-bucket 2m
# Detailed status
kubectl describe bucket my-crossplane-bucket
Creating an RDS Instance
# rds-instance.yaml
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
metadata:
name: my-postgres
spec:
forProvider:
region: ap-northeast-2
allocatedStorage: 20
engine: postgres
engineVersion: '16.4'
instanceClass: db.t3.micro
dbName: myapp
masterUsername: admin
masterPasswordSecretRef:
name: rds-password
namespace: default
key: password
skipFinalSnapshot: true
publiclyAccessible: false
vpcSecurityGroupIdSelector:
matchLabels:
app: my-rds
providerConfigRef:
name: default
writeConnectionSecretToRef:
name: rds-connection
namespace: default
kubectl apply -f rds-instance.yaml
# Connection info is automatically stored in the Secret
kubectl get secret rds-connection -o yaml
Composition — The Core of Abstraction
Composition bundles multiple Managed Resources into a single high-level API. It is similar to Terraform Modules but Kubernetes-native.
CompositeResourceDefinition (XRD)
# xrd-database.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.platform.example.com
spec:
group: platform.example.com
names:
kind: XDatabase
plural: xdatabases
claimNames:
kind: Database
plural: databases
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
size:
type: string
enum: ['small', 'medium', 'large']
default: 'small'
engine:
type: string
enum: ['postgres', 'mysql']
default: 'postgres'
region:
type: string
default: 'ap-northeast-2'
required:
- size
Composition
# composition-database.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: aws-database
labels:
provider: aws
spec:
compositeTypeRef:
apiVersion: platform.example.com/v1alpha1
kind: XDatabase
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
spec:
forProvider:
region: ap-northeast-2
engine: postgres
engineVersion: '16.4'
skipFinalSnapshot: true
publiclyAccessible: false
providerConfigRef:
name: default
patches:
# size to instanceClass mapping
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.micro
medium: db.t3.medium
large: db.r6g.large
# size to allocatedStorage mapping
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.size
toFieldPath: spec.forProvider.allocatedStorage
transforms:
- type: map
map:
small: '20'
medium: '100'
large: '500'
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.engine
toFieldPath: spec.forProvider.engine
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.region
toFieldPath: spec.forProvider.region
- name: security-group
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: SecurityGroup
spec:
forProvider:
region: ap-northeast-2
description: 'Managed by Crossplane'
ingress:
- fromPort: 5432
toPort: 5432
protocol: tcp
cidrBlocks:
- '10.0.0.0/8'
Claim — The Developer Interface
# claim-database.yaml
apiVersion: platform.example.com/v1alpha1
kind: Database
metadata:
name: my-app-db
namespace: team-alpha
spec:
parameters:
size: medium
engine: postgres
region: ap-northeast-2
writeConnectionSecretToRef:
name: db-credentials
kubectl apply -f claim-database.yaml
# This is all the developer needs to know
kubectl get database -n team-alpha
# NAME READY CONNECTION-SECRET AGE
# my-app-db True db-credentials 5m
# Platform engineer verification
kubectl get composite
kubectl get managed
Functions — Advanced Composition Logic
Crossplane Functions allow you to implement complex patching logic in Go, Python, and other languages.
# Using function-go-templating
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: dynamic-database
spec:
compositeTypeRef:
apiVersion: platform.example.com/v1alpha1
kind: XDatabase
mode: Pipeline
pipeline:
- step: render-resources
functionRef:
name: function-go-templating
input:
apiVersion: gotemplating.fn.crossplane.io/v1beta1
kind: GoTemplate
source: Inline
inline:
template: |
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
metadata:
annotations:
gotemplating.fn.crossplane.io/composition-resource-name: rds
spec:
forProvider:
region: {{ .observed.composite.resource.spec.parameters.region }}
engine: {{ .observed.composite.resource.spec.parameters.engine }}
instanceClass: {{ if eq .observed.composite.resource.spec.parameters.size "small" }}db.t3.micro{{ else if eq .observed.composite.resource.spec.parameters.size "medium" }}db.t3.medium{{ else }}db.r6g.large{{ end }}
- step: auto-ready
functionRef:
name: function-auto-detect-ready
GitOps Integration with ArgoCD
Crossplane resources are standard Kubernetes manifests, so they integrate naturally with ArgoCD.
# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/infra-repo.git
targetRevision: main
path: crossplane/claims
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# Push a Claim to Git and infrastructure is automatically provisioned
git add claim-database.yaml
git commit -m "feat: provision medium postgres for team-alpha"
git push origin main
# ArgoCD syncs automatically
argocd app get infrastructure
Usages — Resource Dependency Protection
The Usage resource protects deletion order.
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Usage
metadata:
name: db-used-by-app
spec:
of:
apiVersion: platform.example.com/v1alpha1
kind: Database
resourceRef:
name: my-app-db
by:
apiVersion: apps/v1
kind: Deployment
resourceRef:
name: my-app
namespace: team-alpha
reason: 'Database is used by the application'
Troubleshooting
# Check Provider status
kubectl get providers
kubectl describe provider provider-aws-s3
# Check Managed Resource events
kubectl describe bucket my-crossplane-bucket
# Debug Composition
kubectl get composite -o wide
kubectl describe xdatabase my-app-db-xxxxx
# Crossplane logs
kubectl logs -n crossplane-system deploy/crossplane -f
# Specific Provider logs
kubectl logs -n crossplane-system \
$(kubectl get pods -n crossplane-system -l pkg.crossplane.io/revision -o name | head -1) -f
Summary
Crossplane is not competing with Terraform -- it evolves IaC to the next level in Kubernetes-native environments:
- Continuous Reconciliation: Just declare and the controller manages everything, no Plan/Apply needed
- Self-Service Platform: Abstract complex infrastructure with Compositions and provide it to developers
- GitOps Native: Natural integration with ArgoCD/Flux
- RBAC Integration: Leverage Kubernetes' existing permission system as-is
- Automatic Drift Recovery: Even if someone manually changes resources in the console, they are automatically restored
Quiz: Crossplane Comprehension Check (7 Questions)
Q1. Why is Crossplane stronger than Terraform at drift detection?
The Kubernetes controller's reconciliation loop continuously compares the actual state with the declared state and automatically recovers any drift.
Q2. What is the difference between Managed Resource and Composite Resource?
A Managed Resource maps 1:1 to an actual cloud resource (S3, RDS, etc.), while a Composite Resource bundles multiple Managed Resources into a single high-level API.
Q3. What is the role of a Claim?
A Claim is the namespace-scoped interface through which developers request infrastructure. It uses an abstracted API without directly handling Composite Resources.
Q4. Where is Crossplane's state stored?
It is stored in Kubernetes' etcd. There is no need for separate state file management like Terraform.
Q5. What advantage does mode: Pipeline offer in Composition?
It allows chaining various Functions such as Go Templating and Python to implement complex resource creation logic.
Q6. What is the role of the Usage resource?
It declares dependencies between resources to prevent resources that are in use from being accidentally deleted.
Q7. Does Crossplane require special configuration to integrate with ArgoCD?
No. Crossplane resources are standard Kubernetes manifests, so ArgoCD can sync them as-is.