멀티 클라우드 사례

대기업의 92%가 둘 이상의 클라우드 제공업체를 사용합니다(Flexera State of the Cloud). 2025). 이유는 다양합니다. 벤더 종속성 감소, 비용 최적화 (각 워크로드에 대해 가장 저렴한 공급자 사용), 규정 준수 요구 사항 (Azure의 EU 지역 데이터, GCP의 AI/ML, AWS의 엔터프라이즈 워크로드) 이기종 인프라를 가져오는 인수.

Terraform은 멀티 클라우드 관리에 이상적인 도구입니다. 기본적으로 지원됩니다. 동일한 HCL 구문을 사용하는 모든 주요 클라우드에 대한 공급자입니다. 도전은 아니다 그것은 기술적이고 건축적입니다. 모듈을 구성하는 방법은 다음과 같습니다. 재사용 극대화 e 복잡성을 최소화하다 각 클라우드에 유사한 개념에 대해 서로 다른 API가 있는 경우.

무엇을 배울 것인가

  • 다중 공급자 구성: 별칭, 작업 영역별 ​​공급자, 모듈별 공급자
  • 추상화 계층: 컴퓨팅, 네트워킹, 데이터베이스를 위한 통일된 인터페이스 모듈
  • 클라우드(VPC/VNet, 인스턴스/VM) 간의 의미 차이를 관리하는 패턴
  • 멀티 클라우드 팀을 위한 리포지토리 구조: monorepo와 polyrepo
  • 멀티 클라우드 비밀 관리: 단일 정보 소스로서의 Vault
  • 비용 최적화: 스팟 인스턴스 AWS, 선점형 GCP, 스팟 Azure

별칭을 사용한 다중 공급자 구성

Terraform을 사용하면 동일한 공급자(또는 다른 공급자)의 여러 인스턴스를 사용할 수 있습니다. 같은 형식으로 통해 별명. 이는 다음과 같은 경우에 유용합니다. 동일한 클라우드의 여러 지역이나 계정에 리소스를 배포할 뿐만 아니라 다른 공급자를 구성하십시오.

# 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
}

추상화 계층: 기본 디자인 패턴

멀티 클라우드의 주요 과제는 AWS가 서비스를 EC2라고 부른다는 것입니다. Azure에서는 이를 Virtual Machine 및 GCP Compute Engine이라고 부르지만 개념적으로는 같은 것. 그만큼'추상화 계층 인터페이스로 양식 만들기 이러한 차이점을 숨기는 유니폼.

# 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/

추상화 계층 구현

# 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"
}

멀티 클라우드 데이터베이스 모듈

# 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
}

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
}

애플리케이션의 다중 클라우드 배포

# 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
}

비용 최적화 멀티 클라우드: 스팟/선점형

# 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
}

피해야 할 다중 클라우드 안티 패턴

멀티 클라우드 IaC 아키텍처의 일반적인 실수

  • 추상화가 너무 강요됨: 모든 서비스에 동등한 서비스가 있는 것은 아닙니다. 구름 사이에 직접. AWS SQS, Azure Service Bus 및 GCP Pub/Sub는 유사하지만 동일하지는 않습니다. 너무 일반적인 양식은 중요한 클라우드 관련 기능을 숨깁니다.
  • 모든 클라우드에 대한 단일 상태 파일: 클라우드별로 별도의 상태 파일 그리고 환경에 따라. 모든 것을 단일 상태 파일에 저장하면 손상 위험이 높아집니다. 그리고 작업 속도가 느려집니다.
  • 너무 관대한 공급자: 주지 마세요 AdministratorAccess Terraform 제공업체에 각 환경에 특정한 IAM 최소 권한 역할을 사용합니다.
  • .tfs에 하드 코딩된 자격 증명: 항상 환경 변수를 사용하십시오. OIDC 또는 Vault. Terraform 파일에 AWS 키를 입력하지 마세요.

결론 및 다음 단계

Terraform의 추상화 계층 패턴을 사용하면 인프라를 구축할 수 있습니다. 유지 관리 가능한 멀티 클라우드: 공급자 간의 차이점이 모듈에 캡슐화됩니다. 클라우드 특정, 소비자는 균일한 인터페이스를 사용하고 클라우드 전환 인프라를 다시 작성하는 것이 아니라 변수를 변경하는 것입니다.

핵심은 적절한 추상화 수준을 찾는 것입니다. 숨겨진 차이점이 너무 많습니다. 양식을 사용할 수 없게 만드는 반면 너무 많은 세부 정보를 노출하는 양식 클라우드 관련은 균일성의 이점을 잃습니다.

시리즈의 다음 기사

  • 제8조: Terraform용 GitOps — Flux TF 컨트롤러, Spacelift 드리프트 감지: 지속적인 조정을 통해 Terraform을 GitOps 패러다임으로 가져옵니다. 저장소 환경에서.
  • 제9조: Terraform vs Pulumi vs OpenTofu — 비교 최종 2026: 상황에 따라 어떤 IaC 도구를 선택해야 하는지.