👥 Terraform 팀 협업 가이드

핵심 메시지

실무에서 요구하는 Terraform 능력은 **“혼자 코드를 짤 수 있느냐”**가 아니라 **“팀원들과 사고 없이 협업할 수 있느냐”**입니다.

📑 목차


1. 왜 협업이 중요한가?

🔥 실제 사고 사례

사고 1: State 파일 충돌

상황:
개발자 A: terraform apply (EC2 생성 중...)
개발자 B: terraform apply (동시에 RDS 생성...)

결과:
❌ State 파일 충돌
❌ 한 명의 변경사항이 사라짐
❌ 인프라 불일치 발생

비용: 3시간 복구 작업

해결책: Remote State + Locking

사고 2: 실수로 프로덕션 삭제

상황:
주니어: "테스트 서버 지우려고 terraform destroy 했는데..."
팀장: "지금 어느 디렉토리에 있어요?"
주니어: "...production/ 폴더요..."

결과:
❌ 운영 서버 전체 삭제
❌ 서비스 4시간 중단
❌ 매출 손실 $50,000

비용: 엔지니어 5명 야근, 복구 8시간

해결책: 환경 분리 + 보호 설정

사고 3: 하드코딩된 비밀번호 Git 커밋

상황:
신입: main.tf에 DB 비밀번호 하드코딩
     → Git에 커밋
     → Public Repository

결과:
❌ 1시간 후 해커의 비트코인 채굴 서버로 변신
❌ AWS 청구서 $12,000

비용: 보안 감사, 전체 비밀번호 변경

해결책: 변수 + AWS Secrets Manager


2. 3단계 역량 모델

🟢 1단계: “기본기 & 사고 치지 않기” (신입 필수)

목표

기존 코드를 읽고, 소규모 변경을 안전하게 할 수 있다.

필수 스킬

1. 워크플로우 숙지

# 올바른 순서
terraform init      # 1. 초기화
terraform fmt       # 2. 포맷팅
terraform validate  # 3. 검증
terraform plan      # 4. 계획 확인 (필수!)
terraform apply     # 5. 적용
 
# ❌ 절대 금지
terraform apply -auto-approve  # plan 없이 바로 적용

2. Plan 결과 읽기 능력

Terraform will perform the following actions:
 
  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami           = "ami-0c55b159cbfafe1f0"
      + instance_type = "t3.micro"
    }
 
  # aws_s3_bucket.logs will be destroyed  ← ⚠️ 주의!
  - resource "aws_s3_bucket" "logs" {
      - bucket = "important-logs"
    }
 
  # aws_db_instance.main must be replaced  ← ⚠️ 위험!
  -/+ resource "aws_db_instance" "main" {
        ...
      }
 
Plan: 1 to add, 0 to change, 2 to destroy.

읽는 법:

  • + (초록): 안전 - 새로 생성
  • ~ (노랑): 주의 - 변경
  • - (빨강): 위험 - 삭제
  • -/+ (빨강+초록): 매우 위험 - 재생성 (데이터 손실 가능!)

신입의 올바른 판단:

주니어: "팀장님, plan 결과에 destroy가 떴는데 괜찮을까요?"
팀장: "좋아, 확인해볼게. 잘했어!"

vs

주니어: (혼자 판단) "destroy 1개 정도야 뭐..." → apply
      → 사고 발생

3. 변수 활용

# ❌ 나쁜 예: 하드코딩
resource "aws_instance" "web" {
  instance_type = "t3.micro"
  ami           = "ami-0c55b159cbfafe1f0"
}
 
# ✅ 좋은 예: 변수 사용
resource "aws_instance" "web" {
  instance_type = var.instance_type
  ami           = var.ami_id
}

🟡 2단계: “협업 & 구조화” (실무 투입 가능)

목표

팀원들과 충돌 없이 협업하고, 재사용 가능한 코드를 작성한다.

핵심 스킬

1. Remote State 관리 ⭐⭐⭐ (가장 중요!)

로컬 State의 문제:
┌─────────────┐     ┌─────────────┐
│ 개발자 A PC  │     │ 개발자 B PC  │
│ state: v1   │     │ state: v1   │
└─────────────┘     └─────────────┘
       ↓                   ↓
    apply EC2          apply RDS
       ↓                   ↓
   state: v2           state: v2'
       ↓                   ↓
    ❌ 충돌! (누구 State가 정답?)

Remote State 해결:
┌─────────────┐     ┌─────────────┐
│ 개발자 A     │     │ 개발자 B     │
└──────┬──────┘     └──────┬──────┘
       │                   │
       └─────────┬─────────┘
                 ↓
        ┌────────────────┐
        │  S3 (State)    │ ← 단일 진실 공급원
        │  + DynamoDB    │ ← Locking
        │    (Lock)      │
        └────────────────┘

2. 모듈 재사용

# ❌ 나쁜 예: 복사-붙여넣기
# dev-server.tf
resource "aws_instance" "dev" {
  ami           = "ami-xxx"
  instance_type = "t3.micro"
  # ... 50줄 ...
}
 
# staging-server.tf
resource "aws_instance" "staging" {
  ami           = "ami-xxx"
  instance_type = "t3.small"
  # ... 똑같은 50줄 반복 ...
}
 
# ✅ 좋은 예: 모듈 사용
# main.tf
module "dev_server" {
  source        = "./modules/ec2"
  instance_type = "t3.micro"
  environment   = "dev"
}
 
module "staging_server" {
  source        = "./modules/ec2"
  instance_type = "t3.small"
  environment   = "staging"
}

3. 환경 분리

프로젝트 구조:
terraform/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       └── terraform.tfvars
└── modules/
    ├── vpc/
    ├── ec2/
    └── rds/

🔴 3단계: “자동화 & 트러블슈팅” (DevOps/SRE)

고급 영역

백엔드 개발자보다는 인프라 전담(DevOps/SRE) 역할

고급 스킬

  1. Import 기존 리소스
  2. CI/CD 파이프라인 구축
  3. Terratest (Go 언어 테스트)
  4. 커스텀 Provider 개발

3. Remote State 구성 (실습)

🎯 시나리오: S3 + DynamoDB Backend 구성

Step 1: Backend 리소스 생성

# 📄 setup-backend.tf
# ⚠️ 주의: 이 파일은 별도로 먼저 실행!
 
terraform {
  required_version = ">= 1.0"
}
 
provider "aws" {
  region = "ap-northeast-2"
}
 
# 📊 S3 버킷 (State 저장)
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state-${random_string.suffix.result}"
 
  lifecycle {
    prevent_destroy = true  # 실수로 삭제 방지!
  }
 
  tags = {
    Name        = "Terraform State Bucket"
    Environment = "All"
  }
}
 
resource "random_string" "suffix" {
  length  = 8
  special = false
  upper   = false
}
 
# 📊 S3 버킷 버저닝 (State 히스토리)
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
 
  versioning_configuration {
    status = "Enabled"
  }
}
 
# 📊 S3 버킷 암호화
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
 
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}
 
# 📊 S3 퍼블릭 액세스 차단
resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
 
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
 
# 📊 DynamoDB 테이블 (Lock)
resource "aws_dynamodb_table" "terraform_lock" {
  name           = "terraform-state-lock"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"
 
  attribute {
    name = "LockID"
    type = "S"
  }
 
  lifecycle {
    prevent_destroy = true  # 실수로 삭제 방지!
  }
 
  tags = {
    Name        = "Terraform State Lock Table"
    Environment = "All"
  }
}
 
# 📤 출력
output "s3_bucket_name" {
  description = "Terraform State S3 버킷 이름"
  value       = aws_s3_bucket.terraform_state.bucket
}
 
output "dynamodb_table_name" {
  description = "Terraform Lock DynamoDB 테이블 이름"
  value       = aws_dynamodb_table.terraform_lock.name
}
 
output "backend_configuration" {
  description = "Backend 설정 (복사해서 사용)"
  value = <<-EOT
    terraform {
      backend "s3" {
        bucket         = "${aws_s3_bucket.terraform_state.bucket}"
        key            = "path/to/terraform.tfstate"
        region         = "ap-northeast-2"
        encrypt        = true
        dynamodb_table = "${aws_dynamodb_table.terraform_lock.name}"
      }
    }
  EOT
}

Step 2: Backend 리소스 생성

# 1. Backend 리소스 생성
cd setup-backend
terraform init
terraform apply
 
# 출력 확인:
# s3_bucket_name = "my-terraform-state-a1b2c3d4"
# dynamodb_table_name = "terraform-state-lock"

Step 3: 실제 프로젝트에서 Backend 사용

# 📄 main.tf (실제 인프라 코드)
terraform {
  required_version = ">= 1.0"
 
  # Remote Backend 설정
  backend "s3" {
    bucket         = "my-terraform-state-a1b2c3d4"  # 위에서 생성한 버킷
    key            = "production/terraform.tfstate"  # State 파일 경로
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"  # Lock 테이블
  }
 
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
 
provider "aws" {
  region = "ap-northeast-2"
}
 
# 📊 실제 리소스
resource "aws_instance" "web" {
  ami           = "ami-0c9c942bd7bf113a2"
  instance_type = "t3.micro"
 
  tags = {
    Name = "production-web-server"
  }
}

Step 4: 협업 테스트

터미널 1 (개발자 A):

cd production
terraform init  # Backend 연결
terraform apply
 
# State Locking 동작:
Acquiring state lock. This may take a few moments...

터미널 2 (개발자 B, 동시 실행):

cd production
terraform apply
 
# Lock 대기:
Error: Error acquiring the state lock
 
Error message: ConditionalCheckFailedException
Lock Info:
  ID:        a1b2c3d4-5e6f-7g8h-9i0j-k1l2m3n4o5p6
  Path:      my-terraform-state-a1b2c3d4/production/terraform.tfstate
  Operation: OperationTypeApply
  Who:       developer-a@company.com
  Created:   2025-12-31 10:30:15.123456789 +0000 UTC
 
Terraform acquires a state lock to protect the state from being written
by multiple users at the same time. Please resolve the issue above and try
again. For most commands, you can disable locking with the "-lock=false"
flag, but this is not recommended.

결과:

  • ✅ 개발자 A가 작업 중일 때 개발자 B는 대기
  • ✅ 충돌 방지
  • ✅ 안전한 협업

4. 모듈 설계 패턴

📦 모듈이란?

모듈 = 함수

프로그래밍에서 함수를 만드는 것처럼, Terraform에서도 재사용 가능한 모듈을 만듭니다.

실전 예제: EC2 모듈

디렉토리 구조

terraform/
├── modules/
│   └── ec2-instance/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── environments/
    ├── dev/
    │   └── main.tf
    └── prod/
        └── main.tf

📄 modules/ec2-instance/main.tf

# 📊 보안 그룹
resource "aws_security_group" "instance" {
  name        = "${var.name}-sg"
  description = "Security group for ${var.name}"
  vpc_id      = var.vpc_id
 
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      description = ingress.value.description
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  tags = merge(
    var.tags,
    {
      Name = "${var.name}-sg"
    }
  )
}
 
# 📊 EC2 인스턴스
resource "aws_instance" "this" {
  ami           = var.ami_id
  instance_type = var.instance_type
  subnet_id     = var.subnet_id
 
  vpc_security_group_ids = [aws_security_group.instance.id]
 
  user_data = var.user_data
 
  root_block_device {
    volume_size = var.root_volume_size
    volume_type = "gp3"
    encrypted   = true
  }
 
  tags = merge(
    var.tags,
    {
      Name = var.name
    }
  )
 
  lifecycle {
    create_before_destroy = var.create_before_destroy
  }
}

📄 modules/ec2-instance/variables.tf

variable "name" {
  description = "인스턴스 이름"
  type        = string
}
 
variable "vpc_id" {
  description = "VPC ID"
  type        = string
}
 
variable "subnet_id" {
  description = "서브넷 ID"
  type        = string
}
 
variable "ami_id" {
  description = "AMI ID"
  type        = string
}
 
variable "instance_type" {
  description = "인스턴스 타입"
  type        = string
  default     = "t3.micro"
}
 
variable "ingress_rules" {
  description = "인바운드 규칙"
  type = list(object({
    description = string
    port        = number
    cidr_blocks = list(string)
  }))
  default = []
}
 
variable "user_data" {
  description = "User Data 스크립트"
  type        = string
  default     = ""
}
 
variable "root_volume_size" {
  description = "루트 볼륨 크기 (GB)"
  type        = number
  default     = 20
}
 
variable "create_before_destroy" {
  description = "재생성 시 새 인스턴스를 먼저 생성"
  type        = bool
  default     = true
}
 
variable "tags" {
  description = "태그"
  type        = map(string)
  default     = {}
}

📄 modules/ec2-instance/outputs.tf

output "instance_id" {
  description = "인스턴스 ID"
  value       = aws_instance.this.id
}
 
output "public_ip" {
  description = "공인 IP"
  value       = aws_instance.this.public_ip
}
 
output "private_ip" {
  description = "사설 IP"
  value       = aws_instance.this.private_ip
}
 
output "security_group_id" {
  description = "보안 그룹 ID"
  value       = aws_security_group.instance.id
}

📄 environments/dev/main.tf (모듈 사용)

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-a1b2c3d4"
    key            = "dev/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}
 
provider "aws" {
  region = "ap-northeast-2"
}
 
# 📊 개발 웹 서버 (모듈 사용)
module "dev_web_server" {
  source = "../../modules/ec2-instance"
 
  name          = "dev-web-server"
  vpc_id        = "vpc-xxx"
  subnet_id     = "subnet-xxx"
  ami_id        = "ami-0c9c942bd7bf113a2"
  instance_type = "t3.micro"
 
  ingress_rules = [
    {
      description = "HTTP"
      port        = 80
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      description = "SSH"
      port        = 22
      cidr_blocks = ["1.2.3.4/32"]  # 사무실 IP만
    }
  ]
 
  tags = {
    Environment = "Development"
    ManagedBy   = "Terraform"
  }
}
 
output "dev_web_server_ip" {
  value = module.dev_web_server.public_ip
}

📄 environments/prod/main.tf (동일 모듈, 다른 설정)

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-a1b2c3d4"
    key            = "prod/terraform.tfstate"  # 다른 State!
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
  }
}
 
provider "aws" {
  region = "ap-northeast-2"
}
 
# 📊 운영 웹 서버 (같은 모듈, 다른 설정)
module "prod_web_server" {
  source = "../../modules/ec2-instance"
 
  name          = "prod-web-server"
  vpc_id        = "vpc-yyy"
  subnet_id     = "subnet-yyy"
  ami_id        = "ami-0c9c942bd7bf113a2"
  instance_type = "t3.small"  # 더 큰 인스턴스
 
  ingress_rules = [
    {
      description = "HTTPS"
      port        = 443
      cidr_blocks = ["0.0.0.0/0"]
    }
  ]
 
  root_volume_size = 50  # 더 큰 디스크
 
  tags = {
    Environment = "Production"
    ManagedBy   = "Terraform"
    CriticalLevel = "High"
  }
}
 
# 📊 운영 서버는 삭제 방지!
resource "null_resource" "prevent_destroy_prod" {
  lifecycle {
    prevent_destroy = true
  }
}

5. 환경 분리 전략

🎯 3가지 환경 분리 방법

방법 1: 폴더 분리 (권장 ⭐)

terraform/
├── environments/
│   ├── dev/
│   │   ├── backend.tf      # S3 key: "dev/..."
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── backend.tf      # S3 key: "staging/..."
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── backend.tf      # S3 key: "prod/..."
│       ├── main.tf
│       └── terraform.tfvars
└── modules/
    └── ...

장점:

  • ✅ 명확한 분리 (실수로 prod 건드릴 확률 ↓)
  • ✅ 환경별 다른 Provider 설정 가능
  • ✅ 환경별 다른 State

단점:

  • ❌ 코드 중복 가능성

방법 2: Workspace 사용

# Workspace 생성
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
 
# Workspace 전환
terraform workspace select dev
terraform apply
 
# 현재 Workspace 확인
terraform workspace show
# 📄 main.tf
locals {
  environment = terraform.workspace
 
  instance_type = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.medium"
  }[local.environment]
}
 
resource "aws_instance" "web" {
  ami           = "ami-xxx"
  instance_type = local.instance_type
 
  tags = {
    Environment = local.environment
  }
}

장점:

  • ✅ 코드 중복 없음
  • ✅ 빠른 환경 전환

단점:

  • ❌ 실수로 잘못된 workspace에서 apply 가능
  • ❌ 환경별 큰 차이가 있으면 복잡

방법 3: Git 브랜치 (비권장)

main (prod) ─┬─ dev
              └─ staging

왜 비권장?

  • ❌ 환경별 코드가 diverge (분리)
  • ❌ 머지 충돌
  • ❌ 관리 복잡

🛡️ 프로덕션 보호 설정

# 📄 environments/prod/main.tf
 
# 방법 1: prevent_destroy
resource "aws_instance" "prod_web" {
  # ...
 
  lifecycle {
    prevent_destroy = true  # destroy 금지!
  }
}
 
# 방법 2: 보호된 State Key
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
 
    # State 삭제 방지
    skip_metadata_api_check = false
  }
}
 
# 방법 3: AWS Organizations SCP로 삭제 방지
# (Terraform 외부, AWS 콘솔에서 설정)

6. CI/CD 통합

🤖 GitHub Actions 자동화

📄 .github/workflows/terraform.yml

name: Terraform CI/CD
 
on:
  pull_request:
    paths:
      - 'terraform/**'
  push:
    branches:
      - main
    paths:
      - 'terraform/**'
 
env:
  AWS_REGION: ap-northeast-2
  TF_VERSION: 1.6.0
 
jobs:
  terraform-check:
    name: Terraform Check
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
 
    steps:
      - name: Checkout
        uses: actions/checkout@v3
 
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}
 
      - 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: ${{ env.AWS_REGION }}
 
      - name: Terraform Format Check
        working-directory: ./terraform
        run: terraform fmt -check -recursive
 
      - name: Terraform Init
        working-directory: ./terraform
        run: terraform init
 
      - name: Terraform Validate
        working-directory: ./terraform
        run: terraform validate
 
      - name: Terraform Plan
        working-directory: ./terraform
        run: terraform plan -no-color
        continue-on-error: true
 
      - name: Comment PR
        uses: actions/github-script@v6
        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>
 
            \`\`\`
            ${{ steps.plan.outputs.stdout }}
            \`\`\`
 
            </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
            })
 
  terraform-apply:
    name: Terraform Apply
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    environment:
      name: production
 
    steps:
      - name: Checkout
        uses: actions/checkout@v3
 
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}
 
      - 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: ${{ env.AWS_REGION }}
 
      - name: Terraform Init
        working-directory: ./terraform
        run: terraform init
 
      - name: Terraform Apply
        working-directory: ./terraform
        run: terraform apply -auto-approve
 
      - name: Notify Slack
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Terraform Apply ${{ job.status }}'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

📋 워크플로우 설명

Pull Request 시:

1. terraform fmt -check    # 포맷 확인
2. terraform init          # 초기화
3. terraform validate      # 검증
4. terraform plan          # 계획
5. PR에 Plan 결과 댓글 추가  # 👀 리뷰어가 확인

Main 브랜치 Merge 시:

1. terraform init
2. terraform apply -auto-approve
3. Slack 알림 전송

7. 트러블슈팅 시나리오

🔥 시나리오 1: State Lock 해제

문제:

Error: Error acquiring the state lock
Lock Info:
  ID: abc123-def456
  Who: developer@company.com
  Created: 2025-12-30 10:00:00

원인:

  • 이전 apply가 중단되어 Lock이 남음
  • 네트워크 오류로 Lock 해제 실패

해결:

# 1. Lock 정보 확인
terraform force-unlock abc123-def456
 
# 2. 확인 메시지
Do you really want to force-unlock?
  yes
 
# ⚠️ 주의: 다른 사람이 정말 작업 중이면 안 됨!
# 먼저 팀원에게 확인!

🔥 시나리오 2: State 파일 손상

문제:

Error: state snapshot was created by Terraform v1.5.0, which is newer than current v1.4.0

해결:

# 1. Terraform 업그레이드
brew upgrade hashicorp/tap/terraform
 
# 2. State 백업 (S3 버저닝 활용)
aws s3api list-object-versions \
  --bucket my-terraform-state \
  --prefix production/terraform.tfstate
 
# 3. 이전 버전 복구
aws s3api get-object \
  --bucket my-terraform-state \
  --key production/terraform.tfstate \
  --version-id <VERSION_ID> \
  terraform.tfstate.backup

🔥 시나리오 3: 잘못된 리소스 삭제 복구

문제:

실수로 terraform destroy 실행
→ RDS 데이터베이스 삭제됨!

해결:

# 1. RDS 자동 백업에서 복구
aws rds describe-db-snapshots \
  --db-instance-identifier prod-db
 
# 2. 스냅샷에서 복원
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier prod-db-restored \
  --db-snapshot-identifier rds:prod-db-2025-12-30-03-00
 
# 3. Terraform State에 Import
terraform import aws_db_instance.main prod-db-restored
 
# 4. 코드 업데이트
# (identifier를 prod-db-restored로 변경)

8. 면접 대비 Q&A

🎤 면접관: “Terraform 써보셨나요?”

❌ 약한 답변

“네, EC2 만들어봤습니다.”

✅ 강한 답변

“네, Terraform으로 AWS 인프라를 프로비저닝 해봤습니다.

특히 협업을 위해 S3와 DynamoDB를 활용해 Remote State와 Locking을 구성했고, 반복되는 리소스는 모듈로 만들어 재사용성을 높이는 데 중점을 두었습니다.

Dev/Staging/Prod 환경을 폴더로 분리하여 실수로 운영 환경을 건드리지 않도록 했고, GitHub Actions를 통해 PR 시 자동으로 terraform plan 결과를 댓글로 확인할 수 있도록 CI/CD도 구축했습니다.”

🎤 면접관: “State 파일이 왜 중요한가요?”

✅ 정답

“State 파일은 Terraform이 관리하는 인프라의 현재 상태를 저장하는 파일입니다.

만약 State 파일 없이 terraform apply를 실행하면, Terraform은 이미 존재하는 리소스를 새로 만들려고 시도해서 충돌이 발생합니다.

특히 팀 협업 시에는 State 파일을 S3 같은 원격 저장소에 두고, DynamoDB로 Lock을 걸어서 동시에 여러 사람이 apply 하는 것을 방지해야 합니다.”

🎤 면접관: “모듈은 언제 사용하나요?”

✅ 정답

“같은 패턴의 리소스가 2번 이상 반복될 때 모듈로 만듭니다.

예를 들어 Dev/Staging/Prod 환경에서 동일한 구조의 EC2 인스턴스를 만든다면, 코드를 복사-붙여넣기 하는 대신 모듈로 만들어서 변수만 바꿔가며 재사용합니다.

이렇게 하면 유지보수가 쉽고, 한 곳만 수정하면 모든 환경에 적용되므로 일관성을 유지할 수 있습니다.”


💡 실무 체크리스트

✅ 신입 엔지니어 (1단계)

  • terraform plan 결과를 읽고 위험한 변경 감지 가능
  • 변수(variables.tf)와 출력(outputs.tf) 작성 가능
  • Git에 민감 정보 커밋하지 않기
  • terraform destroy 전에 선임에게 확인

✅ 주니어 엔지니어 (2단계)

  • Remote State (S3 + DynamoDB) 구성 가능
  • 간단한 모듈 작성 및 사용 가능
  • Dev/Staging/Prod 환경 분리 이해
  • terraform import로 기존 리소스 가져오기
  • PR에서 terraform plan 결과 확인 습관화

✅ DevOps 엔지니어 (3단계)

  • 복잡한 모듈 설계 (VPC, EKS 등)
  • CI/CD 파이프라인 구축 (GitHub Actions, Atlantis)
  • Terratest로 인프라 테스트 코드 작성
  • State 복구 및 트러블슈팅 능력
  • 팀 Terraform 컨벤션 수립 및 교육

🎯 다음 단계

지금 바로 실습:

# 1. Remote State 구성
cd ~/terraform-practice
mkdir setup-backend
# setup-backend.tf 파일 작성 (위 예제 참고)
terraform init
terraform apply
 
# 2. 실제 프로젝트에 Backend 적용
mkdir my-app
cd my-app
# main.tf에 backend "s3" 설정 추가
terraform init
 
# 3. 팀원과 함께 테스트
# 동시에 terraform apply 해보고 Lock 확인!

학습 순서:

  1. Remote State 구성 (오늘!)
  2. 간단한 모듈 만들어보기 (내일)
  3. 환경 분리 연습 (3일차)
  4. GitHub Actions 설정 (4일차)

📚 참고 자료


Created: 2025-12-31 Tags: terraform collaboration remote-state module ci-cd 실무 Category: Terraform/고급 난이도: 🟡 중급 (실무 필수)