Skip to content

Terraform_multicloud

Multi-cloud infrastructure management involves deploying and managing resources across multiple cloud providers. Terraform’s provider ecosystem makes it well-suited for multi-cloud deployments.

  • Vendor independence: Avoid lock-in
  • Best-of-breed: Use best services from each provider
  • Disaster recovery: Cross-cloud redundancy
  • Compliance: Meet data residency requirements
  • Cost optimization: Leverage pricing differences
┌─────────────────────────────────────────────────────────────────┐
│ Multi-Cloud Architecture │
│ │
│ ┌──────────────┐ │
│ │ Terraform │ │
│ │ Config │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────────────────────┼──────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ AWS │ │ Azure │ │ GCP │ │
│ │ (us-east) │ │ (eastus) │ │ (us-east) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Networking │ │
│ │ Transit Gateway / ExpressRoute / Cloud Interconnect │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
providers.tf
# AWS Provider
provider "aws" {
alias = "aws_primary"
region = "us-east-1"
}
provider "aws" {
alias = "aws_secondary"
region = "us-west-2"
}
# Azure Provider
provider "azurerm" {
alias = "primary"
subscription_id = var.azure_subscription_id
tenant_id = var.azure_tenant_id
features {}
}
# GCP Provider
provider "google" {
alias = "primary"
project = var.gcp_project
region = "us-central1"
}
# AWS resources
resource "aws_instance" "primary" {
provider = aws.aws_primary
ami = var.aws_ami
# ...
}
resource "aws_instance" "secondary" {
provider = aws.aws_secondary
ami = var.aws_ami
# ...
}
# Azure resources
resource "azurerm_virtual_machine" "primary" {
provider = azurerm.primary
# ...
}
# GCP resources
resource "google_compute_instance" "primary" {
provider = google.primary
# ...
}
modules/
├── compute/
│ ├── main.tf # Abstract compute logic
│ ├── variables.tf # Provider-agnostic variables
│ ├── outputs.tf
│ └── versions.tf
modules/compute/main.tf
# Use conditional logic for provider-agnostic resources
resource "aws_instance" "compute" {
count = var.provider == "aws" ? var.instance_count : 0
ami = var.aws_ami
instance_type = var.instance_type
# AWS-specific config
}
resource "azurerm_linux_virtual_machine" "compute" {
count = var.provider == "azure" ? var.instance_count : 0
admin_username = var.admin_username
size = var.azure_vm_size
# Azure-specific config
}
resource "google_compute_instance" "compute" {
count = var.provider == "gcp" ? var.instance_count : 0
machine_type = var.instance_type
boot_disk {
initialize_params {
image = var.gcp_image
}
}
# GCP-specific config
}
modules/compute/variables.tf
variable "provider" {
description = "Cloud provider: aws, azure, or gcp"
type = string
default = "aws"
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 1
}
variable "instance_type" {
description = "Instance type"
type = string
default = "t3.micro"
}
variable "aws_ami" {
description = "AWS AMI ID"
type = string
default = ""
}
variable "azure_vm_size" {
description = "Azure VM size"
type = string
default = "Standard_B1s"
}
variable "gcp_image" {
description = "GCP image"
type = string
default = "debian-11"
}
# Cross-AWS VPC peering
resource "aws_vpc" "primary" {
provider = aws.aws_primary
cidr_block = "10.1.0.0/16"
}
resource "aws_vpc" "secondary" {
provider = aws.aws_secondary
cidr_block = "10.2.0.0/16"
}
resource "aws_vpc_peering_connection" "primary_to_secondary" {
provider = aws.aws_primary
vpc_id = aws_vpc.primary.id
peer_vpc_id = aws_vpc.secondary.id
peer_region = "us-west-2"
auto_accept = true
}
resource "aws_route" "primary_routes" {
provider = aws.aws_primary
route_table_id = aws_vpc.primary.default_route_table_id
destination_cidr_block = aws_vpc.secondary.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
}
# AWS Transit Gateway
resource "aws_ec2_transit_gateway" "main" {
provider = aws.aws_primary
amazon_asn = 64512
description = "Main Transit Gateway"
default_route_table_association = "enable"
default_route_table_propagation = "enable"
}
# Azure Virtual Hub (requires azurerm provider)
resource "azurerm_virtual_hub" "main" {
provider = azurerm.primary
name = "main-hub"
resource_group_name = azurerm_resource_group.network.name
location = azurerm_resource_group.network.location
address_prefix = "172.16.0.0/23"
}
# Note: Cross-cloud connectivity typically requires:
# - Cloud Exchange (Equinix, Megaport)
# - AWS Direct Connect + Azure ExpressRoute
# - VPN tunnels
data "aws_ami" "ubuntu" {
provider = aws.aws_primary
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-*-amd64-server-*"]
}
}
data "azurerm_subscription" "primary" {
provider = azurerm.primary
}
data "azurerm_image" "ubuntu" {
provider = azurerm.primary
name = "ubuntu-2204"
resource_group_name = "images-rg"
}
data "google_compute_image" "ubuntu" {
provider = google.primary
project = "ubuntu-os-cloud"
family = "ubuntu-2204-lts"
}
data "aws_secretsmanager_secret_version" "db_creds" {
provider = aws.aws_primary
secret_id = "prod/database/credentials"
}
locals {
db_creds = jsondecode(data.aws_secretsmanager_secret_version.db_creds.secret_string)
}
data "azurerm_key_vault" "main" {
provider = azurerm.primary
name = "main-kv"
resource_group_name = "security-rg"
}
data "azurerm_key_vault_secret" "db_password" {
provider = azurerm.primary
name = "db-password"
key_vault_id = data.azurerm_key_vault.main.id
}
data "google_secret_manager_secret_version" "db_creds" {
provider = google.primary
secret = "prod-database-credentials"
}
# Use Infracost for cost estimation
# See terraform_testing.md for details
# Or use provider cost calculators
data "aws_ec2_instance_type" "example" {
provider = aws.aws_primary
instance_type = "t3.micro"
}
output "hourly_cost" {
value = data.aws_ec2_instance_type.example.price_hourly
}
# Don't mix cloud-specific resources in main configs
# Use modules that abstract cloud differences
module "compute" {
source = "./modules/compute"
provider = var.cloud_provider
# ... provider-agnostic inputs
}
environments/
├── dev/
│ ├── main.tf
│ └── terragrunt.hcl
├── staging/
│ └── ...
└── prod/
└── ...
locals {
common_tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
CostCenter = var.cost_center
}
}
resource "aws_instance" "example" {
# ...
tags = local.common_tags
}

4. Use remote state for cross-cloud references

Section titled “4. Use remote state for cross-cloud references”
# Store outputs in shared state bucket
terraform {
backend "s3" {
bucket = "terraform-multi-cloud-state"
key = "networking/terraform.tfstate"
}
}
# GitHub Actions
jobs:
validate:
runs-on: ubuntu-latest
strategy:
matrix:
cloud: [aws, azure, gcp]
steps:
- uses: actions/checkout@v3
- name: Validate ${{ matrix.cloud }}
run: |
terraform init -backend=false
terraform validate

Multi-cloud with Terraform enables:

  • Vendor independence: Avoid lock-in
  • Best-of-breed: Use optimal services
  • Resilience: Cross-cloud redundancy
  • Compliance: Data residency requirements
  • Cost optimization: Leverage pricing

Key patterns:

  • Abstraction: Provider-agnostic modules
  • Consistent tagging: Cross-cloud resource organization
  • Centralized state: Shared state management
  • Secrets management: Cross-cloud secrets
  • Unified networking: Cross-cloud connectivity