Terraform 기초: IaC 개념과 provider·resource·state·plan/apply

상위: 00.DevOps MOC

개요

Terraform은 HashiCorp가 만든 Infrastructure as Code(IaC) 도구다. 클라우드 자원(VPC, EC2, S3, RDS, Kubernetes 오브젝트 등)을 콘솔에서 클릭으로 만드는 대신, **HCL(HashiCorp Configuration Language)**로 “원하는 최종 상태(desired state)“를 선언하면 Terraform이 provider API를 호출해 현재 상태를 그 상태로 **수렴(converge)**시킨다.

핵심 가치는 세 가지다. 첫째 재현성 — 같은 코드로 dev/stage/prod를 동일하게 찍어낸다. 둘째 버전 관리 — 인프라 변경이 Git diff로 남아 리뷰·롤백·감사가 가능하다. 셋째 선언형(declarative) — 절차(어떻게)가 아니라 결과(무엇을)를 기술하므로, Terraform이 생성·수정·삭제 순서를 의존성 그래프로 알아서 계산한다. Ansible 같은 절차형 구성 관리 도구와 달리, Terraform은 프로비저닝(자원 생성) 자체에 특화되어 있다.

원리와 핵심 개념

Provider와 Resource, Data source

  • Provider: 대상 플랫폼(AWS, GCP, Azure, Kubernetes, GitHub 등)의 API 어댑터. terraform init 시 레지스트리에서 플러그인을 내려받는다. 버전을 required_providers로 고정해야 재현성이 깨지지 않는다.
  • Resource: Terraform이 생성·관리하는 객체. resource "aws_s3_bucket" "docs" { ... }처럼 타입로컬 이름으로 식별한다.
  • Data source: 이미 존재하는 것을 조회만 하는 읽기 전용 블록. 예를 들어 최신 Amazon Linux AMI ID나 다른 팀이 만든 VPC ID를 참조할 때 쓴다.
블록역할상태 관리
provider플랫폼 API 연결자원 아님
resource자원 생성/수정/삭제state에 기록
data기존 자원 조회매 plan마다 refresh
variable/output입력 파라미터 / 출력값값 전달

State — Terraform의 심장

Terraform은 코드와 실제 인프라의 매핑을 **state 파일(terraform.tfstate)**에 저장한다. resource.aws_s3_bucket.docs가 실제 어떤 ARN에 대응하는지, 어떤 속성값을 마지막으로 알고 있는지가 여기 들어 있다. plan은 (코드가 말하는 desired) vs (state가 기억하는 last-known) vs (provider가 응답하는 real) 세 값을 비교해 diff를 만든다.

state는 두 가지 이유로 원격 백엔드에 두는 것이 필수다.

  1. 협업: 로컬에 두면 팀원마다 state가 갈라진다. S3 같은 공유 저장소에 둔다.
  2. 잠금(locking): 두 사람이 동시에 apply하면 state가 깨진다. 잠금 장치로 직렬화한다. 전통적으로 S3 + DynamoDB 조합을 썼고, 현재는 S3 백엔드가 자체 락(use_lockfile)을 지원해 DynamoDB 없이도 잠금이 가능하다.

state에는 DB 비밀번호, 액세스 키 같은 평문 민감정보가 들어갈 수 있으므로 원격 백엔드에는 반드시 암호화·접근 제어를 건다.

Plan / Apply 라이프사이클

terraform init   → provider/backend 초기화, .terraform.lock.hcl 생성
terraform plan   → refresh → desired와 diff 계산 → "+생성 ~변경 -삭제" 미리보기
terraform apply  → plan을 실제 API 호출로 실행, state 갱신
terraform destroy→ 관리 중인 자원 일괄 삭제

plan리뷰 게이트다. CI에서 terraform plan -out=tfplan으로 계획을 산출물로 남기고, PR 리뷰에서 무엇이 삭제(-)되는지 확인한 뒤 승인된 그 계획 그대로 apply tfplan을 실행하는 것이 안전한 워크플로다.

실전: S3 원격 백엔드 + 리소스

# versions.tf — provider/버전 고정
terraform {
  required_version = ">= 1.7"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket       = "myorg-tf-state"
    key          = "app/prod/terraform.tfstate"
    region       = "ap-northeast-2"
    encrypt      = true
    use_lockfile = true   # S3 네이티브 잠금 (DynamoDB 불필요)
  }
}
 
provider "aws" {
  region = "ap-northeast-2"
}
# main.tf — 변수, data source, resource, output
variable "env" {
  type    = string
  default = "prod"
}
 
data "aws_caller_identity" "current" {} # 조회 전용
 
resource "aws_s3_bucket" "docs" {
  bucket = "myorg-docs-${var.env}"
  tags = {
    Environment = var.env
    ManagedBy   = "terraform"
  }
}
 
resource "aws_s3_bucket_versioning" "docs" {
  bucket = aws_s3_bucket.docs.id # 참조로 암묵적 의존성 형성
  versioning_configuration {
    status = "Enabled"
  }
}
 
output "bucket_arn" {
  value = aws_s3_bucket.docs.arn
}

aws_s3_bucket_versioningaws_s3_bucket.docs.id를 참조하는 순간 Terraform은 버킷을 먼저 만들고 버저닝을 나중에 붙이는 순서를 자동으로 계산한다. 명시적 순서 지정이 필요하면 depends_on을 쓴다.

함정과 베스트프랙티스

함정왜 위험한가대응
state 수동 편집JSON을 직접 고치면 매핑이 깨져 자원이 유령이 됨terraform state mv/rm 명령만 사용
로컬 state 방치협업 불가, 잠금 없음, 유실 위험첫날부터 원격 백엔드 + 잠금
provider 버전 미고정마이너 업데이트가 plan을 뒤흔듦~>로 핀, lock 파일 커밋
콘솔에서 직접 수정실제 인프라와 코드가 어긋남(drift)변경은 코드로만, plan으로 drift 감지
민감정보 하드코딩state·코드에 평문 노출변수/시크릿 매니저, state 암호화
  • plan을 항상 먼저: 특히 -(삭제) 라인을 눈으로 확인한다. RDS 교체 같은 파괴적 변경은 여기서 걸러야 한다.
  • 작게 쪼개기: 하나의 거대한 state는 blast radius가 크다. 네트워크/데이터/앱 계층을 분리한다.
  • .terraform.lock.hcl을 커밋한다. provider 해시를 고정해 팀·CI가 동일한 플러그인을 쓴다.

정리

Terraform은 인프라를 선언형 코드로 정의해 재현·버전관리·협업을 가능하게 한다. provider로 플랫폼에 연결하고 resource로 자원을 관리하며 data source로 기존 것을 조회한다. 실제 인프라와 코드의 매핑인 state는 원격 백엔드 + 잠금으로 보호하고 절대 손으로 고치지 않는다. plan → apply 사이클에서 plan을 리뷰 게이트로 삼아 파괴적 변경을 걸러내는 것이 안전 운영의 핵심이다. 모듈·워크스페이스·import·드리프트 감지 등 규모 운영 기법은 Terraform - 심화에서 이어진다. 배포 자동화 관점은 배포 전략 - Rolling·Blue-Green·Canary와 함께 본다.

키워드: #terraform #iac provider resource state plan-apply backend