13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Azure IaC

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

    멀티 클라우드, 왜 지금 이 시점에 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 결과를 코멘트로 달아주는 그 워크플로우요. 꽤 실용적이니까 기대해주세요!

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

  • [Cloud] Terraform 멀티 클라우드 관리 가이드 – AWS/GCP/Azure 한 번에 관리하기

    [Cloud] Terraform 멀티 클라우드 관리 가이드 – AWS/GCP/Azure 한 번에 관리하기

    멀티 클라우드, 이제는 선택이 아니라 현실입니다

    혹시 이런 상황 겪어보신 적 있으신가요? 개발팀에서는 AWS를 쓰고, 데이터 분석팀에서는 GCP BigQuery를 쓰고, 어느 날 갑자기 Azure AD 연동도 필요하다고 하고… 처음엔 각 팀이 알아서 하면 되겠지 싶었는데, 인프라 엔지니어 입장에서 이걸 다 관리하려니 머리가 터질 것 같더라고요.

    저도 딱 그 상황이었습니다. 몇 년 전에 회사에서 AWS만 쓰다가 GCP 프로젝트가 하나 붙고, 또 Azure가 붙고… 각 클라우드마다 콘솔 따로 열고, CLI 따로 설치하고, 권한 관리도 따로 하다 보니 진짜 정신없었거든요. 그때 Terraform으로 멀티 클라우드 환경을 구성하면서 삶이 좀 편해졌습니다 ㅎㅎ

    이 글에서는 제가 직접 구성해서 운영 중인 Terraform으로 AWS, GCP, Azure를 동시에 관리하는 멀티 클라우드 IaC(Infrastructure as Code, 코드로 인프라 관리) 환경을 단계별로 풀어드릴게요. 처음 접하시는 분도 따라오실 수 있게 최대한 친절하게 써볼게요.

    Terraform multi-cloud architecture diagram showing AWS, GCP, and Azure connected through a central Terraform control plane with arrows indicating resource provisioning flow

    ▲ Terraform 단일 코드베이스에서 AWS, GCP, Azure 세 클라우드를 동시에 관리하는 전체 아키텍처 개요도

    Terraform 멀티 클라우드란? 한 번에 여러 클라우드를 관리하는 방법

    Terraform은 HashiCorp에서 만든 오픈소스 IaC 도구입니다. 쉽게 말해, 클라우드 인프라를 코드로 정의하고 실행하면 실제 리소스가 만들어지는 방식이에요. 여러 클라우드를 한 번에 관리할 수 있다는 게 가장 큰 장점입니다.

    여기서 핵심은 Provider(프로바이더)라는 개념입니다. Terraform은 각 클라우드사에서 제공하는 Provider 플러그인을 통해 해당 클라우드와 통신하거든요. AWS Provider, Google Provider, AzureRM Provider를 동시에 선언하면 하나의 Terraform 코드로 여러 클라우드를 한 번에 관리할 수 있어요.

    구분 AWS GCP Azure
    Terraform Provider hashicorp/aws hashicorp/google hashicorp/azurerm
    인증 방식 Access Key / IAM Role Service Account JSON / ADC Service Principal / Managed Identity
    상태 저장소 S3 + DynamoDB GCS Bucket Azure Blob Storage
    주요 IaC 용어 Region, VPC, EC2 Project, VPC, Compute Subscription, VNet, VM

    저도 처음엔 각 클라우드마다 용어가 달라서 헷갈렸는데요, Terraform을 쓰면 이 차이를 Provider가 알아서 추상화해줘서 훨씬 편하더라고요.

    디렉터리 구조 설계 — 이게 제일 중요합니다

    멀티 클라우드 Terraform 프로젝트에서 제가 가장 많이 고민한 부분이 바로 디렉터리 구조였어요. 처음에 그냥 막 만들었다가 나중에 전부 갈아엎은 쓴 경험이 있거든요. 그래서 여러분은 처음부터 제대로 잡으셨으면 해서 제가 실제로 쓰는 구조를 공유합니다.

    multi-cloud-infra/
    ├── providers.tf          # 모든 Provider 선언
    ├── versions.tf           # Terraform 버전 및 Provider 버전 고정
    ├── variables.tf          # 공통 변수
    ├── outputs.tf            # 공통 출력값
    │
    ├── modules/              # 재사용 가능한 모듈
    │   ├── aws/
    │   │   ├── vpc/
    │   │   ├── ec2/
    │   │   └── s3/
    │   ├── gcp/
    │   │   ├── vpc/
    │   │   ├── gce/
    │   │   └── gcs/
    │   └── azure/
    │       ├── vnet/
    │       ├── vm/
    │       └── storage/
    │
    └── environments/         # 환경별 실제 구성
        ├── dev/
        │   ├── main.tf
        │   ├── variables.tf
        │   └── terraform.tfvars
        ├── staging/
        └── prod/
    

    💡 핵심 포인트: 환경(dev/staging/prod)을 디렉터리로 분리하고, 각 클라우드별 모듈을 따로 관리하면 나중에 유지보수할 때 훨씬 편합니다. 처음엔 귀찮아 보여도, 실제로 운영하다 보면 정말 감사하게 되더라고요.

    실전 구현 1단계 — Provider 및 버전 설정

    자, 이제 실제 코드로 들어가 볼게요. 먼저 versions.tf에서 Terraform 버전과 각 Provider 버전을 고정해줍니다. 버전 고정 안 하면 나중에 Provider 업데이트되면서 갑자기 코드가 안 돌아가는 참사가 생기거든요. 저 이거로 한 번 크게 당했습니다 ㅎㅎ

    # versions.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"
        }
      }
    
      # 원격 State 백엔드 — AWS S3 예시
      backend "s3" {
        bucket         = "my-terraform-state-bucket"
        key            = "multi-cloud/terraform.tfstate"
        region         = "ap-northeast-2"
        dynamodb_table = "terraform-state-lock"
        encrypt        = true
      }
    }
    

    다음은 providers.tf입니다. 각 클라우드 인증 정보를 여기서 설정해요.

    # providers.tf
    
    # AWS Provider
    provider "aws" {
      region = var.aws_region
      # 로컬에선 ~/.aws/credentials 사용
      # CI/CD에선 환경변수 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 사용
    }
    
    # GCP Provider
    provider "google" {
      project = var.gcp_project_id
      region  = var.gcp_region
      # GOOGLE_APPLICATION_CREDENTIALS 환경변수로 Service Account JSON 경로 지정
    }
    
    # Azure Provider
    provider "azurerm" {
      features {}
      subscription_id = var.azure_subscription_id
      # ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID 환경변수 사용
    }
    

    ⚠️ 경고: 절대로 Access Key나 Secret Key를 코드에 하드코딩하면 안 됩니다! 환경변수나 Vault 같은 시크릿 관리 도구를 꼭 사용하세요. GitHub에 실수로 올렸다가 과금 폭탄 맞은 사례 정말 많거든요.

    실전 구현 2단계 — 각 클라우드 리소스 모듈 만들기

    이제 각 클라우드별로 기본 네트워크 리소스를 만들어볼게요. 실제로 제가 쓰는 패턴입니다.

    Split-screen diagram showing Terraform code on the left and resulting cloud resources on AWS, GCP, and Azure on the right, with color-coded resource blocks

    ▲ 하나의 Terraform 코드가 각 클라우드 플랫폼의 실제 리소스로 프로비저닝되는 과정을 보여주는 구성 다이어그램

    AWS VPC 모듈로 가상 네트워크 구성하기

    # modules/aws/vpc/main.tf
    resource "aws_vpc" "main" {
      cidr_block           = var.cidr_block
      enable_dns_hostnames = true
      enable_dns_support   = true
    
      tags = merge(var.common_tags, {
        Name = "${var.env}-vpc"
        Cloud = "AWS"
      })
    }
    
    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]
    
      tags = merge(var.common_tags, {
        Name = "${var.env}-public-subnet-${count.index + 1}"
      })
    }
    
    resource "aws_internet_gateway" "main" {
      vpc_id = aws_vpc.main.id
    
      tags = var.common_tags
    }
    

    GCP VPC 모듈로 가상 네트워크 구성하기

    # modules/gcp/vpc/main.tf
    resource "google_compute_network" "main" {
      name                    = "${var.env}-vpc"
      auto_create_subnetworks = false
      project                 = var.project_id
    }
    
    resource "google_compute_subnetwork" "main" {
      name          = "${var.env}-subnet"
      ip_cidr_range = var.cidr_block
      region        = var.region
      network       = google_compute_network.main.id
      project       = var.project_id
    
      private_ip_google_access = true
    }
    

    Azure VNet 모듈로 가상 네트워크 구성하기

    # modules/azure/vnet/main.tf
    resource "azurerm_resource_group" "main" {
      name     = "${var.env}-rg"
      location = var.location
    
      tags = var.common_tags
    }
    
    resource "azurerm_virtual_network" "main" {
      name                = "${var.env}-vnet"
      address_space       = [var.cidr_block]
      location            = azurerm_resource_group.main.location
      resource_group_name = azurerm_resource_group.main.name
    
      tags = var.common_tags
    }
    
    resource "azurerm_subnet" "main" {
      name                 = "${var.env}-subnet"
      resource_group_name  = azurerm_resource_group.main.name
      virtual_network_name = azurerm_virtual_network.main.name
      address_prefixes     = [var.subnet_cidr]
    }
    

    환경별 실제 구성 파일로 모듈 조합하기

    # environments/dev/main.tf
    
    # AWS 네트워크
    module "aws_network" {
      source = "../../modules/aws/vpc"
    
      env                  = "dev"
      cidr_block           = "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"]
      common_tags = {
        Environment = "dev"
        ManagedBy   = "Terraform"
      }
    }
    
    # GCP 네트워크
    module "gcp_network" {
      source = "../../modules/gcp/vpc"
    
      env        = "dev"
      project_id = var.gcp_project_id
      region     = "asia-northeast1"
      cidr_block = "10.1.0.0/20"
    }
    
    # Azure 네트워크
    module "azure_network" {
      source = "../../modules/azure/vnet"
    
      env        = "dev"
      location   = "koreacentral"
      cidr_block = "10.2.0.0/16"
      subnet_cidr = "10.2.1.0/24"
      common_tags = {
        Environment = "dev"
        ManagedBy   = "Terraform"
      }
    }
    

    실전 구현 3단계 — 멀티 클라우드 배포 실행하기

    이제 준비가 끝났습니다. 실제로 배포를 해볼게요. 명령어는 어느 클라우드나 동일합니다. 이게 Terraform의 진짜 강점이에요.

    # 1. Terraform 초기화 (Provider 다운로드)
    terraform init
    
    # 2. 실행 계획 확인 (dry-run)
    terraform plan -out=tfplan
    
    # 3. 실제 배포 (AWS, GCP, Azure 동시에 리소스 생성)
    terraform apply tfplan
    
    # 4. 배포된 리소스 확인
    terraform show
    
    # 5. 특정 출력값 확인
    terraform output
    

    명령어 하나로 AWS VPC, GCP VPC, Azure VNet이 동시에 만들어집니다. 정말 신기하더라고요. 각 클라우드 콘솔에 들어가서 확인해보면 실제로 리소스가 생성되어 있을 거예요.

    멀티 클라우드 운영할 때 꼭 알아야 할 팁

    1. State 파일 관리는 신중하게

    Terraform State 파일은 현재 인프라 상태를 기록한 중요한 파일입니다. 절대로 로컬에만 저장하면 안 됩니다. S3, GCS, Azure Blob Storage 같은 원격 백엔드에 저장하고, DynamoDB나 Blob Storage 잠금 기능으로 동시 접근을 방지하세요. 여러 팀이 동시에 배포하면서 State 파일이 꼬이는 참사를 막아야 거든요.

    2. 환경 분리는 필수

    dev, staging, prod를 완전히 분리된 디렉터리로 관리하세요. 실수로 prod 환경을 destroy하는 일을 막기 위해 terraform destroy는 스크립트로 자동화하지 말고 수동으로만 실행하는 게 좋습니다.

    3. 변수 파일은 .gitignore에 추가

    terraform.tfvars 파일에는 민감한 정보(API 키, 구독 ID 등)가 들어갑니다. 절대로 Git에 커밋하면 안 됩니다. .gitignore에 추가하고, CI/CD 파이프라인에서 환경변수로 주입하세요.

    4. 비용 모니터링을 자동화하세요

    멀티 클라우드는 각 클라우드의 비용이 따로 집계되기 때문에 전체 비용을 파악하기 어렵습니다. Terraform Cloud나 Infracost 같은 도구로 배포 전에 비용을 예측하는 게 좋습니다.

    자주 하는 실수와 해결 방법

    Q: 특정 클라우드 리소스만 삭제하고 싶은데 어떻게 하나요?

    A: terraform destroy -target=module.aws_network 명령으로 특정 모듈만 삭제할 수 있습니다. 다만 이 방법은 State 파일 불일치를 초래할 수 있으니 주의하세요.

    Q: Provider 버전을 업그레이드하면 기존 리소스가 삭제되나요?

    A: 보통은 아닙니다. 다만 terraform plan으로 변경사항을 꼭 확인한 후 업그레이드하세요. 간혹 주요 버전 업그레이드 시 리소스 구조가 바뀔 수 있거든요.

    Q: 기존 클라우드 리소스를 Terraform으로 관리하려면?

    A: terraform import 명령을 사용합니다. 예: terraform import aws_vpc.main vpc-12345678. 기존 리소스를 State 파일에 등록한 후 코드를 작성하면 됩니다.

    마치며: Terraform 멀티 클라우드의 미래

    저는 지난 3년간 Terraform으로 AWS, GCP, Azure를 관리해오면서 정말 많은 이점을 느꼈습니다. 각 클라우드의 콘솔을 오갈 필요 없이 코드 한 곳에서 모든 걸 관리할 수 있다는 게 최고의 장점이에요. 팀 규모가 커져도 일관된 방식으로 인프라를 관리할 수 있고, 재해 복구나 환경 복제도 정말 쉬워집니다.

    처음에는 디렉터리 구조를 잡는 데 시간이 걸릴 수 있지만, 한 번 제대로 설계해놓으면 정말 오랫동안 써먹을 수 있는 자산이 됩니다. 여러분도 이 글을 참고해서 Terraform 멀티 클라우드 환경을 구축해보세요. 분명 개발과 운영이 훨씬 편해질 거예요!