hobokai 님의 블로그

Terraform 완전 마스터 가이드: Infrastructure as Code로 클라우드 인프라 자동화 본문

DevOps

Terraform 완전 마스터 가이드: Infrastructure as Code로 클라우드 인프라 자동화

hobokai 2025. 7. 23. 08:48

목차

  1. Terraform이란 무엇인가?
  2. Terraform 아키텍처와 개념
  3. 설치 및 초기 설정
  4. HCL 언어 기초
  5. Provider와 Resource
  6. 변수와 출력값
  7. 상태 관리(State)
  8. 모듈 시스템
  9. 데이터 소스 활용
  10. 조건문과 반복문
  11. 함수와 표현식
  12. 워크스페이스 관리
  13. 클라우드별 실전 예제
  14. CI/CD 통합
  15. 보안과 베스트 프랙티스
  16. 문제 해결 가이드
  17. 결론

Terraform이란 무엇인가?

Terraform은 HashiCorp에서 개발한 오픈소스 Infrastructure as Code (IaC) 도구입니다. 선언적 구성 파일을 사용하여 클라우드 및 온프레미스 리소스를 정의, 배포, 관리할 수 있습니다.

Terraform의 핵심 가치

  • 🏗️ Infrastructure as Code - 인프라를 코드로 관리
  • 🌍 다중 클라우드 지원 - AWS, Azure, GCP 등 700+ 프로바이더
  • 📋 선언적 구성 - 원하는 최종 상태를 정의
  • 🔄 계획과 적용 - 변경사항을 미리 확인 후 적용
  • 📈 버전 관리 - Git으로 인프라 변경 이력 추적
  • 🤝 팀 협업 - 공유 상태와 잠금 메커니즘

Infrastructure as Code의 장점

전통적 방식 Infrastructure as Code
수동 구성 자동화된 프로비저닝
일회성 작업 재사용 가능한 템플릿
문서 별도 관리 코드가 곧 문서
환경별 차이 일관된 환경 구성
변경 추적 어려움 Git을 통한 버전 관리
롤백 복잡 쉬운 롤백과 복구

Terraform vs 다른 IaC 도구

특성 Terraform CloudFormation ARM Templates Pulumi
다중 클라우드 ❌ (AWS 전용) ❌ (Azure 전용)
언어 HCL JSON/YAML JSON 범용 언어
상태 관리 별도 파일 AWS 관리 Azure 관리 별도 파일
커뮤니티 매우 큰 큰 (AWS) 보통 (Azure) 성장 중
학습 곡선 보통 보통 높음 낮음 (개발자)

Terraform 아키텍처와 개념

핵심 구성 요소

┌─────────────────────────────────────────────────────────────┐
│                    Terraform Core                           │
├─────────────────────────────────────────────────────────────┤
│  Configuration Files (.tf)  │  State File (.tfstate)        │
│  - Resources                 │  - Current Infrastructure     │
│  - Providers                 │  - Resource Mapping           │
│  - Variables                 │  - Metadata                   │
│  - Outputs                   │                               │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ (API 호출)
                              │
┌─────────────────────────────────────────────────────────────┐
│                      Providers                              │
├─────────────────────────────────────────────────────────────┤
│  AWS Provider  │  Azure Provider  │  GCP Provider  │ Others │
└─────────────────────────────────────────────────────────────┘
                              │
                              │
┌─────────────────────────────────────────────────────────────┐
│                   Cloud Infrastructure                      │
├─────────────────────────────────────────────────────────────┤
│   EC2   │   VPC   │   RDS   │   VM   │   Storage  │  ...   │
└─────────────────────────────────────────────────────────────┘

Terraform 워크플로우

  1. Write - 인프라 구성 파일 작성 (.tf)
  2. Plan - 변경 계획 생성 및 검토 (terraform plan)
  3. Apply - 변경사항 실제 적용 (terraform apply)
  4. Manage - 상태 관리 및 업데이트

핵심 개념

  • Provider: 클라우드 서비스와의 인터페이스
  • Resource: 관리할 인프라 구성 요소
  • Data Source: 기존 인프라 정보 조회
  • Variable: 구성 값의 매개화
  • Output: 다른 구성에서 사용할 값 출력
  • Module: 재사용 가능한 구성 단위

설치 및 초기 설정

Terraform 설치

Windows

# Chocolatey 사용
choco install terraform

# Scoop 사용
scoop install terraform

# 수동 설치
# 1. https://www.terraform.io/downloads 에서 다운로드
# 2. PATH에 추가

macOS

# Homebrew 사용
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# 또는 기본 tap 사용
brew install terraform

Linux

# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /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

# CentOS/RHEL
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum install terraform

# 바이너리 직접 설치
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform

설치 확인 및 기본 설정

# 버전 확인
terraform version

# 자동 완성 설치
terraform -install-autocomplete

# 기본 디렉토리 구조 생성
mkdir terraform-project
cd terraform-project

# 첫 번째 구성 파일 생성
touch main.tf variables.tf outputs.tf

IDE 및 에디터 설정

# VS Code 확장
# - HashiCorp Terraform
# - Terraform doc snippets

# Vim 플러그인
# - vim-terraform
# - vim-terraform-completion

# IntelliJ/PyCharm 플러그인
# - Terraform and HCL plugin

HCL 언어 기초

HCL (HashiCorp Configuration Language) 구문

# 기본 블록 구조
block_type "block_label" "block_name" {
  # 블록 내용
  argument_name = argument_value

  # 중첩 블록
  nested_block {
    nested_argument = "value"
  }
}

# 예시: AWS EC2 인스턴스
resource "aws_instance" "web" {
  ami           = "ami-0c02fb55956c7d316"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
}

데이터 타입

# 문자열
variable "region" {
  type    = string
  default = "us-west-2"
}

# 숫자
variable "instance_count" {
  type    = number
  default = 2
}

# 불린
variable "enable_monitoring" {
  type    = bool
  default = true
}

# 리스트
variable "availability_zones" {
  type    = list(string)
  default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}

# 맵
variable "instance_tags" {
  type = map(string)
  default = {
    Environment = "dev"
    Project     = "web-app"
  }
}

# 객체
variable "database_config" {
  type = object({
    engine         = string
    engine_version = string
    instance_class = string
    allocated_storage = number
  })
  default = {
    engine         = "postgres"
    engine_version = "13.7"
    instance_class = "db.t3.micro"
    allocated_storage = 20
  }
}

# 집합 (Set)
variable "security_group_ids" {
  type = set(string)
  default = ["sg-12345678", "sg-87654321"]
}

# 튜플
variable "mixed_list" {
  type = tuple([string, number, bool])
  default = ["example", 42, true]
}

주석과 문서화

# 한 줄 주석

/*
여러 줄
주석
*/

// C 스타일 주석도 지원

# 변수에 설명 추가
variable "instance_type" {
  description = "EC2 인스턴스 타입을 지정합니다"
  type        = string
  default     = "t2.micro"
}

Provider와 Resource

Provider 구성

# 기본 AWS Provider
provider "aws" {
  region = "us-west-2"
}

# 여러 리전을 위한 별칭 Provider
provider "aws" {
  alias  = "east"
  region = "us-east-1"
}

# 프로파일 지정
provider "aws" {
  region  = "us-west-2"
  profile = "production"
}

# 역할 가정 (Role Assumption)
provider "aws" {
  region = "us-west-2"

  assume_role {
    role_arn = "arn:aws:iam::123456789012:role/terraform-role"
  }
}

# Azure Provider
provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
  tenant_id       = var.tenant_id
}

# GCP Provider
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

# 버전 제약
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }

  required_version = ">= 1.0"
}

기본 Resource 사용

# AWS VPC 생성
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "main-vpc"
  }
}

# 서브넷 생성
resource "aws_subnet" "public" {
  count = length(var.availability_zones)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

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

# 인터넷 게이트웨이
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "main-igw"
  }
}

# 라우팅 테이블
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "public-rt"
  }
}

# 라우팅 테이블 연결
resource "aws_route_table_association" "public" {
  count = length(aws_subnet.public)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

리소스 간 의존성

# 명시적 의존성
resource "aws_instance" "web" {
  ami           = "ami-0c02fb55956c7d316"
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.public[0].id

  # 명시적 의존성 선언
  depends_on = [
    aws_internet_gateway.main,
    aws_security_group.web
  ]
}

# 암시적 의존성 (참조를 통해 자동 생성)
resource "aws_security_group" "web" {
  name_prefix = "web-sg"
  vpc_id      = aws_vpc.main.id  # 암시적 의존성

  ingress {
    from_port   = 80
    to_port     = 80
    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"]
  }
}

변수와 출력값

변수 정의

# variables.tf
variable "project_name" {
  description = "프로젝트 이름"
  type        = string
  default     = "my-project"
}

variable "environment" {
  description = "배포 환경"
  type        = string
  validation {
    condition = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "instance_config" {
  description = "EC2 인스턴스 설정"
  type = object({
    type  = string
    count = number
  })
  default = {
    type  = "t2.micro"
    count = 1
  }
}

variable "allowed_cidr_blocks" {
  description = "허용할 CIDR 블록 목록"
  type        = list(string)
  default     = ["0.0.0.0/0"]
}

# 민감한 변수
variable "db_password" {
  description = "데이터베이스 비밀번호"
  type        = string
  sensitive   = true
}

변수 값 전달 방법

# 1. terraform.tfvars 파일
project_name = "web-application"
environment  = "prod"
instance_config = {
  type  = "t3.medium"
  count = 3
}

# 2. 환경별 변수 파일
# dev.tfvars
environment = "dev"
instance_config = {
  type  = "t2.micro"
  count = 1
}

# prod.tfvars
environment = "prod"
instance_config = {
  type  = "t3.large"
  count = 5
}

# 3. 환경 변수 (TF_VAR_prefix)
export TF_VAR_db_password="super-secret"
export TF_VAR_environment="prod"

# 4. 명령줄에서 직접 전달
terraform apply -var="environment=prod" -var="instance_count=3"
terraform apply -var-file="prod.tfvars"

출력값 정의

# outputs.tf
output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "퍼블릭 서브넷 ID 목록"
  value       = aws_subnet.public[*].id
}

output "web_instance_public_ips" {
  description = "웹 서버 퍼블릭 IP 목록"
  value       = aws_instance.web[*].public_ip
}

# 민감한 출력값
output "database_password" {
  description = "데이터베이스 비밀번호"
  value       = var.db_password
  sensitive   = true
}

# 조건부 출력
output "load_balancer_dns" {
  description = "로드 밸런서 DNS 이름"
  value       = var.create_load_balancer ? aws_lb.main[0].dns_name : null
}

# 복합 출력
output "connection_info" {
  description = "연결 정보"
  value = {
    vpc_id    = aws_vpc.main.id
    region    = var.region
    endpoints = {
      web = aws_instance.web[*].public_ip
      db  = aws_db_instance.main.endpoint
    }
  }
}

상태 관리(State)

로컬 상태 파일

# 기본적으로 terraform.tfstate 파일에 저장
ls -la terraform.tfstate*

# 상태 파일 내용 확인
terraform show

# 상태 목록 확인
terraform state list

# 특정 리소스 상태 확인
terraform state show aws_instance.web

원격 상태 저장소

# S3 백엔드 설정
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# Azure Storage 백엔드
terraform {
  backend "azurerm" {
    resource_group_name  = "terraform-state"
    storage_account_name = "terraformstate12345"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }
}

# GCS 백엔드
terraform {
  backend "gcs" {
    bucket = "my-terraform-state"
    prefix = "prod"
  }
}

# Terraform Cloud 백엔드
terraform {
  backend "remote" {
    organization = "my-org"
    workspaces {
      name = "production"
    }
  }
}

상태 잠금 설정

# S3 + DynamoDB 잠금 설정
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "global/s3/terraform.tfstate"
    region         = "us-west-2"
    encrypt        = true

    # DynamoDB 테이블로 상태 잠금
    dynamodb_table = "terraform-locks"
  }
}
# DynamoDB 테이블 생성 (별도 구성)
resource "aws_dynamodb_table" "terraform_locks" {
  name           = "terraform-locks"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"

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

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

상태 관리 명령어

# 상태 초기화 (백엔드 설정 후)
terraform init

# 원격 상태로 마이그레이션
terraform init -migrate-state

# 상태 파일 새로고침
terraform refresh

# 상태에서 리소스 제거 (실제 리소스는 유지)
terraform state rm aws_instance.web

# 리소스를 상태에 추가
terraform import aws_instance.web i-1234567890abcdef0

# 리소스 이름 변경
terraform state mv aws_instance.old_name aws_instance.new_name

# 상태 파일 백업
terraform state pull > backup.tfstate

# 상태 파일 복원
terraform state push backup.tfstate

# 상태 잠금 강제 해제 (주의!)
terraform force-unlock <LOCK_ID>

모듈 시스템

모듈 구조

modules/
├── vpc/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── README.md
├── ec2/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── README.md
└── rds/
    ├── main.tf
    ├── variables.tf
    ├── outputs.tf
    └── README.md

VPC 모듈 예시

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.cidr_block
  enable_dns_hostnames = var.enable_dns_hostnames
  enable_dns_support   = var.enable_dns_support

  tags = merge(var.tags, {
    Name = var.name
  })
}

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 = merge(var.tags, {
    Name = "${var.name}-public-${count.index + 1}"
    Type = "Public"
  })
}

resource "aws_subnet" "private" {
  count = length(var.private_subnet_cidrs)

  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = merge(var.tags, {
    Name = "${var.name}-private-${count.index + 1}"
    Type = "Private"
  })
}

resource "aws_internet_gateway" "main" {
  count = length(var.public_subnet_cidrs) > 0 ? 1 : 0

  vpc_id = aws_vpc.main.id

  tags = merge(var.tags, {
    Name = "${var.name}-igw"
  })
}

resource "aws_nat_gateway" "main" {
  count = var.enable_nat_gateway ? length(var.private_subnet_cidrs) : 0

  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = merge(var.tags, {
    Name = "${var.name}-nat-${count.index + 1}"
  })

  depends_on = [aws_internet_gateway.main]
}

resource "aws_eip" "nat" {
  count = var.enable_nat_gateway ? length(var.private_subnet_cidrs) : 0

  domain = "vpc"

  tags = merge(var.tags, {
    Name = "${var.name}-nat-eip-${count.index + 1}"
  })
}
# modules/vpc/variables.tf
variable "name" {
  description = "VPC 이름"
  type        = string
}

variable "cidr_block" {
  description = "VPC CIDR 블록"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "가용 영역 목록"
  type        = list(string)
}

variable "public_subnet_cidrs" {
  description = "퍼블릭 서브넷 CIDR 목록"
  type        = list(string)
  default     = []
}

variable "private_subnet_cidrs" {
  description = "프라이빗 서브넷 CIDR 목록"
  type        = list(string)
  default     = []
}

variable "enable_dns_hostnames" {
  description = "DNS 호스트네임 활성화"
  type        = bool
  default     = true
}

variable "enable_dns_support" {
  description = "DNS 지원 활성화"
  type        = bool
  default     = true
}

variable "enable_nat_gateway" {
  description = "NAT 게이트웨이 생성 여부"
  type        = bool
  default     = true
}

variable "tags" {
  description = "공통 태그"
  type        = map(string)
  default     = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "vpc_cidr_block" {
  description = "VPC CIDR 블록"
  value       = aws_vpc.main.cidr_block
}

output "public_subnet_ids" {
  description = "퍼블릭 서브넷 ID 목록"
  value       = aws_subnet.public[*].id
}

output "private_subnet_ids" {
  description = "프라이빗 서브넷 ID 목록"
  value       = aws_subnet.private[*].id
}

output "internet_gateway_id" {
  description = "인터넷 게이트웨이 ID"
  value       = length(aws_internet_gateway.main) > 0 ? aws_internet_gateway.main[0].id : null
}

output "nat_gateway_ids" {
  description = "NAT 게이트웨이 ID 목록"
  value       = aws_nat_gateway.main[*].id
}

모듈 사용

# main.tf
module "vpc" {
  source = "./modules/vpc"

  name               = "my-app"
  cidr_block         = "10.0.0.0/16"
  availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]

  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

  enable_nat_gateway = true

  tags = {
    Environment = "production"
    Project     = "web-app"
  }
}

module "web_servers" {
  source = "./modules/ec2"

  name           = "web-server"
  instance_type  = "t3.medium"
  instance_count = 3

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.public_subnet_ids

  tags = {
    Environment = "production"
    Role        = "web"
  }
}

# 모듈 출력값 사용
output "vpc_info" {
  value = {
    vpc_id            = module.vpc.vpc_id
    public_subnets    = module.vpc.public_subnet_ids
    private_subnets   = module.vpc.private_subnet_ids
  }
}

외부 모듈 사용

# Terraform Registry에서 모듈 사용
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-west-2a", "us-west-2b", "us-west-2c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = true

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

# Git 저장소에서 모듈 사용
module "networking" {
  source = "git::https://github.com/company/terraform-modules.git//networking?ref=v1.0.0"

  vpc_cidr = "10.0.0.0/16"
  region   = "us-west-2"
}

데이터 소스 활용

기본 데이터 소스

# 기존 VPC 정보 조회
data "aws_vpc" "default" {
  default = true
}

# AMI 정보 조회
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# 가용 영역 목록 조회
data "aws_availability_zones" "available" {
  state = "available"
}

# 현재 AWS 계정 정보
data "aws_caller_identity" "current" {}

# 현재 AWS 리전 정보
data "aws_region" "current" {}

# 기존 보안 그룹 조회
data "aws_security_group" "web" {
  filter {
    name   = "group-name"
    values = ["web-sg"]
  }
}

# 서브넷 정보 조회
data "aws_subnets" "private" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }

  filter {
    name   = "tag:Type"
    values = ["Private"]
  }
}

# Route 53 Hosted Zone 조회
data "aws_route53_zone" "main" {
  name         = "example.com"
  private_zone = false
}

외부 데이터 소스

# HTTP 데이터 소스
data "http" "my_ip" {
  url = "https://ipv4.icanhazip.com"
}

# 로컬 파일 읽기
data "local_file" "ssh_key" {
  filename = "~/.ssh/id_rsa.pub"
}

# 템플릿 파일
data "template_file" "user_data" {
  template = file("${path.module}/user-data.sh")

  vars = {
    server_name = "web-server"
    environment = var.environment
  }
}

# 외부 명령 실행
data "external" "git_info" {
  program = ["bash", "-c", <<EOT
echo "{\"commit\": \"$(git rev-parse HEAD)\", \"branch\": \"$(git branch --show-current)\"}"
EOT
  ]
}

데이터 소스 활용 예시

# 데이터 소스를 활용한 인스턴스 생성
resource "aws_instance" "web" {
  count = 2

  ami                    = data.aws_ami.ubuntu.id
  instance_type          = "t3.micro"
  vpc_security_group_ids = [data.aws_security_group.web.id]
  subnet_id              = data.aws_subnets.private.ids[count.index]

  user_data = data.template_file.user_data.rendered

  tags = {
    Name = "web-${count.index + 1}"
    AMI  = data.aws_ami.ubuntu.name
    AZ   = data.aws_availability_zones.available.names[count.index]
    Account = data.aws_caller_identity.current.account_id
    Region  = data.aws_region.current.name
    Commit  = jsondecode(data.external.git_info.result).commit
  }
}

# 조건부 리소스 생성
resource "aws_route53_record" "web" {
  count = var.create_dns_record ? 1 : 0

  zone_id = data.aws_route53_zone.main.zone_id
  name    = "web.${data.aws_route53_zone.main.name}"
  type    = "A"
  ttl     = 300
  records = aws_instance.web[*].public_ip
}

조건문과 반복문

조건문 (Conditional Expressions)

# 기본 조건문
variable "create_load_balancer" {
  type    = bool
  default = true
}

resource "aws_lb" "main" {
  count = var.create_load_balancer ? 1 : 0

  name               = "main-lb"
  internal           = false
  load_balancer_type = "application"
  subnets            = var.public_subnet_ids
}

# 복잡한 조건문
locals {
  instance_type = var.environment == "prod" ? "t3.large" : (
    var.environment == "staging" ? "t3.medium" : "t2.micro"
  )

  storage_encrypted = var.environment == "prod" ? true : false

  backup_retention = var.environment == "prod" ? 30 : (
    var.environment == "staging" ? 7 : 1
  )
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.instance_type

  root_block_device {
    volume_type = "gp3"
    volume_size = var.environment == "prod" ? 50 : 20
    encrypted   = local.storage_encrypted
  }
}

# null 값 처리
resource "aws_db_instance" "main" {
  identifier = "myapp-db"

  engine         = "postgres"
  engine_version = var.db_version != null ? var.db_version : "13.7"
  instance_class = var.db_instance_class != "" ? var.db_instance_class : "db.t3.micro"

  backup_retention_period = local.backup_retention
  backup_window          = var.environment == "prod" ? "03:00-04:00" : null
}

count를 이용한 반복

# 기본 count 사용
variable "instance_count" {
  type    = number
  default = 3
}

resource "aws_instance" "web" {
  count = var.instance_count

  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
  subnet_id     = var.subnet_ids[count.index % length(var.subnet_ids)]

  tags = {
    Name = "web-${count.index + 1}"
    Index = count.index
  }
}

# 조건부 count
resource "aws_instance" "worker" {
  count = var.create_workers ? var.worker_count : 0

  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.small"

  tags = {
    Name = "worker-${count.index + 1}"
    Role = "worker"
  }
}

# 리스트 기반 count
variable "environments" {
  type = list(string)
  default = ["dev", "staging", "prod"]
}

resource "aws_s3_bucket" "logs" {
  count = length(var.environments)

  bucket = "my-app-logs-${var.environments[count.index]}"

  tags = {
    Environment = var.environments[count.index]
  }
}

for_each를 이용한 반복

# 맵을 이용한 for_each
variable "users" {
  type = map(object({
    role = string
    team = string
  }))
  default = {
    "alice" = {
      role = "admin"
      team = "platform"
    }
    "bob" = {
      role = "developer"
      team = "backend"
    }
    "charlie" = {
      role = "developer"
      team = "frontend"
    }
  }
}

resource "aws_iam_user" "users" {
  for_each = var.users

  name = each.key

  tags = {
    Role = each.value.role
    Team = each.value.team
  }
}

# 세트를 이용한 for_each
variable "bucket_names" {
  type = set(string)
  default = ["logs", "assets", "backups"]
}

resource "aws_s3_bucket" "buckets" {
  for_each = var.bucket_names

  bucket = "my-app-${each.value}"

  tags = {
    Purpose = each.value
  }
}

# 복잡한 for_each 예시
variable "databases" {
  type = map(object({
    engine         = string
    engine_version = string
    instance_class = string
    allocated_storage = number
    multi_az       = bool
  }))
  default = {
    "main" = {
      engine         = "postgres"
      engine_version = "13.7"
      instance_class = "db.t3.medium"
      allocated_storage = 100
      multi_az       = true
    }
    "cache" = {
      engine         = "redis"
      engine_version = "6.2"
      instance_class = "cache.t3.micro"
      allocated_storage = 0
      multi_az       = false
    }
  }
}

resource "aws_db_instance" "databases" {
  for_each = {
    for name, config in var.databases : name => config
    if config.engine == "postgres" || config.engine == "mysql"
  }

  identifier = "myapp-${each.key}"

  engine         = each.value.engine
  engine_version = each.value.engine_version
  instance_class = each.value.instance_class
  allocated_storage = each.value.allocated_storage
  multi_az       = each.value.multi_az

  tags = {
    Name = "myapp-${each.key}"
    Engine = each.value.engine
  }
}

동적 블록 (Dynamic Blocks)

variable "security_group_rules" {
  type = list(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = [
    {
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTP"
    },
    {
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTPS"
    },
    {
      from_port   = 22
      to_port     = 22
      protocol    = "tcp"
      cidr_blocks = ["10.0.0.0/16"]
      description = "SSH"
    }
  ]
}

resource "aws_security_group" "web" {
  name_prefix = "web-sg"
  vpc_id      = var.vpc_id

  # 동적 ingress 규칙 생성
  dynamic "ingress" {
    for_each = var.security_group_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# 복잡한 동적 블록 예시
variable "load_balancer_listeners" {
  type = list(object({
    port     = number
    protocol = string
    ssl_policy = string
    certificate_arn = string
    default_action = object({
      type = string
      target_group_arn = string
    })
  }))
}

resource "aws_lb_listener" "listeners" {
  for_each = {
    for idx, listener in var.load_balancer_listeners : idx => listener
  }

  load_balancer_arn = aws_lb.main.arn
  port              = each.value.port
  protocol          = each.value.protocol
  ssl_policy        = each.value.protocol == "HTTPS" ? each.value.ssl_policy : null
  certificate_arn   = each.value.protocol == "HTTPS" ? each.value.certificate_arn : null

  default_action {
    type             = each.value.default_action.type
    target_group_arn = each.value.default_action.target_group_arn
  }
}

함수와 표현식

문자열 함수

locals {
  # 문자열 조작
  project_name = "my-web-app"
  environment  = "production"

  # 문자열 결합
  resource_name = "${local.project_name}-${local.environment}"

  # 대소문자 변환
  upper_name = upper(local.project_name)    # "MY-WEB-APP"
  lower_name = lower(local.project_name)    # "my-web-app"
  title_name = title(local.project_name)    # "My-Web-App"

  # 문자열 치환
  sanitized_name = replace(local.project_name, "-", "_")  # "my_web_app"

  # 문자열 분할
  name_parts = split("-", local.project_name)  # ["my", "web", "app"]

  # 문자열 결합
  joined_name = join("_", local.name_parts)    # "my_web_app"

  # 부분 문자열
  short_name = substr(local.project_name, 0, 5)  # "my-we"

  # 문자열 길이
  name_length = length(local.project_name)  # 10

  # 문자열 포함 확인
  contains_web = strcontains(local.project_name, "web")  # true
}

# 템플릿 문자열
locals {
  user_data = templatestring(<<-EOT
    #!/bin/bash
    echo "Environment: ${environment}"
    echo "Project: ${project_name}"
    echo "Instance: ${instance_id}"
  EOT
  , {
    environment = var.environment
    project_name = var.project_name
    instance_id = "i-1234567890"
  })
}

숫자 함수

locals {
  numbers = [10, 25, 5, 30, 15]

  # 최대값, 최소값
  max_number = max(local.numbers...)  # 30
  min_number = min(local.numbers...)  # 5

  # 절댓값, 올림, 내림
  abs_value = abs(-10)     # 10
  ceil_value = ceil(4.3)   # 5
  floor_value = floor(4.8) # 4

  # 로그 함수
  log_value = log(100, 10)  # 2 (log base 10 of 100)
}

# 계산 예시
resource "aws_instance" "web" {
  count = ceil(var.expected_users / var.users_per_instance)

  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
}

컬렉션 함수

locals {
  # 리스트 관련
  servers = ["web1", "web2", "db1", "cache1"]
  numbers = [1, 2, 3, 4, 5]

  # 리스트 길이
  server_count = length(local.servers)  # 4

  # 리스트 인덱스
  first_server = local.servers[0]     # "web1"
  last_server = local.servers[length(local.servers) - 1]  # "cache1"

  # 리스트 슬라이싱
  web_servers = slice(local.servers, 0, 2)  # ["web1", "web2"]

  # 리스트 정렬
  sorted_servers = sort(local.servers)  # ["cache1", "db1", "web1", "web2"]
  sorted_numbers = sort(local.numbers)  # [1, 2, 3, 4, 5]

  # 리스트 역순
  reversed_servers = reverse(local.servers)  # ["cache1", "db1", "web2", "web1"]

  # 리스트 연결
  all_servers = concat(local.servers, ["backup1", "backup2"])

  # 중복 제거
  unique_items = distinct(["a", "b", "a", "c", "b"])  # ["a", "b", "c"]

  # 리스트 포함 확인
  contains_web1 = contains(local.servers, "web1")  # true

  # 인덱스 찾기
  web1_index = index(local.servers, "web1")  # 0
}

# 맵 관련 함수
locals {
  server_config = {
    web1 = { type = "t3.medium", az = "us-west-2a" }
    web2 = { type = "t3.medium", az = "us-west-2b" }
    db1  = { type = "t3.large",  az = "us-west-2a" }
  }

  # 키와 값 추출
  server_names = keys(local.server_config)    # ["web1", "web2", "db1"]
  server_types = values(local.server_config)  # [각 서버의 config 객체들]

  # 맵 병합
  additional_config = {
    cache1 = { type = "t3.small", az = "us-west-2c" }
  }
  all_config = merge(local.server_config, local.additional_config)

  # 맵 조회
  web1_config = lookup(local.server_config, "web1", {})
  web3_config = lookup(local.server_config, "web3", { type = "t2.micro", az = "us-west-2a" })
}

for 표현식

locals {
  servers = ["web1", "web2", "db1", "cache1"]

  # 리스트 변환
  server_names = [for s in local.servers : upper(s)]
  # 결과: ["WEB1", "WEB2", "DB1", "CACHE1"]

  # 조건부 필터링
  web_servers = [for s in local.servers : s if substr(s, 0, 3) == "web"]
  # 결과: ["web1", "web2"]

  # 인덱스 포함
  indexed_servers = [for i, s in local.servers : "${i}-${s}"]
  # 결과: ["0-web1", "1-web2", "2-db1", "3-cache1"]
}

# 맵 변환
locals {
  instance_types = {
    web1 = "t3.medium"
    web2 = "t3.medium"
    db1  = "t3.large"
  }

  # 맵 값 변환
  instance_configs = {
    for name, type in local.instance_types : name => {
      instance_type = type
      monitoring    = type != "t3.large"
    }
  }

  # 맵 필터링
  large_instances = {
    for name, type in local.instance_types : name => type
    if type == "t3.large"
  }

  # 복잡한 변환
  server_tags = {
    for name, type in local.instance_types : name => {
      Name         = name
      InstanceType = type
      Environment  = "prod"
      Role         = substr(name, 0, 3) == "web" ? "frontend" : "backend"
    }
  }
}

# 중첩 구조 처리
locals {
  environments = {
    dev = {
      instances = ["web1", "web2"]
      db_size   = "small"
    }
    prod = {
      instances = ["web1", "web2", "web3", "web4"]
      db_size   = "large"
    }
  }

  # 중첩 for 표현식
  all_instances = flatten([
    for env_name, env_config in local.environments : [
      for instance in env_config.instances : {
        name        = "${env_name}-${instance}"
        environment = env_name
        db_size     = env_config.db_size
      }
    ]
  ])
}

날짜/시간 함수

locals {
  # 현재 타임스탬프
  current_time = timestamp()  # "2023-12-01T10:30:00Z"

  # 날짜 포맷팅
  formatted_date = formatdate("YYYY-MM-DD", timestamp())  # "2023-12-01"
  formatted_time = formatdate("hh:mm:ss", timestamp())    # "10:30:00"

  # 시간 계산
  future_time = timeadd(timestamp(), "24h")   # 24시간 후
  past_time   = timeadd(timestamp(), "-7d")   # 7일 전
}

# 시간 기반 리소스 생성
resource "aws_s3_bucket" "daily_logs" {
  bucket = "my-app-logs-${formatdate("YYYY-MM-DD", timestamp())}"

  tags = {
    CreatedAt = timestamp()
    Purpose   = "daily-logs"
  }
}

인코딩 함수

locals {
  # Base64 인코딩/디코딩
  original_text = "Hello, World!"
  encoded_text  = base64encode(local.original_text)  # "SGVsbG8sIFdvcmxkIQ=="
  decoded_text  = base64decode(local.encoded_text)   # "Hello, World!"

  # JSON 인코딩/디코딩
  config_map = {
    database_host = "db.example.com"
    database_port = 5432
    ssl_enabled   = true
  }
  config_json = jsonencode(local.config_map)
  parsed_json = jsondecode(local.config_json)

  # URL 인코딩
  query_param = "Hello World"
  encoded_url = urlencode(local.query_param)  # "Hello%20World"
}

# 사용자 데이터 스크립트 인코딩
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  user_data = base64encode(templatefile("user-data.sh", {
    config = jsonencode(var.app_config)
  }))
}

워크스페이스 관리

워크스페이스 개념

워크스페이스는 동일한 구성으로 여러 환경을 관리할 수 있게 해주는 기능입니다.

기본 워크스페이스 관리

# 현재 워크스페이스 확인
terraform workspace show

# 워크스페이스 목록 확인
terraform workspace list

# 새 워크스페이스 생성
terraform workspace new development
terraform workspace new staging
terraform workspace new production

# 워크스페이스 전환
terraform workspace select development
terraform workspace select production

# 워크스페이스 삭제
terraform workspace delete development

워크스페이스별 구성

# main.tf
locals {
  # 워크스페이스별 설정
  workspace_configs = {
    default = {
      instance_type = "t2.micro"
      instance_count = 1
      environment = "dev"
    }
    development = {
      instance_type = "t2.micro"
      instance_count = 1
      environment = "dev"
    }
    staging = {
      instance_type = "t3.small"
      instance_count = 2
      environment = "staging"
    }
    production = {
      instance_type = "t3.medium"
      instance_count = 3
      environment = "prod"
    }
  }

  # 현재 워크스페이스 설정
  workspace_config = local.workspace_configs[terraform.workspace]
}

# 워크스페이스별 S3 백엔드
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "env:/terraform.tfstate"  # workspace별로 자동 경로 생성
    region = "us-west-2"

    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

# 워크스페이스 정보 활용
resource "aws_instance" "web" {
  count = local.workspace_config.instance_count

  ami           = data.aws_ami.ubuntu.id
  instance_type = local.workspace_config.instance_type

  tags = {
    Name        = "${terraform.workspace}-web-${count.index + 1}"
    Environment = local.workspace_config.environment
    Workspace   = terraform.workspace
  }
}

resource "aws_s3_bucket" "app_data" {
  bucket = "my-app-data-${terraform.workspace}"

  tags = {
    Environment = local.workspace_config.environment
    Workspace   = terraform.workspace
  }
}

환경별 변수 파일

# terraform.tfvars (기본값)
region = "us-west-2"
project_name = "my-app"

# development.tfvars
environment = "dev"
instance_type = "t2.micro"
instance_count = 1
db_instance_class = "db.t3.micro"
enable_monitoring = false

# staging.tfvars
environment = "staging"
instance_type = "t3.small"
instance_count = 2
db_instance_class = "db.t3.small"
enable_monitoring = true

# production.tfvars
environment = "prod"
instance_type = "t3.medium"
instance_count = 5
db_instance_class = "db.t3.medium"
enable_monitoring = true
backup_retention = 30
# 환경별 배포 스크립트
#!/bin/bash

ENVIRONMENT=$1

if [ -z "$ENVIRONMENT" ]; then
  echo "Usage: $0 <environment>"
  exit 1
fi

# 워크스페이스 선택
terraform workspace select $ENVIRONMENT || terraform workspace new $ENVIRONMENT

# 계획 실행
terraform plan -var-file="${ENVIRONMENT}.tfvars" -out="${ENVIRONMENT}.tfplan"

# 사용자 확인 후 적용
read -p "Apply changes for $ENVIRONMENT? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
  terraform apply "${ENVIRONMENT}.tfplan"
fi

클라우드별 실전 예제

AWS 3-Tier 아키텍처

# AWS 3-Tier Architecture
# variables.tf
variable "project_name" {
  description = "프로젝트 이름"
  type        = string
  default     = "webapp"
}

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

variable "vpc_cidr" {
  description = "VPC CIDR 블록"
  type        = string
  default     = "10.0.0.0/16"
}

variable "availability_zones" {
  description = "가용 영역 목록"
  type        = list(string)
  default     = ["us-west-2a", "us-west-2b"]
}

# main.tf
# 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
  }
}

# Public Subnets (Web Tier)
resource "aws_subnet" "public" {
  count = length(var.availability_zones)

  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-public-${count.index + 1}"
    Tier = "Web"
  }
}

# Private Subnets (App Tier)
resource "aws_subnet" "private_app" {
  count = length(var.availability_zones)

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 10}.0/24"
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project_name}-private-app-${count.index + 1}"
    Tier = "Application"
  }
}

# Private Subnets (DB Tier)
resource "aws_subnet" "private_db" {
  count = length(var.availability_zones)

  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 20}.0/24"
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.project_name}-private-db-${count.index + 1}"
    Tier = "Database"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

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

# NAT Gateway
resource "aws_eip" "nat" {
  count = length(var.availability_zones)
  domain = "vpc"

  tags = {
    Name = "${var.project_name}-nat-eip-${count.index + 1}"
  }
}

resource "aws_nat_gateway" "main" {
  count = length(var.availability_zones)

  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  tags = {
    Name = "${var.project_name}-nat-${count.index + 1}"
  }

  depends_on = [aws_internet_gateway.main]
}

# Route Tables
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "${var.project_name}-public-rt"
  }
}

resource "aws_route_table" "private" {
  count = length(var.availability_zones)
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main[count.index].id
  }

  tags = {
    Name = "${var.project_name}-private-rt-${count.index + 1}"
  }
}

# Route Table Associations
resource "aws_route_table_association" "public" {
  count = length(aws_subnet.public)

  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private_app" {
  count = length(aws_subnet.private_app)

  subnet_id      = aws_subnet.private_app[count.index].id
  route_table_id = aws_route_table.private[count.index].id
}

resource "aws_route_table_association" "private_db" {
  count = length(aws_subnet.private_db)

  subnet_id      = aws_subnet.private_db[count.index].id
  route_table_id = aws_route_table.private[count.index].id
}

# Security Groups
resource "aws_security_group" "web" {
  name_prefix = "${var.project_name}-web-"
  vpc_id      = aws_vpc.main.id

  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 = {
    Name = "${var.project_name}-web-sg"
    Tier = "Web"
  }
}

resource "aws_security_group" "app" {
  name_prefix = "${var.project_name}-app-"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-app-sg"
    Tier = "Application"
  }
}

resource "aws_security_group" "db" {
  name_prefix = "${var.project_name}-db-"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  tags = {
    Name = "${var.project_name}-db-sg"
    Tier = "Database"
  }
}

# Application Load Balancer
resource "aws_lb" "main" {
  name               = "${var.project_name}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.web.id]
  subnets            = aws_subnet.public[*].id

  tags = {
    Environment = var.environment
  }
}

# Target Group
resource "aws_lb_target_group" "app" {
  name     = "${var.project_name}-tg"
  port     = 8080
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    enabled             = true
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 5
    interval            = 30
    path                = "/health"
    matcher             = "200"
  }
}

# ALB Listener
resource "aws_lb_listener" "main" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# Auto Scaling Group
resource "aws_launch_template" "app" {
  name_prefix = "${var.project_name}-"

  image_id      = data.aws_ami.ubuntu.id
  instance_type = var.environment == "prod" ? "t3.medium" : "t3.small"

  vpc_security_group_ids = [aws_security_group.app.id]

  user_data = base64encode(templatefile("user-data.sh", {
    db_endpoint = aws_db_instance.main.endpoint
  }))

  tag_specifications {
    resource_type = "instance"
    tags = {
      Name = "${var.project_name}-app"
      Environment = var.environment
    }
  }
}

resource "aws_autoscaling_group" "app" {
  name = "${var.project_name}-asg"

  vpc_zone_identifier = aws_subnet.private_app[*].id
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"

  min_size         = var.environment == "prod" ? 2 : 1
  max_size         = var.environment == "prod" ? 10 : 3
  desired_capacity = var.environment == "prod" ? 3 : 1

  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }

  tag {
    key                 = "Name"
    value               = "${var.project_name}-asg"
    propagate_at_launch = false
  }
}

# RDS Database
resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-db-subnet-group"
  subnet_ids = aws_subnet.private_db[*].id

  tags = {
    Name = "${var.project_name} DB subnet group"
  }
}

resource "aws_db_instance" "main" {
  identifier = "${var.project_name}-db"

  engine         = "mysql"
  engine_version = "8.0"
  instance_class = var.environment == "prod" ? "db.t3.medium" : "db.t3.micro"

  allocated_storage     = var.environment == "prod" ? 100 : 20
  max_allocated_storage = var.environment == "prod" ? 1000 : 100

  db_name  = "webapp"
  username = "admin"
  password = var.db_password  # 변수로 전달

  vpc_security_group_ids = [aws_security_group.db.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name

  backup_retention_period = var.environment == "prod" ? 30 : 7
  backup_window          = "03:00-04:00"
  maintenance_window     = "sun:04:00-sun:05:00"

  skip_final_snapshot = var.environment != "prod"

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

Azure 웹 애플리케이션

# Azure Web Application
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

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

  tags = {
    Environment = var.environment
    Project     = var.project_name
  }
}

# Virtual Network
resource "azurerm_virtual_network" "main" {
  name                = "vnet-${var.project_name}"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  tags = {
    Environment = var.environment
  }
}

# Subnets
resource "azurerm_subnet" "web" {
  name                 = "snet-web"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_subnet" "app" {
  name                 = "snet-app"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_subnet" "db" {
  name                 = "snet-db"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.3.0/24"]

  delegation {
    name = "Microsoft.DBforPostgreSQL/flexibleServers"
    service_delegation {
      name = "Microsoft.DBforPostgreSQL/flexibleServers"
    }
  }
}

# Network Security Groups
resource "azurerm_network_security_group" "web" {
  name                = "nsg-web"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  security_rule {
    name                       = "HTTP"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "HTTPS"
    priority                   = 1002
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# App Service Plan
resource "azurerm_service_plan" "main" {
  name                = "asp-${var.project_name}"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location

  os_type  = "Linux"
  sku_name = var.environment == "prod" ? "P1v2" : "B1"
}

# App Service
resource "azurerm_linux_web_app" "main" {
  name                = "app-${var.project_name}-${var.environment}"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  service_plan_id     = azurerm_service_plan.main.id

  site_config {
    application_stack {
      node_version = "18-lts"
    }
  }

  app_settings = {
    "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
    "DATABASE_URL" = "postgresql://${var.db_username}:${var.db_password}@${azurerm_postgresql_flexible_server.main.fqdn}:5432/${var.db_name}"
  }

  tags = {
    Environment = var.environment
  }
}

# PostgreSQL Database
resource "azurerm_postgresql_flexible_server" "main" {
  name                   = "psql-${var.project_name}-${var.environment}"
  resource_group_name    = azurerm_resource_group.main.name
  location               = azurerm_resource_group.main.location
  version                = "13"
  delegated_subnet_id    = azurerm_subnet.db.id
  private_dns_zone_id    = azurerm_private_dns_zone.db.id
  administrator_login    = var.db_username
  administrator_password = var.db_password

  zone = "1"

  storage_mb = var.environment == "prod" ? 32768 : 32768

  sku_name   = var.environment == "prod" ? "GP_Standard_D2s_v3" : "B_Standard_B1ms"

  depends_on = [azurerm_private_dns_zone_virtual_network_link.db]

  tags = {
    Environment = var.environment
  }
}

resource "azurerm_postgresql_flexible_server_database" "main" {
  name      = var.db_name
  server_id = azurerm_postgresql_flexible_server.main.id
  collation = "en_US.utf8"
  charset   = "utf8"
}

# Private DNS Zone for Database
resource "azurerm_private_dns_zone" "db" {
  name                = "${var.project_name}.postgres.database.azure.com"
  resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "db" {
  name                  = "pdnsz-link-db"
  private_dns_zone_name = azurerm_private_dns_zone.db.name
  virtual_network_id    = azurerm_virtual_network.main.id
  resource_group_name   = azurerm_resource_group.main.name
}

GCP 마이크로서비스 아키텍처

# GCP Microservices Architecture
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

# VPC Network
resource "google_compute_network" "main" {
  name                    = "${var.project_name}-vpc"
  auto_create_subnetworks = false
  routing_mode           = "GLOBAL"
}

# Subnets
resource "google_compute_subnetwork" "gke" {
  name          = "${var.project_name}-gke-subnet"
  ip_cidr_range = "10.0.0.0/16"
  region        = var.region
  network       = google_compute_network.main.id

  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = "192.168.0.0/18"
  }

  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = "192.168.64.0/18"
  }
}

# GKE Cluster
resource "google_container_cluster" "primary" {
  name     = "${var.project_name}-gke"
  location = var.region

  # Remove default node pool
  remove_default_node_pool = true
  initial_node_count       = 1

  network    = google_compute_network.main.name
  subnetwork = google_compute_subnetwork.gke.name

  ip_allocation_policy {
    cluster_secondary_range_name  = "pods"
    services_secondary_range_name = "services"
  }

  # Enable Workload Identity
  workload_identity_config {
    workload_pool = "${var.project_id}.svc.id.goog"
  }

  # Enable network policy
  network_policy {
    enabled = true
  }

  # Enable binary authorization
  enable_binary_authorization = true

  # Enable private nodes
  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = false
    master_ipv4_cidr_block  = "172.16.0.0/28"
  }

  master_auth {
    client_certificate_config {
      issue_client_certificate = false
    }
  }
}

# GKE Node Pool
resource "google_container_node_pool" "primary_nodes" {
  name       = "${google_container_cluster.primary.name}-node-pool"
  location   = var.region
  cluster    = google_container_cluster.primary.name
  node_count = 1

  autoscaling {
    min_node_count = 1
    max_node_count = 5
  }

  node_config {
    preemptible  = var.environment == "dev"
    machine_type = var.environment == "prod" ? "e2-standard-2" : "e2-medium"

    service_account = google_service_account.gke.email
    oauth_scopes = [
      "https://www.googleapis.com/auth/cloud-platform"
    ]

    labels = {
      environment = var.environment
    }

    tags = ["gke-node", "${var.project_name}-gke"]
  }

  management {
    auto_repair  = true
    auto_upgrade = true
  }
}

# Service Account for GKE
resource "google_service_account" "gke" {
  account_id   = "${var.project_name}-gke"
  display_name = "GKE Service Account"
}

# Cloud SQL (PostgreSQL)
resource "google_sql_database_instance" "main" {
  name             = "${var.project_name}-db"
  database_version = "POSTGRES_13"
  region           = var.region

  settings {
    tier = var.environment == "prod" ? "db-standard-2" : "db-f1-micro"

    disk_size = var.environment == "prod" ? 100 : 20
    disk_type = "PD_SSD"

    backup_configuration {
      enabled                        = true
      start_time                     = "03:00"
      location                       = var.region
      point_in_time_recovery_enabled = var.environment == "prod"
    }

    ip_configuration {
      ipv4_enabled    = false
      private_network = google_compute_network.main.id
    }

    database_flags {
      name  = "log_checkpoints"
      value = "on"
    }
  }

  deletion_protection = var.environment == "prod"

  depends_on = [google_service_networking_connection.private_vpc_connection]
}

resource "google_sql_database" "database" {
  name     = var.db_name
  instance = google_sql_database_instance.main.name
}

resource "google_sql_user" "user" {
  name     = var.db_username
  instance = google_sql_database_instance.main.name
  password = var.db_password
}

# Private Service Connection
resource "google_compute_global_address" "private_ip_address" {
  name          = "${var.project_name}-private-ip"
  purpose       = "VPC_PEERING"
  address_type  = "INTERNAL"
  prefix_length = 16
  network       = google_compute_network.main.id
}

resource "google_service_networking_connection" "private_vpc_connection" {
  network                 = google_compute_network.main.id
  service                 = "servicenetworking.googleapis.com"
  reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}

# Cloud Pub/Sub Topic
resource "google_pubsub_topic" "events" {
  name = "${var.project_name}-events"

  labels = {
    environment = var.environment
  }
}

# Cloud Storage Bucket
resource "google_storage_bucket" "assets" {
  name     = "${var.project_name}-assets-${random_string.bucket_suffix.result}"
  location = var.region

  uniform_bucket_level_access = true

  versioning {
    enabled = var.environment == "prod"
  }

  lifecycle_rule {
    condition {
      age = 365
    }
    action {
      type = "Delete"
    }
  }

  labels = {
    environment = var.environment
  }
}

resource "random_string" "bucket_suffix" {
  length  = 8
  special = false
  upper   = false
}

# IAM
resource "google_project_iam_member" "gke_sql" {
  project = var.project_id
  role    = "roles/cloudsql.client"
  member  = "serviceAccount:${google_service_account.gke.email}"
}

resource "google_project_iam_member" "gke_storage" {
  project = var.project_id
  role    = "roles/storage.objectAdmin"
  member  = "serviceAccount:${google_service_account.gke.email}"
}

CI/CD 통합

GitLab CI/CD 파이프라인

# .gitlab-ci.yml
variables:
  TF_ROOT: ${CI_PROJECT_DIR}/infrastructure
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/production

cache:
  key: production
  paths:
    - ${TF_ROOT}/.terraform

before_script:
  - cd ${TF_ROOT}
  - terraform --version
  - terraform init -backend-config="address=${TF_ADDRESS}" -backend-config="lock_address=${TF_ADDRESS}/lock" -backend-config="unlock_address=${TF_ADDRESS}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"

stages:
  - validate
  - plan
  - apply

validate:
  stage: validate
  script:
    - terraform fmt -check
    - terraform validate

plan:
  stage: plan
  script:
    - terraform plan -out="planfile"
  dependencies:
    - validate
  artifacts:
    name: plan
    paths:
      - ${TF_ROOT}/planfile
    reports:
      terraform: ${TF_ROOT}/planfile.json
  only:
    - merge_requests
    - main

apply:
  stage: apply
  script:
    - terraform apply -input=false "planfile"
  dependencies:
    - plan
  when: manual
  only:
    - main

GitHub Actions 워크플로우

# .github/workflows/terraform.yml
name: 'Terraform'

on:
  push:
    branches: [ main ]
  pull_request:

permissions:
  contents: read
  pull-requests: write

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: production

    defaults:
      run:
        shell: bash
        working-directory: ./infrastructure

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

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: 1.6.0
        terraform_wrapper: false

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-west-2

    - name: Terraform Format
      id: fmt
      run: terraform fmt -check

    - name: Terraform Init
      id: init
      run: terraform init

    - name: Terraform Validate
      id: validate
      run: terraform validate -no-color

    - name: Terraform Plan
      id: plan
      if: github.event_name == 'pull_request'
      run: terraform plan -no-color -input=false
      continue-on-error: true

    - name: Update Pull Request
      uses: actions/github-script@v6
      if: github.event_name == 'pull_request'
      env:
        PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        script: |
          const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
          #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
          #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
          #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

          <details><summary>Show Plan</summary>

          \`\`\`\n
          ${process.env.PLAN}
          \`\`\`

          </details>

          *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;

          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: output
          })

    - name: Terraform Plan Status
      if: steps.plan.outcome == 'failure'
      run: exit 1

    - name: Terraform Apply
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      run: terraform apply -auto-approve -input=false

Azure DevOps 파이프라인

# azure-pipelines.yml
trigger:
  branches:
    include:
      - main
  paths:
    include:
      - infrastructure/*

variables:
  - group: terraform-variables
  - name: terraformWorkingDirectory
    value: '$(System.DefaultWorkingDirectory)/infrastructure'

stages:
- stage: Validate
  displayName: 'Validate Terraform'
  jobs:
  - job: Validate
    displayName: 'Validate'
    pool:
      vmImage: 'ubuntu-latest'
    steps:
    - task: TerraformInstaller@0
      displayName: 'Install Terraform'
      inputs:
        terraformVersion: '1.6.0'

    - task: TerraformTaskV4@4
      displayName: 'Terraform Init'
      inputs:
        provider: 'azurerm'
        command: 'init'
        workingDirectory: $(terraformWorkingDirectory)
        backendServiceArm: 'Azure Service Connection'
        backendAzureRmResourceGroupName: 'rg-terraform-state'
        backendAzureRmStorageAccountName: 'stterraformstate12345'
        backendAzureRmContainerName: 'tfstate'
        backendAzureRmKey: 'prod.terraform.tfstate'

    - task: TerraformTaskV4@4
      displayName: 'Terraform Format Check'
      inputs:
        provider: 'azurerm'
        command: 'custom'
        workingDirectory: $(terraformWorkingDirectory)
        customCommand: 'fmt'
        commandOptions: '-check -diff'

    - task: TerraformTaskV4@4
      displayName: 'Terraform Validate'
      inputs:
        provider: 'azurerm'
        command: 'validate'
        workingDirectory: $(terraformWorkingDirectory)

- stage: Plan
  displayName: 'Plan Terraform'
  dependsOn: Validate
  condition: succeeded()
  jobs:
  - job: Plan
    displayName: 'Plan'
    pool:
      vmImage: 'ubuntu-latest'
    steps:
    - task: TerraformTaskV4@4
      displayName: 'Terraform Plan'
      inputs:
        provider: 'azurerm'
        command: 'plan'
        workingDirectory: $(terraformWorkingDirectory)
        environmentServiceNameAzureRM: 'Azure Service Connection'
        commandOptions: '-out=tfplan'

    - task: PublishPipelineArtifact@1
      displayName: 'Publish Plan'
      inputs:
        targetPath: '$(terraformWorkingDirectory)/tfplan'
        artifact: 'tfplan'

- stage: Apply
  displayName: 'Apply Terraform'
  dependsOn: Plan
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
  - deployment: Apply
    displayName: 'Apply'
    environment: 'production'
    pool:
      vmImage: 'ubuntu-latest'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: DownloadPipelineArtifact@2
            displayName: 'Download Plan'
            inputs:
              artifactName: 'tfplan'
              targetPath: $(terraformWorkingDirectory)

          - task: TerraformTaskV4@4
            displayName: 'Terraform Apply'
            inputs:
              provider: 'azurerm'
              command: 'apply'
              workingDirectory: $(terraformWorkingDirectory)
              environmentServiceNameAzureRM: 'Azure Service Connection'
              commandOptions: 'tfplan'

Terraform Cloud 통합

# Terraform Cloud 설정
terraform {
  cloud {
    organization = "my-organization"

    workspaces {
      tags = ["production", "web-app"]
    }
  }
}

# 또는 특정 워크스페이스
terraform {
  cloud {
    organization = "my-organization"

    workspaces {
      name = "web-app-production"
    }
  }
}

보안과 베스트 프랙티스

시크릿 관리

# AWS Secrets Manager 사용
data "aws_secretsmanager_secret" "db_credentials" {
  name = "prod/database/credentials"
}

data "aws_secretsmanager_secret_version" "db_credentials" {
  secret_id = data.aws_secretsmanager_secret.db_credentials.id
}

locals {
  db_creds = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string)
}

resource "aws_db_instance" "main" {
  identifier = "myapp-db"

  engine         = "postgres"
  engine_version = "13.7"
  instance_class = "db.t3.micro"

  allocated_storage = 20

  db_name  = "myapp"
  username = local.db_creds.username
  password = local.db_creds.password

  skip_final_snapshot = true
}

# Azure Key Vault 사용
data "azurerm_key_vault" "main" {
  name                = var.key_vault_name
  resource_group_name = var.key_vault_resource_group
}

data "azurerm_key_vault_secret" "db_password" {
  name         = "database-password"
  key_vault_id = data.azurerm_key_vault.main.id
}

# 환경 변수 사용 (민감한 값)
variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true
}

# terraform.tfvars에서 제외하고 환경 변수로 설정
# export TF_VAR_db_password="super-secret-password"

리소스 태깅 전략

# 공통 태그 정의
locals {
  common_tags = {
    Environment   = var.environment
    Project       = var.project_name
    Owner         = var.team_name
    CostCenter    = var.cost_center
    CreatedBy     = "terraform"
    CreatedDate   = formatdate("YYYY-MM-DD", timestamp())
  }

  # 보안 관련 태그
  security_tags = var.environment == "prod" ? {
    DataClassification = "confidential"
    Compliance        = "required"
    BackupRequired    = "yes"
  } : {}

  # 모든 태그 결합
  all_tags = merge(local.common_tags, local.security_tags, var.additional_tags)
}

# 태그 적용
resource "aws_instance" "web" {
  count = var.instance_count

  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = merge(local.all_tags, {
    Name = "${var.project_name}-web-${count.index + 1}"
    Role = "web-server"
  })
}

# 태그 검증
resource "aws_instance" "web" {
  # ... 다른 설정들

  tags = local.all_tags

  lifecycle {
    # 태그가 없으면 생성 실패
    precondition {
      condition     = length(local.all_tags) > 0
      error_message = "All resources must have tags."
    }
  }
}

정책 기반 보안

# OPA (Open Policy Agent) 정책 검증
data "external" "policy_check" {
  program = ["opa", "eval", "-d", "policies/", "-i", "terraform.json", "data.terraform.deny[x]"]
}

# Sentinel 정책 (Terraform Cloud/Enterprise)
# sentinel.hcl
policy "restrict-instance-type" {
  source = "./restrict-instance-type.sentinel"
  enforcement_level = "hard-mandatory"
}

# restrict-instance-type.sentinel
import "tfplan/v2" as tfplan

allowed_instance_types = ["t2.micro", "t2.small", "t3.micro", "t3.small"]

main = rule {
  all tfplan.resource_changes as _, rc {
    rc.type is "aws_instance" and
    rc.mode is "managed" and
    (rc.change.actions contains "create" or rc.change.actions contains "update") implies
    rc.change.after.instance_type in allowed_instance_types
  }
}

코드 품질 관리

# .terraform.lock.hcl 파일 버전 관리
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  required_version = ">= 1.0"
}

# 변수 검증
variable "environment" {
  description = "Environment name"
  type        = string

  validation {
    condition = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be one of: dev, staging, prod."
  }
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string

  validation {
    condition = can(regex("^[tm][2-9]\\.", var.instance_type))
    error_message = "Instance type must be t2/t3/m4/m5 family."
  }
}

# 리소스 이름 규칙
locals {
  # 이름 규칙: {project}-{environment}-{service}-{suffix}
  name_prefix = "${var.project_name}-${var.environment}"

  # 이름 검증
  valid_name = can(regex("^[a-z][a-z0-9-]{2,62}[a-z0-9]$", local.name_prefix))
}

resource "aws_s3_bucket" "example" {
  bucket = "${local.name_prefix}-data"

  lifecycle {
    precondition {
      condition     = local.valid_name
      error_message = "Resource names must follow naming convention."
    }
  }
}

드리프트 감지

#!/bin/bash
# drift-detection.sh

# Terraform 상태와 실제 인프라 비교
terraform plan -detailed-exitcode

EXIT_CODE=$?

if [ $EXIT_CODE -eq 1 ]; then
  echo "ERROR: Terraform plan failed"
  exit 1
elif [ $EXIT_CODE -eq 2 ]; then
  echo "WARNING: Infrastructure drift detected"
  terraform show -no-color > drift-report.txt

  # Slack 알림
  curl -X POST -H 'Content-type: application/json' \
    --data '{"text":"Infrastructure drift detected in '"$ENV"' environment"}' \
    $SLACK_WEBHOOK_URL

  exit 2
else
  echo "INFO: No infrastructure drift detected"
  exit 0
fi

문제 해결 가이드

일반적인 오류 해결

1. Provider 설정 오류

# 오류: Provider configuration not present
Error: Provider configuration not present

To work with aws_instance.example its original provider configuration
at provider["registry.terraform.io/hashicorp/aws"] is required, but it
has been removed. This occurs when a provider configuration is removed
while objects created by that provider still exist in the state.

# 해결책: Provider 재설정
terraform init -upgrade
terraform providers

2. 상태 파일 잠금 오류

# 오류: State locked
Error: Error locking state: Error acquiring the state lock

# 해결책: 잠금 강제 해제 (주의!)
terraform force-unlock <LOCK_ID>

# 더 안전한 해결책: 잠금 대기 시간 설정
terraform apply -lock-timeout=300s

3. 리소스 충돌 오류

# 오류: Resource already exists
Error: Error creating S3 bucket: BucketAlreadyExists

# 해결책 1: 기존 리소스 import
terraform import aws_s3_bucket.example existing-bucket-name

# 해결책 2: 리소스 이름 변경
resource "aws_s3_bucket" "example" {
  bucket = "my-unique-bucket-${random_string.suffix.result}"
}

resource "random_string" "suffix" {
  length  = 8
  special = false
  upper   = false
}

4. 버전 호환성 문제

# terraform versions.tf
terraform {
  required_version = ">= 1.0, < 2.0"

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

# 버전 업그레이드
terraform init -upgrade

디버깅 도구

# 로그 레벨 설정
export TF_LOG=DEBUG
export TF_LOG_PATH="./terraform.log"

# 특정 리소스만 계획/적용
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web

# 상태 파일 검사
terraform state list
terraform state show aws_instance.web

# 구성 검증
terraform validate
terraform fmt -check -diff

# 그래프 생성 (시각화)
terraform graph | dot -Tpng > graph.png

성능 최적화

# 병렬 처리 설정
terraform apply -parallelism=20

# 리소스 타겟팅
terraform apply -target=module.database

# 부분 새로고침
terraform apply -refresh=false

# 계획 파일 사용
terraform plan -out=tfplan
terraform apply tfplan

재해 복구

# 상태 파일 백업
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate

# 상태 파일 복원
terraform state push backup.tfstate

# 리소스 재생성
terraform taint aws_instance.web
terraform apply

# 전체 상태 재구축
terraform refresh

결론

Terraform은 현대 인프라 관리의 필수 도구입니다. Infrastructure as Code의 개념을 통해 인프라의 버전 관리, 자동화, 일관성을 실현할 수 있습니다.

Terraform 마스터 로드맵

  1. 기초: HCL 언어, Provider/Resource 이해
  2. 중급: 모듈 시스템, 상태 관리, 워크스페이스
  3. 고급: 함수/표현식, 동적 블록, 복잡한 구성
  4. 전문가: CI/CD 통합, 정책 관리, 멀티 클라우드 전략

실무 적용 단계

  1. 학습 환경: 로컬에서 단순한 리소스 생성
  2. 개발 환경: 기본적인 3-tier 아키텍처 구축
  3. 스테이징: 프로덕션과 유사한 환경 자동화
  4. 프로덕션: 완전 자동화된 인프라 관리

성공을 위한 핵심 원칙

  • 점진적 도입: 작은 단위부터 시작하여 확장
  • 모듈화: 재사용 가능한 모듈 설계
  • 상태 관리: 원격 상태 저장소 필수 사용
  • 보안 우선: 시크릿 관리와 접근 제어
  • 문서화: 코드 자체가 문서가 되도록 작성

지속적 학습 리소스

마지막 조언

  • 실습 중심: 실제 클라우드 환경에서 실습
  • 베스트 프랙티스: 처음부터 올바른 패턴 학습
  • 팀 협업: 모듈과 표준 정립
  • 지속적 개선: 정기적인 코드 리뷰와 리팩토링
  • 보안 의식: 항상 보안을 최우선으로 고려

Terraform을 마스터하고 Infrastructure as Code의 힘을 경험해보세요! 🚀