[2026] Terraform 완벽 가이드 — 엔진·그래프·프로토콜·State·프로덕션
이 글의 핵심
Terraform은 선언적 IaC 도구이며, 내부적으로는 구성과 State를 바탕으로 의존성 그래프를 만들고 프로바이더 플러그인(gRPC)을 통해 API와 통신합니다. 이 글은 HCL·모듈·멀티클라우드 운영에 더해, 그래프 구성·프로토콜·State 잠금·플랜/적용 파이프라인·프로덕션 패턴까지 엔진 관점에서 깊이 있게 다룹니다.
이 글의 핵심
Terraform은 HashiCorp가 만든 오픈소스 Infrastructure as Code(IaC) 도구입니다. 콘솔에서 클릭으로 만든 인프라는 재현이 어렵고 감사 추적이 부족합니다. Terraform은 원하는 최종 상태를 코드로 남기고, 현재 상태(State) 와의 차이를 계산한 뒤 안전하게 적용하는 흐름을 제공합니다.
이 글에서 다루는 범위는 다음과 같습니다.
- Terraform의 핵심 개념(프로바이더, 리소스, 의존성, 플랜/적용, State)
- HCL(HashiCorp Configuration Language) 문법과 모듈 설계
- AWS, Microsoft Azure, Google Cloud 프로바이더 사용 시 유의점
- State 관리, 원격 백엔드, 잠금(Lock) 과 협업
- Workspace와 환경(dev/stage/prod) 분리 전략
- 보안 베스트 프랙티스(시크릿, 최소 권한, 공급망)
- 실전 예제: VPC·서브넷·컴퓨트·로드밸런서 수준의 구성 골격
- 엔진 심화: 의존성 그래프·프로바이더 플러그인 프로토콜·State 잠금·플랜/적용 알고리즘·프로덕션 패턴
전제: Terraform CLI 설치, 대상 클라우드 계정 및 결제·권한이 있다고 가정합니다. 예제는 교육용이므로 실제 적용 전 비용·쿼터·조직 보안 정책을 반드시 확인하십시오.
1. Terraform과 Infrastructure as Code
1.1 선언적 모델과 멱등성
Terraform은 선언적(declarative) 입니다. “어떤 순서로 스크립트를 실행할지”가 아니라 “어떤 인프라가 존재해야 하는지”를 기술합니다. 동일한 구성을 다시 적용하면(리소스가 이미 목표 상태이면) 변경이 없거나 필요한 부분만 조정되는 멱등성(idempotency) 을 목표로 설계됩니다.
실무에서는 다음이 큰 이점으로 작용합니다.
- 재현성: Git에 코드를 두면 동일한 환경을 다시 만들 수 있습니다.
- 리뷰 가능: Pull Request로 인프라 변경을 코드 리뷰할 수 있습니다.
- 사전 검증:
terraform plan으로 적용 전 영향 범위를 확인할 수 있습니다.
1.2 핵심 구성 요소
| 개념 | 설명 |
|---|---|
| Provider | AWS, Azure, GCP 등 클라우드 API에 연결하는 플러그인 |
| Resource | 생성·관리할 인프라 객체(예: VPC, VM, 버킷) |
| Data source | 생성하지 않고 기존 리소스 정보를 읽기만 할 때 사용 |
| State | Terraform이 추적하는 현재 리소스와 속성의 스냅샷 |
| Module | 재사용 가능한 .tf 묶음(입력 변수·출력으로 캡슐화) |
Terraform은 리소스 간 참조를 분석해 의존성 그래프를 만들고, 병렬로 생성 가능한 작업은 동시에 수행합니다. 명시적 의존이 필요하면 depends_on을 사용할 수 있습니다.
1.3 기본 워크플로
- 작성:
.tf파일에resource등을 정의합니다. - 초기화:
terraform init으로 프로바이더와 백엔드 모듈을 내려받습니다. - 계획:
terraform plan으로 변경 예정 내용을 확인합니다. - 적용:
terraform apply로 실제 인프라에 반영합니다. - 파괴(선택): 학습·검증 환경에서는
terraform destroy로 정리합니다.
운영 환경에서는 plan 결과를 아티팩트로 저장하고, 승인 후 apply하는 CI/CD 파이프라인과 결합하는 경우가 일반적입니다.
2. HCL 문법과 모듈
2.1 terraform 블록과 required_providers
버전을 고정하면 팀 전체가 동일한 프로바이더 동작을 기대할 수 있습니다. ~> 같은 버전 제약은 Semantic Versioning 관례에 맞춥니다.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
위 설정은 Terraform 코어 버전 하한과 AWS 프로바이더 5.x 대를 허용합니다. 조직 표준이 있다면 required_version을 CI에서 강제하는 것이 좋습니다.
2.2 변수·출력·지역 값
입력 변수(variable) 로 환경별 차이를 주고, 출력(output) 으로 다른 스택이나 모듈 소비자에게 값을 노출합니다. 지역 값(locals) 은 같은 디렉터리 내에서 반복 표현을 줄이는 데 씁니다.
variable "environment" {
type = string
description = "배포 환경 이름"
}
locals {
name_prefix = "app-${var.environment}"
}
output "name_prefix" {
value = local.name_prefix
description = "리소스 이름 접두사"
}
description과 type을 습관적으로 적어 두면 모듈 사용자가 실수로 잘못된 값을 넣는 것을 줄일 수 있습니다.
2.3 모듈 설계
모듈은 디렉터리 단위로 묶인 Terraform 구성입니다. 루트 모듈이 하위 디렉터리를 module 블록으로 호출합니다.
- 재사용: 동일한 네트워크 패턴을 여러 프로젝트에 복사하지 않고 모듈로 공유합니다.
- 인터페이스:
variables.tf와outputs.tf로 경계를 명확히 합니다. - 합성: 작은 모듈(VPC, 서브넷, 보안 그룹)을 조합해 상위 스택을 만듭니다.
모듈을 Git URL·Terraform Registry에서 가져오는 경우, 태그된 버전을 참조해 공급망을 고정하는 것이 안전합니다.
module "network" {
source = "./modules/network"
cidr_block = "10.0.0.0/16"
az_count = 3
}
모듈 내부에서는 가능한 한 환경 고유 문자열을 입력으로만 받고, 하드코딩된 계정 ID나 리전을 피하는 편이 이식성이 높습니다.
3. AWS, Azure, GCP 프로바이더
클라우드마다 리소스 이름·ID 규칙·태그·IAM 모델이 다릅니다. Terraform은 API를 추상화하지만, 각 프로바이더 문서를 함께 읽는 것이 필수입니다.
3.1 AWS (hashicorp/aws)
가장 널리 쓰이는 프로바이더 중 하나입니다. 리전, 자격 증명 체인(환경 변수, 공유 자격 증명 파일, IAM 역할 등)을 이해해야 합니다.
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "logs" {
bucket = "example-logs-unique-suffix"
}
실무 팁: S3 버킷 이름은 전역 유일해야 하고, 퍼블릭 액세스 차단·암호화·수명 주기 정책을 함께 정의하는 경우가 많습니다. 민감 로그는 별도 거버넌스 정책과 연동합니다.
3.2 Azure (hashicorp/azurerm)
구독(Subscription)·리소스 그룹·테넌트 개념이 중요합니다. 서비스 프린시펄이나 Managed Identity로 CI에서 비대화형 인증을 구성합니다.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "main" {
name = "rg-example"
location = "Korea Central"
}
Azure는 리소스마다 이름 규칙과 제약이 다르므로, 모듈에서 유효성 검사(문자 길이 등)를 변수 제약으로 두면 배포 실패를 줄일 수 있습니다.
3.3 Google Cloud (hashicorp/google)
프로젝트 ID, 리전/존, 서비스 API 활성화가 선행되는 경우가 많습니다.
provider "google" {
project = var.gcp_project_id
region = "asia-northeast3"
}
resource "google_storage_bucket" "assets" {
name = "${var.gcp_project_id}-assets-unique"
location = "ASIA-NORTHEAST3"
force_destroy = false
}
GCP에서는 IAM을 최소 권한으로 쪼개고, VPC 서비스 컨트롤·조직 정책 같은 상위 거버넌스와 충돌하지 않는지 확인해야 합니다.
3.4 멀티 클라우드 운영 시 유의점
동일한 HCL “문법”을 쓰더라도 리소스 의미는 클라우드마다 다릅니다. “VPC에 해당한다”는 추상화를 팀 내 용어집으로 맞추고, 네트워크·아이덴티티·과금 단위를 문서화하는 것이 협업에 도움이 됩니다.
4. State 관리와 백엔드
4.1 State가 필요한 이유
Terraform은 선언 코드만으로는 클라우드에 존재하는 실제 ID를 모두 알 수 없습니다. State는 Terraform이 관리하는 리소스와 원격 ID 매핑, 일부 속성 캐시를 저장합니다. State가 없거나 오래되면 계획이 부정확해지고, 중복 생성·잘못된 삭제 위험이 커집니다.
4.2 원격 백엔드와 잠금
로컬 terraform.tfstate 파일은 개인 학습에는 편하지만, 팀에서는 원격 백엔드로 옮기는 것이 일반적입니다.
| 백엔드 예시 | 잠금(Locking) | 비고 |
|---|---|---|
| AWS S3 + DynamoDB | DynamoDB 테이블로 락 | 널리 쓰이는 패턴 |
| Azure Storage | Blob 잠금 기능 활용 | azurerm 백엔드 문서 참고 |
| GCS | GCS가 제공하는 잠금 | 프로젝트 IAM과 함께 설계 |
| Terraform Cloud / HCP Terraform | SaaS가 락·RBAC 제공 | 팀 협업·정책 강화에 유리 |
원격 백엔드 예시(S3, 개념 설명용):
terraform {
backend "s3" {
bucket = "my-org-terraform-state"
key = "prod/network/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
key는 스택별로 분리하는 것이 State 충돌을 줄입니다. 암호화, 버전 관리(Versioning), 접근 로그는 운영 필수에 가깝습니다.
4.3 State 분할과 terraform import
거대한 단일 State는 플랜 시간과 변경 폭을 키웁니다. 팀 단위·도메인 단위로 State를 쪼개고, terraform_remote_state 또는 공유 서비스(예: DNS, 허브 VPC)만 느슨하게 연결하는 패턴이 흔합니다. 이미 콘솔로 만든 리소스는 terraform import 로 State에 편입할 수 있으나, 코드와 실제 구성을 일치시키는 작업이 별도로 필요합니다.
5. Workspace와 환경 분리
5.1 Workspace
Workspace는 동일 구성에 대해 여러 State를 분리하는 내장 메커니즘입니다. terraform workspace new staging처럼 생성하고, terraform.workspace 값으로 이름을 참조할 수 있습니다.
장점은 설정이 단순할 때 빠르게 환경을 나눌 수 있다는 점입니다. 단점은 백엔드 key 분리 실수, 동일 브랜치에서 workspace만 바꿔 운영에 적용하는 인적 오류 위험입니다.
5.2 디렉터리·브랜치·파이프라인으로 분리
많은 조직은 env/dev, env/prod처럼 루트 모듈을 분리하고, 다른 백엔드·다른 변수 파일(terraform.tfvars)·다른 승인 게이트를 둡니다. 프로덕션은 main 브랜치 병합 후에만 apply되도록 제한하는 방식이 안전합니다.
5.3 변수 파일과 시크릿
비밀번호·API 키를 .tf에 직접 쓰지 마십시오. 환경 변수, CI 시크릿, 클라우드 시크릿 매니저(AWS Secrets Manager, Azure Key Vault, GCP Secret Manager)와 외부 데이터 소스를 조합합니다. State에 민감 값이 남지 않도록 민감 리소스 설계를 검토합니다.
6. 보안 베스트 프랙티스
6.1 최소 권한과 역할 분리
Terraform을 실행하는 CI 서비스 계정에는 스택에 필요한 최소 IAM 권한만 부여합니다. 개발자 개인 자격 증명으로 프로덕션을 적용하는 관행은 감사·추적 측면에서 불리합니다.
6.2 시크릿과 State
- State 파일은 민감 데이터가 포함될 수 있으므로 암호화 저장, 접근 제어, 감사 로그를 적용합니다.
.tfvars에 시크릿을 커밋하지 않습니다..gitignore와 pre-commit 훅으로 방지합니다.
6.3 공급망과 모듈 출처
- 공식 레지스트리와 검증된 모듈을 우선하고, Git 소스는 커밋 해시 또는 태그로 고정합니다.
- 프로바이더 버전을 제약해 재현 가능한 플랜을 유지합니다.
6.4 정책 as Code
조직 규모가 커지면 Sentinel(Terraform Enterprise/Cloud), OPA(Open Policy Agent) 로 “퍼블릭 S3 금지”, “필수 태그” 같은 규칙을 자동 검증합니다.
7. 실전 인프라 구축 예제
아래는 교육용으로 축약한 AWS 예시입니다. 실제 운영에서는 서브넷 CIDR, NAT 비용, 로드밸런서 헬스 체크, WAF, 백업 정책 등을 추가로 설계합니다.
7.1 목표 구조
- VPC와 퍼블릭·프라이빗 서브넷
- 애플리케이션은 프라이빗 서브넷에 두고, 로드밸런서로만 노출(개념)
- 보안 그룹으로 최소 포트만 허용
7.2 네트워크 및 ALB 골격 (예시)
# 예시: 개념 이해용. 실제 배포 전 CIDR·가용 영역·이름은 환경에 맞게 조정하십시오.
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "app-vpc"
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-${count.index}"
}
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index + 8)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "private-${count.index}"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
resource "aws_lb" "app" {
name = "app-alb"
load_balancer_type = "application"
subnets = aws_subnet.public[*].id
security_groups = [aws_security_group.alb.id]
}
resource "aws_security_group" "alb" {
name = "alb-sg"
description = "ALB ingress"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
위 코드에서 cidrsubnet으로 서브넷을 나누고, count로 가용 영역별 반복을 표현합니다. 인터넷 게이트웨이만으로는 프라이빗 서브넷에 아웃바운드가 필요할 때 NAT 게이트웨이·라우트 테이블을 추가해야 합니다(비용 발생). 운영 환경에서는 단일 AZ NAT vs 다중 AZ NAT 비용과 가용성을 트레이드오프합니다.
애플리케이션 계층은 aws_instance 또는 ECS/EKS 등 컨테이너 오케스트레이션으로 올리는 경우가 많습니다. Terraform으로 클러스터·노드 그룹·IRSA(IAM Roles for Service Accounts) 까지 한 스택에 넣을지, 플랫폼 팀과 앱 팀의 경계를 어디에 둘지는 조직 문맥에 따라 달라집니다.
7.3 Azure·GCP로 옮길 때
- Azure:
azurerm_virtual_network,azurerm_subnet,azurerm_lb등으로 대응하며, 리소스 그룹·위치를 일관되게 둡니다. - GCP:
google_compute_network,google_compute_subnetwork,google_compute_forwarding_rule또는 GLB 리소스로 유사 패턴을 구현합니다.
동일한 아키텍처라도 로드밸런서·헬스 체크·방화벽 모델이 다르므로, “클라우드별 모듈”을 두고 추상화 레이어를 억지로 통일하기보다 참조 아키텍처 문서를 맞추는 편이 현실적입니다.
8. Terraform 엔진 심화: 그래프·프로토콜·플랜과 적용
CLI에서 terraform plan 한 줄로 보이는 결과 뒤에는 구성 파싱, 상태와의 조합, 의존성 그래프, 프로바이더 RPC가 맞물립니다. 운영 장애나 플랜 비결정성을 줄이려면 “무엇이 언제, 어떤 순서로 실행되는가”를 개념적으로 이해하는 것이 중요합니다.
8.1 그래프 구성과 의존성 해석
Terraform은 .tf 묶음을 설정 그래프(configuration graph) 로 먼저 모델링한 뒤, 리소스·모듈·프로바이더 바인딩을 거쳐 실행 그래프(dependency graph) 를 만듭니다. 리소스 A의 인자가 resource "..." "B"의 속성을 참조하면 B → A 방향의 간선이 생깁니다. 참조가 없어도 순서가 필요하면 depends_on으로 명시적 간선을 추가합니다.
엔진은 이 방향 그래프에 대해 위상 정렬(topological sort) 을 수행해, 가능한 한 병렬로 독립 작업을 실행합니다. 기본적으로 -parallelism=n으로 동시성을 제한할 수 있으며, 대규모 스택에서는 API 속도 제한·백오프와 함께 튜닝 대상이 됩니다. 삭제(destroy) 는 생성과 반대로, 의존성을 역순으로 처리하는 쪽이 안전합니다(예: 보안 그룹이 ENI를 참조하면 ENI가 먼저 정리되는 식).
순환 의존성이 있으면 Terraform은 계획 단계에서 이를 오류로 보고합니다. 실무에서는 모듈 간 순환 참조를 끊기 위해 중간 출력값·별도 스택으로 경계를 나눕니다. 또한 replace가 필요한 변경(인자를 바꿀 수 없어 재생성해야 하는 경우)은 그래프 상에서 삭제 후 생성 또는 생성 후 삭제 등 전략이 붙으며, 리소스 유형과 lifecycle 블록(create_before_destroy 등)에 따라 달라집니다.
8.2 프로바이더 플러그인 프로토콜
프로바이더는 Terraform 코어와 별도 프로세스로 실행되는 플러그인 바이너리입니다. 현재 세대는 Go gRPC 기반의 Terraform Plugin Protocol을 사용하며, 코어가 프로세스를 띄운 뒤 스키마 조회·설정·리소스 수명주기를 RPC로 요청합니다.
대표적인 흐름은 다음과 같습니다.
- 초기화(
terraform init): 레지스트리에서 프로바이더를 내려받아.terraform/providers에 캐시하고, 의존성 잠금 파일(.terraform.lock.hcl) 에 버전 해시를 기록합니다. - GetProviderSchema: 리소스·데이터 소스의 속성·타입·어떤 인자 변경이 재생성을 유발하는지 같은 메타데이터를 코어가 받습니다.
- 계획/적용: 코어는 “목표 구성”과 “현재 State의 알려진 속성”을 바탕으로 PlanResourceChange 요청을 보내고, 프로바이더는 클라우드 API 규칙에 맞는 속성 차이(diff) 와 액션을 반환합니다. 적용 시에는 ApplyResourceChange 등으로 실제 API 호출이 일어납니다.
즉, AWS API를 직접 부르는 주체는 코어가 아니라 프로바이더 플러그인입니다. 그래서 프로바이더 버전을 고정하지 않으면, 동일 HCL이라도 내부 매핑이 바뀌어 플랜 결과가 달라지는 일이 생깁니다. 팀 표준으로 required_providers와 lock 파일을 CI에서 검증하는 이유가 여기에 있습니다.
8.3 State 잠금과 백엔드 메커니즘
원격 백엔드는 “State 파일을 어디에 두고, 어떻게 읽고 쓰는가”를 추상화합니다. 로컬 백엔드는 단일 머신의 terraform.tfstate에 불과하지만, S3·GCS·Azure Blob·Terraform Cloud 등은 팀이 동시에 작업할 수 있게 합니다.
핵심은 원자적(atomic) 읽기-수정-쓰기와 동시 apply 방지입니다. 예를 들어 AWS S3 + DynamoDB 패턴에서는 State 객체는 S3에 저장하고, 잠금은 DynamoDB 항목을 조건부 쓰기로 획득합니다. 이미 다른 세션이 락을 잡았다면 Lock Error로 apply가 거절됩니다. GCS 백엔드는 저장소가 제공하는 객체 잠금 메커니즘을 활용합니다.
운영 시 알아두어야 할 점은 다음과 같습니다.
- 비정상 종료로 락이 남으면
terraform force-unlock <LOCK_ID>가 필요할 수 있으나, 실제로 다른 작업이 돌고 있는지 먼저 확인해야 합니다. - State 버전 관리(S3 versioning 등) 는 실수로 덮어쓴 State를 복구하는 데 도움이 되지만, 민감 정보 노출 가능성도 있으므로 접근 통제와 암호화가 전제입니다.
- 백엔드 마이그레이션(
terraform init -migrate-state)은 한 번의 실수로 State를 잃을 수 있으므로, 절차를 문서화하고 운영 시간대·승인을 둡니다.
8.4 Plan과 Apply 알고리즘 개요
고수준으로 보면 plan과 apply는 다음 단계를 포함합니다(세부는 버전·설정에 따라 다름).
- 구성 로드: 모듈 트리를 펼치고 변수·로컬·리소스를 평가합니다.
- State 로드: 백엔드에서 State를 읽고, 필요 시 잠금을 시도합니다.
- Refresh(갱신): 기본적으로 프로바이더를 통해 실제 인프라의 현재 값을 읽어 State와 비교합니다.
-refresh=false로 끄면 drift가 플랜에 덜 잡힐 수 있어, CI 최적화와 트레이드오프가 있습니다. - 계획 수립: 목표 구성과(갱신된) State를 합쳐 변경 집합을 계산합니다. 여기서 추가·변경·삭제·교체가 결정됩니다.
- 그래프 실행(apply): 의존성 순서대로 프로바이더 RPC를 호출하고, 성공한 단계마다 State를 갱신합니다. 중간 실패 시 부분 적용과 다음 플랜에서 재시도가 가능하도록 State가 남습니다.
Drift(콘솔에서 수동 변경)는 refresh 단계에서 감지되면 플랜에 되돌림으로 나타나는 경우가 많습니다. 다만 Terraform 외부에서 삭제된 리소스는 다음 실행에서 재생성으로 이어질 수 있어, 운영 규칙으로 “수동 변경 금지” 또는 정기 drift 점검을 두는 편이 안전합니다.
8.5 프로덕션 Terraform 패턴
엔진 동작을 알면 다음 같은 프로덕션 패턴의 이유가 분명해집니다.
- State 분할: 거대한 단일 State는 그래프가 커져 플랜 시간·실패 반경(blast radius) 이 함께 커집니다. 팀·도메인·환경 단위로 스택을 나누고,
terraform_remote_state나 공유 데이터 저장소로 느슨한 결합을 유지합니다. - 플랜 아티팩트와 승인 게이트: PR에서
terraform plan -out=plan.tfplan을 저장하고, 승인된 파이프라인만terraform apply plan.tfplan을 실행하면 승인된 변경만 적용됩니다. - 정책 as Code: Sentinel, OPA, 또는 클라우드 정책으로 퍼블릭 노출·필수 태그를 자동 거절합니다.
- 프로바이더·모듈 버전 고정:
.terraform.lock.hcl과 모듈 소스의 태그/커밋 고정으로 공급망을 닫습니다. - 외부 오케스트레이션: Terragrunt로 백엔드·원격 상태·의존 순서를 표준화하거나, 스택 간 의존을 파이프라인으로 조율합니다.
결국 Terraform은 “선언적 언어”처럼 보이지만, 실행 시에는 그래프 스케줄러 + 플러그인 RPC + State 저장소의 조합입니다. 이 세 축을 염두에 두고 백엔드·권한·CI를 설계하면, 장애 시 원인 분석과 팀 온보딩이 훨씬 수월해집니다.
9. 트러블슈팅: 현장 진단 플레이북
플랜이 비어 있거나 예상과 다를 때 먼저 terraform plan -refresh=true로 실제 클라우드 상태와 State의 불일치(drift) 를 다시 끌어옵니다. 누군가 콘솔에서 태그만 바꿔도 플랜은 “되돌림”으로 보일 수 있습니다. -refresh=false로 최적화한 CI에서는 drift가 숨을 수 있으므로, 주기적으로 refresh 포함 플랜을 돌리거나 별도 drift 감지 잡을 둡니다.
Lock 에러가 반복될 때 CI와 로컬 중 어느 세션이 락을 잡고 있는지 백엔드(예: DynamoDB 락 테이블, Terraform Cloud UI)에서 확인합니다. force-unlock은 다른 apply가 없다는 팀 합의 후에만 사용합니다. 락 TTL이 짧은 환경에서는 네트워크 단절로 반납 실패한 케이스도 있으므로, 파이프라인 타임아웃과 함께 봅니다.
프로바이더 업그레이드 직후 이상한 교체(replace) 가 늘면 스키마 변경으로 속성이 “불변”으로 바뀐 것일 수 있습니다. terraform providers schema -json이나 레지스트리 릴리스 노트로 Breaking changes를 추적하고, lifecycle { ignore_changes = [...] }는 일시적 완화로만 쓰고 이슈로 추적합니다.
모듈 출력이 null/알 수 없음일 때는 의존 모듈 적용 순서와 조건부 리소스 count/for_each 조합을 의심합니다. depends_on으로 데이터 소스 평가 순서를 고정할 수 있으나, 순환 참조가 생기지 않게 모듈 경계를 나눕니다.
원격 백엔드 마이그레이션 실패 시에는 State 백업(S3 versioning, terraform state pull 산출물)을 먼저 확보하고, 읽기 전용으로 init -backend=false로 로컬 검증한 뒤 절차를 재개합니다.
10. 운영 체크리스트
- 원격 State, 잠금, 암호화, IAM 최소 권한이 준비되었는가.
- 프로덕션 apply는 CI와 승인 절차로만 수행되는가.
- 모듈·프로바이더 버전이 고정되어 재현 가능한가.
- 비용 알림, 태그 정책, 삭제 보호(S3 버전 관리, RDS 삭제 보호 등)가 정의되었는가.
- 장애 시
terraform plan이 예상과 다른 이유(수동 변경, drift)를 추적할 프로세스가 있는가.
마무리
Terraform은 인프라를 소프트웨어처럼 다루게 해 주는 강력한 도구입니다. HCL과 모듈로 구조를 명확히 하고, State와 백엔드로 협업과 안전성을 확보하며, Workspace나 디렉터리 전략으로 환경을 분리하는 패턴을 익히면 운영 성숙도가 빠르게 올라갑니다. 의존성 그래프·프로바이더 RPC·플랜/적용 파이프라인을 이해하면 플랜 비결정성·락 충돌·drift 같은 현장 이슈를 원인부터 짚기 쉬워집니다. AWS·Azure·GCP 중 어디를 쓰든 프로바이더 문서와 조직 보안 기준을 함께 보는 습관이 가장 중요합니다.
같은 주제의 개요형 글(terraform-infrastructure-as-code-guide)·실무 요약(terraform-practical-guide)과 함께 읽으면, 이 파일(terraform-complete-guide)은 멀티 클라우드·보안·백엔드·환경 분리에 더해 엔진 동작까지 묶은 확장판으로 활용할 수 있습니다.
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「[2026] Terraform 완벽 가이드 — 엔진·그래프·프로토콜·State·프로덕션」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「[2026] Terraform 완벽 가이드 — 엔진·그래프·프로토콜·State·프로덕션」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.