Infrastructure as Code

State와 Module

meellon 2026. 7. 3. 19:00

학습 목표

Terraform state가 멱등·plan·팀 협업에 왜 필요한지 설명할 수 있다.

local stateremote state(S3 등) ·state locking 차이를 설명할 수 있다.

module로 인프라를 재사용하고 root module에서 variable·output으로 연결할 수 있다.

terraform initmodule source·backend 마이그레이션 흐름을 설명할 수 있다.

state 민감 정보·충돌 위험과 실무 운영 습관을 짚을 수 있다.

문제 상황

  • 동료와 같은 VPC를 각자 terraform apply — 리소스 이중 생성·state 덮어쓰기
  • terraform.tfstateGit에 커밋 — RDS password가 평문 노출
  • laptop state만 있어 CI apply가 매번 전부 create 시도
  • staging·prod 코드 복붙 — SG rule 하나 바꿀 때 두 repo 수정
  • module source로컬 경로로만 쓰다 팀원 PC에서 경로 not found
  • terraform apply 중 다른 사람 apply — state corruption (lock 없음)
  • 3편에서 로컬 state로 S3 하나 올렸는데 — 팀·환경으로 확장이 막힘

앞 편에서 HCL·init/plan/applystate 개념을 봤다. 이번 편은 remote state·lockingmodule 조합으로 협업·재사용을 가능하게 한다. workspace·환경 디렉터리는 5편이다.

1. State란?

Terraform state실제 클라우드 리소스와 HCL의 매핑 테이블이다.

state에 담기는 것

  • resource address → cloud ID (aws_s3_bucket.artifactsarn:...)
  • 속성 스냅샷 — plan 시 API refresh와 비교
  • metadata — serial, lineage, (backend 설정 시) lock info
{
  "resources": [
    {
      "type": "aws_s3_bucket",
      "name": "artifacts",
      "instances": [
        {
          "attributes": {
            "id": "myapp-artifacts-staging",
            "arn": "arn:aws:s3:::myapp-artifacts-staging"
          }
        }
      ]
    }
  ]
}
  • desired는 Git, ID 매핑은 state — 둘이 합쳐져 plan이 동작
  • state 없으면 Terraform은 "이미 존재"를 모름 → 중복 create 시도

local vs remote

  Local state Remote state
파일 terraform.tfstate (워킹 디렉터리) S3·GCS·Terraform Cloud 등
협업 불가 (복사·충돌) 공유 backend
CI state 없음 동일 backend 참조
locking 없음 DynamoDB·native lock
보안 .gitignore 필수 암호화·IAM·버전
  • 솔로·학습 — local OK
  • 팀·CI·prodremote가 기본

2. Remote backend와 locking

AWS에서 흔한 패턴: S3 + DynamoDB.

# code/backend-s3.example.tf
terraform {
  backend "s3" {
    bucket         = "my-org-tf-state"
    key            = "staging/app/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}
구성 역할
S3 bucket state 파일 저장, versioning 권장
key 스택별 경로 (env/service/terraform.tfstate)
encrypt at-rest 암호화 (SSE-S3/KMS)
DynamoDB lock 테이블 — 동시 apply 방지

lock 동작

User A: apply 시작 → lock 획득
User B: apply 시도 → Error: lock already held
User A: apply 완료 → lock 해제
User B: 재시도 → 성공
  • plan도 lock 옵션 가능 (-lock=false비상만)
  • stale lock — 중단된 apply 후 DynamoDB에 lock 잔류 → force-unlock (신중)

backend 마이그레이션

로컬 → remote 전환:

# backend 블록 추가 후
terraform init -migrate-state
# 기존 local state를 S3로 복사할지 질문 → yes
  • key 설계 — 환경·서비스·계층별 분리 (5편 workspace와 조합)
  • state bucket별도 스택·계정 — 앱 리소스와 lifecycle 분리

state 보안

  • 민감 값password, private_key가 state에 평문 저장될 수 있음
  • 조치
    • state bucket IAM 최소 권한
    • KMS·버전·접근 로그
    • Git 커밋 금지.gitignore*.tfstate*
    • 가능하면 Sensitive·외부 secret store (Security·22편)

3. Module

module.tf 묶음을 이름 붙여 재사용.

구조 예

4-terraform-state-module/code/stack/
├── main.tf              # module 호출
├── variables.tf
├── outputs.tf
├── backend.tf
└── modules/
    └── network/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

child module (network)

# modules/network/variables.tf
variable "vpc_cidr" {
  type = string
}

variable "environment" {
  type = string
}
# modules/network/main.tf
resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
  tags = {
    Name = "vpc-${var.environment}"
  }
}
# modules/network/outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

root module에서 호출

# code/stack/main.tf
module "network" {
  source = "./modules/network"

  vpc_cidr    = var.vpc_cidr
  environment = var.environment
}

resource "aws_security_group" "app" {
  name   = "app-sg"
  vpc_id = module.network.vpc_id
}
  • source — 로컬 ./modules/..., Git git::https://..., registry terraform-aws-modules/vpc/aws
  • module outputmodule.network.vpc_id참조
  • child 안 resource는 state에 module.network.aws_vpc.main 주소로 기록

variable · output 흐름

방향 용도
root → module variable입력
module → root output으로 값 반환
module → module root가 중개 (형제 직접 참조 지양)
# root outputs.tf — CI·다른 stack에 전달
output "vpc_id" {
  value = module.network.vpc_id
}
  • 버전 pin — registry module은 version = "~> 5.0"
  • 작은 module — VPC, SG, RDS 등 한 책임 — root는 조합

4. init · plan · apply (module·backend)

단계 module·state 관점
init module 다운로드·.terraform/modules, backend 설정
plan remote state 읽기, module 리소스 전개 후 diff
apply lock → API → state 갱신 (S3)
cd code/stack
terraform init          # modules + backend
terraform plan
terraform apply
  • module 코드 변경 → terraform init -upgrade (provider·module 버전)
  • root module = apply 단위 — state 하나 per backend key

5. 실무 패턴

패턴 설명
mono repo + multi key repo 하나, backend key로 env·stack 분리
module registry 공통 VPC·EKS를 버전 태그 module로
composition root modules/network + modules/data + modules/app
plan in CI PR에 plan comment, apply는 main·승인 후
state 분리 network state ≠ app state — blast radius·권한 분리
  • anti-pattern — 거대 single module, state 한 파일에 전 계정
  • drift·import — 20편; policy on plan — 21편

정리

  • State — address → ID; plan·멱등·협업의 기준
  • Remote + lock — S3·DynamoDB로 공유·동시성 제어
  • Module — variable in · output out; root가 조합
  • init — backend·module 준비; key·source 설계가 운영의 출발점
  • 환경 분리 — workspace·directory·tfvars는 5편

다음에 다룰 것

  • 환경과 Workspace
  • dev/staging/prod 분리, workspace vs directory, tfvars

해당 내용은 HashiCorp Terraform Documentation (State, Modules), Brikman — Terraform: Up & Running, 3/E Ch 3~4 를 기반으로 합니다.

'Infrastructure as Code' 카테고리의 다른 글

AWS CDK 개요  (0) 2026.07.07
환경과 Workspace  (0) 2026.07.05
Terraform 기초  (0) 2026.07.01
Infrastructure as Code란  (0) 2026.06.30
IaC 오리엔테이션  (0) 2026.06.29