From Provisioning to Control Plane: Designing a Hybrid Terraform + Crossplane Architecture at Scale

Search for a command to run...

No comments yet. Be the first to comment.
The Ops Fix Hub is a dynamic series designed to tackle real-world challenges in the DevOps and CloudOps domains. "Breaking Down Barriers, One Ops Fix at a Time."
Suppose AWS doesn't currently have enough available On-Demand capacity to complete your request. In that case, you may encounter the InsufficientInstanceCapacity error when you try to launch a new instance or restart a stopped instance. Resolution St...
This migration was performed on a production workload where cost reduction was prioritized over zone-level high availability.

Cross-cloud VM migration is not a disk copy task. It is: An access model transformation A replication lifecycle management exercise A downtime control operation A cost boundary decision We execu

When AWS introduced AWS DevOps Agent, I was less interested in feature lists and more interested in one practical question. Can it actually reduce investigation time during real production-style failu

Migrating object storage across cloud providers is not a copy task.It is a cost, network, and security boundary problem. We migrated 10+ TB of object data from Google Cloud Storage to Amazon S3 under

I designed a hybrid infrastructure architecture:
Terraform → Foundation Layer
Crossplane → Dynamic Lifecycle Layer
ArgoCD → GitOps Enforcement
This created a continuously reconciling cloud control plane inside Kubernetes.
Our platform crossed:
50+ microservices
Multiple engineering teams
Multi-region expansion
PR-driven infrastructure workflows
Feature branch–based short-lived environments
Terraform workflows became operationally slow due to:
State contention
Long plan times
PR bottlenecks
Manual drift detection
Provisioning was working.
Lifecycle control was missing.
No full rewrite
No infrastructure instability
Zero data loss
Minimal migration risk
Separate provisioning from lifecycle reconciliation.
Provision once.
Reconcile continuously.
Terraform model:
Plan → Apply → Exit
After apply:
No continuous reconciliation
Drift detection only on next plan
Manual console changes remain undetected
If an engineer modified:
RDS storage encryption
Deletion protection
Security groups
IAM policies
Terraform would not react until the next plan/apply cycle.
Drift became silent operational risk.
Compliance posture
Backup guarantees
Encryption enforcement
Network boundaries
Incident recovery confidence
At scale:
Manual governance does not work.
Infrastructure must enforce its declared state.
Control plane flow:
Developer commits YAML
↓
ArgoCD syncs to cluster
↓
Kubernetes API stores desired state
↓
Crossplane controller watches resource
↓
Crossplane calls AWS API
↓
Cloud resource created/updated
↑
Continuous reconciliation loop
Terraform foundation layer:
Terraform
↓
VPC
Subnets
EKS Control Plane
Core Networking
Clear separation of responsibilities.
Terraform for foundation
Why I chose it:
Mature state handling
Strong bootstrap ecosystem
Clear isolation of foundational infrastructure
Multi-account governance was enforced via separate state isolation and account-factory patterns; Terraform itself does not natively provide org-level governance.
Trade-off:
Risk accepted:
Crossplane for dynamic infrastructure
Why I chose it:
Kubernetes-native control loop
GitOps-friendly
CRD-based lifecycle management
Trade-off:
Adds API server load
Adds controller complexity
Risk accepted:
Reconciliation loop ensures:
Actual State == Desired State
Manual console change → Crossplane reconciles.
Trade-off:
AWS API throttling possible
Eventual consistency delays
Risk accepted:
Git Commit
→ ArgoCD
→ Kubernetes API
→ Crossplane Controller
→ AWS API
Rollback = git revert.
Trade-off:
Risk accepted:
Platform team defined Compositions.
Example:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xpostgres
spec:
compositeTypeRef:
apiVersion: platform.io/v1alpha1
kind: XPostgres
resources:
- name: database
base:
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
spec:
deletionPolicy: Orphan
forProvider:
storageEncrypted: true
deletionProtection: true
Application team used Claim:
apiVersion: platform.io/v1alpha1
kind: PostgresClaim
metadata:
name: app-db
spec:
parameters:
storage: 20
Why I chose this:
Central policy enforcement
Developer abstraction
Clear ownership boundary
Trade-off:
Composition update blast radius
Requires versioning discipline
Risk accepted:
DNS and core networking remained Terraform-managed.
Reason:
High blast radius
Low change frequency
Complex dependency graph
Control plane expansion was phased deliberately.
Install Crossplane:
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace
Install AWS Provider:
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.54.2
Configure ProviderConfig (IRSA recommended):
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: aws
spec:
credentials:
source: IRSA
ProviderConfig was configured using IRSA to avoid static credentials.
RDS Example:
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
name: platform-db
spec:
deletionPolicy: Orphan
forProvider:
region: us-east-1
dbInstanceClass: db.t3.micro
allocatedStorage: 20
engine: postgres
storageEncrypted: true
deletionProtection: true
providerConfigRef:
name: aws
Assumes a default VPC and subnet group already exist; in hardened environments, explicit subnetGroupName and securityGroupIds must be specified.
Database endpoints remained AWS-managed.
No DNS switching automated through Crossplane.
Reason:
Database endpoints are stable
DNS manipulation has high blast radius
Networking remained Terraform-owned
Modified RDS parameter in AWS Console.
Observed:
Crossplane detected change
Reconciliation restored desired state
Deleted CR with:
deletionPolicy: Orphan
Observed:
Cloud resource retained
CR removed
Changed to:
deletionPolicy: Delete
Deleted CR.
Observed:
Lifecycle behavior verified.
Created multiple resources in parallel.
Observed:
AWS API throttling errors
Provider retry with exponential backoff
Validated concurrency and backoff tuning necessity.
Additional Crossplane controller pods
Increased etcd object count
Higher AWS API call volume
Cost impact: Moderate.
Increased:
Controller debugging
Composition versioning
CRD lifecycle management
Reduced:
Manual drift remediation
Terraform PR bottlenecks
Apply-time surprises
Improved.
Drift auto-corrected without manual intervention.
No direct change.
Depends on AWS-native backup policies.
Pros:
Safe self-service for app teams
Git-auditable infrastructure
Continuous compliance enforcement
Cons:
API server load increases
AWS rate limit sensitivity
Composition update blast radius
✔️ 50+ services
✔️ Dedicated platform team
✔️ GitOps maturity
✔️ Kubernetes-native organization
✔️ High infrastructure churn
Small teams
Low churn infrastructure
No Kubernetes maturity
Multi-account bootstrap phase
Extremely complex networking requirements
Terraform builds infrastructure.
Crossplane manages the infrastructure lifecycle.
GitOps enforces declared intent.
This was not a tool replacement exercise.
It was an architectural shift from:
Provisioning mindset → Control plane mindset
At scale, lifecycle enforcement matters more than provisioning speed.
Hybrid architecture made lifecycle enforcement operationally viable at scale.