Fatskills
Practice. Master. Repeat.
Study Guide: TECH **Terraform Multi-Provider & Multi-Cloud Architectures: Zero-Fluff Study Guide**
Source: https://www.fatskills.com/cloud-application-developer/chapter/tech-terraform-multi-provider-multi-cloud-architectures-zero-fluff-study-guide

TECH **Terraform Multi-Provider & Multi-Cloud Architectures: Zero-Fluff Study Guide**

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~10 min read

Terraform Multi-Provider & Multi-Cloud Architectures: Zero-Fluff Study Guide

(For engineers who need to deploy across AWS, Azure, GCP, or hybrid clouds—without the theory fluff.)


1. What This Is & Why It Matters

You’re a cloud engineer at a company that just acquired a startup running on Azure. Your existing infrastructure is on AWS. Leadership wants a unified monitoring dashboard (Grafana on GCP) and a single CI/CD pipeline (GitHub Actions) that deploys to all three clouds. Oh, and compliance requires that no single cloud provider holds all your data.

Multi-provider Terraform lets you: - Avoid vendor lock-in (e.g., migrate from AWS to Azure without rewriting everything).
- Leverage best-of-breed services (e.g., AWS Lambda + Azure Cognitive Services + GCP BigQuery).
- Enforce consistent security policies (e.g., IAM roles, networking, logging) across clouds.
- Failover between clouds (e.g., if AWS us-east-1 goes down, traffic shifts to Azure).

What breaks if you ignore this?
- You’ll end up with snowflake deployments (e.g., AWS uses Terraform, Azure uses ARM templates, GCP uses gcloud CLI).
- Security drift: One cloud’s IAM policies might allow public S3 buckets while another’s doesn’t.
- Cost overruns: You might accidentally spin up duplicate resources (e.g., a NAT gateway in AWS and Azure) because you didn’t centralize control.

Real-world scenario:
You inherit a Terraform codebase that deploys a multi-region AWS VPC and a GCP Cloud SQL database. Your task: 1. Add Azure Blob Storage for backups.
2. Ensure all three clouds use the same Terraform state file (stored in AWS S3).
3. Make sure secrets (API keys, DB passwords) are never hardcoded in the repo.


2. Core Concepts & Components


1. provider Block

  • Definition: Declares which cloud provider (AWS, Azure, GCP) Terraform should interact with.
  • Production insight: Always pin provider versions (version = "~> 4.0") to avoid breaking changes.
    hcl provider "aws" {
    region = "us-east-1"
    version = "~> 4.0" }

2. alias (Multi-Region/Account Support)

  • Definition: Lets you configure multiple instances of the same provider (e.g., AWS us-east-1 and eu-west-1).
  • Production insight: Useful for multi-region failover or separate dev/prod accounts.
    hcl provider "aws" {
    alias = "east"
    region = "us-east-1" } provider "aws" {
    alias = "west"
    region = "us-west-2" }

3. terraform Block (Backend & Required Providers)

  • Definition: Defines where Terraform stores state (backend) and which providers are required.
  • Production insight: Never store state locally—use remote backends (S3, Azure Blob, GCS) for team collaboration.
    hcl terraform {
    backend "s3" {
    bucket = "my-terraform-state"
    key = "prod/terraform.tfstate"
    region = "us-east-1"
    }
    required_providers {
    aws = {
    source = "hashicorp/aws"
    version = "~> 4.0"
    }
    azurerm = {
    source = "hashicorp/azurerm"
    version = "~> 3.0"
    }
    } }

4. data Sources (Cross-Cloud Lookups)

  • Definition: Fetches existing resources (e.g., AWS VPC, Azure Resource Group) to reference in other clouds.
  • Production insight: Useful for hybrid networking (e.g., AWS VPC peering to Azure VNet).
    hcl data "aws_vpc" "main" {
    id = "vpc-123456" }

5. module (Reusable Multi-Cloud Components)

  • Definition: A self-contained Terraform config (e.g., a "secure VPC" module that works on AWS, Azure, and GCP).
  • Production insight: Standardize naming/tagging across clouds to avoid drift.
    hcl module "aws_vpc" {
    source = "./modules/vpc"
    cloud = "aws" } module "azure_vnet" {
    source = "./modules/vpc"
    cloud = "azure" }

6. remote_state (Cross-Cloud Dependencies)

  • Definition: Reads Terraform state from another cloud’s backend (e.g., AWS S3 → Azure Blob).
  • Production insight: Avoid circular dependencies—use depends_on if needed.
    hcl data "terraform_remote_state" "aws_network" {
    backend = "s3"
    config = {
    bucket = "my-aws-state"
    key = "network/terraform.tfstate"
    region = "us-east-1"
    } }

7. dynamic Blocks (Multi-Cloud Resource Variations)

  • Definition: Generates cloud-specific configurations (e.g., AWS security_group vs. Azure network_security_group).
  • Production insight: Keep logic DRY (Don’t Repeat Yourself) when clouds have similar but not identical resources.
    hcl dynamic "ingress" {
    for_each = var.cloud == "aws" ? [1] : []
    content {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    } }

8. null_resource + local-exec (Cross-Cloud Scripting)

  • Definition: Runs arbitrary scripts (Bash, Python) after Terraform applies (e.g., configure a VPN between AWS and Azure).
  • Production insight: Use sparingly—prefer native Terraform resources when possible.
    hcl resource "null_resource" "configure_vpn" {
    provisioner "local-exec" {
    command = "python3 configure_vpn.py ${aws_vpn_gateway.main.id} ${azurerm_virtual_network_gateway.main.id}"
    } }


3. Step-by-Step: Deploy a Multi-Cloud App (AWS + Azure + GCP)

Scenario: Deploy a web app with: - Frontend: AWS S3 + CloudFront (static website).
- Backend: Azure App Service (Node.js API).
- Database: GCP Cloud SQL (PostgreSQL).
- Monitoring: Grafana (hosted on GCP).

Prerequisites

✅ AWS account with IAM admin permissions.
✅ Azure account with Owner role.
✅ GCP account with Editor role.
✅ Terraform v1.3+ installed.
aws, az, and gcloud CLI tools installed.


Step 1: Configure Providers & Backend

Create main.tf:


terraform {
  backend "s3" {
bucket = "my-terraform-state-bucket" # Replace with your bucket
key = "multi-cloud-app/terraform.tfstate"
region = "us-east-1" } required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
google = {
source = "hashicorp/google"
version = "~> 4.0"
} } } provider "aws" { region = "us-east-1" } provider "azurerm" { features {} } provider "google" { project = "my-gcp-project" # Replace with your GCP project region = "us-central1" }

Verify:


terraform init

✅ Should output:


Initializing the backend...
Successfully configured the backend "s3"!


Step 2: Deploy AWS Frontend (S3 + CloudFront)

Add to main.tf:


resource "aws_s3_bucket" "frontend" {
  bucket = "my-multi-cloud-app-frontend"  # Must be globally unique
  acl    = "public-read"
  website {
index_document = "index.html" } } resource "aws_cloudfront_distribution" "frontend" { origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "S3-${aws_s3_bucket.frontend.id}" } enabled = true default_root_object = "index.html" default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.frontend.id}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https" } restrictions {
geo_restriction {
restriction_type = "none"
} } viewer_certificate {
cloudfront_default_certificate = true } }

Apply:


terraform apply -target=aws_s3_bucket.frontend -target=aws_cloudfront_distribution.frontend

✅ Verify: - Upload index.html to the S3 bucket.
- Visit the CloudFront URL (output by Terraform).


Step 3: Deploy Azure Backend (App Service)

Add to main.tf:


resource "azurerm_resource_group" "backend" {
  name     = "my-multi-cloud-app-rg"
  location = "East US"
}

resource "azurerm_app_service_plan" "backend" {
  name                = "my-app-service-plan"
  location            = azurerm_resource_group.backend.location
  resource_group_name = azurerm_resource_group.backend.name
  kind                = "Linux"
  reserved            = true
  sku {
tier = "Basic"
size = "B1" } } resource "azurerm_app_service" "backend" { name = "my-multi-cloud-app-backend" location = azurerm_resource_group.backend.location resource_group_name = azurerm_resource_group.backend.name app_service_plan_id = azurerm_app_service_plan.backend.id site_config {
linux_fx_version = "NODE|14-lts" } }

Apply:


terraform apply -target=azurerm_resource_group.backend -target=azurerm_app_service_plan.backend -target=azurerm_app_service.backend

✅ Verify: - Check the Azure Portal → App Services → my-multi-cloud-app-backend.
- Deploy a Node.js app to test.


Step 4: Deploy GCP Database (Cloud SQL)

Add to main.tf:


resource "google_sql_database_instance" "db" {
  name             = "my-multi-cloud-db"
  database_version = "POSTGRES_13"
  region           = "us-central1"
  settings {
tier = "db-f1-micro" } deletion_protection = false # ⚠️ Disable for demo (enable in prod!) } resource "google_sql_database" "db" { name = "app_db" instance = google_sql_database_instance.db.name } resource "google_sql_user" "db_user" { name = "app_user" instance = google_sql_database_instance.db.name password = "SuperSecret123!" # ⚠️ Use Terraform secrets in prod! }

Apply:


terraform apply -target=google_sql_database_instance.db -target=google_sql_database.db -target=google_sql_user.db_user

✅ Verify: - Check GCP Console → SQL → my-multi-cloud-db.
- Connect via psql: bash gcloud sql connect my-multi-cloud-db --user=app_user


Step 5: Connect All Components (Outputs + Remote State)

Add to outputs.tf:


output "frontend_url" {
  value = aws_cloudfront_distribution.frontend.domain_name
}

output "backend_url" {
  value = azurerm_app_service.backend.default_site_hostname
}

output "db_connection_name" {
  value = google_sql_database_instance.db.connection_name
}

Apply all:


terraform apply

✅ Verify: - The frontend (CloudFront) should call the backend (Azure App Service).
- The backend should connect to the database (GCP Cloud SQL).


4. ? Production-Ready Best Practices


Security

  • Never hardcode secrets: Use sops, vault, or Terraform Cloud’s remote backend with encryption.
    hcl data "aws_secretsmanager_secret_version" "db_password" {
    secret_id = "prod/db_password" }
  • Least privilege IAM: Restrict Terraform’s AWS/Azure/GCP roles to only what it needs.
  • Network isolation: Use private subnets for databases and VPC peering/VPN for cross-cloud traffic.

Cost Optimization

  • Right-size resources: Use t3.micro (AWS) instead of t3.large if you don’t need the power.
  • Spot instances: For non-critical workloads (e.g., batch jobs).
  • Reserved instances: Commit to 1- or 3-year terms for predictable workloads.

Reliability & Maintainability

  • Tag everything: Use a consistent tagging scheme (e.g., Environment=prod, Owner=team-devops).
  • Modularize: Break code into reusable modules (e.g., modules/vpc, modules/database).
  • Immutable infrastructure: Never modify running resources—destroy and recreate instead.

Observability

  • Centralized logging: Ship logs to AWS CloudWatch, Azure Monitor, or GCP Stackdriver.
  • Metrics: Use Prometheus + Grafana (hosted on GCP) to monitor all clouds.
  • Alerts: Set up SNS (AWS), Action Groups (Azure), or Cloud Monitoring (GCP) for critical failures.


5. ⚠️ Common Mistakes & Traps

Mistake Symptom Fix/Prevention
Hardcoded secrets Git history leaks API keys. Use sops, vault, or Terraform Cloud’s remote backend.
No provider version pinning Terraform apply fails after a provider update. Always pin versions (version = "~> 4.0").
State file conflicts Two engineers overwrite each other’s changes. Use remote state locking (S3 + DynamoDB, Azure Blob + Locks).
Cross-cloud networking misconfig AWS can’t talk to Azure. Use VPC peering, VPN, or PrivateLink.
Ignoring cloud-specific limits AWS has a 5 VPC peering limit per region. Check service quotas before designing.


6. ? Exam/Certification Focus

Typical question patterns:
1. "Which Terraform feature lets you deploy to AWS and Azure in the same config?"
- ✅ Answer: provider blocks with alias.
- ❌ Trap: "Use module" (modules are for reusability, not multi-cloud).


  1. "How do you reference an AWS VPC in an Azure Terraform config?"
  2. Answer: data "terraform_remote_state" + S3 backend.
  3. Trap: "Use aws_vpc data source in Azure" (won’t work).

  4. "What’s the risk of not pinning provider versions?"

  5. Answer: Breaking changes in new provider versions.
  6. Trap: "It’s fine, Terraform auto-updates" (no, it doesn’t).

Key trap distinctions:
- provider vs. module: Providers define where to deploy; modules define what to deploy.
- alias vs. default provider: alias is for multiple instances of the same provider (e.g., AWS us-east-1 and eu-west-1).
- backend vs. remote_state: backend stores your state; remote_state reads another state file.


7. ? Hands-On Challenge

Task: Deploy a multi-region AWS setup with: 1. A VPC in us-east-1 (default provider).
2. A VPC in eu-west-1 (aliased provider).
3. A VPC peering connection between them.

Solution:


provider "aws" {
  alias  = "europe"
  region = "eu-west-1"
}

resource "aws_vpc" "us" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_vpc" "eu" {
  provider   = aws.europe
  cidr_block = "10.1.0.0/16"
}

resource "aws_vpc_peering_connection" "peer" {
  vpc_id      = aws_vpc.us.id
  peer_vpc_id = aws_vpc.eu.id
  peer_region = "eu-west-1"
  auto_accept = false  # Must be manually accepted in the other region
}

Why it works: - alias lets you define a second AWS provider for eu-west-1.
- peer_region tells AWS to create the peering connection across regions.


8. ? Rapid-Reference Crib Sheet

Command/Concept Usage Notes
terraform init Initializes providers and backend. Run this first after adding a new provider.
provider "aws" { alias = "east" } Defines a secondary AWS provider. Use provider = aws.east in resources.
terraform state list Lists all resources in state. Useful for debugging.
terraform import aws_instance.my_vm i-123456 Imports an existing resource into state. Always back up state first!
backend "s3" Stores state in AWS S3. Enable versioning and encryption.
data "terraform_remote_state" Reads another state file. Useful for cross-cloud dependencies.
dynamic "ingress" Generates cloud-specific configs. Avoids repeating similar blocks.
⚠️ Default AWS VPC CIDR 172.31.0.0/16 Override this in production.
⚠️ Azure Resource Group Must exist before deploying. Use azurerm_resource_group.
⚠️ GCP Project ID Must be set in provider. Not the same as project name!


9. ? Where to Go Next

  1. [Terraform Multi-Cloud Docs](https://developer.hash


ADVERTISEMENT