🌐 Terraform 멀티클라우드 전략

문서 목적

실무에서 사용하는 멀티클라우드 패턴과 Terraform으로 구현하는 방법을 제공합니다. AWS, GCP, Azure를 조합하여 각 클라우드의 강점을 활용하는 실전 예제 모음입니다.

📑 목차


1. 왜 멀티클라우드인가?

💡 실무 사례별 이유

🎯 Case 1: 각 클라우드의 특화 서비스 활용

실제 시나리오:
"우리 회사는 AWS에서 메인 인프라를 운영하지만,
데이터 분석은 GCP BigQuery가 훨씬 저렴하고 빠릅니다."

결정:
AWS (Compute, Storage) + GCP (BigQuery, Data Analytics)

클라우드별 강점:

클라우드특화 서비스선택 이유
AWSEC2, Lambda, S3, RDS가장 많은 서비스, 성숙한 생태계
GCPBigQuery, GKE, AI/ML데이터 분석 최강, K8s 네이티브
AzureActive Directory, Office 365엔터프라이즈 통합, MS 생태계

🛡️ Case 2: 재해 복구 (Disaster Recovery)

실제 시나리오:
"2021년 AWS 서울 리전 장애로 서비스가 6시간 중단됐습니다.
이제 백업 리전을 다른 클라우드에 둡니다."

결정:
Primary: AWS (서울)
DR: GCP (도쿄) 또는 Azure (한국 중부)

🔓 Case 3: 벤더 락인 방지

실제 시나리오:
"AWS만 쓰다가 가격이 올라도 선택지가 없었습니다.
이제 워크로드를 다른 클라우드로 옮길 수 있는 구조를 만듭니다."

결정:
컨테이너 기반 (Docker/K8s) + Terraform IaC
→ 언제든 마이그레이션 가능

💰 Case 4: 비용 최적화

실제 시나리오:
"같은 워크로드인데 GCP가 30% 저렴합니다.
하지만 기존 AWS 인프라와 통합이 필요합니다."

결정:
비용 민감 워크로드 → GCP
기존 서비스와 통합 필요 → AWS

2. 멀티클라우드 패턴

📊 Pattern 1: 하이브리드 (Hybrid)

구조:

AWS (메인 인프라)
├── EC2 (웹 서버)
├── RDS (데이터베이스)
└── S3 (파일 저장)
     ↓ 데이터 전송
GCP (데이터 분석)
└── BigQuery (데이터 웨어하우스)

장점:

  • ✅ 각 클라우드의 강점 활용
  • ✅ 비용 최적화

단점:

  • ❌ 복잡도 증가
  • ❌ 네트워크 레이턴시
  • ❌ 데이터 전송 비용

📊 Pattern 2: 재해 복구 (DR)

구조:

Primary: AWS (서울)
├── 실시간 트래픽 100%
└── State: Active

DR: GCP (도쿄)
├── 대기 상태 (Standby)
└── 주기적 데이터 동기화

장애 발생 시:
→ DNS 전환 (Route 53 → Cloud DNS)
→ GCP가 Primary로 승격

장점:

  • ✅ 리전 장애 대응
  • ✅ 벤더 장애 대응

단점:

  • ❌ DR 환경 유지 비용
  • ❌ 데이터 동기화 복잡도

📊 Pattern 3: 멀티 리전 액티브-액티브

구조:

AWS (서울) ←→ GCP (도쿄) ←→ Azure (싱가포르)
     ↓              ↓              ↓
   50% 트래픽    30% 트래픽    20% 트래픽

Global Load Balancer (Cloudflare, Akamai)
→ 지역별 최적 클라우드로 라우팅

장점:

  • ✅ 글로벌 최적 성능
  • ✅ 높은 가용성

단점:

  • ❌ 매우 높은 복잡도
  • ❌ 데이터 일관성 문제

3. AWS + GCP 통합 실전 예제

🎯 시나리오: AWS 인프라 + GCP BigQuery 데이터 분석

아키텍처:

┌─────────────────────────────────────────────────────┐
│                      AWS                            │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│  │ EC2 Web  │───→│   RDS    │───→│ S3 Logs  │     │
│  └──────────┘    └──────────┘    └─────┬────┘     │
│                                         │          │
└─────────────────────────────────────────┼──────────┘
                                          │
                              데이터 전송 (매일 1회)
                                          ↓
┌─────────────────────────────────────────┼──────────┐
│                      GCP                │          │
│  ┌─────────────┐    ┌──────────────────▼───────┐  │
│  │ Data Studio │←───│    BigQuery             │  │
│  │ (대시보드)   │    │  (데이터 분석)           │  │
│  └─────────────┘    └─────────────────────────┘  │
└─────────────────────────────────────────────────────┘

📄 main.tf

# 📊 AWS Provider
provider "aws" {
  region = "ap-northeast-2"
}
 
# 📊 GCP Provider
provider "google" {
  project = "my-gcp-project"
  region  = "asia-northeast3"
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# AWS 리소스
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
# 📊 S3 버킷 (로그 저장)
resource "aws_s3_bucket" "logs" {
  bucket = "my-app-logs-${random_string.suffix.result}"
 
  tags = {
    Purpose = "Application Logs"
    Cloud   = "AWS"
  }
}
 
resource "random_string" "suffix" {
  length  = 8
  special = false
  upper   = false
}
 
# 📊 S3 버킷 라이프사이클 (30일 후 삭제)
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id
 
  rule {
    id     = "delete-old-logs"
    status = "Enabled"
 
    expiration {
      days = 30
    }
  }
}
 
# 📊 IAM User for BigQuery Data Transfer
resource "aws_iam_user" "bigquery_transfer" {
  name = "bigquery-data-transfer"
 
  tags = {
    Purpose = "GCP BigQuery Data Transfer"
  }
}
 
resource "aws_iam_access_key" "bigquery_transfer" {
  user = aws_iam_user.bigquery_transfer.name
}
 
# 📊 S3 Read-Only Policy
resource "aws_iam_user_policy" "bigquery_s3_read" {
  name = "s3-read-only"
  user = aws_iam_user.bigquery_transfer.name
 
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:ListBucket"
        ]
        Resource = [
          aws_s3_bucket.logs.arn,
          "${aws_s3_bucket.logs.arn}/*"
        ]
      }
    ]
  })
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# GCP 리소스
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
# 📊 BigQuery Dataset
resource "google_bigquery_dataset" "logs" {
  dataset_id  = "application_logs"
  location    = "asia-northeast3"
  description = "Application logs from AWS S3"
 
  labels = {
    purpose = "analytics"
    source  = "aws-s3"
  }
}
 
# 📊 BigQuery Table
resource "google_bigquery_table" "access_logs" {
  dataset_id = google_bigquery_dataset.logs.dataset_id
  table_id   = "access_logs"
 
  schema = jsonencode([
    {
      name = "timestamp"
      type = "TIMESTAMP"
      mode = "REQUIRED"
    },
    {
      name = "user_id"
      type = "STRING"
      mode = "NULLABLE"
    },
    {
      name = "action"
      type = "STRING"
      mode = "REQUIRED"
    },
    {
      name = "status_code"
      type = "INTEGER"
      mode = "REQUIRED"
    },
    {
      name = "response_time_ms"
      type = "INTEGER"
      mode = "NULLABLE"
    }
  ])
 
  time_partitioning {
    type  = "DAY"
    field = "timestamp"
  }
 
  labels = {
    source = "aws-s3"
  }
}
 
# 📊 BigQuery Data Transfer (S3 → BigQuery)
resource "google_bigquery_data_transfer_config" "s3_transfer" {
  display_name           = "AWS S3 to BigQuery Transfer"
  location               = "asia-northeast3"
  data_source_id         = "amazon_s3"
  schedule               = "every day 03:00"
  destination_dataset_id = google_bigquery_dataset.logs.dataset_id
 
  params = {
    data_path_template      = "s3://${aws_s3_bucket.logs.bucket}/logs/*"
    destination_table_name  = google_bigquery_table.access_logs.table_id
    file_format             = "JSON"
    max_bad_records         = "100"
    access_key_id           = aws_iam_access_key.bigquery_transfer.id
    secret_access_key       = aws_iam_access_key.bigquery_transfer.secret
  }
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 크로스 클라우드 모니터링
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
# 📊 CloudWatch Metric for S3 Object Count
resource "aws_cloudwatch_metric_alarm" "s3_object_count" {
  alarm_name          = "s3-logs-count"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name         = "NumberOfObjects"
  namespace           = "AWS/S3"
  period              = 86400
  statistic           = "Average"
  threshold           = 10000
 
  dimensions = {
    BucketName  = aws_s3_bucket.logs.bucket
    StorageType = "AllStorageTypes"
  }
 
  alarm_description = "S3 버킷에 로그가 너무 많이 쌓였습니다"
}

📄 outputs.tf

# 📤 AWS 출력
output "aws_s3_bucket_name" {
  description = "AWS S3 버킷 이름"
  value       = aws_s3_bucket.logs.bucket
}
 
output "aws_iam_access_key_id" {
  description = "BigQuery 전송용 IAM Access Key ID"
  value       = aws_iam_access_key.bigquery_transfer.id
}
 
output "aws_iam_secret_access_key" {
  description = "BigQuery 전송용 IAM Secret Key"
  value       = aws_iam_access_key.bigquery_transfer.secret
  sensitive   = true
}
 
# 📤 GCP 출력
output "gcp_bigquery_dataset_id" {
  description = "GCP BigQuery Dataset ID"
  value       = google_bigquery_dataset.logs.dataset_id
}
 
output "gcp_bigquery_table_id" {
  description = "GCP BigQuery Table ID"
  value       = google_bigquery_table.access_logs.table_id
}
 
output "bigquery_query_url" {
  description = "BigQuery 콘솔 쿼리 URL"
  value       = "https://console.cloud.google.com/bigquery?project=${var.gcp_project_id}&d=${google_bigquery_dataset.logs.dataset_id}&t=${google_bigquery_table.access_logs.table_id}"
}

🚀 실행 방법

# 1. 초기화
terraform init
 
# 2. 실행 계획
terraform plan
 
# 3. 배포
terraform apply
 
# 4. AWS S3에 샘플 로그 업로드
echo '{"timestamp":"2025-12-31T10:00:00Z","user_id":"user123","action":"login","status_code":200,"response_time_ms":150}' > sample.json
aws s3 cp sample.json s3://$(terraform output -raw aws_s3_bucket_name)/logs/
 
# 5. 다음날 03:00에 자동으로 BigQuery로 전송됨
# 또는 수동 실행:
# gcloud transfer jobs run JOB_NAME
 
# 6. BigQuery에서 쿼리
# https://console.cloud.google.com/bigquery
# SELECT * FROM application_logs.access_logs LIMIT 100

4. AWS + Azure 통합 실전 예제

🎯 시나리오: AWS 인프라 + Azure Active Directory

아키텍처:

┌────────────────────────────────────────────────────┐
│                    Azure                           │
│  ┌──────────────────────────────────────────────┐  │
│  │   Azure Active Directory (AAD)               │  │
│  │   - 사용자 관리                               │  │
│  │   - SSO (Single Sign-On)                     │  │
│  └────────────────┬─────────────────────────────┘  │
└───────────────────┼────────────────────────────────┘
                    │
              SAML 2.0 / OIDC
                    ↓
┌───────────────────┼────────────────────────────────┐
│                  AWS                    │          │
│  ┌───────────────▼──────────────────┐   │          │
│  │    IAM Identity Provider         │   │          │
│  │    (Azure AD 연동)                │   │          │
│  └───────────────┬──────────────────┘   │          │
│                  │                      │          │
│  ┌───────────────▼──────────────────┐   │          │
│  │    IAM Role                      │   │          │
│  │    (Azure AD 사용자에게 권한 부여) │   │          │
│  └───────────────┬──────────────────┘   │          │
│                  │                      │          │
│  ┌───────────────▼──────────────────┐   │          │
│  │    EC2, S3, RDS 등               │   │          │
│  │    (리소스 접근)                  │   │          │
│  └──────────────────────────────────┘   │          │
└─────────────────────────────────────────┴──────────┘

📄 main.tf

# 📊 AWS Provider
provider "aws" {
  region = "ap-northeast-2"
}
 
# 📊 Azure Provider
provider "azurerm" {
  features {}
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Azure AD 설정
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
# 📊 Azure AD Application
resource "azuread_application" "aws_sso" {
  display_name = "AWS SSO Integration"
 
  web {
    redirect_uris = [
      "https://signin.aws.amazon.com/saml"
    ]
  }
 
  api {
    requested_access_token_version = 2
  }
}
 
# 📊 Service Principal
resource "azuread_service_principal" "aws_sso" {
  application_id = azuread_application.aws_sso.application_id
}
 
# 📊 Azure AD Group for AWS Admin
resource "azuread_group" "aws_admins" {
  display_name     = "AWS Administrators"
  security_enabled = true
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# AWS IAM 설정
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
# 📊 AWS IAM SAML Provider (Azure AD)
resource "aws_iam_saml_provider" "azure_ad" {
  name                   = "AzureAD"
  saml_metadata_document = file("azure-ad-metadata.xml")
}
 
# 📊 IAM Role for Azure AD Users
resource "aws_iam_role" "azure_ad_admin" {
  name = "AzureAD-Admin"
 
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_saml_provider.azure_ad.arn
        }
        Action = "sts:AssumeRoleWithSAML"
        Condition = {
          StringEquals = {
            "SAML:aud" = "https://signin.aws.amazon.com/saml"
          }
        }
      }
    ]
  })
 
  tags = {
    IntegratedWith = "AzureAD"
  }
}
 
# 📊 Admin 권한 부여
resource "aws_iam_role_policy_attachment" "azure_ad_admin" {
  role       = aws_iam_role.azure_ad_admin.name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
 
# 📊 읽기 전용 Role
resource "aws_iam_role" "azure_ad_readonly" {
  name = "AzureAD-ReadOnly"
 
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_saml_provider.azure_ad.arn
        }
        Action = "sts:AssumeRoleWithSAML"
        Condition = {
          StringEquals = {
            "SAML:aud" = "https://signin.aws.amazon.com/saml"
          }
        }
      }
    ]
  })
}
 
resource "aws_iam_role_policy_attachment" "azure_ad_readonly" {
  role       = aws_iam_role.azure_ad_readonly.name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

5. 3개 클라우드 동시 사용 (Tri-Cloud)

🎯 시나리오: 글로벌 재해 복구

아키텍처:

┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│   AWS        │      │    GCP       │      │   Azure      │
│   서울       │      │    도쿄      │      │   싱가포르    │
│              │      │              │      │              │
│ ┌──────────┐ │      │ ┌──────────┐ │      │ ┌──────────┐ │
│ │ EC2 Web  │ │      │ │ GCE Web  │ │      │ │  VM Web  │ │
│ └──────────┘ │      │ └──────────┘ │      │ └──────────┘ │
│ ┌──────────┐ │      │ ┌──────────┐ │      │ ┌──────────┐ │
│ │   RDS    │◄┼──────┼─│Cloud SQL │◄┼──────┼─│Azure SQL │ │
│ └──────────┘ │ 복제 │ └──────────┘ │ 복제 │ └──────────┘ │
└──────┬───────┘      └──────┬───────┘      └──────┬───────┘
       │                     │                     │
       └─────────────────────┴─────────────────────┘
                  Global Load Balancer
                    (Cloudflare)

📄 main.tf

# 📊 Providers
terraform {
  required_version = ">= 1.0"
 
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}
 
provider "aws" {
  region = "ap-northeast-2"  # 서울
}
 
provider "google" {
  project = var.gcp_project_id
  region  = "asia-northeast1"  # 도쿄
}
 
provider "azurerm" {
  features {}
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 공통 변수
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
variable "app_name" {
  default = "global-app"
}
 
variable "container_image" {
  description = "Docker 이미지 (모든 클라우드에서 동일)"
  default     = "nginx:latest"
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# AWS (서울) - Primary
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
module "aws_infrastructure" {
  source = "./modules/aws-web-server"
 
  app_name       = "${var.app_name}-aws"
  instance_count = 2
  instance_type  = "t3.micro"
  region         = "ap-northeast-2"
 
  tags = {
    Cloud    = "AWS"
    Location = "Seoul"
    Role     = "Primary"
  }
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# GCP (도쿄) - Secondary
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
module "gcp_infrastructure" {
  source = "./modules/gcp-web-server"
 
  app_name       = "${var.app_name}-gcp"
  instance_count = 2
  machine_type   = "e2-micro"
  region         = "asia-northeast1"
 
  labels = {
    cloud    = "gcp"
    location = "tokyo"
    role     = "secondary"
  }
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Azure (싱가포르) - Tertiary
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
resource "azurerm_resource_group" "main" {
  name     = "${var.app_name}-rg"
  location = "Southeast Asia"  # 싱가포르
 
  tags = {
    Cloud    = "Azure"
    Location = "Singapore"
    Role     = "Tertiary"
  }
}
 
module "azure_infrastructure" {
  source = "./modules/azure-web-server"
 
  app_name            = "${var.app_name}-azure"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  instance_count      = 2
  vm_size             = "Standard_B1s"
 
  tags = {
    Cloud    = "Azure"
    Location = "Singapore"
    Role     = "Tertiary"
  }
}
 
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# 출력 - 글로벌 엔드포인트
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
output "endpoints" {
  description = "모든 클라우드 엔드포인트"
  value = {
    aws = {
      region      = "Seoul (ap-northeast-2)"
      public_ip   = module.aws_infrastructure.public_ip
      status      = "Primary"
      health_url  = "http://${module.aws_infrastructure.public_ip}/health"
    }
    gcp = {
      region      = "Tokyo (asia-northeast1)"
      public_ip   = module.gcp_infrastructure.public_ip
      status      = "Secondary (DR)"
      health_url  = "http://${module.gcp_infrastructure.public_ip}/health"
    }
    azure = {
      region      = "Singapore (Southeast Asia)"
      public_ip   = module.azure_infrastructure.public_ip
      status      = "Tertiary (DR)"
      health_url  = "http://${module.azure_infrastructure.public_ip}/health"
    }
  }
}
 
output "global_dns_configuration" {
  description = "Cloudflare 등 Global LB 설정 정보"
  value = {
    primary_origin   = module.aws_infrastructure.public_ip
    secondary_origin = module.gcp_infrastructure.public_ip
    tertiary_origin  = module.azure_infrastructure.public_ip
 
    recommended_weights = {
      aws   = 50
      gcp   = 30
      azure = 20
    }
  }
}

📄 modules/aws-web-server/main.tf (모듈 예시)

variable "app_name" {}
variable "instance_count" {}
variable "instance_type" {}
variable "region" {}
variable "tags" {}
 
# 📊 Security Group
resource "aws_security_group" "web" {
  name        = "${var.app_name}-sg"
  description = "Allow HTTP and HTTPS"
 
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  tags = var.tags
}
 
# 📊 EC2 Instances
resource "aws_instance" "web" {
  count         = var.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
 
  vpc_security_group_ids = [aws_security_group.web.id]
 
  user_data = <<-EOF
              #!/bin/bash
              apt-get update
              apt-get install -y nginx
              CLOUD="AWS"
              REGION="${var.region}"
              INSTANCE_ID=$(ec2-metadata --instance-id | cut -d " " -f 2)
 
              cat > /var/www/html/index.html <<HTML
              <!DOCTYPE html>
              <html>
              <head><title>Multi-Cloud App</title></head>
              <body>
                <h1>Hello from $CLOUD</h1>
                <p>Region: $REGION</p>
                <p>Instance: $INSTANCE_ID</p>
              </body>
              </html>
              HTML
 
              systemctl start nginx
              EOF
 
  tags = merge(var.tags, {
    Name = "${var.app_name}-${count.index}"
  })
}
 
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
 
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}
 
# 📊 Elastic IP
resource "aws_eip" "web" {
  count    = 1
  instance = aws_instance.web[0].id
  domain   = "vpc"
 
  tags = var.tags
}
 
output "public_ip" {
  value = aws_eip.web[0].public_ip
}

6. 네트워크 연결

🌉 클라우드 간 VPN 연결

AWS ↔ GCP VPN

# 📊 AWS Customer Gateway
resource "aws_customer_gateway" "gcp" {
  bgp_asn    = 65000
  ip_address = google_compute_address.vpn_gateway.address
  type       = "ipsec.1"
 
  tags = {
    Name = "GCP-VPN-Gateway"
  }
}
 
# 📊 AWS VPN Gateway
resource "aws_vpn_gateway" "main" {
  vpc_id = aws_vpc.main.id
 
  tags = {
    Name = "AWS-VPN-Gateway"
  }
}
 
# 📊 AWS VPN Connection
resource "aws_vpn_connection" "gcp" {
  vpn_gateway_id      = aws_vpn_gateway.main.id
  customer_gateway_id = aws_customer_gateway.gcp.id
  type                = "ipsec.1"
  static_routes_only  = false
 
  tags = {
    Name = "AWS-to-GCP-VPN"
  }
}
 
# 📊 GCP VPN Gateway
resource "google_compute_vpn_gateway" "aws" {
  name    = "aws-vpn-gateway"
  network = google_compute_network.main.id
  region  = "asia-northeast3"
}
 
# 📊 GCP External IP for VPN
resource "google_compute_address" "vpn_gateway" {
  name   = "vpn-gateway-ip"
  region = "asia-northeast3"
}
 
# 📊 GCP VPN Tunnel
resource "google_compute_vpn_tunnel" "aws" {
  name          = "aws-vpn-tunnel"
  peer_ip       = aws_vpn_connection.gcp.tunnel1_address
  shared_secret = aws_vpn_connection.gcp.tunnel1_preshared_key
 
  target_vpn_gateway = google_compute_vpn_gateway.aws.id
 
  local_traffic_selector  = ["10.0.0.0/16"]
  remote_traffic_selector = ["172.16.0.0/16"]
}

7. 주의사항 및 트레이드오프

⚠️ 복잡도 증가

항목단일 클라우드멀티 클라우드
학습 곡선1개 클라우드 학습2-3개 클라우드 학습
관리 포인트1개 콘솔3개 콘솔
비용 추적간단복잡 (통합 필요)
네트워크단순VPN/Peering 필요
보안1개 정책3개 정책 통합

💰 비용 함정

멀티클라우드 숨은 비용:
1. 데이터 전송 비용 (Egress)
   - AWS → GCP: $0.09/GB
   - 월 1TB 전송 시: $92.16

2. VPN 연결 비용
   - AWS VPN: $0.05/시간 = $36/월
   - GCP VPN: $0.05/시간 = $36/월
   - 총: $72/월

3. 관리 시간 비용
   - 엔지니어 학습 시간: 주 5시간
   - 3개 클라우드 모니터링: 주 3시간

🔒 보안 고려사항

# 📊 네트워크 격리
# AWS VPC: 10.0.0.0/16
# GCP VPC: 10.1.0.0/16
# Azure VNet: 10.2.0.0/16
 
# 📊 CIDR 충돌 방지 필수!
 
# 📊 통합 인증/인가
# - Azure AD (Primary IdP)
# - AWS IAM (Federated)
# - GCP IAM (Federated)
 
# 📊 암호화
# - 전송 중: VPN/TLS
# - 저장: KMS (각 클라우드별)

📊 데이터 주권 (Data Sovereignty)

규제 고려사항:
1. GDPR (유럽): 유럽 리전 필수
2. 개인정보보호법 (한국): 국내 리전 권장
3. 중국: 중국 내 리전 필수

멀티클라우드 시:
- 데이터 위치 명확히 추적
- 리전 간 복제 규제 확인
- 로그/감사 통합

💡 베스트 프랙티스

✅ Do

  1. 컨테이너 사용: Docker/K8s로 클라우드 간 이동성 확보
  2. 모듈화: 클라우드별 모듈 분리
  3. 통합 모니터링: Datadog, New Relic 등 통합 도구
  4. IaC 필수: Terraform으로 모든 인프라 코드화
  5. 비용 알람: 각 클라우드별 예산 설정

❌ Don’t

  1. 과도한 복잡도: 필요 없으면 단일 클라우드 사용
  2. 수동 관리: 콘솔 클릭 금지, 모두 코드로
  3. CIDR 충돌: 네트워크 계획 없이 시작 금지
  4. 보안 간과: 각 클라우드별 보안 정책 필수
  5. 비용 무시: 데이터 전송 비용 사전 계산

🎯 의사결정 가이드

”멀티클라우드를 해야 할까?”

✅ YES (멀티클라우드 추천):
- 글로벌 서비스 (저지연 필요)
- 고가용성 필수 (99.99% 이상)
- 각 클라우드 특화 서비스 필요 (BigQuery, Azure AD 등)
- 벤더 락인 우려

❌ NO (단일 클라우드 권장):
- 스타트업 초기 (리소스 부족)
- 팀 규모 5명 이하
- 국내만 서비스
- 복잡도 관리 어려움

📚 참고 자료


Created: 2025-12-31 Tags: terraform multi-cloud aws gcp azure hybrid Category: Terraform/멀티클라우드