Terraform マルチクラウド: 共有モジュールを使用した AWS + Azure + GCP
マルチクラウド環境向けの Terraform アーキテクチャ: 抽象化レイヤー コンピューティング、ネットワーキング、データベース、プロバイダー管理用の統一インターフェイス 複数の構成と環境ごとの構成の分離。
マルチクラウドの事例
大企業の 92% が複数のクラウド プロバイダーを使用しています (Flexera のクラウドの現状) 2025年)。理由はさまざまです: ベンダーロックインの削減、コストの最適化 (ワークロードごとに最も安価なプロバイダーを使用)、コンプライアンス要件 (EU 地域のデータは Azure のみ、AI/ML は GCP、エンタープライズ ワークロードは AWS)、 そして異種インフラストラクチャをもたらす買収。
Terraform はマルチクラウドを管理するための理想的なツールです。ネイティブにサポートします。 同じ HCL 構文を持つすべての主要なクラウドのプロバイダーです。挑戦はそうではありません それは技術的なものであり、アーキテクチャ的なものです。モジュールをどのように構成するか 再利用を最大化する e 複雑さを最小限に抑える 各クラウドに同様のコンセプトの異なる API がある場合。
何を学ぶか
- マルチプロバイダー構成: エイリアス、ワークスペースごとのプロバイダー、モジュールごとのプロバイダー
- 抽象化レイヤー: コンピューティング、ネットワーキング、データベース用の統一インターフェイス モジュール
- クラウド間のセマンティックの違いを管理するパターン (VPC/VNet、インスタンス/VM)
- マルチクラウド チームのリポジトリ構造: モノリポジトリとポリリポジトリ
- マルチクラウドの秘密管理: 単一の真実の情報源としての 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 は似ていますが、同一ではありません。 あまりにも汎用的なフォームでは、重要なクラウド固有の機能が隠れてしまいます。
- すべてのクラウドに対する単一の状態ファイル: クラウドごとに個別の状態ファイル そして環境によっても。すべてを 1 つの状態ファイルに入れると破損のリスクが増加します そして動作が遅くなります。
-
寛容すぎるプロバイダー: 与えないでください
AdministratorAccessTerraform プロバイダーに送信します。各環境に固有の IAM 最小特権ロールを使用します。 - .tfs にハードコードされた資格情報: 常に環境変数を使用してください。 OIDC またはボールト。 Terraform ファイルに AWS キーを含めないでください。
結論と次のステップ
Terraform の抽象化レイヤー パターンを使用すると、インフラストラクチャを構築できます 保守可能なマルチクラウド: プロバイダー間の違いはモジュールにカプセル化されます クラウド固有、消費者は統一インターフェイスを使用し、クラウド スイッチングを行う これは変数の変更であり、インフラストラクチャの書き換えではありません。
重要なのは、適切な抽象化レベルを見つけることです: 隠れた違いが多すぎる フォームを使用できなくする一方で、詳細が多すぎるフォーム クラウド固有の場合、均一性の利点が失われます。
シリーズの次の記事
- 第8条: Terraform 用の GitOps — Flux TF コントローラー、Spacelift およびドリフト検出: 継続的な調整により、Terraform を GitOps パラダイムに導入します。 リポジトリ環境から。
- 第9条: Terraform vs Pulumi vs OpenTofu — 比較 最終 2026 年: コンテキストに基づいてどの IaC ツールを選択するか。







