The Multi-Cloud Reality
Nobody chooses multi-cloud. It happens to you. An acquisition brings GCP. A client requires Azure. Your ML team needs AWS SageMaker. Suddenly youβre managing infrastructure across three clouds.
The question isnβt whether multi-cloud is a good idea β itβs how to survive it.
Pattern 1: Provider-Agnostic Modules
Abstract cloud differences behind a consistent interface:
# modules/database/main.tf
variable "provider" {
type = string
validation {
condition = contains(["aws", "gcp", "azure"], var.provider)
error_message = "Provider must be aws, gcp, or azure"
}
}
variable "config" {
type = object({
name = string
engine = string
engine_version = string
instance_size = string
storage_gb = number
multi_az = bool
})
}
module "aws_rds" {
source = "./aws"
count = var.provider == "aws" ? 1 : 0
config = var.config
}
module "gcp_cloudsql" {
source = "./gcp"
count = var.provider == "gcp" ? 1 : 0
config = var.config
}
module "azure_flexible" {
source = "./azure"
count = var.provider == "azure" ? 1 : 0
config = var.config
}
output "connection_string" {
value = coalesce(
try(module.aws_rds[0].connection_string, ""),
try(module.gcp_cloudsql[0].connection_string, ""),
try(module.azure_flexible[0].connection_string, "")
)
}Usage:
module "payments_db" {
source = "./modules/database"
provider = "aws"
config = {
name = "payments"
engine = "postgres"
engine_version = "16"
instance_size = "medium"
storage_gb = 100
multi_az = true
}
}Same interface, any cloud. I maintain a library of these at Terraform Pilot.
Pattern 2: Repository Structure
terraform/
βββ modules/ # Reusable, provider-agnostic
β βββ database/
β βββ kubernetes/
β βββ networking/
β βββ monitoring/
βββ environments/
β βββ production/
β β βββ aws-eu/ # AWS eu-west-1
β β β βββ main.tf
β β β βββ variables.tf
β β β βββ terraform.tfvars
β β βββ gcp-eu/ # GCP europe-west4
β β βββ azure-eu/ # Azure westeurope
β βββ staging/
β βββ dev/
βββ shared/ # Cross-cloud resources
βββ dns/ # Route53/Cloud DNS
βββ monitoring/ # Datadog/Grafana Cloud
βββ iam/ # Cross-cloud identityPattern 3: State Management
# Per-cloud, per-environment state
# AWS environments β S3 backend
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "aws-eu/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# GCP environments β GCS backend
terraform {
backend "gcs" {
bucket = "terraform-state-prod-gcp"
prefix = "gcp-eu"
}
}Never share state across clouds. Each cloud environment gets its own state file. Cross-cloud references use data sources or remote state:
# In GCP config, reference AWS outputs
data "terraform_remote_state" "aws" {
backend = "s3"
config = {
bucket = "terraform-state-prod"
key = "aws-eu/terraform.tfstate"
region = "eu-west-1"
}
}
# Use AWS VPN endpoint in GCP
resource "google_compute_vpn_tunnel" "to_aws" {
peer_ip = data.terraform_remote_state.aws.outputs.vpn_endpoint_ip
}Pattern 4: Size Mapping
Cloud instance sizes donβt map 1:1. Abstract them:
locals {
size_map = {
small = {
aws = "t3.small"
gcp = "e2-small"
azure = "Standard_B1ms"
}
medium = {
aws = "t3.medium"
gcp = "e2-medium"
azure = "Standard_B2s"
}
large = {
aws = "m6i.xlarge"
gcp = "n2-standard-4"
azure = "Standard_D4s_v5"
}
}
actual_size = local.size_map[var.instance_size][var.provider]
}Anti-Patterns
- Lowest common denominator β Donβt avoid cloud-specific features. Use them, but abstract the interface.
- One mega-module β Donβt create a god module that handles every cloud. Keep modules focused.
- Copy-paste across clouds β Use the module pattern above. Duplicated code drifts.
- Managing all clouds from one CI pipeline β Separate pipelines per cloud. A GCP change shouldnβt risk AWS.
Automating Multi-Cloud with Ansible
Terraform handles provisioning. Ansible handles configuration. Together theyβre powerful for multi-cloud:
# Ansible inventory from Terraform outputs
- name: Configure databases across clouds
hosts: databases
tasks:
- name: Apply security hardening
include_role:
name: database-hardening
# Same role works on RDS, CloudSQL, and Azure FlexibleI detail the Ansible + Terraform integration at Ansible Pilot and maintain ready-to-use Terraform modules at Terraform Pilot.
The Honest Truth
Multi-cloud doubles your operational complexity. Donβt do it unless you have a business reason. But when you must, these patterns keep it manageable. The key: standardize the interface, not the implementation.
