Skip to main content

Phase 11 — Crossplane — Kubernetes-Native IaC

Crossplane extends Kubernetes with Custom Resource Definitions (CRDs) to manage external infrastructure. Instead of running tofu apply from a terminal, you kubectl apply a YAML file — and Kubernetes continuously reconciles the desired state, just like it does for pods.


Crossplane vs Terraform

Terraform / OpenTofuCrossplane
InterfaceCLI (tofu apply)kubectl apply (YAML)
State managementState file / backendKubernetes etcd
Drift detectionOn plan onlyContinuous reconciliation
GitOps compatibleVia CI pipelineNative (ArgoCD applies YAML)
ComplexityModerateHigher (more Kubernetes knowledge)
Best forInitial provisioningOngoing platform management

How It Works

Developer writes YAML:
kind: RDSInstance (example: AWS RDS)
→ Crossplane provider calls AWS API
→ Creates real RDS instance
→ Continuously reconciles

Same pattern for:
→ k8s namespaces
→ Vault secrets engines
→ Harbor projects
→ Any system with a provider

Install Crossplane

helm repo add crossplane-stable https://charts.crossplane.io/stable

helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace

kubectl get pods -n crossplane-system

Install the Kubernetes Provider

Manage Kubernetes resources as Crossplane objects:

kubectl apply -f - <<EOF
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-kubernetes
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-kubernetes:v0.13.0
EOF

Install the Helm Provider

Manage Helm releases as Crossplane objects:

kubectl apply -f - <<EOF
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-helm
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-helm:v0.18.1
EOF

Example — Manage a Namespace via Crossplane

apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
metadata:
name: production-namespace
spec:
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
environment: production
managed-by: crossplane
providerConfigRef:
name: kubernetes-provider
kubectl apply -f namespace.yaml

# Crossplane creates + reconciles the namespace
# If someone deletes it manually, Crossplane recreates it automatically

Example — Manage a Helm Release via Crossplane

apiVersion: helm.crossplane.io/v1beta1
kind: Release
metadata:
name: prometheus-stack
spec:
forProvider:
chart:
name: kube-prometheus-stack
repository: https://prometheus-community.github.io/helm-charts
version: "57.0.0"
namespace: monitoring
values:
grafana:
adminPassword: changeme
providerConfigRef:
name: helm-provider
kubectl apply -f prometheus-release.yaml
kubectl get release prometheus-stack

ArgoCD can now sync this YAML from Git — full GitOps for Helm releases.


Composite Resources (XRDs)

The real power of Crossplane: define your own "platform API" that abstracts complexity from developers.

# Platform team defines:
apiVersion: platform.minicloud.io/v1alpha1
kind: Application
metadata:
name: my-api
spec:
namespace: production
image: harbor.local/platform/my-api:v1.2
replicas: 3
database: true # automatically provisions a DB

# Crossplane composes this into:
# → Deployment
# → Service
# → Ingress
# → PVC
# → DB credentials in Vault

Developers request an "Application" — they never touch the underlying complexity.


Crossplane + ArgoCD

Store all Crossplane YAMLs in Git. ArgoCD applies them:

Git commit → ArgoCD detects → kubectl apply → Crossplane reconciles → infra updated

This is full GitOps for infrastructure, not just applications.


Done When

✔ Crossplane installed in crossplane-system namespace
✔ Kubernetes provider and Helm provider installed
✔ At least one namespace managed via Crossplane Object
✔ One Helm release managed via Crossplane Release
✔ ArgoCD syncing Crossplane manifests from Git