Terraform IaC 인프라 자동화 가이드 — 상태 파일과 S3 잠금

DevOps 2026년 8월 5일  ·  2026. 10. 30 업데이트  ·  junetapa  ·  약 12분

Terraform IaC(Infrastructure as Code)는 클라우드 자원을 콘솔에서 손으로 만드는 대신 코드로 선언하고, 계획을 본 뒤 적용하는 방식이다. 문법은 하루면 익히지만 실제로 사고가 생기는 곳은 상태 파일, 잠금, 리소스 주소 같은 운영 쪽이다. 이 글은 Terraform 1.17 공식 문서를 기준으로 AWS 인프라를 코드로 세울 때 첫날 정해야 할 것과, 운영하면서 자주 걸리는 지점을 순서대로 정리했다.

Terraform이 하는 일 — 세 가지 상태 비교

클라우드 리소스를 콘솔에서 손으로 만들면 어제 만든 것과 오늘 만든 것이 같다는 보장이 없다. 누가 언제 무엇을 바꿨는지도 남지 않는다. IaC는 이 문제를 코드와 버전 관리로 옮기는 접근이고, Terraform은 특정 클라우드에 묶이지 않는 선언형 도구다. 구성 파일은 HCL로 쓰고, 명령은 terraform 하나로 다룬다.

콘솔에서 손으로 만드는 방식과 코드로 선언하는 방식을 비교한 도해
손으로 만들면 어제와 오늘이 같다는 보장이 없다.

목표 상태 · 기록된 상태 · 실제 상태

Terraform 구성 파일은 «무엇을 만들라»가 아니라 «최종 상태가 무엇이어야 하는가»를 적는다. 도구는 세 가지를 비교해 차이만큼의 작업을 계산한다. 구성 파일에 적힌 목표 상태, 상태 파일(terraform.tfstate)에 기록된 마지막 관리 상태, 그리고 프로바이더 API로 조회한 실제 상태다. 상태 파일은 단순 캐시가 아니라 «이 리소스는 Terraform이 관리한다»는 소유권 대장이다. 이 파일을 잃어버리면 도구 입장에서는 관리 대상 자체가 사라진 것이 되고, 다음 apply에서 같은 리소스를 새로 만들려고 한다.

프로바이더 플러그인 구조

Terraform 코어는 클라우드 API를 직접 알지 못한다. AWS 자원을 다루는 것은 AWS 프로바이더 플러그인이고, 같은 방식으로 Kubernetes·GitHub 같은 프로바이더가 레지스트리에 공개돼 있다. 여러 공급자를 한 워크플로로 묶을 수 있다는 점이 단일 클라우드 전용 도구와 갈리는 지점이다. 반대로 특정 기능이 프로바이더에 아직 구현되지 않았다면 그 기능은 코드로 관리할 수 없다. 도입할 서비스가 있다면 그 리소스 타입이 프로바이더 문서에 있는지부터 확인한다.

지금 판(2026-10-30 확인)

도구최신 안정판나온 날
Terraform CLI1.17.1 (1.16 계열은 1.16.7)2026-10-28
AWS 프로바이더(hashicorp/aws)6.71.02026-10-28
OpenTofu1.13.22026-10-21

판 번호는 자주 오른다. 글의 명령과 블록은 1.11 이후 판이면 그대로 쓸 수 있도록 골랐고, 판마다 다른 항목은 본문에 몇 판부터인지 적었다.

첫날 정할 것 — 버전 고정과 S3 백엔드 잠금

버전을 먼저 고정한다

구성 맨 위 terraform 블록에 required_version과 required_providers를 적는 것이 출발점이다. 프로바이더 버전을 열어 두면 어제까지 통과하던 코드가 새 메이저 판이 나오는 날 깨진다. init 때 생기는 종속성 잠금 파일(.terraform.lock.hcl)은 팀원과 CI가 같은 프로바이더 바이너리를 쓰도록 맞춰 주므로 저장소에 커밋한다.

terraform {
  required_version = ">= 1.11"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
  backend "s3" {
    bucket       = "example-tfstate"
    key          = "network/terraform.tfstate"
    region       = "ap-northeast-2"
    encrypt      = true
    use_lockfile = true
  }
}
이닛과 플랜, 어플라이로 이어지는 테라폼 기본 절차를 나타낸 도해
plan 이 「무엇을 바꿀 것인가」를 미리 보여 준다.

원격 상태 — S3 백엔드

상태 파일을 로컬에 두면 협업이 안 된다. AWS 인프라라면 S3 백엔드가 기본 선택이다. 공식 문서는 상태 버킷에 버킷 버전 관리를 켜라고 강하게 권한다. 실수로 지우거나 잘못 덮어쓴 상태를 이전 판으로 되돌리기 위해서다. encrypt = true는 상태 파일과 잠금 파일을 서버 측 암호화로 저장하고, KMS 키를 쓰려면 kms_key_id를 함께 적는다. 이때 실행 권한에는 그 키의 kms:Encrypt·kms:Decrypt·kms:GenerateDataKey가 필요하다.

잠금 — DynamoDB 대신 use_lockfile

두 사람이 동시에 apply를 돌리면 상태 파일이 깨지므로 잠금이 필요하다. 예전에는 DynamoDB 테이블을 따로 두는 방식이 정석이었지만, Terraform 1.11.0(2025-02-27) 변경 기록에서 S3 자체 잠금이 정식 기능이 됐고 DynamoDB 관련 인자는 사용 중단(deprecated) 표시가 붙었다. 현재 백엔드 문서도 «DynamoDB 기반 잠금은 사용 중단됐고 이후 마이너 판에서 빠진다»고 적는다.

init에서 apply까지, CI에 올리는 순서

기본 흐름은 init(백엔드 초기화·플러그인 내려받기) → validate(문법·타입 검증) → plan(변경 계획) → apply(적용) → destroy(회수)다. 혼자 쓸 때는 plan과 apply를 이어 쳐도 되지만, 여럿이 쓰는 저장소라면 아래 순서로 CI에 올린다.

단계명령보는 것
1. 서식terraform fmt -check -recursive서식이 다르면 실패시켜 리뷰 잡음을 줄임
2. 검증terraform init · terraform validate문법·타입·참조 오류
3. 시험terraform test모듈의 입력·출력 조건
4. 계획terraform plan -out=tfplan -detailed-exitcode바뀔 자원 목록 · 종료 코드
5. 승인사람이 plan 결과 확인교체(replace)·삭제 항목
6. 적용terraform apply tfplan검토한 계획 그대로 적용

plan을 파일로 저장하는 이유

-out으로 저장한 계획 파일을 terraform apply에 넘기면, 검토한 계획과 실제 적용 대상이 어긋나는 상황을 막는다. 저장하지 않고 apply를 다시 돌리면 그 사이 바뀐 실제 상태를 기준으로 새 계획이 만들어진다. -detailed-exitcode를 붙이면 종료 코드가 0(바뀔 것 없음)·1(오류)·2(바뀔 것 있음)으로 갈려 CI에서 «변경이 있을 때만 승인 단계로» 같은 분기를 만들기 쉽다.

계획 파일도 민감 자료다. 공식 문서는 계획에 민감한 값이 있으면 계획 파일에 평문으로 저장된다고 적고, 저장한 계획 파일을 민감할 수 있는 산출물로 다루라고 한다. CI 산출물로 보관한다면 보관 기간과 접근 권한을 좁힌다.

교체 표시를 먼저 본다

plan 결과에서 «교체(replace)»로 표시된 항목은 기존 리소스를 지우고 새로 만든다는 뜻이다. 데이터베이스나 EBS 볼륨이라면 곧바로 데이터 손실로 이어질 수 있다. 지워지면 안 되는 자원에는 lifecycle의 prevent_destroy = true를 둔다. 공식 문서 설명대로, 이 자원을 없애는 계획은 오류로 거절된다. 끊김 없이 바꿔야 하는 자원은 create_before_destroy로 새 자원을 먼저 만들고, 바깥에서 바뀌는 속성(자동 조정되는 개수 등)은 ignore_changes로 계획에서 뺀다.

terraform test

terraform test는 1.6.0에서 정식 기능이 됐다. 시험은 .tftest.hcl 파일에 run 블록을 이어 쓰는 방식이다. 모듈을 여러 팀이 같이 쓴다면 «이 변수 조합이면 이런 출력이 나와야 한다»는 조건을 시험으로 남겨 두는 편이 리뷰보다 확실하다. GitHub Actions로 이 단계를 묶는 방법은 GitHub Actions CI/CD 고급 워크플로우에 따로 정리했다.

상태 파일과 민감 정보

공식 문서는 구성에 비밀 값을 직접 넣으면 Terraform이 그 값을 상태 파일과 계획 파일에 저장한다고 적는다. 흔히 오해하는 것이 sensitive다. sensitive = true는 CLI 출력에서 값을 가릴 뿐이고, 문서 표현대로 sensitive 값도 상태와 계획 파일에는 그대로 저장된다. 그래서 상태 버킷은 일반 소스 저장소와 분리하고, 버킷 정책으로 읽을 수 있는 역할을 좁힌다.

ephemeral과 write-only — 상태에 남기지 않는 길

방법필요한 판상태·계획 파일쓰는 곳
sensitive0.15+저장됨(출력만 가림)변수·출력 가리기
ephemeral 변수·출력·블록1.10+아예 남기지 않음실행 중에만 필요한 토큰·비밀번호
write-only 인자1.11+남기지 않음DB 초기 비밀번호처럼 자원에 넘기기만 하는 값

문서는 ephemeral 값이 실행 중에는 쓸 수 있지만 상태와 계획 파일에서 완전히 빠진다고 설명한다. write-only 인자는 프로바이더가 그 속성을 «쓰기 전용»으로 정해 둔 경우에만 쓸 수 있으므로, 쓰려는 자원 문서에 write-only 인자가 있는지 먼저 본다. 두 방법 모두 값이 그 실행 밖에 남지 않는다는 점이 같다.

OpenTofu의 상태 암호화

Terraform에서 갈라진 OpenTofu는 상태와 계획 파일을 저장 상태에서 암호화하는 기능을 문서로 제공한다. 로컬 저장과 백엔드 저장 모두에 쓸 수 있고, 키 제공 방식은 암호문에서 키를 만드는 PBKDF2, AWS KMS, GCP KMS, Azure Key Vault, OpenBao, 외부 명령 등이다. 문서는 이 기능이 데이터 손실이나 tofu를 실행하는 사람 자체를 막지는 않는다고 선을 긋는다. 암호화를 켜면 올바른 키 없이는 상태를 읽을 수 없으므로 키 보관 계획이 먼저다.

기존 리소스 편입과 리팩터링

import 블록으로 편입한다

이미 콘솔로 만든 AWS 자원을 코드로 옮길 때는 import를 쓴다. 지금 문서가 권하는 방식은 구성에 import 블록을 적고 plan에서 결과를 미리 보는 것이다. CLI 명령으로 곧바로 상태에 넣던 방식보다 검토 여지가 크다. 자원 블록을 직접 쓰기 번거롭다면 import 블록만 적고 terraform plan -generate-config-out=generated.tf로 자원 블록 초안을 만든 뒤 다듬는다.

import {
  to = aws_s3_bucket.logs
  id = "example-logs-bucket"
}

moved — 주소가 바뀌어도 다시 만들지 않게

Terraform은 리소스를 주소로 추적한다. 모듈 이름을 바꾸거나 리소스를 다른 모듈로 옮기면 주소가 달라지고, 도구는 이를 «기존 것 삭제 후 새로 생성»으로 읽는다. moved 블록으로 이동을 선언하면 실제 리소스를 건드리지 않고 상태만 고친다(모듈 주소 리팩터링은 v1.1 이후). 공식 문서는 모듈을 다른 사람이 쓴다면 예전 판의 moved 블록을 모두 남겨 두라고 강하게 권하고, moved 블록을 지우는 것은 호환을 깨는 변경이라고 적는다.

moved {
  from = aws_instance.web
  to   = module.web.aws_instance.this
}

count보다 for_each

count는 목록 중간 항목이 빠지면 뒤 번호가 밀리면서 상관없는 리소스가 다시 만들어진다. 이름처럼 키가 분명한 대상은 for_each를 쓰는 편이 안전하다. 이미 count로 만든 자원을 for_each로 바꿀 때도 moved 블록으로 번호와 키를 짝지으면 다시 만들지 않는다. 문서는 count가 없던 자원에 count를 붙일 때도 자동 처리에 맡기지 말고 moved 블록을 직접 쓰라고 권한다.

removed — 관리만 끊고 자원은 남기기

어떤 자원을 Terraform 관리에서 빼되 실제 자원은 지우면 안 될 때가 있다. 다른 팀으로 넘기는 경우가 그렇다. removed 블록은 상태에서 자원을 빼는 블록이고, 기본 동작은 상태에서 빼면서 자원도 지운다. lifecycle { destroy = false }를 함께 적어야 실제 자원은 남고 추적만 멈춘다. 이 차이를 놓치면 넘겨줄 자원을 지우게 된다.

상태 나누기와 환경 분리

폭발 반경을 나눈다

네트워크, 데이터 계층, 애플리케이션을 상태 파일 하나에 넣으면 작은 변경에도 전체 plan을 돌려야 하고 실수가 미치는 범위도 커진다. 수명 주기가 다른 자원은 상태를 나누고, 필요한 값은 원격 상태 참조나 데이터 소스로 읽어 온다. S3 백엔드라면 위 예제처럼 key를 network/·data/·app/으로 나누는 것만으로 시작할 수 있다.

workspace는 환경 분리 도구가 아니다

공식 문서는 workspace가 시스템을 나누거나 자격 증명과 접근 통제를 따로 둬야 하는 배포에는 맞지 않는다고 적는다. workspace는 상태만 나뉘고 코드와 백엔드 설정은 같이 쓰기 때문이다. 운영과 개발이 다른 AWS 계정·다른 권한을 써야 한다면 디렉터리(또는 저장소)를 나누고 각자 백엔드를 둔다.

드리프트 확인

콘솔에서 누군가 보안 그룹 규칙을 손으로 고치면 코드와 실제가 어긋난다. 보통 plan은 이 차이를 원래 정의대로 되돌리려 하는데, 장애 대응 중 넣은 임시 조치를 지우는 경우가 있다. 먼저 terraform plan -refresh-only로 바깥에서 바뀐 것만 확인한다. 문서 설명대로 이 모드는 상태와 출력 값을 실제에 맞추는 계획만 만든다. 그 변경을 코드에 반영할지, 되돌릴지는 그다음에 정한다. 서버 안쪽 설정(SSH·방화벽·업데이트)은 Terraform 바깥 몫이라 Linux 서버 보안 설정 체크리스트를 함께 본다.

도구 비교와 라이선스

항목TerraformAWS CloudFormationAWS CDKPulumi
대상 범위멀티 클라우드·SaaS(프로바이더 기반)AWS 전용AWS 전용(합성 뒤 CloudFormation 실행)멀티 클라우드
작성 방식HCL 선언형YAML/JSON 선언형TypeScript·Python 등 범용 언어범용 언어
상태 보관사용자가 백엔드 선택(S3 등)AWS가 스택으로 관리AWS가 스택으로 관리Pulumi 서비스 또는 자체 백엔드
변경 미리보기plan변경 세트(Change Set)diffpreview

라이선스 — BUSL 1.1

Terraform 저장소의 LICENSE 파일 기준으로 1.6.0 이후 판은 Business Source License 1.1이고, 라이선스 주체는 IBM이다(HashiCorp 라이선스 FAQ 첫 게시는 2023-08-10). 추가 사용 허가 조항은 운영 환경 사용을 허용하되, IBM의 유료판과 경쟁하는 형태로 호스팅하거나 제품에 넣어 제공하는 경우를 뺀다. 각 판은 공개 4년 뒤 MPL 2.0으로 바뀐다. 사내 인프라를 관리하는 데 쓰는 일반적인 경우는 이 조항에 걸리지 않지만, Terraform을 감싸 서비스로 파는 사업이라면 조항을 먼저 검토한다. 같은 배경에서 갈라진 OpenTofu(명령 tofu)는 위에서 본 상태 암호화처럼 자체 기능을 더해 가고 있어, 판이 오를수록 두 도구의 차이도 넓어진다.

고르는 기준

정리 — 첫날 체크리스트

Terraform은 «여러 공급자에 걸친 인프라를 여러 사람이 반복해서 바꾸는» 상황에서 값을 한다. 도입을 정했다면 아래 다섯 가지를 첫날 맞춰 두는 것이 이후 복구 비용을 가장 크게 줄인다.

항목설정확인
버전 고정required_version · required_providers · .terraform.lock.hcl 커밋CI와 로컬의 init 결과가 같은지
원격 상태S3 백엔드 · 버킷 버전 관리 · encrypt이전 판 상태로 되돌릴 수 있는지
잠금use_lockfile = true(DynamoDB는 옮기는 기간에만)동시에 apply할 때 한쪽이 기다리는지
비밀 값ephemeral·write-only · 상태 버킷 접근 좁히기상태 파일에 비밀번호가 없는지
적용 절차plan 저장 → 사람 승인 → 저장한 plan으로 applyreplace·삭제 항목을 누가 봤는지

세부 옵션은 판에 따라 달라지므로, 쓰고 있는 판의 공식 문서에서 백엔드와 lifecycle 항목을 확인한 뒤 적용한다.

Terraform IaC 인프라 자동화 가이드 대표 그림 — 왼쪽에 제목 글자가 있고, 오른쪽에 설계도 위로 쌓이는 서버 블록과 자물쇠가 걸린 상태 파일 보관함이 있는 장면
Terraform IaC 인프라 자동화 가이드 — 상태 파일과 S3 잠금.

자주 묻는 것

S3 백엔드 잠금에 DynamoDB 테이블이 아직 필요한가요?

Terraform 1.11부터 S3 자체 잠금(use_lockfile)이 정식 기능이 됐고, DynamoDB 기반 잠금은 사용 중단 표시가 붙어 이후 마이너 판에서 빠질 예정입니다. 새로 만든다면 use_lockfile = true를 쓰고, 옮기는 기간에만 두 설정을 함께 둡니다.

sensitive = true로 표시하면 상태 파일에서도 가려지나요?

아닙니다. sensitive는 CLI 출력만 가리고 값은 상태와 계획 파일에 그대로 저장됩니다. 상태에 남기지 않으려면 1.10 이후의 ephemeral 값이나 1.11 이후의 write-only 인자를 쓰고, 상태 버킷 접근도 좁힙니다.

이미 콘솔로 만든 자원도 Terraform으로 관리할 수 있나요?

import 블록을 구성에 적고 plan으로 결과를 미리 본 뒤 apply하면 됩니다. 자원 블록을 직접 쓰기 어렵다면 terraform plan -generate-config-out 옵션으로 초안을 만든 뒤 다듬습니다.

workspace로 개발과 운영 환경을 나눠도 되나요?

공식 문서는 자격 증명과 접근 통제를 따로 둬야 하는 배포에는 workspace가 맞지 않는다고 적습니다. 운영과 개발이 다른 계정이나 권한을 쓴다면 디렉터리를 나누고 각자 백엔드를 둡니다.

참고 자료

  1. HashiCorp. Terraform S3 backend — use_lockfile·.tflock 권한, DynamoDB 잠금 사용 중단, encrypt·kms_key_id, 버킷 버전 관리 권장(2026-10-30 확인). developer.hashicorp.com
  2. HashiCorp. Terraform CHANGELOG v1.11 — 1.11.0(2025-02-27) S3 자체 잠금 정식·DynamoDB 인자 사용 중단·write-only 속성(2026-10-30 확인). github.com
  3. HashiCorp. Manage sensitive data — 상태·계획 파일 저장, sensitive 0.15+, ephemeral 1.10+, write-only 1.11+(2026-10-30 확인). developer.hashicorp.com
  4. HashiCorp. terraform plan command — -out 저장 계획·-detailed-exitcode 0/1/2·-refresh-only·계획 파일 평문 주의(2026-10-30 확인). developer.hashicorp.com
  5. HashiCorp. Import · Refactoring(moved) · removed 블록 · lifecycle 문서 — generate-config-out, v1.1 모듈 주소 리팩터링, moved 블록 유지 권고, destroy = false(2026-10-30 확인). developer.hashicorp.com
  6. HashiCorp. Workspaces — 자격 증명·접근 통제가 다른 배포에 맞지 않음(2026-10-30 확인). developer.hashicorp.com
  7. HashiCorp. Terraform 저장소 LICENSE·CHANGELOG v1.6 — BUSL 1.1·1.6.0 이후·4년 뒤 MPL 2.0 · terraform test 정식(2026-10-30 확인). github.com
  8. Terraform·OpenTofu GitHub 릴리스, Terraform Registry hashicorp/aws — 1.17.1(2026-10-28)·OpenTofu 1.13.2(2026-10-21)·AWS 프로바이더 6.71.0(2026-10-28)(2026-10-30 확인). github.com
  9. OpenTofu. State and plan encryption — 저장 상태 암호화·키 제공 방식·막지 못하는 것(2026-10-30 확인). opentofu.org
Terraform IaC AWS 인프라 DevOps junetapa
junetapa(박준철)
개발자가 아니라 쓰는 사람. IT 현장 27년의 기준으로 도구를 고르고 기록한다.
Share Twitter Facebook URL 복사