티스토리 뷰

들어가며 — plan은 무엇과 무엇을 비교하는가

Terraform을 처음 만지면 가장 먼저 외우게 되는 명령이 terraform plan입니다. 코드를 고치고 plan을 돌리면 "이 리소스를 새로 만들고, 저 리소스의 이 속성을 바꾸겠다"는 미리보기가 깔끔하게 떨어집니다. 이게 워낙 자연스럽게 느껴져서 한동안은 plan이 "내 코드와 실제 클라우드를 직접 비교한다"고 막연히 생각했습니다. 코드에 적힌 모습과 AWS 콘솔에 있는 실제 모습을 Terraform이 그때그때 대조해 차이를 뽑아 준다고 믿은 것입니다.

이 직감은 절반만 맞습니다. plan이 비교하는 대상은 둘이 아니라 셋입니다. 코드(원하는 모습)와 실제 클라우드(지금 모습), 그리고 그 사이에 끼어 있는 state(Terraform이 마지막으로 기억하는 모습)입니다. Terraform 공식 문서는 state를 두고 "원격 시스템의 객체와 설정에 선언된 리소스 인스턴스 사이의 바인딩을 저장한다"고 설명합니다. 다시 말해 plan은 코드만 읽어서 결론을 내는 게 아니라, 자기가 적어 둔 장부를 먼저 펼친 다음 그 장부와 코드, 장부와 실제를 함께 견주어 봅니다.

이 한 가지 사실을 분명히 잡고 나면 Terraform의 많은 동작이 갑자기 이해되기 시작합니다. 왜 state 파일을 잃으면 큰일이 나는지, 왜 팀에서 쓰려면 state를 어딘가에 모아 둬야 하는지, 왜 누가 콘솔에서 손으로 자원을 바꾸면 plan이 그걸 잡아내는지 — 전부 이 장부의 존재에서 출발합니다. 이 글에서는 그 장부, 즉 state가 무엇이고 왜 없으면 안 되는지부터 시작해, 팀 단위로 Terraform을 쓸 때 state를 어디에 어떻게 두어야 하는지를 차근차근 풀어 보려 합니다. 기술적 사실은 가능한 한 HashiCorp와 AWS의 공식 문서에서 확인되는 범위 안에서만 다루겠습니다.


State란 무엇인가 — Terraform이 만든 것들의 장부

State를 한 문장으로 줄이면 "Terraform이 자기가 만든 리소스를 적어 둔 장부"입니다. 코드에는 aws_s3_bucket.tfstate처럼 사람이 붙인 논리적 이름이 적혀 있습니다. 그런데 AWS 안에 실제로 만들어진 버킷에는 techai-tfstate-111122223333-apne2 같은 실제 이름과 식별자가 따로 있습니다. 코드의 논리적 이름과 실제 세계의 식별자, 이 둘을 잇는 다리가 state입니다. Terraform 공식 문서가 "state의 가장 중요한 목적은 원격 시스템의 객체와 설정에 선언된 리소스 인스턴스 사이의 바인딩을 저장하는 것"이라고 못 박는 이유가 여기 있습니다.

state 파일은 사람이 직접 손대라고 만든 파일이 아니지만, 열어 보면 그저 JSON입니다. 각 리소스마다 코드상의 주소(module.network.aws_vpc.this 같은 경로)와 실제 클라우드의 ID(vpc-0abc1234 같은 값), 그리고 그 리소스의 현재 속성값이 줄줄이 적혀 있습니다. Terraform은 plan을 돌릴 때 이 장부를 먼저 읽어 "내가 관리하는 자원이 무엇이고, 각각이 실제로 어떤 ID를 가졌는지"를 확인한 다음, 그 ID로 클라우드에 질의해 지금 모습을 가져옵니다. 그렇게 모은 세 가지 — 코드, 장부, 실제 — 를 견주어 차이를 계산합니다.

공식 문서는 state가 자원 매핑만 담는 게 아니라 "메타데이터를 추적하고 큰 인프라에서 성능을 높이는 데에도" 쓰인다고 덧붙입니다. 자원이 수백 개로 늘어났을 때 매번 클라우드 전체를 훑는 대신 장부에 적힌 자원만 골라 확인하면 plan이 훨씬 빨라집니다. 그래서 state를 가볍게 "그냥 캐시"라고 부르고 싶은 유혹이 생기는데, 이 표현은 위험합니다. 캐시는 잃어도 다시 채우면 그만이지만, state는 잃으면 Terraform이 자기가 만든 자원을 더 이상 자기 것으로 인식하지 못합니다. 이 차이가 왜 치명적인지는 곧 이어서 보겠습니다.

여기서 Java 개발자라면 익숙한 비유를 찾고 싶어집니다. 굳이 대보자면 JPA의 영속성 컨텍스트가 가장 가깝습니다. 영속성 컨텍스트도 지금 관리 중인 엔티티 객체와 DB의 실제 row를 매핑해 두고, flush 시점에 변경을 감지해 쿼리를 만듭니다. 하지만 이 비유는 끝까지 밀어붙이면 어긋납니다. 영속성 컨텍스트는 트랜잭션이 끝나면 사라지는 메모리 안의 일시적 구조인 반면, Terraform의 state는 파일로 떨어져 디스크에 남고 팀 전체가 같은 파일을 공유합니다. 솔직히 말하면 state는 Java/Gradle 세계에 깔끔한 대응물이 없는 개념입니다. 이 글에서 가장 정직한 안내는 "여기서부터는 Java 직관이 잘 통하지 않으니, 새 개념으로 받아들이는 편이 낫다"는 말일 것입니다.


로컬 state의 문제 — 혼자일 땐 안 보이는 벽

Terraform을 처음 쓰면 state는 별다른 설정 없이 현재 디렉터리에 terraform.tfstate라는 파일로 그냥 생깁니다. 혼자 연습할 때는 이게 전혀 문제가 되지 않습니다. 내 노트북 안에서 코드와 장부와 클라우드가 한 사람의 손안에 다 있으니 어긋날 일이 없습니다. 그런데 이 평온함이 팀으로 넘어가는 순간 깨집니다.

상황을 그려 보겠습니다. 같은 인프라 저장소를 두 사람이 각자 자기 노트북에 클론해서 작업한다고 해 봅시다. A가 보안 그룹 규칙 하나를 추가하고 apply하면, 그 변경은 A의 노트북에 있는 state 파일에만 기록됩니다. 비슷한 시각에 B가 다른 규칙을 추가하고 apply하면, B의 state는 A가 만든 규칙의 존재를 전혀 모릅니다. B의 장부에는 그 규칙이 적혀 있지 않으니까요. 이 상태에서 B가 다음 plan을 돌리면 Terraform은 "어, 내 장부에 없는 규칙이 클라우드에 떠 있네? 코드에도 없으니 지워야겠다"고 판단할 수 있습니다. A의 작업이 B의 apply 한 번에 조용히 사라지는 사고입니다. 두 사람은 서로의 변경을 보지 못한 채 같은 인프라를 번갈아 덮어쓰게 됩니다.

더 무서운 시나리오는 state 파일을 잃는 경우입니다. 노트북을 포맷했거나, .gitignore에 넣는 걸 깜빡해 민감한 state를 지웠거나, 단순히 파일을 실수로 삭제했다고 해 봅시다. Terraform 입장에서는 장부가 통째로 사라진 것이고, 그러면 자기가 만든 VPC도 RDS도 더 이상 "내가 관리하던 것"으로 보이지 않습니다. 코드에는 aws_vpc.this가 있는데 장부에 대응하는 ID가 없으니, Terraform은 그 VPC를 "아직 안 만든 것"으로 판단하고 새로 만들려 듭니다. 이미 운영 중인 자원 옆에 똑같은 자원이 하나 더 생기거나, 이름이 충돌해 apply가 실패하는 식의 사고로 이어집니다. state를 "그냥 캐시"로 가볍게 여기면 안 되는 이유가 바로 이 지점입니다. 장부를 잃는 것은 캐시를 비우는 일이 아니라, 내가 가진 자산의 소유 증서를 잃는 일에 가깝습니다.

결국 로컬 state가 드러내는 벽은 두 가지입니다. 하나는 여러 사람이 같은 장부를 공유하지 못한다는 점이고, 다른 하나는 그 장부가 한 사람의 디스크에 매여 있어 잃기 쉽다는 점입니다. 팀에서 Terraform을 쓰려면 이 두 문제를 함께 풀어야 하고, 그 답이 원격 백엔드입니다.


원격 백엔드 — S3에 장부를 두고 모두가 같은 걸 본다

원격 백엔드의 발상은 단순합니다. 장부를 각자의 노트북이 아니라 모두가 접근할 수 있는 한 곳에 두자는 것입니다. AWS 위에서 일하는 팀이라면 그 한 곳으로 S3를 고르는 게 자연스럽고, Terraform도 S3 백엔드를 1급 기능으로 지원합니다. state를 S3 버킷에 두면 누가 apply를 하든 같은 장부를 읽고 같은 장부에 씁니다. 앞에서 본 "서로의 변경을 모르는" 문제가 여기서 풀립니다.

다만 장부를 한곳에 모았다는 것은, 그 한곳이 무너지면 모두가 함께 무너진다는 뜻이기도 합니다. 그래서 이 저장소의 state 버킷은 단순한 버킷이 아니라 여러 겹의 보호 장치를 두른 형태로 만들어 두었습니다. 실제 코드(bootstrap/state.tf)를 보면 버킷에 버전 관리를 켜고, KMS 고객 관리 키로 서버 측 암호화를 걸고, Object Lock을 거버넌스 모드로 설정하고, 퍼블릭 액세스를 네 가지 옵션 모두로 차단합니다. 여기에 prevent_destroy = true를 걸어 terraform destroy로도 이 버킷이 지워지지 않게 막아 두었습니다. 장부를 담는 그릇 자체가 사고로 사라지는 일을 코드 차원에서 봉쇄한 셈입니다.

resource "aws_s3_bucket" "tfstate" {
  bucket              = local.state_bucket_name
  object_lock_enabled = true   # 한 번 켜면 못 끔 — 사고성 삭제 보호

  lifecycle {
    prevent_destroy = true     # terraform destroy 로도 못 지움
  }
}


버전 관리를 켜 두는 것이 특히 중요합니다. state는 apply 때마다 갱신되는데, 버전 관리가 켜져 있으면 갱신 전의 모든 버전이 S3에 그대로 남습니다. 나중에 state가 깨졌을 때 직전의 정상 버전으로 되돌릴 수 있는 안전망이 여기서 생깁니다. 이 복구 절차는 글 뒤쪽에서 다시 짚겠습니다.

그렇다면 각 환경이 자기 장부를 어떻게 찾아갈까요. 그 연결을 담당하는 파일이 backend.tf입니다. 이 저장소의 beta 환경 backend.tf는 "이 환경의 state는 S3의 envs/beta/terraform.tfstate라는 키에 둔다"고 가리킵니다. 환경마다 키가 다르기 때문에 dev·beta·prod의 장부가 서로 섞이지 않고, 디렉터리 자체가 곧 "지금 내가 어느 환경을 만지고 있는가"라는 맥락이 됩니다.

terraform {
  backend "s3" {
    key            = "envs/beta/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    kms_key_id     = "alias/techai/tfstate"
    # 잠금 설정은 다음 절에서 — 여기엔 버전 맥락이 얽혀 있습니다.
  }
}


환경을 별도 state로 나눈 결정에는 이유가 있습니다. dev에서 무언가 잘못되더라도 그 사고가 prod의 장부에 닿지 못하게, 즉 사고가 번지는 범위(흔히 blast radius라고 부르는)를 환경 경계에서 끊어 두려는 것입니다. 한 환경 안에서 network·data·apps 모듈을 더 잘게 쪼개 별도 state로 나누는 방법도 있지만, 이 저장소는 아직 거기까지 가지 않고 한 환경을 단일 state로 묶어 두었습니다. 도메인별 state 분리는 규모가 더 커질 때를 위한 향후 옵션으로 남겨 두었고, 이 주제는 시리즈 뒤편 환경 조립 편에서 따로 다룹니다.


잠금 — 동시에 못 건드리게, 그리고 버전이 바꾼 권장 방식

장부를 한곳에 모았더니 새로운 문제가 따라옵니다. 두 사람이 같은 장부에 동시에 쓰려고 하면 어떻게 될까요. A의 apply가 장부를 읽어 계산하는 사이에 B의 apply가 끼어들어 장부를 갈아엎으면, A는 이미 낡아 버린 장부를 기준으로 쓰기를 마쳐 B의 변경을 지워 버립니다. 두 쓰기가 같은 장부를 덮어써 장부 자체가 깨질 수도 있습니다. 이걸 막으려면 "한 번에 한 사람만 장부에 쓸 수 있다"는 규칙, 즉 잠금이 필요합니다.

Java 개발자에게 이 개념은 비교적 익숙합니다. 여러 인스턴스가 같은 자원을 동시에 건드리지 못하게 막는 분산 락과 발상이 같습니다. Redis나 ZooKeeper로 분산 락을 걸어 본 경험이 있다면 "한 사람이 작업하는 동안 락을 쥐고, 끝나면 놓고, 다른 사람은 그동안 기다린다"는 흐름이 그대로 옮겨집니다. Terraform의 잠금도 정확히 그 일을 합니다. apply가 시작될 때 락을 잡고, 끝나면 풀고, 그사이 다른 apply는 "지금 잠겨 있다"는 메시지를 받고 멈춥니다.

문제는 이 잠금을 무엇으로 구현하느냐인데, 여기에 버전 맥락이 얽혀 있어 조심스럽게 짚어야 합니다. 이 저장소는 Terraform 1.9 기준으로 만들어졌고, 그 시점의 표준 방식은 DynamoDB 테이블을 잠금 장치로 쓰는 것이었습니다. 실제로 bootstrap/state.tf에는 LockID를 파티션 키로 갖는 techai-tflock 테이블이 있고, backend.tf의 dynamodb_table 인자가 이 테이블을 가리킵니다. apply가 시작되면 Terraform이 이 테이블에 잠금 레코드를 한 줄 써넣고, 다른 apply는 그 레코드가 있는 동안 진입하지 못합니다.

resource "aws_dynamodb_table" "tflock" {
  name         = var.lock_table_name   # techai-tflock
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

여기서 중요한 단서를 하나 분명히 해 두어야 합니다. HashiCorp의 S3 백엔드 공식 문서는 현재 이 DynamoDB 기반 잠금을 deprecated로 표기하고 있습니다. 문서의 표현을 그대로 옮기면 "잠금은 S3 또는 DynamoDB로 켤 수 있지만, DynamoDB 기반 잠금은 deprecated되었으며 향후 마이너 버전에서 제거될 예정"이라고 적혀 있습니다. 대신 권장되는 방식은 use_lockfile이라는 옵션을 켜는 S3 네이티브 잠금입니다. 별도의 DynamoDB 테이블 없이 S3 안에 잠금 파일을 두어 같은 일을 처리하는 방식으로, Terraform 1.10에서 도입되었습니다.

그렇다면 이 저장소는 왜 여전히 DynamoDB를 쓰고 있을까요. 단순히 버전 때문입니다. use_lockfile은 1.10 이상에서만 동작하는데 이 저장소의 기준 버전은 1.9이고, 1.9에서는 그 옵션을 쓸 수 없습니다. 그래서 1.9 시점의 표준이던 DynamoDB 잠금을 그대로 두고, 코드 주석에도 "상위 버전으로 올리면 DynamoDB 락을 제거하고 use_lockfile = true로 전환할 수 있다"는 메모를 남겨 두었습니다. 정리하면 이렇습니다. DynamoDB 잠금은 틀린 방식이 아니라 특정 버전대의 표준이었던 방식이고, 1.10 이후로는 S3 네이티브 잠금으로 대체되는 흐름입니다. 지금 새로 시작하는 1.10+ 프로젝트라면 처음부터 use_lockfile을 쓰는 편이 공식 권장에 맞습니다.


state에는 민감 정보가 평문으로 들어간다

state를 처음 들여다보면 다소 충격적인 사실 하나를 마주하게 됩니다. 데이터베이스 비밀번호 같은 민감한 값이 state 파일 안에 평문으로 남을 수 있다는 점입니다. Terraform 공식 문서는 "state에는 민감한 데이터가 평문으로 저장될 수 있다"고 분명히 밝히고 있습니다. 예를 들어 RDS 인스턴스를 만들면서 초기 비밀번호를 코드로 넘기면, 그 값이 클라우드에 반영되는 동시에 state의 해당 리소스 속성에도 그대로 적힙니다. state는 "Terraform이 아는 모든 것"을 적어 두는 장부이기 때문에, 자원이 가진 민감한 속성까지 빠짐없이 기록하는 것입니다.

이 사실은 앞에서 본 보호 장치들이 왜 그렇게 겹겹이 둘러쳐 있었는지를 다시 설명해 줍니다. state 버킷에 KMS 암호화를 걸고, 퍼블릭 액세스를 모두 막고, 접근할 수 있는 IAM Role을 최소 권한으로 좁게 잡아 둔 까닭이 여기 있습니다. 장부 안에 비밀이 평문으로 들어 있을 수 있으니, 그 장부가 담긴 버킷을 비밀 금고처럼 다루는 것입니다. 이 저장소는 한 발 더 나아가 버킷 정책으로 HTTPS가 아닌 접근과 KMS로 암호화되지 않은 업로드를 아예 거부하도록 막아 두었습니다.

접근 권한을 나눈 방식도 같은 맥락입니다. PR 단계에서 plan만 돌리는 Role은 state를 읽기만 할 수 있고, apply를 수행하는 Role만 state에 쓸 수 있도록 분리해 두었습니다. plan을 위해 장부를 읽을 권한과 apply를 위해 장부에 쓸 권한을 같은 Role에 한꺼번에 주지 않는 것입니다. 다만 한 가지는 분명히 해 두겠습니다. 암호화와 접근 제한은 "이미 평문으로 적힌 비밀을 잘 숨기는" 방어일 뿐, "비밀이 애초에 state에 안 적히게 하는" 해법은 아닙니다. 후자는 런타임에 비밀을 따로 조회해 코드와 state 어디에도 비밀을 남기지 않는 방식으로 풀어야 하고, 그 주제는 시리즈의 팀 운영 편에서 따로 다룹니다. 이 글에서는 "그래서 state 버킷을 금고처럼 암호화하고 잠가 둔다"는 데까지만 짚어 두겠습니다.


drift — 코드와 실제가 어긋날 때

지금까지는 코드와 state를 같은 편으로 두고 이야기했지만, 현실에서는 이 둘과 실제 클라우드가 서로 어긋나는 일이 생깁니다. 가장 흔한 경로는 누군가 콘솔에서 손으로 자원을 바꾸는 경우입니다. 급한 장애 대응 중에 보안 그룹 규칙 하나를 콘솔에서 직접 열었다고 해 봅시다. 그 순간 실제 클라우드의 모습은 바뀌었지만, 코드도 state도 그 변경을 모릅니다. 코드가 기억하는 모습, state가 기억하는 모습, 실제 모습이 서로 달라진 이 어긋남을 drift라고 부릅니다.

여기서 글 첫머리의 "plan은 셋을 비교한다"는 이야기가 다시 살아납니다. drift가 생긴 뒤 plan을 돌리면, Terraform은 state에 적힌 모습과 실제 클라우드의 모습을 대조하다가 그 차이를 발견하고 diff로 드러냅니다. 손으로 연 규칙이 "코드에는 없는데 실제로는 존재하는" 형태로 plan에 잡히는 것입니다. 즉 plan은 단순히 "내 코드대로 만들 것"을 보여 주는 도구가 아니라, "실제가 내 장부에서 얼마나 벗어났는지"를 비춰 주는 거울이기도 합니다. state라는 장부가 있기 때문에 이 거울이 성립합니다. 장부가 없다면 무엇을 기준으로 어긋남을 판단할지조차 정할 수 없습니다.

drift를 다루는 원칙은 "숨기지 않는다"입니다. plan에 drift가 잡혔을 때 가장 쉬운 도피처는 ignore_changes 같은 설정으로 그 차이를 못 본 척 덮는 것인데, 이걸 습관처럼 쓰면 코드는 점점 실제와 멀어지고 장부는 거짓을 적게 됩니다. 더 건강한 방향은 drift가 보이면 그것이 의도된 변경인지 먼저 따져 보고, 의도된 것이면 코드에 반영하고, 사고였다면 코드 기준으로 되돌리는 것입니다. 어느 쪽이든 결론은 "코드와 실제를 다시 일치시킨다"입니다. drift를 자동으로 주기적으로 감지하는 운영 방식도 있지만, 그 이야기는 팀 운영 편으로 넘기고 여기서는 "drift가 무엇이고 왜 생기며 plan이 그걸 어떻게 비추는가"까지만 정리해 두겠습니다.


state를 잃거나 깨졌을 때 — 복구의 기본기

장부를 한곳에 잘 모아 두고 잠가 두어도, state가 깨지거나 꼬이는 일은 드물게 일어납니다. plan이 설명하기 힘든 drift를 계속 뱉거나, 잠금이 풀리지 않은 채 멈춰 있거나, state 파일 자체가 손상되는 식입니다. 이때를 위해 앞에서 버전 관리를 켜 두었습니다.

복구의 첫 단추는 잠금 해제입니다. apply가 중간에 비정상 종료되면 DynamoDB의 잠금 레코드가 풀리지 않고 남아, 이후 모든 apply가 "잠겨 있다"며 막히는 상황이 생길 수 있습니다. 이럴 때 terraform force-unlock <LOCK_ID>로 잠금을 강제로 풀 수 있는데, 이 명령은 신중하게 다뤄야 합니다. 정말로 아무도 작업 중이 아닐 때만 풀어야지, 다른 사람이 실제로 apply 중인데 잠금을 강제로 풀면 두 apply가 같은 장부를 덮어쓰는 바로 그 사고를 다시 부르게 됩니다. 이 저장소가 force-unlock에 최소 2인 승인을 절차로 둔 이유가 여기 있습니다.

장부 내용 자체가 깨졌다면 S3 버전 관리가 안전망이 됩니다. 버전 관리가 켜져 있으니 apply 때마다 갱신되기 전의 모든 버전이 버킷에 남아 있고, 그중 마지막으로 정상이던 버전을 골라 현재 키로 복사하면 장부를 그 시점으로 되돌릴 수 있습니다. 되돌린 뒤에는 곧장 운영을 재개하지 말고 terraform state list로 자원 목록을 확인하고 terraform plan을 돌려 diff가 최소로 수렴하는지부터 봐야 합니다. 복구가 끝나면 무엇이 어쩌다 깨졌는지 사후 기록을 남기는 일까지가 한 묶음입니다. 이 모든 절차가 가능한 것은 결국 장부를 버전 관리되는 한곳에 모아 두었기 때문이고, 로컬 state였다면 이런 복구는 애초에 선택지에 없습니다.


마무리 — state를 이해하면 Terraform의 절반을 이해한 것

Terraform을 배우면서 가장 크게 남은 인상은, 이 도구의 거의 모든 동작이 state라는 한 장의 장부에서 출발한다는 점이었습니다. 처음에는 plan과 apply라는 명령부터 외웠지만, 한참 뒤에야 그 명령들이 사실은 장부를 읽고 장부와 코드와 실제를 견주고 장부를 갱신하는 일의 다른 이름이라는 걸 알게 되었습니다. 왜 팀에서는 state를 S3에 모아야 하는지, 왜 동시 작업을 잠가야 하는지, 왜 콘솔에서 손으로 바꾼 게 plan에 잡히는지, 왜 state 버킷을 비밀 금고처럼 다루는지 — 이 질문들의 답이 전부 "장부가 있기 때문"이라는 한 곳으로 모였습니다.

Java를 하던 사람으로서 솔직히 적어 두자면, state는 끝까지 깔끔한 비유를 찾지 못한 개념이었습니다. 영속성 컨텍스트에 빗대 보기도 하고 분산 락에 빗대 보기도 했지만, 매번 어딘가에서 비유가 어긋났습니다. 지금은 비유를 억지로 맞추기보다 "Terraform은 자기가 만든 것을 파일로 기억하고, 그 파일을 팀이 함께 공유한다"는 사실 자체를 새 개념으로 받아들이는 편이 더 정확하다고 생각합니다. 비유가 안 통하는 지점을 인정하고 나니 오히려 동작들이 한결 또렷하게 보였습니다.

그래서 이 글의 결론은 제목 그대로입니다. state를 이해하면 Terraform의 절반을 이해한 것이라는 말은 과장이 아니라, 배우는 동안 실제로 그렇게 느낀 순서에 가깝습니다. 나머지 절반 — HCL 언어의 세부, 모듈 설계, 환경별 조립, 팀에서 안전하게 운영하는 법 — 도 결국은 이 장부를 어떻게 잘 쪼개고 잘 공유하고 잘 지키느냐의 변주였습니다. 이 시리즈의 다음 편들에서는 그 변주들을 하나씩 이어 가 보려 합니다. 비슷한 자리에서 Terraform을 막 팀으로 가져오려는 분께, "먼저 state부터 이해하면 그다음이 한결 수월해진다"는 이 글의 순서가 작은 참고가 된다면 좋겠습니다.


참고한 공식 문서