본문 바로가기
IT/Cloud

[Cloud] Terraform으로 AWS/GCP/Azure 멀티 클라우드 환경 구축 가이드

by 수누다 2026. 4. 29.

멀티 클라우드, 왜 지금 이 시점에 Terraform인가요?

솔직히 말씀드리면, 저도 처음엔 멀티 클라우드가 '대기업 얘기'인 줄 알았거든요. AWS 하나만 잘 써도 충분하지 않냐고 생각했는데... 현실은 달랐습니다. 어느 날 갑자기 고객사가 "GCP도 써야 한다"고 했을 때, 그 막막함이란 😅

요즘은 규모에 상관없이 AWS, GCP, Azure를 동시에 다뤄야 하는 상황이 생각보다 자주 옵니다. 벤더 종속(Vendor Lock-in)을 피하려는 전략적 이유도 있고, 각 클라우드의 강점을 조합하려는 경우도 많죠. ML/AI 워크로드는 GCP, 기업 솔루션은 Azure, 메인 인프라는 AWS처럼요.

여기서 Terraform 멀티 클라우드 구성이 빛을 발합니다. 하나의 도구로 세 개의 클라우드를 동시에 관리할 수 있다는 건, 13년 동안 인프라 일을 해온 저한테도 꽤 충격적인 경험이었어요. 이 글에서는 제가 직접 홈랩에서 구성해보고 실무에 적용한 경험을 바탕으로, Terraform으로 AWS/GCP/Azure 멀티 클라우드 환경을 구축하는 방법을 단계별로 알려드릴게요.

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

Terraform 멀티 클라우드가 뭔지 쉽게 풀어볼게요

Terraform은 HashiCorp에서 만든 IaC(Infrastructure as Code) 도구예요. 쉽게 말해, 클라우드 콘솔에서 마우스로 클릭클릭 하던 걸 코드로 대체하는 거죠. 인프라 구성을 코드로 정의하고 관리하는 방식이라고 보면 됩니다.

핵심 개념 몇 가지만 짚고 넘어갈게요.

  • Provider(프로바이더): 각 클라우드 벤더와 통신하는 플러그인. AWS, GCP, Azure 각각 별도 프로바이더가 있어요.
  • Resource(리소스): 실제로 생성할 인프라 구성 요소. EC2 인스턴스, GCS 버킷, Azure VM 같은 것들이죠.
  • State(스테이트): Terraform이 현재 인프라 상태를 추적하는 파일. 멀티 클라우드에서 이게 특히 중요합니다.
  • Module(모듈): 재사용 가능한 Terraform 코드 묶음. 멀티 클라우드 구성에서 필수예요.
  • Workspace(워크스페이스): 동일한 코드베이스로 여러 환경(dev/staging/prod)을 관리하는 방법.

멀티 클라우드 구성에서 핵심은 하나의 Terraform 프로젝트에 여러 프로바이더를 동시에 선언하는 거예요. 처음엔 이게 되나 싶었는데, 실제로 해보니까 생각보다 깔끔하게 동작하더라고요.

단일 클라우드 vs 멀티 클라우드 Terraform 비교

구분 단일 클라우드 멀티 클라우드
Provider 수 1개 2개 이상
State 관리 단일 backend 분리 또는 통합 backend
인증 관리 단순 각 클라우드별 별도 인증 필요
코드 복잡도 낮음 중~높음 (모듈화 필수)
장점 단순, 빠른 구성 벤더 독립성, 최적 서비스 조합

사전 준비: 환경 세팅부터 제대로

본격적인 구성 전에 준비해야 할 것들이 있어요. 저도 이 부분에서 삽질을 좀 했습니다 ㅎㅎ. 특히 인증 부분에서요.

1. Terraform 설치

Terraform은 1.0 버전 이후로 꽤 안정화됐어요. 멀티 클라우드 구성을 처음 하신다면 최신 stable 버전을 사용하시길 권장합니다.

# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# 버전 확인
terraform version

2. 각 클라우드 CLI 설치 및 인증

Terraform이 각 클라우드와 통신하려면 인증 정보가 필요해요. CLI를 통한 인증이 가장 깔끔합니다.

# AWS CLI 인증
aws configure
# AWS Access Key ID, Secret Access Key, Region 입력

# GCP 인증
gcloud auth application-default login
# 브라우저에서 Google 계정 인증

# Azure CLI 인증
az login
# 브라우저에서 Microsoft 계정 인증

💡 : 실무에서는 각 클라우드의 서비스 계정(Service Account) 또는 IAM Role을 사용하는 게 보안상 훨씬 좋아요. 개인 계정 인증은 개발/테스트 환경에서만 쓰세요.

실전 구현: 멀티 클라우드 Terraform 프로젝트 구성

자, 이제 본론입니다. 제가 실제로 구성한 디렉터리 구조부터 보여드릴게요. 처음에 플랫 구조로 했다가 나중에 너무 복잡해져서 모듈 구조로 전면 개편했던 경험이 있어서, 처음부터 제대로 된 구조로 시작하시길 권해드립니다.

프로젝트 디렉터리 구조

multi-cloud-infra/
├── main.tf              # 메인 진입점, 프로바이더 설정
├── variables.tf         # 공통 변수 정의
├── outputs.tf           # 출력값 정의
├── terraform.tfvars     # 실제 변수값 (git에 올리지 마세요!)
├── backend.tf           # State 저장소 설정
└── modules/
    ├── aws/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    ├── gcp/
    │   ├── main.tf
    │   ├── variables.tf
    │   └── outputs.tf
    └── azure/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Step 1: 멀티 프로바이더 선언 (main.tf)

여기가 핵심이에요. 세 개의 프로바이더를 동시에 선언합니다. alias(별칭)를 사용하면 같은 프로바이더를 여러 리전에서 사용할 수도 있어요.

terraform {
  required_version = ">= 1.3.0"

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

# AWS 프로바이더
provider "aws" {
  region = var.aws_region
  # 환경변수 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 사용 권장
}

# GCP 프로바이더
provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
  # GOOGLE_APPLICATION_CREDENTIALS 환경변수 사용 권장
}

# Azure 프로바이더
provider "azurerm" {
  features {}
  subscription_id = var.azure_subscription_id
  # az login으로 인증된 정보 사용
}

Step 2: 변수 정의 (variables.tf)

variable "aws_region" {
  description = "AWS 배포 리전"
  type        = string
  default     = "ap-northeast-2"  # 서울 리전
}

variable "gcp_project_id" {
  description = "GCP 프로젝트 ID"
  type        = string
}

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

variable "azure_subscription_id" {
  description = "Azure 구독 ID"
  type        = string
  sensitive   = true
}

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

variable "project_name" {
  description = "프로젝트명 (리소스 태그에 사용)"
  type        = string
}

▲ 모듈화된 Terraform 프로젝트 구조 — 각 클라우드별 모듈이 독립적으로 관리되는 모습

Step 3: 각 클라우드 모듈 구현

이제 각 클라우드별 리소스를 모듈로 만들어볼게요. 예시로 각 클라우드에 VPC/네트워크와 기본 컴퓨팅 리소스를 만드는 구성입니다.

AWS 모듈 (modules/aws/main.tf)

# AWS VPC 생성
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

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

# 퍼블릭 서브넷
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidr
  availability_zone       = "${var.aws_region}a"
  map_public_ip_on_launch = true

  tags = {
    Name        = "${var.project_name}-public-subnet"
    Environment = var.environment
  }
}

# S3 버킷 (State 저장용 또는 앱 스토리지)
resource "aws_s3_bucket" "app_storage" {
  bucket = "${var.project_name}-${var.environment}-storage"

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

resource "aws_s3_bucket_versioning" "app_storage" {
  bucket = aws_s3_bucket.app_storage.id
  versioning_configuration {
    status = "Enabled"
  }
}

GCP 모듈 (modules/gcp/main.tf)

# GCP VPC 네트워크
resource "google_compute_network" "main" {
  name                    = "${var.project_name}-network"
  auto_create_subnetworks = false
  project                 = var.gcp_project_id
}

# GCP 서브넷
resource "google_compute_subnetwork" "main" {
  name          = "${var.project_name}-subnet"
  ip_cidr_range = var.subnet_cidr
  region        = var.gcp_region
  network       = google_compute_network.main.id
  project       = var.gcp_project_id
}

# GCS 버킷 (Google Cloud Storage)
resource "google_storage_bucket" "app_storage" {
  name          = "${var.project_name}-${var.environment}-gcs"
  location      = "ASIA"
  project       = var.gcp_project_id
  force_destroy = false

  versioning {
    enabled = true
  }

  labels = {
    environment = var.environment
    managed_by  = "terraform"
  }
}

Azure 모듈 (modules/azure/main.tf)

# Azure 리소스 그룹
resource "azurerm_resource_group" "main" {
  name     = "${var.project_name}-${var.environment}-rg"
  location = var.azure_location

  tags = {
    environment = var.environment
    managed_by  = "terraform"
  }
}

# Azure Virtual Network
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

  tags = {
    environment = var.environment
  }
}

# Azure 서브넷
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]
}

# Azure Storage Account
resource "azurerm_storage_account" "main" {
  name                     = "${replace(var.project_name, "-", "")}${var.environment}sa"
  resource_group_name      = azurerm_resource_group.main.name
  location                 = azurerm_resource_group.main.location
  account_tier             = "Standard"
  account_replication_type = "LRS"

  tags = {
    environment = var.environment
  }
}

Step 4: 모듈 호출 (루트 main.tf에 추가)

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

  project_name       = var.project_name
  environment        = var.environment
  aws_region         = var.aws_region
  vpc_cidr           = "10.0.0.0/16"
  public_subnet_cidr = "10.0.1.0/24"
}

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

  project_name    = var.project_name
  environment     = var.environment
  gcp_project_id  = var.gcp_project_id
  gcp_region      = var.gcp_region
  subnet_cidr     = "10.1.0.0/24"
}

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

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

Step 5: State Backend 설정 (backend.tf)

멀티 클라우드에서 State 관리는 정말 중요해요. 팀 작업을 한다면 로컬 state 파일은 절대 안 됩니다. 저는 AWS S3를 State 저장소로, DynamoDB를 락(Lock) 관리에 사용했어요.

terraform {
  backend "s3" {
    bucket         = "your-terraform-state-bucket"
    key            = "multi-cloud/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}

Step 6: 배포 실행

# 초기화 (프로바이더 플러그인 다운로드)
terraform init

# 변경사항 미리보기
terraform plan -var-file="terraform.tfvars"

# 실제 배포
terraform apply -var-file="terraform.tfvars"

# 특정 모듈만 배포하고 싶을 때
terraform apply -target=module.aws_infra

# 리소스 삭제
terraform destroy -var-file="terraform.tfvars"

⚠️ 삽질 기록: 이런 문제들 겪었습니다

제가 처음 멀티 클라우드 Terraform 구성할 때 겪었던 문제들이에요. 여러분은 같은 삽질 안 하셨으면 해서 솔직하게 공유합니다.

문제 1: Provider 버전 충돌

terraform init 할 때 프로바이더 버전 충돌 에러가 났어요. ~> 연산자로 버전 범위를 지정하면 대부분 해결됩니다. 너무 엄격하게 고정하면 나중에 업그레이드할 때 고생해요.

# 이렇게 하면 마이너 버전 업그레이드는 허용
version = "~> 5.0"   # 5.x 버전 허용, 6.0은 안 됨

# 이렇게 하면 패치 버전만 허용
version = "~> 5.1.0" # 5.1.x만 허용

문제 2: Azure Storage Account 이름 규칙

Azure Storage Account 이름은 소문자와 숫자만 되고, 하이픈(-) 사용이 불가예요. 이거 모르고 프로젝트명에 하이픈 넣었다가 에러 폭탄 맞았습니다... replace() 함수로 해결했어요.

# 하이픈 제거
name = "${replace(var.project_name, "-", "")}${var.environment}sa"

문제 3: GCP API 활성화 누락

GCP는 사용하려는 API를 사전에 활성화해야 해요. Terraform으로 리소스 만들려는데 API가 비활성화 상태면 에러가 납니다. 이것도 Terraform으로 관리할 수 있어요.

resource "google_project_service" "compute" {
  project = var.gcp_project_id
  service = "compute.googleapis.com"

  disable_on_destroy = false
}

resource "google_project_service" "storage" {
  project = var.gcp_project_id
  service = "storage.googleapis.com"

  disable_on_destroy = false
}

# 리소스가 API 활성화 후 생성되도록 의존성 설정
resource "google_compute_network" "main" {
  depends_on = [google_project_service.compute]
  # ...
}

문제 4: State 파일 잠금(Lock) 충돌

팀원이랑 동시에 terraform apply를 실행했다가 State Lock 에러가 났어요. DynamoDB 락 테이블을 꼭 설정하세요. 혼자 작업해도 비정상 종료 후 락이 안 풀리는 경우가 있는데, 이때는 terraform force-unlock 명령어로 해결합니다.

# 락 강제 해제 (Lock ID는 에러 메시지에서 확인)
terraform force-unlock LOCK_ID

검증: 제대로 만들어졌는지 확인하기

드디어 됐다! 싶을 때 꼭 확인해야 할 것들이 있어요. terraform apply가 성공했다고 끝이 아니거든요.

# 현재 State 확인
terraform show

# 특정 리소스 상세 확인
terraform state show module.aws_infra.aws_vpc.main

# 모든 리소스 목록 확인
terraform state list

# 출력값 확인
terraform output

각 클라우드 콘솔에서도 직접 확인해보세요. AWS 콘솔에서 VPC가 생겼는지, GCP 콘솔에서 네트워크가 보이는지, Azure 포털에서 리소스 그룹이 만들어졌는지 확인하는 거예요. 코드만 믿지 말고 눈으로 직접 확인하는 습관이 정말 중요합니다.

▲ terraform apply 완료 후 각 클라우드 콘솔에서 리소스가 정상 생성된 결과 확인 화면

Outputs으로 중요 정보 추출하기

# outputs.tf
output "aws_vpc_id" {
  description = "생성된 AWS VPC ID"
  value       = module.aws_infra.vpc_id
}

output "gcp_network_name" {
  description = "생성된 GCP 네트워크 이름"
  value       = module.gcp_infra.network_name
}

output "azure_resource_group" {
  description = "생성된 Azure 리소스 그룹 이름"
  value       = module.azure_infra.resource_group_name
}

💡 실무에서 쓰는 Best Practice 몇 가지

13년 동안 인프라 일 하면서 쌓인 노하우를 좀 더 공유할게요.

  • terraform.tfvars는 절대 Git에 올리지 마세요. .gitignore에 꼭 추가하고, 민감 정보는 환경변수나 Vault로 관리하세요.
  • 환경별 tfvars 파일을 분리하세요. dev.tfvars, staging.tfvars, prod.tfvars처럼요. -var-file 옵션으로 지정해서 사용합니다.
  • Terraform Cloud 또는 Atlantis 도입을 고려하세요. 팀 규모가 커지면 PR 기반 Terraform 실행 환경이 필요해집니다.
  • 모든 리소스에 태그/라벨을 달아두세요. 멀티 클라우드에서 비용 관리하려면 태그 전략이 필수예요. ManagedBy = terraform, Environment = dev 같은 공통 태그부터 시작하세요.
  • Plan 결과는 팀원과 공유하세요. terraform plan -out=plan.tfplan으로 저장하고, terraform show plan.tfplan으로 리뷰받는 프로세스를 만들면 좋아요.

▲ Terraform 멀티 클라우드 운영을 위한 Best Practice 요약 — 보안, 협업, 비용 관리 핵심 포인트

자주 묻는 질문 (FAQ)

Q. Terraform과 Pulumi 중 어떤 걸 써야 하나요?

Pulumi는 일반 프로그래밍 언어(Python, TypeScript 등)로 인프라를 정의할 수 있어서 개발자 친화적이에요. 반면 Terraform은 HCL이라는 전용 언어를 쓰지만 생태계가 훨씬 크고, 커뮤니티 모듈이 풍부하거든요. 처음 IaC를 시작한다면 Terraform을 추천해요.

Q. 멀티 클라우드 구성에서 State를 어디에 저장해야 하나요?

저는 AWS S3 + DynamoDB 조합을 주로 사용합니다. Terraform Cloud를 쓰면 State 관리, 락, 협업 기능을 한 번에 해결할 수 있어요. 팀 규모가 있다면 Terraform Cloud 무료 플랜부터 시작해보세요.

Q. 멀티 클라우드 비용이 너무 많이 나올 것 같아요.

맞아요, 이게 현실적인 고민이죠. 각 클라우드의 프리 티어(Free Tier)를 최대한 활용하고, 개발 환경은 작업이 끝나면 terraform destroy로 날리는 습관을 들이세요. 저도 홈랩에서는 퇴근 전에 꼭 destroy 합니다 😅

마무리: 멀티 클라우드는 복잡하지만, Terraform이 있으면 달라요

처음에 AWS IaC 하나 배우는 것도 버거웠는데, GCP IaC, Azure IaC까지 동시에 다루게 될 줄은 몰랐어요. 근데 Terraform 멀티 클라우드 구성을 제대로 한 번 해놓으면, 그다음부터는 정말 편하더라고요. 새로운 리소스 추가할 때 코드 몇 줄 추가하고 apply만 하면 되거든요.

이 글에서 다룬 내용을 정리하면:

  1. Terraform에서 여러 Provider를 동시에 선언해 멀티 클라우드 관리 가능
  2. 모듈(Module) 구조로 각 클라우드 코드를 분리해 유지보수성 향상
  3. State는 반드시 원격 Backend(S3 + DynamoDB 등)에 저장
  4. 인증 정보, 민감 변수는 절대 코드에 하드코딩 금지
  5. 태그 전략을 처음부터 설계해야 멀티 클라우드 비용 관리가 됨

다음 글에서는 Terraform과 GitHub Actions를 연동해서 CI/CD 파이프라인을 구축하는 방법을 다룰 예정이에요. Pull Request가 올라오면 자동으로 terraform plan 결과를 코멘트로 달아주는 그 워크플로우요. 꽤 실용적이니까 기대해주세요!

질문이나 다른 삽질 경험 있으시면 댓글로 남겨주세요. 같이 고민해봐요 😊