Terraform Multi-Cloud: AWS + Azure + GCP cu module partajate
Arhitectură Terraform pentru medii multi-cloud: strat de abstractizare cu interfețe uniforme pentru calcul, rețele și baze de date, managementul furnizorilor multipli şi separarea configuraţiei după mediu.
Cazul pentru Multi-Cloud
92% dintre companiile mari folosesc mai mult de un furnizor de cloud (Flexera State of the Cloud 2025). Motivele sunt diverse: reducerea blocării furnizorului, optimizarea costurilor (utilizați cel mai ieftin furnizor pentru fiecare sarcină de lucru), cerințe de conformitate (datele din regiunile UE numai pe Azure, AI/ML pe GCP, volumul de lucru al întreprinderii pe AWS), și achiziții care aduc infrastructură eterogenă.
Terraform este instrumentul ideal pentru gestionarea multi-cloud: acceptă nativ furnizor pentru toate norii majore cu aceeași sintaxă HCL. Provocarea nu este este tehnic, este arhitectural: cum se structurează modulele pt maximizați reutilizarea e minimiza complexitatea când fiecare nor are API-uri diferite pentru concepte similare.
Ce vei învăța
- Configurație cu mai mulți furnizori: alias, furnizor pe spațiu de lucru, furnizor pe modul
- Strat de abstractizare: modul de interfață uniform pentru calcul, rețele, baze de date
- Model pentru gestionarea diferențelor semantice dintre nori (VPC/VNet, Instanță/VM)
- Structura depozitului pentru echipe multi-cloud: monorepo vs polyrepo
- Gestionarea secretelor multi-cloud: Seif ca sursă unică de adevăr
- Optimizarea costurilor: instanțe spot AWS, GCP preemptibil, Azure spot
Configurație cu mai mulți furnizori cu alias
Terraform vă permite să utilizați mai multe instanțe ale aceluiași furnizor (sau furnizori diferiți) în aceeași formă prin intermediul alias. Acest lucru este util pentru implementați resurse în mai multe regiuni sau conturi ale aceluiași cloud, precum și pentru configurați diferiți furnizori.
# providers.tf - Configurazione centralizzata di tutti i provider
terraform {
required_version = "~> 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.90"
}
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
vault = {
source = "hashicorp/vault"
version = "~> 3.0"
}
}
}
# AWS: provider principale (EU) e secondario (US) con alias
provider "aws" {
region = "eu-west-1" # Provider default
}
provider "aws" {
alias = "us_east"
region = "us-east-1" # Alias per risorse in US
}
provider "aws" {
alias = "disaster_recovery"
region = "eu-central-1" # Alias per DR
}
# Azure: richiede features{} minimo
provider "azurerm" {
features {}
subscription_id = var.azure_subscription_id
# Autenticazione tramite Service Principal o Managed Identity
}
# GCP: configurazione base
provider "google" {
project = var.gcp_project_id
region = "europe-west1"
}
# Vault: per gestione centralizzata dei segreti multi-cloud
provider "vault" {
address = "https://vault.mycompany.com"
# Token da variabile d'ambiente VAULT_TOKEN o via AppRole
}
Stratul de abstractizare: modelul fundamental de design
Principala provocare a multi-cloud-ului este că AWS își numește serviciul EC2, Azure îl numește Virtual Machine și GCP Compute Engine, dar conceptual sunt acelasi lucru. THE'stratul de abstractizare creați formulare cu interfețe uniforme care ascund aceste deosebiri.
# Struttura del repository con abstraction layer
terraform-multicloud/
modules/
# Layer 1: Moduli cloud-specific (implementazione)
aws/
compute/ # EC2, Auto Scaling Groups
networking/ # VPC, Subnets, Security Groups
database/ # RDS, Aurora
azure/
compute/ # Virtual Machine Scale Sets
networking/ # VNet, NSG, Subnets
database/ # Azure Database for PostgreSQL
gcp/
compute/ # Instance Groups, MIGs
networking/ # VPC, Firewall Rules
database/ # Cloud SQL
# Layer 2: Moduli di interfaccia (abstraction)
compute/ # Interfaccia uniforme, delega a aws/ o azure/ o gcp/
networking/
database/
# Layer 3: Composite modules (pattern applicativi)
three-tier-app/ # Frontend + Backend + Database su cloud specificato
kubernetes-cluster/
environments/
dev/
main.tf # Usa composite modules
providers.tf
production-aws/
production-azure/
Implementați stratul de abstracție
# modules/compute/variables.tf
# Interfaccia uniforme per il modulo compute (cloud-agnostic)
variable "cloud" {
type = string
description = "Cloud provider: aws, azure, gcp"
validation {
condition = contains(["aws", "azure", "gcp"], var.cloud)
error_message = "cloud deve essere aws, azure o gcp"
}
}
variable "name" {
type = string
description = "Nome del gruppo di compute (snake_case)"
}
variable "environment" {
type = string
description = "Ambiente: dev, staging, production"
}
variable "instance_type" {
type = string
description = "Tipo istanza nel formato normalizzato: small, medium, large, xlarge"
validation {
condition = contains(["small", "medium", "large", "xlarge"], var.instance_type)
error_message = "instance_type deve essere small|medium|large|xlarge"
}
}
variable "min_size" {
type = number
default = 1
}
variable "max_size" {
type = number
default = 10
}
variable "subnet_ids" {
type = list(string)
description = "Lista di subnet/subnetwork IDs dove deployare le istanze"
}
variable "ami_or_image_id" {
type = string
description = "AMI ID (AWS), Image ID (Azure/GCP)"
}
variable "user_data" {
type = string
description = "Script di inizializzazione (cloud-init compatible)"
default = ""
}
variable "tags" {
type = map(string)
default = {}
}
# modules/compute/main.tf
# Abstraction layer: delega all'implementazione cloud-specifica
locals {
# Mapping instance_type -> tipo istanza per ogni cloud
instance_type_map = {
aws = {
small = "t3.small"
medium = "t3.medium"
large = "t3.large"
xlarge = "t3.xlarge"
}
azure = {
small = "Standard_B2s"
medium = "Standard_B4ms"
large = "Standard_D4s_v3"
xlarge = "Standard_D8s_v3"
}
gcp = {
small = "e2-small"
medium = "e2-medium"
large = "e2-standard-4"
xlarge = "e2-standard-8"
}
}
resolved_instance_type = local.instance_type_map[var.cloud][var.instance_type]
}
# Delegazione condizionale all'implementazione cloud-specifica
module "aws_compute" {
source = "../aws/compute"
count = var.cloud == "aws" ? 1 : 0
name = var.name
environment = var.environment
instance_type = local.resolved_instance_type
min_size = var.min_size
max_size = var.max_size
subnet_ids = var.subnet_ids
ami_id = var.ami_or_image_id
user_data = var.user_data
tags = var.tags
}
module "azure_compute" {
source = "../azure/compute"
count = var.cloud == "azure" ? 1 : 0
name = var.name
environment = var.environment
vm_size = local.resolved_instance_type
min_instances = var.min_size
max_instances = var.max_size
subnet_ids = var.subnet_ids
source_image = var.ami_or_image_id
custom_data = var.user_data
tags = var.tags
}
module "gcp_compute" {
source = "../gcp/compute"
count = var.cloud == "gcp" ? 1 : 0
name = var.name
environment = var.environment
machine_type = local.resolved_instance_type
min_replicas = var.min_size
max_replicas = var.max_size
subnetwork_ids = var.subnet_ids
source_image = var.ami_or_image_id
metadata = var.user_data != "" ? { "user-data" = var.user_data } : {}
labels = var.tags
}
# modules/compute/outputs.tf
# Output uniformi indipendentemente dal cloud
output "instance_group_id" {
value = var.cloud == "aws" ? module.aws_compute[0].autoscaling_group_id :
var.cloud == "azure" ? module.azure_compute[0].scale_set_id :
module.gcp_compute[0].instance_group_id
description = "ID del gruppo di compute (ASG ID, Scale Set ID, MIG ID)"
}
output "load_balancer_dns" {
value = var.cloud == "aws" ? module.aws_compute[0].alb_dns_name :
var.cloud == "azure" ? module.azure_compute[0].load_balancer_fqdn :
module.gcp_compute[0].load_balancer_ip
description = "DNS o IP del load balancer frontale"
}
Modul de bază de date Multi-Cloud
# modules/database/main.tf
# Astrazione per PostgreSQL su AWS (RDS), Azure (Flexible Server) e GCP (Cloud SQL)
variable "cloud" {
type = string
}
variable "engine_version" {
type = string
default = "15" # PostgreSQL major version
}
variable "size" {
type = string
default = "small" # small, medium, large
}
variable "storage_gb" {
type = number
default = 50
}
variable "backup_retention_days" {
type = number
default = 7
}
variable "multi_az" {
type = bool
default = false
description = "Alta disponibilità: Multi-AZ (AWS), Zone-Redundant (Azure), HA (GCP)"
}
locals {
db_size_map = {
aws = {
small = "db.t3.medium"
medium = "db.t3.large"
large = "db.r6g.xlarge"
}
azure = {
small = "Standard_D2ds_v4"
medium = "Standard_D4ds_v4"
large = "Standard_D8ds_v4"
}
gcp = {
small = "db-custom-2-7680"
medium = "db-custom-4-15360"
large = "db-custom-8-30720"
}
}
}
# AWS: RDS PostgreSQL
resource "aws_db_instance" "main" {
count = var.cloud == "aws" ? 1 : 0
engine = "postgres"
engine_version = var.engine_version
instance_class = local.db_size_map.aws[var.size]
allocated_storage = var.storage_gb
storage_encrypted = true # Sempre: CKV_AWS_17
deletion_protection = true # Sempre in non-dev
backup_retention_period = var.backup_retention_days
multi_az = var.multi_az
# Performance Insights
performance_insights_enabled = true
performance_insights_retention_period = 7
tags = {
ManagedBy = "terraform"
Cloud = "aws"
}
}
# Azure: PostgreSQL Flexible Server
resource "azurerm_postgresql_flexible_server" "main" {
count = var.cloud == "azure" ? 1 : 0
name = var.name
resource_group_name = var.resource_group_name
location = var.location
sku_name = local.db_size_map.azure[var.size]
version = var.engine_version
storage_mb = var.storage_gb * 1024
backup_retention_days = var.backup_retention_days
geo_redundant_backup_enabled = var.multi_az
high_availability {
mode = var.multi_az ? "ZoneRedundant" : "Disabled"
}
}
# GCP: Cloud SQL PostgreSQL
resource "google_sql_database_instance" "main" {
count = var.cloud == "gcp" ? 1 : 0
name = var.name
database_version = "POSTGRES_${var.engine_version}"
settings {
tier = local.db_size_map.gcp[var.size]
disk_size = var.storage_gb
disk_autoresize = true
backup_configuration {
enabled = true
point_in_time_recovery_enabled = true
transaction_log_retention_days = var.backup_retention_days
}
availability_type = var.multi_az ? "REGIONAL" : "ZONAL"
insights_config {
query_insights_enabled = true
}
}
deletion_protection = true
}
Managementul secretelor multi-cloud cu Vault
# modules/secrets/main.tf
# HashiCorp Vault come source of truth unica per segreti multi-cloud
variable "cloud" {
type = string
}
variable "environment" {
type = string
}
variable "application" {
type = string
}
# Leggi i segreti da Vault
data "vault_generic_secret" "app_secrets" {
path = "secret/${var.environment}/${var.application}"
}
# Distribuisci i segreti al cloud appropriato
# AWS: crea Secrets Manager entry dal segreto Vault
resource "aws_secretsmanager_secret" "app" {
count = var.cloud == "aws" ? 1 : 0
name = "${var.environment}/${var.application}"
tags = {
ManagedBy = "terraform"
Source = "vault"
}
}
resource "aws_secretsmanager_secret_version" "app" {
count = var.cloud == "aws" ? 1 : 0
secret_id = aws_secretsmanager_secret.app[0].id
secret_string = jsonencode(data.vault_generic_secret.app_secrets.data)
}
# Azure: crea Key Vault secrets dal segreto Vault
resource "azurerm_key_vault_secret" "app" {
for_each = var.cloud == "azure" ? data.vault_generic_secret.app_secrets.data : {}
name = replace(each.key, "_", "-") # Azure Key Vault: no underscore
value = each.value
key_vault_id = var.azure_key_vault_id
}
# GCP: crea Secret Manager entries
resource "google_secret_manager_secret" "app" {
for_each = var.cloud == "gcp" ? data.vault_generic_secret.app_secrets.data : {}
secret_id = "${var.environment}-${var.application}-${each.key}"
replication {
auto {}
}
}
resource "google_secret_manager_secret_version" "app" {
for_each = var.cloud == "gcp" ? data.vault_generic_secret.app_secrets.data : {}
secret = google_secret_manager_secret.app[each.key].id
secret_data = each.value
}
Implementarea în mai multe cloud a unei aplicații
# environments/production-multicloud/main.tf
# Deploy della stessa applicazione su AWS (primary) e Azure (DR)
locals {
app_name = "catalog-api"
environment = "production"
common_tags = {
Application = local.app_name
Environment = local.environment
ManagedBy = "terraform"
CostCenter = "product-team"
}
}
# Networking AWS (Primary)
module "aws_networking" {
source = "../../modules/aws/networking"
name = "${local.app_name}-${local.environment}"
cidr_block = "10.0.0.0/16"
az_count = 3
tags = local.common_tags
}
# Networking Azure (DR)
module "azure_networking" {
source = "../../modules/azure/networking"
name = "${local.app_name}-${local.environment}"
resource_group_name = azurerm_resource_group.dr.name
location = "West Europe"
address_space = ["10.1.0.0/16"]
tags = local.common_tags
}
# Compute AWS (Primary) - Usa il modulo uniforme
module "compute_primary" {
source = "../../modules/compute"
cloud = "aws"
name = "${local.app_name}-primary"
environment = local.environment
instance_type = "large"
min_size = 3
max_size = 20
subnet_ids = module.aws_networking.private_subnet_ids
ami_or_image_id = data.aws_ami.app.id
tags = local.common_tags
}
# Compute Azure (DR) - Stessa interfaccia, cloud diverso
module "compute_dr" {
source = "../../modules/compute"
cloud = "azure"
name = "${local.app_name}-dr"
environment = local.environment
instance_type = "large"
min_size = 1 # DR: capacità ridotta finché non necessaria
max_size = 20
subnet_ids = module.azure_networking.subnet_ids
ami_or_image_id = var.azure_vm_image_id
tags = local.common_tags
}
# Database AWS (Primary)
module "database_primary" {
source = "../../modules/database"
cloud = "aws"
name = "${local.app_name}-primary"
size = "large"
storage_gb = 200
backup_retention_days = 30
multi_az = true # HA in production
}
# Database Azure (DR)
module "database_dr" {
source = "../../modules/database"
cloud = "azure"
name = "${local.app_name}-dr"
resource_group_name = azurerm_resource_group.dr.name
location = "West Europe"
size = "medium"
storage_gb = 200
backup_retention_days = 7
multi_az = false # DR: single zone per costi
}
# Output per entrambi gli ambienti
output "primary_endpoint" {
value = module.compute_primary.load_balancer_dns
}
output "dr_endpoint" {
value = module.compute_dr.load_balancer_dns
}
Optimizarea costurilor Multi-Cloud: Spot/Preemptibil
# modules/compute-spot/main.tf
# Modulo unificato per spot/preemptible instances (70-90% risparmio vs on-demand)
variable "cloud" {
type = string
}
variable "spot_percentage" {
type = number
default = 70
description = "Percentuale di istanze spot (0-100). Il resto è on-demand."
}
# AWS: Mixed Instance Policy con Spot
resource "aws_autoscaling_group" "mixed" {
count = var.cloud == "aws" ? 1 : 0
mixed_instances_policy {
instances_distribution {
on_demand_base_capacity = 2 # Minimo garantito on-demand
on_demand_percentage_above_base_capacity = 100 - var.spot_percentage
spot_allocation_strategy = "price-capacity-optimized"
}
launch_template {
launch_template_specification {
launch_template_id = aws_launch_template.app[0].id
version = "$Latest"
}
# Tipi istanza diversi per aumentare disponibilità spot
override {
instance_type = "t3.large"
}
override {
instance_type = "t3a.large"
}
override {
instance_type = "m5.large"
}
}
}
min_size = var.min_size
max_size = var.max_size
}
# GCP: Preemptible instances nel MIG
resource "google_compute_instance_template" "preemptible" {
count = var.cloud == "gcp" ? 1 : 0
scheduling {
preemptible = var.spot_percentage > 0
automatic_restart = false # Obbligatorio per preemptible
on_host_maintenance = "TERMINATE"
}
}
# Azure: Spot VMs con eviction policy
resource "azurerm_orchestrated_virtual_machine_scale_set" "spot" {
count = var.cloud == "azure" ? 1 : 0
priority = "Spot"
eviction_policy = "Deallocate" # o "Delete" per risparmio storage
max_bid_price = -1 # -1 = paga fino al prezzo on-demand
}
Multi-Cloud Anti-Pattern de evitat
Greșeli frecvente în arhitectura Multi-Cloud IaC
- Abstracția prea forțată: Nu toate serviciile au echivalente direct între nori. AWS SQS, Azure Service Bus și GCP Pub/Sub sunt similare, dar nu identice. Un formular prea generic ascunde caracteristici importante specifice norului.
- Un singur fișier de stare pentru toți norii: Fișier de stare separat pentru fiecare nor și după mediu. Punerea totul într-un singur fișier de stat crește riscul de corupție și încetinește operațiunile.
-
Furnizorii care sunt prea permisivi: Nu da
AdministratorAccesscătre furnizorul Terraform. Utilizați roluri IAM cu cele mai mici privilegii specifice fiecărui mediu. - Acreditări hard-codate în .tfs: Folosiți întotdeauna variabile de mediu, OIDC sau Vault. Niciodată chei AWS în fișierele Terraform.
Concluzii și pașii următori
Modelul stratului de abstractizare din Terraform vă permite să construiți infrastructură multi-cloud care poate fi întreținut: diferențele dintre furnizori sunt încapsulate în module specific norului, consumatorii folosesc o interfață uniformă și comutarea în cloud este o modificare a unei variabile, nu o rescriere a infrastructurii.
Cheia este găsirea nivelului potrivit de abstractizare: prea multe diferențe ascunse face forma inutilizabilă, în timp ce o formă care expune prea multe detalii specific norului pierde beneficiul uniformității.
Următoarele articole din serie
- Articolul 8: GitOps pentru Terraform — Controler Flux TF, Spacelift și Drift Detection: Aduce Terraform în paradigma GitOps cu reconciliere continuă din mediul de depozit.
- Articolul 9: Terraform vs Pulumi vs OpenTofu — Comparație Final 2026: Când să alegeți ce instrument IaC în funcție de context.







