왜 환경 분리가 문제가 되는가

dev, staging, prod를 하나의 테라폼 코드로 관리할 때 가장 흔한 사고는 잘못된 환경에 apply하는 것이다. 상태 파일(state)이 섞이거나, 프로덕션 변수 값이 개발 환경에 새어 들어가면 복구 비용이 크다. 테라폼이 제공하는 두 가지 접근, 즉 워크스페이스(workspace)와 디렉터리 분리는 각각 격리 수준과 운영 편의성에서 트레이드오프가 다르다. 어느 쪽을 선택하느냐가 실수의 발생 확률을 결정한다.

워크스페이스 방식의 동작과 한계

워크스페이스는 동일한 코드와 백엔드를 공유하면서 상태만 env:/<workspace>/ 경로로 분리한다. 코드 중복이 없어 간편하지만, 백엔드 설정(버킷, 계정)이 공유되므로 진짜 격리가 아니다.

terraform workspace new prod
terraform workspace select prod

# main.tf 안에서 환경 분기
locals {
  env = terraform.workspace
  instance_type = {
    dev  = "t3.small"
    prod = "m5.large"
  }[terraform.workspace]
}

문제는 terraform.workspace가 문자열이라 조건 분기가 코드 전반에 흩어진다는 점이다. 또 terraform init은 워크스페이스를 구분하지 않으므로, prod에 있다는 걸 잊고 apply하기 쉽다. 프롬프트에 현재 워크스페이스를 표시하지 않으면 위험하다.

디렉터리 분리 방식

환경마다 별도 디렉터리를 두고, 각자 독립된 백엔드와 tfvars를 가진다. 코드 중복은 늘지만 격리가 명확하고, prod 백엔드를 다른 계정/버킷으로 완전히 분리할 수 있다.

envs/
  dev/
    backend.tf      # dev-tfstate 버킷
    terraform.tfvars
  prod/
    backend.tf      # prod-tfstate 버킷(별도 계정)
    terraform.tfvars
modules/
  network/
  compute/

공통 로직은 modules/에 두고 각 환경 디렉터리는 모듈 호출과 값만 담는다. 중복은 코드가 아니라 얇은 호출부에 한정된다.

# envs/prod/main.tf
module "network" {
  source     = "../../modules/network"
  cidr_block = var.cidr_block
  env        = "prod"
}

두 방식 비교

기준워크스페이스디렉터리 분리
상태 격리같은 백엔드 내 경로 분리완전 분리(버킷/계정)
계정 분리어려움쉬움
코드 중복없음호출부에 소량
실수 위험높음(현재 상태 은닉)낮음(경로가 곧 환경)
적합 상황단기·유사 환경계정 분리된 실서비스

실무 권장: 디렉터리 기본, 워크스페이스는 보조

프로덕션과 개발이 서로 다른 클라우드 계정을 쓰는 조직이라면 디렉터리 분리가 사실상 표준이다. 계정 경계가 곧 폭발 반경(blast radius)의 경계가 되기 때문이다. 워크스페이스는 동일 계정 안에서 PR별 임시 환경, 리뷰 환경처럼 수명이 짧고 구조가 같은 경우에 적합하다. 두 방식을 섞어 쓰는 것도 가능하다. 디렉터리로 계정을 나누고, 그 안에서 워크스페이스로 단기 인스턴스를 파생시키는 식이다.

주의점과 안전장치

어느 방식이든 apply 전에 대상 환경을 강제로 확인시키는 장치가 필요하다. CI에서 prod 변경은 수동 승인 단계를 두고, 로컬에서는 셸 프롬프트에 워크스페이스나 디렉터리를 노출한다.

deploy:
  stage: deploy
  script:
    - cd envs/${TARGET_ENV}
    - terraform init
    - terraform plan -out=tfplan
    - terraform apply tfplan
  rules:
    - if: '$TARGET_ENV == "prod"'
      when: manual   # prod는 반드시 수동 승인

추가로 상태 잠금(state locking)을 반드시 활성화하고, prod 백엔드에는 버전 관리와 삭제 방지를 건다. 워크스페이스를 쓴다면 CI 스크립트 첫 줄에서 terraform workspace select를 명시해 기본 워크스페이스로 실수하는 경로를 막아야 한다. 격리는 코드가 아니라 백엔드와 파이프라인 규칙에서 완성된다.