- devops
- l2
- topic-pack
- crossplane
- terraform --- Portal | Level: L2: Operations | Topics: Crossplane, Terraform | Domain: DevOps & Tooling
Crossplane - Primer¶
Why This Matters¶
Crossplane brings infrastructure management into Kubernetes. Instead of running terraform apply from a CI pipeline or an operator's laptop, you declare cloud resources as Kubernetes custom resources and let the Crossplane controller reconcile them. This matters because it unifies the control plane: application workloads and the infrastructure they depend on (databases, queues, buckets, DNS records) are all managed by the same Kubernetes API, with the same RBAC, the same GitOps workflow, and the same reconciliation loop. For platform teams, Crossplane enables self-service infrastructure — developers create a claim for "give me a PostgreSQL database" without knowing which cloud provider or configuration is behind it.
Core Concepts¶
1. Architecture¶
Developer creates a Claim (e.g., "PostgreSQL database")
↓
Crossplane matches Claim to a Composition
↓
Composition defines which Managed Resources to create
↓
Provider controller creates real cloud resources (RDS, Cloud SQL, etc.)
↓
Crossplane continuously reconciles desired state vs actual state
Key terms: - Provider — a Crossplane package that knows how to manage resources for a specific platform (AWS, GCP, Azure, Kubernetes, Helm, etc.) - Managed Resource (MR) — a Kubernetes CR that maps 1:1 to an external resource (e.g., an RDS instance) - Composite Resource (XR) — an abstraction that bundles multiple managed resources into one logical unit - Composition — defines how an XR is composed from managed resources (the template) - Claim (XRC) — a namespace-scoped request for an XR (what developers interact with) - CompositeResourceDefinition (XRD) — the schema for an XR/Claim (like a CRD for your abstraction)
2. Installation¶
# Install Crossplane into a Kubernetes cluster
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
kubectl get pods -n crossplane-system
kubectl api-resources | grep crossplane
3. Provider Installation¶
# Install the AWS provider
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-s3
spec:
package: xpkg.upbound.io/upbound/provider-aws-s3:v1.2.0
---
# Provider credentials
apiVersion: v1
kind: Secret
metadata:
name: aws-creds
namespace: crossplane-system
type: Opaque
stringData:
credentials: |
[default]
aws_access_key_id = AKIAEXAMPLE
aws_secret_access_key = secretkey
---
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: credentials
# Apply provider config
kubectl apply -f provider-aws.yaml
# Check provider status
kubectl get providers
kubectl get providerconfigs
# Wait for provider to become healthy
kubectl wait provider/provider-aws-s3 --for=condition=Healthy --timeout=300s
4. Managed Resources (Direct Cloud Resource Management)¶
# Create an S3 bucket directly (no abstraction)
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: my-app-data
spec:
forProvider:
region: us-east-1
tags:
Environment: production
Team: platform
providerConfigRef:
name: default
kubectl apply -f s3-bucket.yaml
kubectl get bucket my-app-data
# Check sync status
kubectl describe bucket my-app-data
# Look for:
# Status:
# Conditions:
# Type: Ready
# Status: True
# Type: Synced
# Status: True
5. Compositions and Claims (Platform Abstractions)¶
Step 1: Define the schema (XRD):
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresqlinstances.database.example.org
spec:
group: database.example.org
names:
kind: XPostgreSQLInstance
plural: xpostgresqlinstances
claimNames:
kind: PostgreSQLInstance
plural: postgresqlinstances
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
storageGB:
type: integer
default: 20
version:
type: string
default: "15"
required:
- storageGB
required:
- parameters
Step 2: Define the composition (how to build it):
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xpostgresqlinstances.aws.database.example.org
labels:
provider: aws
spec:
compositeTypeRef:
apiVersion: database.example.org/v1alpha1
kind: XPostgreSQLInstance
resources:
- name: rdsinstance
base:
apiVersion: rds.aws.upbound.io/v1beta1
kind: Instance
spec:
forProvider:
region: us-east-1
engine: postgres
instanceClass: db.t3.micro
skipFinalSnapshot: true
publiclyAccessible: false
patches:
- fromFieldPath: "spec.parameters.storageGB"
toFieldPath: "spec.forProvider.allocatedStorage"
- fromFieldPath: "spec.parameters.version"
toFieldPath: "spec.forProvider.engineVersion"
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.forProvider.dbName"
transforms:
- type: string
string:
fmt: "db%s"
type: Format
Step 3: Developer creates a claim:
apiVersion: database.example.org/v1alpha1
kind: PostgreSQLInstance
metadata:
name: my-app-db
namespace: team-alpha
spec:
parameters:
storageGB: 50
version: "15"
compositionSelector:
matchLabels:
provider: aws
# Developer applies claim in their namespace
kubectl apply -f my-db-claim.yaml -n team-alpha
# Check claim status
kubectl get postgresqlinstance -n team-alpha
kubectl describe postgresqlinstance my-app-db -n team-alpha
# See the underlying composite and managed resources
kubectl get composite
kubectl get managed
6. Debugging¶
# Check the Crossplane controller logs
kubectl logs -n crossplane-system -l app=crossplane --tail=100
# Check provider controller logs
kubectl logs -n crossplane-system -l pkg.crossplane.io/revision -c provider --tail=100
# Trace a claim through the stack
kubectl get postgresqlinstance my-app-db -n team-alpha -o yaml # claim
kubectl get xpostgresqlinstance -o yaml # composite
kubectl get instance.rds -o yaml # managed resource
# Common status conditions to check
kubectl get managed -o custom-columns='NAME:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,SYNCED:.status.conditions[?(@.type=="Synced")].status'
# Events
kubectl get events --field-selector involvedObject.name=my-app-db -n team-alpha
# Common issues:
# Synced=False — provider cannot reach the cloud API (credentials, permissions)
# Ready=False — resource exists but is not ready (still provisioning, or config error)
# LastTransitionTime stuck — reconciliation loop may be failing silently
7. Operational Patterns¶
GitOps with Crossplane: Store XRDs, Compositions, and Claims in Git. Use ArgoCD or Flux to sync them. This gives you declarative infrastructure with audit trails.
Multi-cloud abstraction: Create one XRD with multiple Compositions (one per cloud). Developers select via labels:
Resource lifecycle:
# Delete a claim (will delete the underlying cloud resource)
kubectl delete postgresqlinstance my-app-db -n team-alpha
# Prevent accidental deletion
kubectl annotate postgresqlinstance my-app-db \
crossplane.io/deletion-policy=Orphan -n team-alpha
# Orphan: delete CR but keep cloud resource
# Default (Delete): delete CR and cloud resource
Quick Reference¶
# Install
helm install crossplane crossplane-stable/crossplane -n crossplane-system --create-namespace
# Check health
kubectl get providers # installed providers
kubectl get providerconfigs # provider credentials
kubectl get managed # all managed cloud resources
kubectl get composite # all composite resources
kubectl get claim --all-namespaces # all claims
# Debugging
kubectl describe <managed-resource> # check Ready/Synced conditions
kubectl logs -n crossplane-system -l app=crossplane
kubectl get events --sort-by='.lastTimestamp'
# Cleanup
kubectl delete claim <name> -n <ns> # deletes claim + cloud resource
Wiki Navigation¶
Prerequisites¶
- Kubernetes Exercises (Quest Ladder) (CLI) (Exercise Set, L1)
- Terraform / IaC (Topic Pack, L1)
Related Content¶
- Case Study: SSH Timeout — MTU Mismatch, Fix Is Terraform Variable (Case Study, L2) — Terraform
- Case Study: Terraform Apply Fails — State Lock Stuck, DynamoDB Throttle (Case Study, L2) — Terraform
- Deep Dive: Terraform State Internals (deep_dive, L2) — Terraform
- Mental Models (Core Concepts) (Topic Pack, L0) — Terraform
- OpenTofu & Terraform Ecosystem (Topic Pack, L2) — Terraform
- Pulumi (Topic Pack, L2) — Terraform
- Runbook: Cloud Capacity Limit Hit (Runbook, L2) — Terraform
- Runbook: Terraform Drift Detection Response (Runbook, L2) — Terraform
- Runbook: Terraform State Lock Stuck (Runbook, L2) — Terraform
- Skillcheck: Terraform / IaC (Assessment, L1) — Terraform
Pages that link here¶
- Anti-Primer: Crossplane
- Certification Prep: HashiCorp Terraform Associate
- Comparison: Infrastructure as Code Tools
- Crossplane
- Mental Models (Core Concepts)
- Opentofu
- Pulumi
- Runbook: Cloud Capacity Limit Hit
- Runbook: Terraform Drift Detection Response
- Runbook: Terraform State Lock Stuck
- Symptoms: SSH Timeout, MTU Mismatch, Fix Is Terraform Variable
- Symptoms: Terraform Apply Fails, State Lock Stuck, Root Cause Is DynamoDB Throttle
- Terraform / IaC
- Terraform / Infrastructure as Code - Skill Check
- Terraform Deep Dive