Terraform을 처음 사용할 때는 보통 하나의 디렉토리에 모든 파일을 두고 시작한다.
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── provider.tf
└── terraform.tfvars
이 구조는 단순하고 빠르게 시작하기에는 적합하다.
하지만 프로젝트 규모가 커질수록 다음과 같은 문제가 발생한다.
- 하나의 파일에 모든 리소스가 몰림
- 리소스 간 의존 관계 파악이 어려움
- 협업 시 충돌 가능성 증가
따라서 Terraform은 프로젝트 규모에 맞는 폴더 구조를 선택하는 것이 중요하다.
1. Terraform 폴더 구조 종류
Terraform 구조는 정답이 있는 것이 아니라, 프로젝트 규모와 팀 구조에 따라 달라진다.
일반적으로 아래와 같은 흐름으로 확장된다.
1-1. 단일 구조
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── provider.tf
└── terraform.tfvars
가장 기본적인 구조로, 소규모 프로젝트나 테스트 환경에 적합하다.
구성이 단순하지만, 리소스가 많아지면 관리가 어려워진다.
1-2. modules + environments 구조
terraform/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ └── terraform.tfvars
│ ├── staging/
│ └── prod/
│
├── modules/
│ ├── vpc/
│ ├── ec2/
│ └── rds/
│
└── global/
├── provider.tf
└── backend.tf
가장 일반적으로 사용하는 구조이다.
환경 분리와 모듈화를 통해 유지보수성을 확보할 수 있다.
environments
환경(dev, staging, prod)을 분리하여 관리한다.
- dev: 개발 환경
- staging: 운영 전 검증 환경
- prod: 실제 운영 환경
환경을 분리하면 운영과 테스트를 분리할 수 있어 안정적인 배포가 가능하다.
modules
재사용 가능한 리소스를 정의하는 영역이다.
같은 리소스를 여러 번 생성해야 하는 경우, 모듈을 사용하면 코드 중복을 줄일 수 있다.
예를 들어 VPC, EC2, RDS 등을 모듈로 정의해두고 변수만 변경하여 재사용한다.
global
모든 환경에서 공통으로 사용하는 설정을 정의한다.
- provider.tf: 클라우드 설정
- backend.tf: state 저장 위치
특히 backend 설정은 협업 시 중요한 역할을 한다.
1-3. 서비스 단위 분리 구조 (MSA 환경)
terraform/
├── services/
│ ├── user-service/
│ ├── order-service/
│ └── payment-service/
│
├── shared/
│ ├── vpc/
│ └── iam/
│
└── modules/
서비스별로 Terraform을 분리하는 구조이다.
각 서비스가 독립적인 인프라를 가지며, 공통 리소스는 shared 또는 modules에서 관리한다.
특징
- 서비스별 독립 배포 가능
- 팀 단위 ownership 분리
- 서비스 간 의존성 감소
단점
- 공통 리소스 관리 복잡
- 구조 복잡도 증가
MSA 환경이나 팀이 여러 개인 조직에서 주로 사용된다.
1-4. live + modules 구조 (대규모 환경)
terraform/
├── live/
│ ├── prod/
│ │ ├── vpc/
│ │ ├── app/
│ │ └── db/
│ ├── dev/
│ └── staging/
│
└── modules/
├── vpc/
├── ecs/
└── rds/
이 구조는 Terraform Up & Running 에서 소개되는 방식으로, 대규모 환경에서 많이 사용된다.
핵심 개념
- live: 실제 배포 코드
- modules: 재사용 가능한 인프라 정의
특징
- 변경 영향 범위가 명확함
- 환경별 관리 용이
- 대규모에서도 확장 가능
초기 설계는 어렵지만 장기적으로 유지보수에 유리하다.
2. 구조 선택 기준
Terraform 구조는 프로젝트 상황에 따라 선택해야 한다.
- 소규모 프로젝트 → 단일 구조
- 일반적인 서비스 → modules + environments
- MSA 구조 → 서비스 단위 분리
- 대규모 조직 → live + modules
구조를 선택할 때는 다음을 고려하는 것이 좋다.
- 프로젝트 규모
- 팀 규모
- 서비스 구조 (모놀리식 / MSA)
3. 실무에서 중요한 추가 포인트
구조만 잘 잡는다고 끝이 아니라, 몇 가지 실무 포인트를 함께 고려해야 한다.
3-1. Module 설계
모듈은 하나의 책임만 가지도록 설계하는 것이 중요하다.
- 너무 큰 모듈(God module) 지양
- 입력 변수 명확히 정의
- output을 통해 의존성 연결
모듈이 커질수록 재사용성이 떨어지고 유지보수가 어려워진다.
3-2. 환경별 변수 관리
환경마다 다른 설정은 별도로 관리하는 것이 좋다.
또는 dev.tfvars, staging.tfvars 등으로 분리한다.
민감한 정보는 tfvars에 직접 넣기보다는
AWS SSM, Secrets Manager 등을 사용하는 것이 안전하다.
3-3. Terraform State 관리
Terraform은 state 파일을 기준으로 인프라를 관리한다.
즉, Terraform이 바라보는 “현재 인프라 상태”가 이 파일에 저장된다.
따라서 state 관리가 잘못되면 다음과 같은 문제가 발생할 수 있다.
- 리소스 중복 생성
- 설정 충돌
- 인프라 상태 불일치
- 협업 중 apply 충돌
특히 여러 명이 동시에 terraform apply를 실행하면 state가 꼬일 수 있기 때문에, 실무에서는 보통 Remote Backend와 State Locking을 함께 사용한다.
Remote Backend란?
기본적으로 Terraform state는 로컬에 terraform.tfstate 파일로 저장된다.
하지만 협업 환경에서 로컬 state를 사용하면 각자 다른 상태를 기준으로 인프라를 변경하게 된다.
이를 방지하기 위해 state 파일을 원격 저장소에 저장한다.
AWS 기준으로는 일반적으로 S3를 사용한다.
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "dev/terraform.tfstate"
region = "ap-northeast-2"
}
}
이렇게 하면 state 파일이 로컬이 아니라 S3에 저장된다.
State Locking이 필요한 이유
Remote Backend만 사용한다고 끝이 아니다.
예를 들어 A 개발자와 B 개발자가 동시에 terraform apply를 실행하면 같은 state 파일을 동시에 수정하려고 할 수 있다.
이를 방지하기 위한 기능이 State Locking이다.
Lock이 걸리면 한 명이 작업 중일 때 다른 사람은 apply를 실행할 수 없다.
기존 방식: S3 + DynamoDB Lock
기존에는 AWS에서 Terraform state를 관리할 때 아래 구성이 많이 사용되었다.
- S3: state 파일 저장
- DynamoDB: state lock 관리
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "dev/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-lock"
}
}
DynamoDB 테이블은 LockID라는 String 타입의 Partition Key를 가져야 한다.
하지만 현재 Terraform 공식 문서 기준으로 DynamoDB 기반 locking은 deprecated 되었고, 향후 minor version에서 제거될 예정이다.
최근 방식: S3 Lockfile
최근에는 DynamoDB 대신 S3 backend의 use_lockfile 옵션을 사용하는 방식이 권장된다.
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "dev/terraform.tfstate"
region = "ap-northeast-2"
use_lockfile = true
}
}
use_lockfile = true를 설정하면 state 파일과 함께 .tflock 파일을 사용하여 lock을 관리한다.
이 방식은 별도의 DynamoDB 테이블을 만들 필요가 없어 구성이 더 단순하다. Terraform 공식 문서에서도 S3 backend는 use_lockfile 옵션으로 state locking을 지원한다고 설명한다.
권한 설정 주의
S3 lockfile 방식을 사용할 경우 state 파일뿐 아니라 lock 파일에 대한 권한도 필요하다.
예를 들어 state 경로가 다음과 같다면:
lock 파일은 다음과 같이 생성된다.
따라서 IAM 권한에 lock 파일에 대한 GetObject, PutObject, DeleteObject 권한도 포함해야 한다.
정리
현재 기준으로는 다음처럼 이해하면 된다.
S3 + DynamoDB
최근 권장 방식:
S3 + use_lockfile = true
협업 환경에서 중요한 것은 단순히 state를 S3에 저장하는 것이 아니라,
동시에 여러 명이 state를 수정하지 못하도록 locking 구조를 반드시 갖추는 것이다.
기본 원칙
실무에서는 다음과 같은 원칙을 반드시 지켜야 한다.
- state 파일을 Git에 포함하지 않는다
- 로컬 state 사용을 지양한다
- 반드시 remote backend를 사용한다
3-4. workspace vs directory
Terraform은 workspace 기능을 제공하지만, 실무에서는 directory 기반 분리를 더 많이 사용한다.
- workspace: 같은 코드, 다른 state
- directory: 코드 자체를 분리
환경이 복잡해질수록 directory 구조가 더 명확하다.
정리
Terraform 폴더 구조는 프로젝트 규모에 따라 선택해야 한다.
- 단일 구조: 빠른 시작, 소규모
- modules + environments: 일반적인 실무
- 서비스 단위 분리: MSA 환경
- live + modules: 대규모 조직
Terraform은 단순한 설정 파일이 아니라 인프라를 코드로 관리하는 도구이기 때문에,
초기 구조 설계가 전체 운영과 유지보수에 큰 영향을 미친다.
4. 필자가 사용한 구조 (modules + stacks 분리)
위에서 소개한 구조 외에도, 실제로는 modules만으로는 부족한 경우가 있다.
특히 서비스 규모가 커질수록 “모듈 재사용”과 “서비스 구성 로직”을 분리할 필요가 생긴다.
왜 modules만으로는 부족한가
Terraform에서 modules는 기본적으로 재사용 단위다.
하지만 서비스는 다음과 같이 여러 리소스의 조합으로 구성된다.
- VPC
- ALB
- EC2
- RDS
- CloudFront
즉, 단순한 리소스 하나가 아니라
여러 모듈을 조합한 구조가 필요하다.
문제는 이 조합 로직을 modules 안에 넣기 시작하면 발생한다.
- 모듈이 비대해짐
- 재사용성 감소
- 가독성 저하
그래서 재사용 모듈과 조합 로직을 분리하는 구조가 필요해진다.
기존 구조의 문제
초기에는 다음과 같은 방식으로 구성할 수 있다.
- environments/dev에서 app_stack 같은 큰 모듈 하나를 호출
- 해당 모듈 내부에서 모든 리소스를 처리
하지만 이 방식에는 문제가 있다.
- 모듈인데 사실상 루트 스택처럼 비대해짐
- 재사용 모듈과 조합 로직의 경계가 모호해짐
- 의존 관계 파악이 어려워짐
결국 유지보수가 어려워지는 구조가 된다.
개선 방향: 역할 분리
이 문제를 해결하기 위해 다음과 같이 구조를 분리할 수 있다.
- modules/ : 순수 재사용 컴포넌트
- stacks/ : 서비스 단위 조합 (orchestration)
레이어 구조 도입
추가로, 의존 관계를 명확하게 하기 위해 레이어를 나눈다.
- network → platform → app → edge
이렇게 구성하면:
- 의존 방향이 명확해지고
- 구조 자체가 문서 역할을 하게 된다
최종 구조
├── modules/
│ ├── vpc/
│ ├── alb/
│ ├── ec2_service/
│ ├── rds/
│ └── lambda_worker/
│
├── stacks/
│ └── app/
│ ├── network.tf
│ ├── platform.tf
│ ├── app.tf
│ └── edge.tf
│
└── environments/
└── dev/
각 영역의 역할
modules
- 재사용 가능한 인프라 구성 요소
- 최대한 작고 독립적으로 유지
stacks
- 여러 모듈을 조합하는 orchestration 영역
- 서비스 단위 구성 정의
environments
- 환경별 변수 관리
- provider / backend 설정 연결
이 구조의 장점
- 재사용성과 조합 책임이 명확히 분리됨
- 변경 영향 범위 예측이 쉬움
- 환경(dev/staging/prod) 확장 시 stacks 재사용 가능
- 모듈 단위 테스트 및 검증이 쉬움
트레이드오프
- 디렉토리 구조가 복잡해짐
- 팀 내 규칙이 필요함 (모듈에 조합 로직 넣지 않기)
정리
이 구조는 다음과 같은 상황에서 특히 유용하다.
- 서비스 규모가 커지는 경우
- 여러 리소스를 조합하는 구조가 필요한 경우
- 재사용성과 유지보수를 동시에 고려해야 하는 경우
참고 자료
Terraform 공식 문서
- Terraform 공식 사이트
https://developer.hashicorp.com/terraform - S3 Backend (state 저장 및 lock 관련)
https://developer.hashicorp.com/terraform/language/backend/s3 - Terraform Modules
https://developer.hashicorp.com/terraform/language/modules - Terraform State
https://developer.hashicorp.com/terraform/language/state
구조 및 베스트 프랙티스
- Terraform Up & Running
https://www.terraformupandrunning.com/ - Gruntwork Terraform Style Guide
https://docs.gruntwork.io/guides/style/terraform-style-guide/
추가 참고
- Terraform Workspace
https://developer.hashicorp.com/terraform/language/state/workspaces - Terraform Backend 설정 가이드
https://developer.hashicorp.com/terraform/language/settings/backends/configuration
'테라폼' 카테고리의 다른 글
| 3-3. 테라폼 - State는 왜 나눠야 할까? (2) | 2026.05.05 |
|---|---|
| 3-2. 테라폼 - 순환 참조는 왜 발생하고 어떻게 끊어야 할까? (0) | 2026.05.05 |
| 3-1. 테라폼 - apply는 성공했는데 왜 서비스는 동작하지 않을까? (0) | 2026.05.05 |
| 3-0. 테라폼 - 리소스의 분류 및 구현 (0) | 2026.05.05 |
| 1. 테라폼 - 효율적인 클라우드 관리 (0) | 2026.04.22 |