본문 바로가기
IT/Cloud

[Cloud] Terraform 멀티 클라우드 IaC 자동화 가이드 — AWS/GCP/Azure 통합 관리

by 수누다 2026. 4. 21.

멀티 클라우드, 왜 이렇게 복잡한 걸까요?

솔직히 말씀드리면, 저도 처음 멀티 클라우드 환경을 맡았을 때 머리가 좀 아팠습니다. AWS 콘솔 따로, GCP 콘솔 따로, Azure 포털 따로... 각각 로그인하고, 각각 다른 방식으로 리소스 만들고, 나중에 뭘 어디에 만들었는지 파악도 안 되는 그 상황 말이에요. 혹시 이런 경험 있으신가요?

13년 동안 인프라 엔지니어로 일하면서 클라우드 환경이 단일 벤더에서 멀티 클라우드로 넘어가는 걸 직접 겪었는데요. 이게 비즈니스 연속성 확보나 벤더 종속(Vendor Lock-in) 방지 측면에서는 분명히 좋은 전략이에요. 근데 관리가 지옥이 되기 시작하거든요.

그 해결책이 바로 Terraform 멀티 클라우드 IaC 자동화입니다. 오늘은 Terraform을 이용해서 AWS, GCP, Azure를 하나의 코드베이스로 관리하는 방법을 실제 경험 기반으로 풀어드릴게요.

Terraform 멀티 클라우드 아키텍처 다이어그램 — AWS, GCP, Azure 동시 관리

▲ Terraform 하나로 AWS, GCP, Azure를 동시에 관리하는 멀티 클라우드 아키텍처 개요

Terraform IaC가 뭔지 먼저 짚고 넘어가요

IaC(Infrastructure as Code, 코드로 인프라를 정의하는 방식)는 이름 그대로 서버, 네트워크, 데이터베이스 같은 인프라를 코드 파일로 정의하고 버전 관리하는 방식입니다. 쉽게 말해, 클릭클릭으로 콘솔에서 만들던 걸 코드로 적어두는 거예요.

Terraform은 HashiCorp가 만든 오픈소스 IaC 도구인데요. 가장 큰 장점이 프로바이더(Provider) 개념입니다. AWS용 프로바이더, GCP용 프로바이더, Azure용 프로바이더를 각각 선언하면, 하나의 Terraform 코드베이스에서 세 클라우드를 동시에 다룰 수 있어요. 이게 진짜 강력한 포인트예요.

비교 항목 콘솔 수동 관리 Terraform IaC 자동화
반복 작업 매번 클릭 필요 코드 한 번 작성 후 재사용
버전 관리 불가능 (변경 이력 추적 어려움) Git으로 완전한 이력 관리
멀티 클라우드 각 콘솔 별도 접근 필요 단일 코드베이스로 통합 관리
팀 협업 "누가 뭘 만들었는지" 파악 어려움 코드 리뷰로 변경 사항 투명 공유
재현성 동일 환경 재현 어려움 동일 코드로 동일 환경 재현 보장

프로젝트 구조 잡기 — 이게 제일 중요합니다

처음 멀티 클라우드 Terraform을 짤 때 가장 많이 실수하는 게 디렉토리 구조거든요. 저도 처음엔 파일 다 때려넣고 나중에 엉망이 돼서 처음부터 다시 짠 적이 있습니다. 클라우드별로 분리하고, 환경(dev/prod)별로도 분리하는 구조를 추천드려요.

multi-cloud-infra/
├── modules/                    # 재사용 가능한 모듈 모음
│   ├── aws/
│   │   ├── vpc/
│   │   │   ├── main.tf
│   │   │   ├── variables.tf
│   │   │   └── outputs.tf
│   │   └── ec2/
│   │       ├── main.tf
│   │       ├── variables.tf
│   │       └── outputs.tf
│   ├── gcp/
│   │   ├── vpc/
│   │   └── compute/
│   └── azure/
│       ├── vnet/
│       └── vm/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       ├── variables.tf
│       └── terraform.tfvars
└── backend.tf                  # 원격 상태 저장소 설정

💡 : modules 디렉토리에 클라우드별로 공통 모듈을 만들어두면, 나중에 환경을 추가할 때 모듈만 호출하면 되니까 진짜 편해요. 처음 구조 잡는 데 시간 좀 써도 나중에 열 배로 돌아옵니다.

실전 구현 — Terraform 멀티 클라우드 프로바이더 설정부터

자, 이제 실제로 코드를 작성해볼게요. 먼저 세 클라우드의 프로바이더를 한 파일에 선언하는 것부터 시작합니다.

1단계: 프로바이더(Provider) 설정

# providers.tf

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }

  # 원격 백엔드 (Remote Backend) — 팀 협업 시 필수!
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "multi-cloud/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"  # 상태 잠금(State Locking)용
  }
}

# AWS 프로바이더
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      ManagedBy   = "Terraform"
      Environment = var.environment
      Project     = var.project_name
    }
  }
}

# GCP 프로바이더
provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
}

# Azure 프로바이더
provider "azurerm" {
  features {}
  subscription_id = var.azure_subscription_id
}

여기서 중요한 포인트! 원격 백엔드(Remote Backend)는 팀으로 작업할 때 절대 빠지면 안 됩니다. 상태 파일(State File)을 로컬에 두면 팀원끼리 충돌이 생기거든요. 저도 초반에 이걸 몰라서 상태 파일 날려먹은 적이 있습니다.

2단계: 변수 파일 설정

# variables.tf

variable "environment" {
  description = "배포 환경 (dev, staging, prod)"
  type        = string
  default     = "dev"
}

variable "project_name" {
  description = "프로젝트 이름"
  type        = string
}

# AWS 관련 변수
variable "aws_region" {
  description = "AWS 리전"
  type        = string
  default     = "ap-northeast-2"  # 서울 리전
}

# GCP 관련 변수
variable "gcp_project_id" {
  description = "GCP 프로젝트 ID"
  type        = string
}

variable "gcp_region" {
  description = "GCP 리전"
  type        = string
  default     = "asia-northeast3"  # 서울 리전
}

# Azure 관련 변수
variable "azure_subscription_id" {
  description = "Azure 구독 ID"
  type        = string
  sensitive   = true  # 민감 정보 마스킹
}

variable "azure_location" {
  description = "Azure 지역"
  type        = string
  default     = "Korea Central"
}
Terraform 멀티 클라우드 프로바이더 설정 코드 — AWS GCP Azure 동시 구성

▲ 세 클라우드의 프로바이더를 하나의 코드베이스에서 관리하는 실제 구성 예시

3단계: AWS VPC 모듈 작성

# modules/aws/vpc/main.tf

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.project_name}-${var.environment}-vpc"
  }
}

resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-public-subnet-${count.index + 1}"
    Type = "Public"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-igw"
  }
}

4단계: GCP VPC 네트워크 모듈

# modules/gcp/vpc/main.tf

resource "google_compute_network" "main" {
  name                    = "${var.project_name}-${var.environment}-vpc"
  auto_create_subnetworks = false  # 커스텀 서브넷 사용
  project                 = var.project_id
}

resource "google_compute_subnetwork" "main" {
  name          = "${var.project_name}-subnet"
  ip_cidr_range = var.subnet_cidr
  region        = var.region
  network       = google_compute_network.main.id
  project       = var.project_id

  # Private Google Access — GCP 내부 서비스 접근용
  private_ip_google_access = true
}

# 방화벽 규칙 (Firewall Rule)
resource "google_compute_firewall" "allow_internal" {
  name    = "${var.project_name}-allow-internal"
  network = google_compute_network.main.name
  project = var.project_id

  allow {
    protocol = "tcp"
    ports    = ["0-65535"]
  }

  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }

  allow {
    protocol = "icmp"
  }

  source_ranges = [var.subnet_cidr]
}

5단계: Azure VNet 모듈

# modules/azure/vnet/main.tf

resource "azurerm_resource_group" "main" {
  name     = "${var.project_name}-${var.environment}-rg"
  location = var.location

  tags = {
    ManagedBy   = "Terraform"
    Environment = var.environment
  }
}

resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-vnet"
  address_space       = [var.vnet_cidr]
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_subnet" "main" {
  name                 = "${var.project_name}-subnet"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = [var.subnet_cidr]
}

# NSG (Network Security Group, 네트워크 보안 그룹)
resource "azurerm_network_security_group" "main" {
  name                = "${var.project_name}-nsg"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
}

6단계: 환경별 메인 파일에서 모듈 호출

# environments/dev/main.tf

# AWS 모듈 호출
module "aws_vpc" {
  source = "../../modules/aws/vpc"

  project_name         = var.project_name
  environment          = var.environment
  vpc_cidr             = "10.0.0.0/16"
  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
  availability_zones   = ["ap-northeast-2a", "ap-northeast-2c"]
}

# GCP 모듈 호출
module "gcp_vpc" {
  source = "../../modules/gcp/vpc"

  project_name = var.project_name
  environment  = var.environment
  project_id   = var.gcp_project_id
  region       = var.gcp_region
  subnet_cidr  = "10.1.0.0/16"
}

# Azure 모듈 호출
module "azure_vnet" {
  source = "../../modules/azure/vnet"

  project_name = var.project_name
  environment  = var.environment
  location     = var.azure_location
  vnet_cidr    = "10.2.0.0/16"
  subnet_cidr  = "10.2.1.0/24"
}

⚠️ 삽질 포인트 — 이것만 조심하세요

제가 멀티 클라우드 Terraform 구축하면서 진짜 고생했던 부분들을 공유드릴게요. 여러분은 같은 실수 안 하셨으면 해서요.

문제 1: 인증 정보 관리

각 클라우드마다 인증 방식이 달라서 처음엔 환경변수를 어디다 어떻게 설정해야 하는지 헷갈렸습니다. 절대 코드에 크리덴셜(Credential, 인증 정보)을 하드코딩하지 마세요!

# AWS 인증 — AWS CLI 프로파일 사용 권장
export AWS_PROFILE=my-profile
# 또는 환경변수로
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"

# GCP 인증 — 서비스 계정 키 파일 사용
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
# 또는 gcloud CLI 인증
gcloud auth application-default login

# Azure 인증 — Service Principal 사용
export ARM_CLIENT_ID="your-client-id"
export ARM_CLIENT_SECRET="your-client-secret"
export ARM_TENANT_ID="your-tenant-id"
export ARM_SUBSCRIPTION_ID="your-subscription-id"

💡 : CI/CD 파이프라인에서는 각 클라우드의 OIDC(OpenID Connect) 방식 인증을 사용하면 시크릿 관리가 훨씬 깔끔해집니다. GitHub Actions랑 연동하면 특히 좋아요.

문제 2: 상태 파일(State File) 충돌

팀원이 동시에 terraform apply 돌리면 상태 파일이 꼬입니다. 이게 진짜 무서운 상황이에요. DynamoDB 테이블로 상태 잠금(State Locking)을 반드시 설정하세요.

# 상태 잠금용 DynamoDB 테이블 생성
resource "aws_dynamodb_table" "terraform_state_lock" {
  name           = "terraform-state-lock"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  tags = {
    Name = "Terraform State Lock Table"
  }
}

문제 3: 프로바이더 버전 충돌

이거 진짜 골치 아팠는데요. required_providers에 버전 범위를 명확히 지정하지 않으면 팀원마다 다른 버전이 설치돼서 동작이 달라지는 일이 생겨요. 반드시 .terraform.lock.hcl 파일을 Git에 커밋하세요. 이게 npm의 package-lock.json 같은 역할을 합니다.

# 프로바이더 초기화 및 잠금 파일 생성
terraform init

# 잠금 파일 확인
cat .terraform.lock.hcl

# 잠금 파일을 Git에 커밋
git add .terraform.lock.hcl
git commit -m "chore: update terraform provider lock file"

배포 및 결과 검증

드디어 실제 배포 단계입니다! Terraform의 기본 워크플로우는 init → plan → apply 순서로 진행됩니다.

# 1. 초기화 (프로바이더 다운로드)
terraform init

# 2. 플랜 확인 — 실제로 뭐가 만들어질지 미리 보기
terraform plan -var-file="terraform.tfvars" -out=tfplan

# 3. 플랜 파일 내용 사람이 읽을 수 있게 출력
terraform show -json tfplan | jq '.'

# 4. 실제 적용!
terraform apply tfplan

# 5. 결과 확인
terraform output

# 6. 상태 파일에서 특정 리소스 확인
terraform state list
terraform state show module.aws_vpc.aws_vpc.main

🎉 apply가 성공하면 이런 출력이 나옵니다:

Apply complete! Resources: 15 added, 0 changed, 0 destroyed.

Outputs:

aws_vpc_id = "vpc-0a1b2c3d4e5f67890"
aws_public_subnet_ids = [
  "subnet-0a1b2c3d4e5f67891",
  "subnet-0a1b2c3d4e5f67892",
]
gcp_network_id = "projects/my-project/global/networks/myproject-dev-vpc"
azure_vnet_id = "/subscriptions/.../resourceGroups/myproject-dev-rg/providers/Microsoft.Network/virtualNetworks/myproject-vnet"
Terraform apply 성공 결과 화면 — 멀티 클라우드 리소스 동시 프로비저닝 완료

▲ terraform apply 성공 시 세 클라우드에 동시 프로비저닝된 결과 화면

Terraform 멀티 클라우드 자동화 — 한 단계 더 나아가기

여기까지 기본 구조를 잡았다면, 이제 실제 팀 환경에서 쓸 수 있는 수준으로 올려야죠. 제가 실제로 도입해서 효과 본 것들을 공유드릴게요.

Terragrunt로 반복 코드 줄이기

Terragrunt는 Terraform의 래퍼(Wrapper) 도구인데요. 환경별로 반복되는 backend 설정이나 공통 변수를 DRY(Don't Repeat Yourself, 반복하지 않기) 원칙에 맞게 관리할 수 있게 해줍니다. 규모가 커지면 진짜 필요해져요.

CI/CD 파이프라인 연동

GitHub Actions나 GitLab CI와 연동해서 PR(Pull Request) 올릴 때 자동으로 terraform plan 결과를 코멘트로 달아주는 워크플로우를 구성하면, 코드 리뷰 단계에서 인프라 변경 사항을 팀 전체가 확인할 수 있어요. 이거 도입하고 나서 팀 내 사고가 확 줄었습니다.

# .github/workflows/terraform.yml
name: Terraform CI/CD

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  terraform:
    name: Terraform Plan & Apply
    runs-on: ubuntu-latest
    
    permissions:
      id-token: write   # OIDC 인증용
      contents: read
      pull-requests: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.6.0"

      - name: Configure AWS Credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: ap-northeast-2

      - name: Terraform Init
        run: terraform init
        working-directory: environments/dev

      - name: Terraform Plan
        id: plan
        run: terraform plan -no-color
        working-directory: environments/dev

      - name: Comment PR with Plan
        uses: actions/github-script@v7
        if: github.event_name == 'pull_request'
        with:
          script: |
            const output = `#### Terraform Plan 결과 🗺️
            \`\`\`\n${{ steps.plan.outputs.stdout }}\n\`\`\`
            `;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

      - name: Terraform Apply
        if: github.ref == 'refs/heads/main'
        run: terraform apply -auto-approve
        working-directory: environments/dev
Terraform 멀티 클라우드 IaC 자동화 베스트 프랙티스 — 코드에서 배포까지 워크플로우 요약

▲ Terraform 멀티 클라우드 IaC 자동화 베스트 프랙티스 요약 — 코드부터 배포까지의 전체 워크플로우

자주 묻는 질문 (FAQ)

Q. Terraform Cloud를 써야 하나요, 아니면 자체 백엔드로 충분한가요?

소규모 팀이라면 S3 + DynamoDB 조합의 자체 백엔드로 충분합니다. 팀이 커지거나 RBAC(역할 기반 접근 제어), 정책 관리가 필요해지면 HCP Terraform(구 Terraform Cloud) 유료 플랜을 고려해볼 만해요.

Q. 각 클라우드 인증 정보를 어떻게 안전하게 관리하나요?

CI/CD 환경에서는 각 클라우드의 OIDC 연동을 추천드립니다. 로컬 개발 환경에서는 각 클라우드 CLI 도구의 프로파일 기능을 활용하고, 절대 코드나 tfvars 파일에 시크릿을 하드코딩하지 마세요.

Q. 멀티 클라우드에서 네트워크를 연결하려면 어떻게 하나요?

VPN Gateway나 클라우드 간 피어링 서비스를 사용해야 하는데, 이 부분은 별도 글에서 자세히 다룰 예정이에요. 각 클라우드의 VPN 리소스도 Terraform으로 코드화할 수 있습니다.

마무리 — Terraform 멀티 클라우드, 이제 시작해보세요

처음 멀티 클라우드 Terraform 구조를 잡을 때는 진짜 막막했는데, 이제 돌아보면 이게 없던 시절로 돌아가기 싫을 만큼 편해졌습니다. 코드로 모든 인프라가 관리되니까 감사 추적(Audit Trail)도 되고, 실수로 뭔가 지워도 코드로 복구할 수 있고, 팀 온보딩도 훨씬 수월해졌어요.

오늘 다룬 내용을 정리하면:

  • 프로젝트 구조: 클라우드별, 환경별 디렉토리 분리
  • 프로바이더 설정: AWS, GCP, Azure를 단일 코드베이스에서 선언
  • 모듈화: 재사용 가능한 모듈로 반복 코드 제거
  • 원격 백엔드: 상태 파일 중앙화 및 잠금 설정
  • CI/CD 연동: GitHub Actions로 자동화 파이프라인 구성

다음 글에서는 Terraform 모듈 레지스트리 활용법과 실제 프로덕션 환경에서 쓰는 보안 설정들을 다뤄볼 예정이에요. 이전 글에서 Kubernetes 클러스터 구성을 다뤘으니 함께 참고하시면 더 좋을 것 같습니다.

궁금한 점이나 다른 삽질 경험 있으시면 댓글로 공유해주세요. 같이 고민해봐요! 🎉