IaC가 필요한 이유
클릭으로 만든 인프라는 재현이 불가능합니다. Terraform으로 인프라를 코드로 정의하면 버전 관리, 코드 리뷰, 자동화가 가능해집니다.
프로젝트 디렉터리 구조
terraform/
├── environments/
│ ├── prod/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ └── staging/
└── modules/
├── vpc/
├── eks/
└── rds/모듈 설계
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
tags = merge(var.common_tags, {
Name = "${var.project}-${var.environment}-vpc"
})
}
variable "cidr_block" {
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "유효한 CIDR 형식이어야 합니다."
}
}상태(State) 원격 관리
terraform {
backend "s3" {
bucket = "my-company-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}# State 잠금 테이블 생성
aws dynamodb create-table \
--table-name terraform-state-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUESTCI/CD 통합
name: Terraform
on:
pull_request:
paths: ['terraform/**']
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform fmt -check -recursive
- run: terraform validate
- run: terraform plan -out=tfplan실전 팁
tfstate 파일 절대 git에 커밋 금지 (.gitignore)
*.tfstate
*.tfstate.backup
.terraform/sensitive 변수 마스킹
variable "db_password" {
type = string
sensitive = true
}Terraform은 단순한 도구가 아닙니다. 팀의 인프라 운영 방식 자체를 바꾸는 문화적 전환입니다.
콘솔 수동 변경(드리프트)과 리팩터링 시 재생성 막기
state와 잠금을 갖춰도, 운영 중에는 누군가 콘솔에서 직접 바꾼 설정이나 모듈 구조 변경 때문에 plan이 예상치 못한 삭제·재생성을 제안하는 일이 생깁니다.
원인
- 장애 대응 중 콘솔에서 보안 그룹 규칙·인스턴스 크기를 직접 수정(드리프트)
- 리소스를 모듈 안으로 옮기거나 이름을 바꿔 주소가 달라짐 → Terraform은 "기존 삭제 + 새로 생성"으로 해석
- Terraform 밖에서 만든 리소스를 코드에 추가만 하고 state에 가져오지 않음
검증 명령
# 드리프트만 확인 (인프라 변경 없이 state와 실제 비교)
terraform plan -refresh-only
# CI용: 변경이 있으면 종료 코드 2
terraform plan -detailed-exitcode -out=tfplan; echo "exit=$?"
# 삭제·재생성 대상만 골라 보기
terraform show -json tfplan | jq -r '.resource_changes[] | select(.change.actions | index("delete")) | .address'해결
- 드리프트가 의도한 변경이면 코드에 반영하고, 아니면
apply로 코드 상태로 되돌립니다. - 리팩터링으로 주소가 바뀌면
moved블록을 추가해 삭제 없이 state 주소만 옮깁니다.
moved {
from = aws_security_group.web
to = module.web.aws_security_group.this
}- 기존 리소스는 Terraform 1.5 이상의
import블록으로 가져온 뒤plan이 "변경 없음"이 될 때까지 코드를 맞춥니다. - 참고: 최근 Terraform 버전의 S3 백엔드는
use_lockfile = true로 S3 자체 잠금을 지원하므로, 사용 중인 버전의 백엔드 문서에서 DynamoDB 잠금 권장 여부를 확인하세요.
재발 방지
-detailed-exitcode기반 드리프트 점검을 매일 스케줄로 돌려 종료 코드 2면 알림을 보냅니다.
참고: 공식 문서
이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.
모듈 경계 잡는 법
modules/
network/ # VPC, subnet, SG
data/ # RDS, bucket
compute/ # ASG, EKS node group
envs/
dev/
staging/
prod/- 리소스 단위 모듈보다 도메인 단위(network/data/compute)가 재사용·리뷰에 유리.
- 모듈
version을 고정(Git tag/registry).main추적 금지.
State·잠금
terraform {
backend "s3" {
bucket = "org-tf-state"
key = "prod/network/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "tf-locks"
encrypt = true
}
}| 항목 | 권장 |
|---|---|
| State 위치 | 원격(S3/GCS/Azure Blob) + 잠금 |
| 시크릿 | state에 평문 넣지 말 것 → Vault/SSM |
| 환경 분리 | workspace보다 디렉터리+별도 state |
CI 게이트
# PR: plan만, main: apply
- run: terraform fmt -check
- run: terraform validate
- run: terraform plan -out=tfplan
# apply는 protected branch + 수동 승인참고 자료
자주 묻는 질문 (FAQ)
Q. workspace로 dev/prod를 나눠도 되나요?
가능하지만 실수로 잘못된 workspace에 apply하기 쉽습니다. 디렉터리 분리가 더 안전합니다.
Q. state가 꼬이면?
terraform state list → rm/mv로 정리하고, 인프라와 드리프트를 refresh/import로 맞춥니다. 백업본 없이 state 파일을 직접 편집하지 마세요.
검증 환경·편집자 주
- 검증 시점: 2026-09 (문서·CLI 플래그는 벤더 업데이트에 따라 달라질 수 있음)
- 명령어는 읽기 전용 조회 위주이며, 프로덕션 적용 전 스테이징에서 plan/dry-run을 권장합니다.
- 본문은 공식 문서 링크와 운영에서 반복되는 실패 패턴을 기준으로 편집 검토했습니다.
실무 적용 순서 (요약)
- 현재 상태 측정(메트릭·비용·오류율) 없이 도구부터 바꾸지 말 것
- 가장 작은 범위에서 변경 → 롤백 조건 명시
- 체크리스트를 런북/티켓 템플릿에 옮겨 팀 공유
- 한 달 뒤 지표로 효과 재평가 후 확대
글마다 출처와 확인 범위가 다릅니다. 아래에 확인일·확인 범위·출처가 기록된 글만 그 범위에서 확인된 것입니다. 기록이 없는 글은 적용 전 사용 중인 버전의 공식 문서로 직접 확인하고, 오류는 이메일로 알려주세요. 편집 정책 · 이메일로 제보
이 글에는 사람이 확인한 날짜·범위 기록이 아직 없습니다.
댓글
첫 번째 댓글을 남겨보세요.