테라폼 운영 강좌 #5 대규모 구조화: 스택 분할 기준, 스택 간 참조, Terragrunt 도입 시점

5 분 소요

실전 #9의 modules와 envs 구조는 환경을 분리했지만, 환경 하나의 안은 여전히 state 하나입니다. myapp 규모에서는 그것으로 충분합니다. 그런데 리소스가 수백 개가 되고 여러 팀이 같은 코드를 만지기 시작하면 state 하나가 병목이 됩니다. 이번 편은 그 시점에 필요한 구조 이야기입니다. 미리 말해 두면, 이 구조들은 커지기 전에 도입하면 오히려 짐입니다. 각 단계의 도입 신호를 함께 정리하는 이유입니다.

state 하나가 커지면 생기는 세 가지 문제 #

  1. plan이 느려집니다: plan은 관리 중인 모든 리소스를 refresh 합니다(기초 #5). 리소스 수백 개면 태그 하나 고치는 plan에도 몇 분이 걸립니다.
  2. 블라스트 반경이 커집니다: 실수 한 번의 영향권이 state 전체입니다. 앱 설정을 고치다 잘못된 plan을 승인하면 네트워크까지 갈 수 있습니다.
  3. 잠금 경합이 생깁니다: state 하나에 apply는 동시에 하나입니다(기초 #9). 팀이 여럿이면 서로의 배포를 기다리는 줄이 생깁니다.

스택 분할: 나누는 기준 세 가지 #

해법은 state를 여러 개의 스택(stack)으로 나누는 것입니다. 스택은 자기 backend key를 가진 독립 실행 단위로, envs 안을 다시 나눈다고 생각하면 됩니다.

폴더 구조
envs/prod/
├── network/    # VPC, 서브넷, NAT       (거의 안 바뀜)
├── data/       # RDS, S3, 시크릿        (가끔 바뀜)
└── app/        # ECS, ALB, 오토스케일링 (매주 바뀜)

무엇을 기준으로 자를 것인가가 관건이고, 실무에서 검증된 기준은 셋입니다.

  • 변경 빈도: 매주 바뀌는 앱 계층과 분기에 한 번 바뀌는 네트워크가 한 state에 있으면, 잦은 배포마다 네트워크가 블라스트 반경에 들어갑니다. 빈도가 다른 것끼리 나눕니다.
  • 수명: 기초 #8의 모듈 기준과 같은 원리입니다. 함께 만들어지고 함께 사라지는 것끼리 묶습니다.
  • 소유 팀: 팀이 다르면 스택을 나눠 각자 배포 줄을 갖게 합니다. 잠금 경합의 직접 해소입니다.

myapp을 위 셋으로 나누면, 앱 배포 plan은 앱 스택의 리소스만 refresh 하니 빨라지고, 실수의 영향권도 그 스택 안으로 줄어듭니다.

스택 간 참조: remote state와 파라미터 #

나누는 순간 새 문제가 생깁니다. app 스택이 network 스택의 서브넷 ID를 알아야 합니다. 방법은 둘입니다.

첫째는 terraform_remote_state 데이터 소스입니다. 다른 스택의 state를 직접 읽어 output을 가져옵니다.

envs/prod/app/main.tf
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "myapp-tfstate"
    key    = "myapp/prod/network/terraform.tfstate"
    region = "ap-northeast-2"
  }
}

# 사용: data.terraform_remote_state.network.outputs.private_subnet_ids

직관적이지만 결합이 강합니다. 읽는 쪽이 상대 state 파일 전체에 접근해야 하고(기초 #9에서 본 대로 state에는 민감 정보가 있을 수 있습니다), 상대 스택의 output 이름 변경이 곧바로 이쪽의 깨짐이 됩니다.

둘째는 공유 값을 SSM 파라미터로 발행하는 방식입니다. network 스택이 서브넷 ID를 SSM에 쓰고(aws_ssm_parameter 리소스), app 스택은 SSM에서 읽습니다(data 소스). 한 단계 번거롭지만 스택 간 계약이 “파라미터 경로"라는 좁은 인터페이스로 명시되고, state 접근 권한을 팀 간에 나눠 줄 필요가 없습니다. 팀 경계를 넘는 참조는 파라미터 방식, 한 팀 안의 스택끼리는 remote state로 간단히, 정도가 무난한 절충입니다.

모노레포 운영 규약 #

스택이 늘어도 저장소는 하나(모노레포)로 두는 것이 일반적입니다. 모듈 공유와 원자적 변경(모듈과 사용처를 한 PR에)이 쉽기 때문입니다. 대신 규약이 필요해집니다.

  • CI 경로 필터: #1의 paths 필터를 스택 단위로 확장해, 바뀐 스택의 plan만 돌게 합니다.
  • CODEOWNERS: 스택 디렉터리별로 소유 팀을 지정해 리뷰 라우팅을 자동화합니다.
  • 모듈 버전 정책: 공유 모듈이 바뀔 때 모든 스택이 즉시 따라갈지(모노레포 상대 경로), 스택별로 버전을 고를지(모듈 레지스트리·Git 태그 참조)를 정합니다. 팀이 적으면 전자, 많으면 후자가 편합니다.

Terragrunt: 보일러플레이트가 임계를 넘을 때 #

스택 × 환경이 늘면 새 반복이 눈에 들어옵니다. 스택 디렉터리마다 backend 블록과 provider 설정이 거의 같은 내용으로 복사되어 있는 것입니다. 스택이 5개면 손으로 관리할 만하고, 50개면 지옥입니다. Terragrunt는 이 반복을 없애는 래퍼 도구로, 공통 설정을 한 파일에 두면 각 스택의 설정 파일은 몇 줄로 줄어들고, 여러 스택을 의존 순서대로 한 번에 실행하는 기능도 줍니다. 도입 판단 기준은 단순하게 잡는 것을 권합니다. backend·provider 보일러플레이트 복사가 실제로 고통이 된 시점, 대략 스택 수십 개부터입니다. myapp 규모에서 미리 도입하면 도구 한 겹만 늘어난다는 것이 커뮤니티의 일치된 경험담입니다.

정리 #

이번 글에서 다룬 내용입니다.

  • 큰 state는 plan 지연, 블라스트 반경, 잠금 경합을 만듭니다. 해법은 스택 분할이며 기준은 변경 빈도, 수명, 소유 팀입니다
  • 스택 간 참조는 remote state(간단·강결합)와 SSM 파라미터(명시적 계약·약결합) 중 경계의 성격으로 고릅니다
  • 모노레포는 경로 필터, CODEOWNERS, 모듈 버전 정책의 세 규약과 함께 운영합니다
  • Terragrunt는 backend·provider 보일러플레이트의 고통이 실재할 때(스택 수십 개) 도입합니다. 미리 도입하면 짐입니다
  • 이 편의 모든 구조는 “커지기 전 도입은 손해"라는 단서가 붙습니다. 도입 신호를 기억해 두는 것이 구조 자체보다 중요합니다

다음 글(#6 실행 플랫폼 비교)에서는 지금까지 GitHub Actions로 직접 짠 것들을 상품으로 제공하는 플랫폼들(HCP Terraform, Atlantis, Spacelift)을 비교하고 선택 기준을 세웁니다.

X