How to Build Multi‑Cloud GitOps 2.0 with ArgoCD and Crossplane
This guide walks through implementing a GitOps 2.0 workflow that combines ArgoCD and Crossplane to manage both application deployments and multi‑cloud infrastructure as declarative YAML stored in Git, covering architecture, environment setup, step‑by‑step installation, example use cases, best‑practice recommendations, troubleshooting, monitoring, and backup strategies.
Overview
In the cloud‑native era, managing infrastructure across multiple clouds and clusters with rapid iteration requires a declarative, version‑controlled approach. GitOps uses a Git repository as the single source of truth, providing versioning, auditability, and automation for both applications and underlying cloud resources.
Technical Features
Declarative Management : All infrastructure and application configurations are stored as YAML in Git.
Automated Synchronization : ArgoCD continuously watches the repository and applies changes to target clusters.
Multi‑Cloud Abstraction : Crossplane exposes cloud resources (RDS, S3, VPC, etc.) as Kubernetes CRDs, enabling a unified API.
Drift Detection & Self‑Healing : ArgoCD detects state drift and can automatically reconcile or alert.
Audit & Traceability : Every change is a Git commit, providing a complete history.
Applicable Scenarios
Multi‑cloud environment management (AWS, Azure, GCP).
Large‑scale cluster orchestration (dozens to hundreds of clusters).
Self‑service infrastructure requests for development teams.
Disaster‑recovery and environment cloning.
Compliance‑driven audit requirements.
Environment Requirements
Kubernetes 1.24+ (recommended 1.26+ for CRD v1 support).
ArgoCD 2.8+ (2.9+ for full ApplicationSet support).
Crossplane 1.14+.
Helm 3.10+ for installing ArgoCD and Crossplane.
kubectl 1.24+ and Git client.
Step‑by‑Step Implementation
Preparation
Verify cluster health and version.
Install Helm, add required repos, and update.
# Check cluster status
kubectl cluster-info
kubectl get nodes
kubectl version --short
kubectl top nodes
kubectl get pods -A # Install Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm repo add argo https://argoproj.github.io/argo-helm
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm krew install crossplaneInstall ArgoCD
# Create namespace
kubectl create namespace argocd
# Install via Helm
helm install argocd argo/argo-cd \
--namespace argocd \
--version 5.51.0 \
--set server.service.type=LoadBalancer \
--set configs.params."application.namespaces"="*" \
--set server.extraArgs[0]="--insecure"
# Wait for pods
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -dInstall Crossplane
# Install Crossplane core
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace \
--version 1.14.5 \
--set args[0]="--enable-composition-revisions"
# Verify installation
kubectl get pods -n crossplane-system
kubectl api-resources | grep crossplaneConfigure Cloud Provider (AWS example)
# Provider CRD
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/upbound/provider-aws:v0.41.0
controllerConfigRef:
name: aws-provider-config
---
# ControllerConfig with resource limits
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: aws-provider-config
spec:
resources:
limits:
cpu: "1000m"
memory: "2Gi"
requests:
cpu: "500m"
memory: "1Gi"
args:
- --poll=5m
- --max-reconcile-rate=50Create ArgoCD Application for Infrastructure
# infrastructure-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-infra.git
targetRevision: main
path: infrastructure
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3mValidate Deployment
Check Application status: argocd app get infrastructure --refresh Verify Crossplane resources: kubectl get managed Confirm cloud resources (e.g., S3 bucket) exist.
Example Use Cases
Self‑Service Database Request
# PostgreSQL claim (developers create this file)
apiVersion: database.example.com/v1alpha1
kind: PostgreSQLInstance
metadata:
name: my-app-db
namespace: my-app
spec:
parameters:
storageGB: 50
instanceClass: db.t3.medium
engine: "14.5"
writeConnectionSecretToRef:
name: my-app-db-connDevelopers commit the claim, ArgoCD syncs it, Crossplane provisions an RDS instance, and the application reads connection details from the generated secret.
Multi‑Cluster Application Distribution
# ApplicationSet for deploying nginx to many clusters
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: nginx-multi-cluster
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: prod-us-east-1
url: https://prod-us-east-1.k8s.local
- cluster: prod-us-west-2
url: https://prod-us-west-2.k8s.local
- cluster: prod-eu-west-1
url: https://prod-eu-west-1.k8s.local
- cluster: staging-us-east-1
url: https://staging-us-east-1.k8s.local
template:
metadata:
name: '{{cluster}}-nginx'
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-infra.git
targetRevision: main
path: apps/nginx/overlays/{{cluster}}
destination:
server: '{{url}}'
namespace: nginx
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=trueThe ApplicationSet generates one Application per cluster, ensuring consistent deployment across all environments.
Best Practices & Security
Performance Tuning : Increase application.sync.concurrency in ArgoCD ConfigMap, allocate sufficient CPU/memory for ArgoCD server and controller.
Provider Resource Limits : Use ControllerConfig to set CPU/memory limits for each cloud provider to avoid OOM.
Repository Structure : Separate apps/, infrastructure/, and clusters/ directories for clear ownership.
Secret Management : Store credentials with Sealed Secrets or External Secrets; never keep plain text in Git.
RBAC Least‑Privilege : Define ArgoCD policy.csv and rbac-cm to restrict teams to only the resources they need.
Network Policies : Isolate Crossplane and ArgoCD pods, allow only required outbound ports (443 for cloud APIs, 53 for DNS).
Known Issues & Compatibility
ArgoCD 2.8+ requires Kubernetes 1.24+.
Crossplane 1.14+ requires Kubernetes 1.21+.
AWS Provider in China regions needs custom endpoints; Azure may hit subscription quota limits.
Long‑running cloud resources (e.g., RDS) need increased reconcile timeout.
Troubleshooting & Monitoring
Log Access
# ArgoCD controller logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller -f
# ArgoCD server logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server -f
# Crossplane core logs
kubectl logs -n crossplane-system deployment/crossplane -f
# Provider logs (example AWS)
kubectl logs -n crossplane-system -l pkg.crossplane.io/provider=provider-aws -fCommon Problems
Application stuck in Progressing : Check health checks, add argocd.argoproj.io/sync-wave annotations, or adjust custom health scripts.
Crossplane resource stuck in Creating : Verify provider IAM permissions, increase --poll interval, and inspect provider logs.
Git sync failures : Ensure repository credentials are up‑to‑date, enable SSH or HTTPS with proper tokens, and verify network connectivity.
Prometheus Metrics
Key metrics to monitor include argocd_app_sync_total, argocd_app_reconcile_duration_seconds, crossplane_managed_resource_ready, and controller reconcile error counters. Configure ServiceMonitors and PrometheusRule alerts for sync failures, out‑of‑sync applications, and resources that remain unready for more than 10 minutes.
Backup & Recovery
# Full backup script (run daily)
#!/bin/bash
set -e
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/backup/gitops/$TIMESTAMP"
mkdir -p "$BACKUP_DIR"
# ArgoCD
kubectl get applications -n argocd -o yaml > "$BACKUP_DIR/argocd-apps.yaml"
kubectl get appprojects -n argocd -o yaml > "$BACKUP_DIR/argocd-projects.yaml"
# Crossplane
kubectl get providers -o yaml > "$BACKUP_DIR/crossplane-providers.yaml"
kubectl get compositeresourcedefinitions -o yaml > "$BACKUP_DIR/xrds.yaml"
kubectl get compositions -o yaml > "$BACKUP_DIR/compositions.yaml"
kubectl get managed -o yaml > "$BACKUP_DIR/managed.yaml"
# Git repository (mirror)
cd /tmp
git clone --mirror https://github.com/your-org/gitops-infra.git "$BACKUP_DIR/git"
# Archive and upload to S3
tar -czf "gitops-backup-$TIMESTAMP.tar.gz" -C "$BACKUP_DIR" .
aws s3 cp "gitops-backup-$TIMESTAMP.tar.gz" s3://my-backup-bucket/gitops/
# Cleanup old backups (keep 7 days)
find /backup/gitops -name "gitops-backup-*.tar.gz" -mtime +7 -deleteRestoration consists of stopping ArgoCD sync, applying the saved YAML files, restoring the Git mirror, and re‑enabling automated sync.
Conclusion
The GitOps 2.0 stack built with ArgoCD and Crossplane provides a unified, declarative workflow for managing both applications and cloud resources across multiple providers. By following the outlined installation steps, security hardening measures, monitoring setup, and backup strategy, teams can achieve reliable, auditable, and scalable infrastructure automation while empowering developers with self‑service capabilities.
Ops Community
A leading IT operations community where professionals share and grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
