한 줄 정의

환경 은 특정 목적을 위한 인프라 리소스의 개념적 집합이며, 하나의 재사용 가능한 스택 프로젝트로 환경마다 별도의 스택 인스턴스를 만드는 것이 멀티 환경을 일관성 있게 구현하는 방법입니다.

쉽게 말하면

환경 만들기는 붕어빵 굽기입니다. 테스트·스테이징·프로덕션이라는 붕어빵 세 개가 필요할 때 선택지는 셋입니다.

세 마리를 한 덩어리로 붙여 구우면(멀티 환경 스택) 하나만 뒤집으려 해도 판 전체를 건드려야 하고, 태우면 셋 다 태웁니다.

틀 없이 손으로 하나씩 빚으면(복사-붙여넣기 환경) 처음엔 비슷해 보여도 구울수록 모양이 제각각으로 어긋납니다.

정답은 틀 하나(재사용 가능한 스택 프로젝트)로 한 번에 한 마리씩 굽는 것입니다. 모든 붕어빵이 같은 모양임이 보장되고, 하나를 태워도 나머지는 무사합니다. 팥이냐 슈크림이냐(환경별 파라미터)만 바꿔 넣습니다.

왜 중요한가?

멀티 환경은 동일한 시스템의 인스턴스를 여러 개 실행하기 위한 것이므로, 환경 간 일관성 유지가 핵심이고 이것이 코드형 인프라를 사용하는 주요 이유입니다.

한 환경에서 테스트한 인프라 코드가 다른 환경에서도 정상 동작한다는 확신은 환경들이 같은 코드에서 나올 때만 성립합니다.

스택과 환경의 구조를 잘못 잡으면 프로덕션이 다른 환경 변경의 폭발 반경에 들어가거나(멀티 환경 스택), 환경마다 구성 드리프트가 쌓여 테스트의 유효성이 무너집니다(복사-붙여넣기 환경).

핵심 내용

환경은 개념, 스택은 구현 수단

환경은 테스트 단계 지원이나 서비스 제공 같은 특정 목적을 중심으로 구성된 소프트웨어·인프라 리소스의 모음입니다.

스택은 인프라 리소스 모음을 정의하고 관리하는 수단이므로, 하나 이상의 스택을 사용하여 환경을 구현합니다.

하나의 스택에 여러 환경을 만들 수도 있지만 그렇게 해서는 안 됩니다. 이 장 전체가 그 이유에 대한 설명입니다.

멀티 환경의 두 가지 용도

딜리버리 환경

가장 친숙한 사용 사례로, ‘프로덕션으로 가는 경로’라고도 합니다. 애플리케이션 빌드가 테스트 → 스테이징 → 프로덕션 순으로 차례로 배포되며 서로 다른 개발·테스트 활동을 지원합니다.

멀티 프로덕션 환경

프로덕션에서 시스템의 완전하고 독립적인 복사본을 여러 개 운영하는 경우입니다.

이유내용
장애 허용한 환경이 실패하면 장애 대응(failover)으로 로드를 다른 환경으로 이동해 서비스를 지속. 인프라가 중복되어 비용은 늘지만 더 높은 장애 허용 확보
확장성워크로드를 여러 환경(리전별 독립 환경)에 분산. 한 리전에 오류가 있으면 로드가 다른 리전 환경으로 이동
분리고객(클라이언트)별 인스턴스를 다른 환경에서 실행해 독립성 강화. 법률·규정 준수 요구사항 충족, 고객 신뢰 확보

ShopSpinner는 고객 지원이 확장되면서 북미·유럽·아시아 태평양 리전마다 별도의 환경을 만들었습니다. 리전별로 고객 데이터 규정을 다르게 따를 수 있고, 다운타임이 필요한 변경을 리전마다 다른 시간대에 실행할 수 있습니다.

이후 규제로 인해 고객 데이터를 다른 회사와 분리 호스팅해야 하는 제약 상점 체인(The Medicine Barn)과 계약하면서, 더 높은 비용을 받고 완전히 분리된 전용 환경을 제공하기로 합니다. 분리 수준이 곧 상품이 되는 사례입니다.

환경 간 차이는 피할 수 없다

각 환경의 인프라는 일관성이 있어야 하지만, 최소한 appserver-test, appserver-stage, appserver-prod처럼 이름과 ID는 다를 수밖에 없습니다. 테스트 환경이 프로덕션보다 작거나, 환경마다 권한·기능이 다를 수도 있습니다.

따라서 환경의 일부분은 직접 구성해야 하며, 관건은 그 차이를 간단한 파라미터 수준으로 유지하는 것입니다. 인스턴스마다 인프라가 크게 다르면 “한 환경에서 테스트한 것이 다른 환경에서도 동작한다”는 확신을 얻을 수 없습니다.

환경 구현 패턴 — 안티패턴 2개, 패턴 1개

환경은 인프라 구성 요소의 개념적 집합이고 스택은 구체적 집합입니다. 스택 프로젝트와 인스턴스를 어떻게 조합해 환경을 구현하느냐에 따라 세 가지 형태가 나옵니다.

flowchart LR
    subgraph anti1["안티패턴 — 멀티 환경 스택"]
        direction TB
        p1["프로젝트 1개"] --> i1["인스턴스 1개<br/>테스트+스테이징+프로덕션"]
    end
    subgraph anti2["안티패턴 — 복사-붙여넣기 환경"]
        direction TB
        p2a["프로젝트 A"] --> i2a["테스트"]
        p2b["프로젝트 B"] --> i2b["스테이징"]
        p2c["프로젝트 C"] --> i2c["프로덕션"]
    end
    subgraph pattern["패턴 — 재사용 가능한 스택"]
        direction TB
        p3["프로젝트 1개"] --> i3a["테스트"]
        p3 --> i3b["스테이징"]
        p3 --> i3c["프로덕션"]
    end

안티패턴 — 멀티 환경 스택

멀티 환경의 인프라를 단일 스택 인스턴스로 정의하고 관리합니다. 새로운 스택 도구를 배울 때 기존 프로젝트에 환경을 추가하는 것이 자연스럽기 때문에 많이 만들어지는 구조입니다.

문제는 스택 도구로 업데이트할 때 잠재적 변경 범위가 항상 스택 전체라는 점입니다. 테스트 환경만 변경하려 해도 코딩 오류, 예기치 않은 의존성, 도구의 버그로 인해 같은 인스턴스에 있는 프로덕션 환경이 중단될 수 있습니다.

안티패턴 — 복사-붙여넣기 환경

각 스택 인스턴스에 별도의 스택 소스 코드 프로젝트를 둡니다. 한 환경에서 코드를 편집한 후 복사하여 다른 환경을 변경합니다.

멀티 환경 스택의 폭발 반경 문제를 피하고 환경별 재정의가 쉬워 직관적이지만, 대가가 큽니다.

  • 코드를 변경하려면 모든 프로젝트에 복사해야 하고, 한 인스턴스에서 동작한 변경이 다른 인스턴스에서 동작하지 않을 수 있어 모든 인스턴스에서 테스트해야 합니다
  • 처음에는 일관성이 있어도 시간이 지나며 구성 드리프트가 쌓입니다
  • 딜리버리 환경에 쓰면 환경 간 불일치로 배포 프로세스의 안정성과 테스트의 유효성이 감소합니다

환경 브랜치와 래퍼 스택도 변형이 될 수 있다

환경별 git 브랜치에 코드 사본을 두고 병합으로 전파하는 환경 브랜치는 복사-붙여넣기 환경의 한 형태입니다. 병합 과정에서 코드를 수정하는 순간 안티패턴이 됩니다. 래퍼 스택도 환경별 프로젝트에 구성 파라미터 외의 무언가를 추가하기 시작하면 같은 길로 갑니다.

패턴 — 재사용 가능한 스택

스택의 여러 인스턴스를 만드는 데 사용하는 하나의 인프라 소스 코드 프로젝트입니다. 코드를 변경하면 한 인스턴스에 적용해 테스트한 후, 동일한 코드로 신규 인스턴스를 만들거나 기존 인스턴스를 업데이트합니다.

Terraform은 인스턴스마다 다른 상태 파일이나 작업 공간을 지정하고, CloudFormation은 인스턴스마다 고유한 스택 ID를 전달하는 식으로 구현합니다.

> stack up env=test --source mystack/src
SUCCESS: stack 'test' created
> stack up env=staging --source mystack/src
SUCCESS: stack 'staging' created

인스턴스 간의 차이는 문자열·숫자·리스트 수준의 간단한 파라미터로만 정의합니다. 환경 간 차이가 크지 않을 때 유용하며, 일부 환경에 특별한 구성이 많은 경우에는 적용할 수 없습니다.

ShopSpinner 사례가 이 패턴의 발전 과정을 보여줍니다. 처음에는 고객별 스택 프로젝트에서 공통 코드를 모듈로 추출했지만, 모듈을 쓰고도 각 프로젝트가 여전히 매우 비슷하다는 것을 알게 됩니다. 매달 신규 고객이 등록되는 상황에서 고객 코드를 하나씩 변경·테스트하는 것은 번거로우므로, 고객 애플리케이션 스택을 정의하는 단일 스택 프로젝트로 통합했습니다. 이제 신규 고객 환경은 신규 인스턴스 생성일 뿐이고, 코드 수정은 테스트 인스턴스에서 확인한 다음 고객 인스턴스에 하나씩 배포합니다.

멀티 스택으로 환경 구축하기

전체 시스템이 단일 스택이면 인스턴스 하나를 프로비저닝하는 것이 곧 완전한 환경입니다. 그러나 더 큰 시스템은 서비스 스택 패턴처럼 여러 스택으로 분할되므로, 환경은 각 서비스 스택 인스턴스들의 집합이 됩니다.

멀티 환경을 만들려면 환경별로 각 서비스 스택의 인스턴스를 프로비저닝합니다.

> stack up env=staging --source product_browse_stack/src
SUCCESS: stack 'product_browse-staging' created
> stack up env=staging --source product_search_stack/src
SUCCESS: stack 'product_search-staging' created
> stack up env=staging --source shopping_basket_stack/src
SUCCESS: stack 'shopping_basket-staging' created

즉 환경과 시스템 구조는 서로 다른 두 차원입니다. 스택 프로젝트는 시스템 구조(서비스)를 따라 나누고, 환경은 파라미터로 구분한 인스턴스 집합으로 구현합니다.

비교 / 트레이드오프

패턴구조동기대가
멀티 환경 스택 (안티패턴)프로젝트 1개 → 인스턴스 1개에 모든 환경기존 프로젝트에 환경 추가가 가장 쉬움모든 변경의 폭발 반경에 프로덕션 포함
복사-붙여넣기 환경 (안티패턴)환경마다 프로젝트·인스턴스 복사직관적, 폭발 반경 분리, 환경별 재정의 자유변경을 수작업 전파, 구성 드리프트, 모든 인스턴스 재테스트
재사용 가능한 스택 (패턴)프로젝트 1개 → 환경마다 인스턴스일관성 보장 + 폭발 반경 격리 동시 달성환경 간 차이를 간단한 파라미터 수준으로 유지해야 함

스택은 변경을 테스트하고 딜리버리하는 단위입니다. 모듈이 아니라 스택을 변경 단위로 삼는 것이 변경을 쉽고 빠르게, 자주 딜리버리할 수 있게 합니다.

내 생각

  • Terraform workspace가 곧 이 패턴입니다. 같은 코드에 workspace(또는 state 파일)만 바꿔 apply하는 것이 재사용 가능한 스택이고, 환경별 tfvars 파일이 “간단한 파라미터”에 해당합니다.
  • 환경별 조건 분기가 늘어나는 순간이 패턴 붕괴 신호입니다. var.env == "prod" ? ... 같은 분기가 쌓이면 “한 환경에서 테스트한 코드가 다른 환경에서도 동작한다”는 전제가 깨져, 사실상 복사-붙여넣기와 같은 검증 부담이 돌아옵니다.
  • 환경 브랜치가 안티패턴의 변형이라는 지적이 뼈아픕니다. git-flow식 env 브랜치 운영은 결국 merge라는 이름의 복사-붙여넣기이고, 병합하면서 코드에 손대는 순간 환경 간 일관성 보장이 사라집니다.

관련 개념