운영 환경은 잘 도는데, 스테이징을 새로 만들라는 지시가 떨어졌습니다. 그런데 운영을 만든 사람은 퇴사했고, 콘솔 어디를 어떻게 클릭했는지 기록이 없습니다. 보안그룹 규칙 하나, 서브넷 하나가 다르면 "운영에선 됐는데 스테이징에선 안 되는" 지옥이 시작됩니다. 손으로 만든 인프라는 재현이 안 됩니다. IaC는 인프라를 코드로 적어, 같은 환경을 버튼 하나로 다시 찍어냅니다.
- 1IaC가 인프라를 코드화해 얻는 가치(재현·리뷰·버전관리)를 안다
- 2Terraform의 plan/apply 흐름을 설명할 수 있다
- 3상태(state) 파일의 역할과 안전한 관리법을 이해한다
- 4팀 협업 시 원격 백엔드·상태 잠금이 왜 필요한지 안다
- 5드리프트(코드와 실제의 차이) 개념을 안다
콘솔 클릭을 코드로 바꾸기
IaC — 인프라를 git에 올리는 순간 달라지는 것
IaC는 VPC·서브넷·인스턴스·DB 같은 인프라를 선언적 코드로 적습니다. 그러면:
- 재현: 같은 코드로 dev/stg/prod를 동일하게 생성
- 리뷰: 인프라 변경을 PR로 리뷰(PR과 코드 리뷰)
- 버전 관리: 누가 언제 무엇을 바꿨는지 git에 기록
- 재해 복구: 통째로 날아가도 코드로 재구축
"콘솔에서 누군가 손으로 만든, 아무도 모르는" 스노우플레이크 인프라가 사라집니다.
선언적 — '어떻게'가 아니라 '무엇을'
Terraform 코드는 절차(클릭 순서)가 아니라 원하는 최종 상태를 적습니다. "이 VPC, 이 서브넷 2개, 이 보안그룹이 존재해야 한다"라고 선언하면, Terraform이 현재 상태와 비교해 차이만큼만 만들거나 바꿉니다.
resource "aws_instance" "web" {
ami = "ami-0abc123"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
tags = { Name = "web", Env = "prod" }
}
확대
확대
위 그림처럼 tfstate는 코드와 실제 인프라를 잇는 매핑 테이블입니다. 팀 협업 시 S3+DynamoDB 원격 백엔드로 상태를 공유하고 잠금으로 충돌을 막습니다.
확대
위 그림처럼 인프라 변경도 코드 리뷰를 거칩니다. PR에서 plan 출력을 검토하고 승인된 뒤에만 apply가 실행됩니다.
plan → apply, 그리고 state
- plan: 코드와 실제 인프라(state 기준)를 비교해
+생성 / ~변경 / -삭제를 미리 보여줌. 적용 안 함. - apply: plan의 변경을 실제로 적용.
- state: Terraform이 만든 자원과 코드의 매핑 기록. 다음 plan의 차이 계산 근거.
핵심 습관: apply 전에 plan을 반드시 읽는다. 특히 -(삭제)나 강제 재생성(replace)이 보이면 의도와 맞는지 확인 — DB 같은 자원이 무심코 재생성되면 데이터가 날아갈 수 있습니다.
terraform apply를 누르면 실제로 무슨 일이 일어나나 — 파싱부터 state 갱신까지 6단계
terraform apply 한 줄. Enter를 누르면 잠깐 멈췄다가 Apply complete!가 뜨고 클라우드에 자원이 생깁니다. 이 짧은 순간에 Terraform은 코드를 읽어 그래프를 만들고 → 실제와 state를 대조(refresh)하고 → 차이를 계산(plan)하고 → 승인을 받아 → provider API로 적용하고 → state를 갱신하는 6단계를 순서대로 밟습니다. 이 흐름을 알면 "왜 삭제가 뜨지", "apply가 중간에 죽으면 어떻게 되지", "왜 잠겼지"를 단계로 좁혀 이해할 수 있습니다. apply는 마법이 아니라 아래 6단계의 결과입니다.
[내 PC] terraform apply
│
① 코드 파싱 + 그래프 구성 (.tf 읽기 → 자원 의존성 DAG 생성)
│
② refresh: state ↔ 실제 대조 (provider API로 현재 상태를 읽어 state와 비교)
│
③ plan: diff 산출 (원하는 상태 - 실제 = +생성 / ~변경 / -삭제)
│
④ 승인 대기 + state 잠금(lock) (yes 입력 — -auto-approve면 건너뜀)
│
⑤ provider API 호출로 적용 (의존성 순서대로 create / update / delete)
│
⑥ state 갱신 (적용 결과·실제 ID를 tfstate에 기록)
▼
[결과] Apply complete! 2 added, 1 changed, 0 destroyed
각 단계에서 무슨 일이 일어나고, 틀어지면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 틀어지면 |
|---|---|---|
| ① 파싱·그래프 | HCL 문법 검사, 변수·모듈 해석, 자원 간 참조로 실행 의존성 그래프(DAG)를 만든다 | 문법 오류·순환 참조 → Error: Cycle … 또는 파싱 에러로 즉시 중단 |
| ② refresh | provider API로 실제 자원의 현재 값을 읽어 state와 대조. 콘솔에서 손으로 바꾼 변경(드리프트)이 이 단계에서 드러난다 | 콘솔 수동 변경 → plan에 ~변경(드리프트)으로 표시 / 자격증명 만료 → refresh에서 인증 실패 |
| ③ plan | 코드가 선언한 '원하는 상태'에서 refresh한 '실제'를 빼서 무엇을 +생성·~변경·-삭제할지 계산 | 변경 불가 속성(예: 서브넷) 수정 → ~가 아니라 강제 재생성(-/+ replace)으로 계산 |
| ④ 승인·잠금 | 사람이 diff를 읽고 yes. 이 실행이 state 잠금(lock)을 점유해 동시 apply를 막는다 | 동료가 이미 apply 중 → Error acquiring the state lock으로 대기 |
| ⑤ 적용 | 의존성 순서대로(VPC → 서브넷 → 인스턴스) provider API 호출. 서로 무관한 자원은 병렬로 | 중간에 API 실패·타임아웃 → 일부만 적용된 채 중단(부분 적용) |
| ⑥ state 갱신 | 성공한 자원의 실제 ID·속성을 tfstate에 기록해 다음 plan의 기준으로 삼는다 | ⑤가 중간에 죽어도 이미 만든 자원은 state에 반영, 못 만든 건 누락 → 다음 plan이 남은 차이를 다시 계산 |
즉 apply 실패가 치명적이지 않은 이유는 이 6단계가 이렇게 분리돼 있기 때문입니다. ⑤에서 절반만 적용되고 죽어도 ⑥이 성공분을 state에 남기므로, 다시 apply하면 ②refresh가 현재 실제 상태를 새로 읽어 '남은 차이'만 이어서 적용합니다(멱등성). 그래서 부분 적용은 대개 재실행으로 수렴하고, 진짜 위험한 건 apply 실패 자체가 아니라 ③plan을 읽지 않고 넘긴 -삭제·replace입니다. 잠금(④)은 이 6단계가 두 사람에게서 동시에 돌아 state가 엉키는 것을 막는 장치이고, 드리프트(②)는 apply 밖에서 벌어진 변경을 plan이 되잡아 오는 지점입니다.
terraform apply — plan을 안 보고 실행하지 말 것
안전한 실행 조건: 운영 환경에서는 반드시 terraform plan을 사람이 읽고 -(삭제)·replace 항목을 확인한 뒤 apply. -auto-approve는 plan을 이미 검토한 CI 파이프라인 등 통제된 환경에서만.
실행 전 반드시 확인
- plan 출력에서 -(삭제)·replace 항목이 의도한 것인가
- DB·스토리지 등 상태가 있는 자원이 재생성 대상은 아닌가
- 대상 환경(workspace/계정/리전)이 맞는가 — prod에 dev 코드 적용 아닌가
- state가 최신이고 다른 사람이 동시에 apply 중은 아닌가(잠금)
terraform apply -auto-approve위 항목을 모두 확인한 후 복사할 수 있습니다
무엇이 바뀔지 먼저 봅니다. 숫자(+/-/~)와 특히 삭제·재생성을 확인합니다.
terraform plan
Plan: 2 to add, 1 to change, 0 to destroy.
+ aws_subnet.private_b
~ aws_instance.web (tags 변경)
terraform plan누군가 콘솔에서 손으로 바꾼 변경이 있는지 확인합니다. 차이가 나오면 코드와 실제가 어긋난 것입니다.
terraform plan -refresh-only
Note: Objects have changed outside of Terraform
~ aws_security_group.web ingress(22) 가 콘솔에서 추가됨 ← 드리프트
terraform plan -refresh-only- plan의 'to destroy' 숫자 — 0이 아니면 무엇이 삭제되는지 한 줄씩 확인. DB·스토리지면 특히 경계
- ~replace(강제 재생성) 표시 — 변경이 아니라 삭제 후 재생성. 상태 있는 자원이면 데이터 유실 위험
- refresh-only에서 드리프트 발견 — 콘솔 수동 변경이 있었다는 신호. 코드에 반영하거나 되돌려 일관성 유지
- state 파일이 git/로컬에 평문으로 있는지 — 민감정보 유출 위험. 원격 백엔드+암호화+잠금으로 이전
상황: 여러 명이 같은 인프라에 동시에 apply해 state 충돌·잠금 오류.
원인: state를 공유 원격 백엔드에 두고 잠금을 걸지 않으면, 동시 apply가 같은 자원을 두고 경합합니다. 로컬 state면 서로의 변경을 아예 모릅니다.
진단: 백엔드 설정 확인(원격인지 로컬인지) → 잠금 메커니즘 유무 확인 → 누가 apply 중인지 확인.
해결: state를 원격 백엔드(예: S3)에 두고 상태 잠금(예: DynamoDB lock)을 활성화해 한 번에 한 명만 apply. CI 파이프라인에서만 apply하도록 통제하면 더 안전합니다. 잠금이 비정상 종료로 남았으면 신중히 force-unlock(정말 아무도 실행 안 할 때만).
심화 — state가 커질수록 달라지는 것
심화: 하나의 state가 전부를 담으면 — 분할과 무중단 리팩터링
IaC가 자리 잡으면 다음 병목은 코드가 아니라 state의 크기입니다. 자원 수백 개가 한 state에 들어 있으면:
- plan이 느려집니다: plan은 state의 모든 자원을 실제와 대조(refresh)하므로, 자원 수에 비례해 수 분~수십 분까지 늘어납니다.
- 폭발 반경이 커집니다: 태그 하나 고치려는 apply가 같은 state의 DB·네트워크까지 건드릴 수 있는 위치에 놓입니다. 잠금도 state 단위라, 사소한 변경 하나가 팀 전체의 apply를 막습니다.
그래서 성숙한 팀은 state를 변경 주기와 위험도로 분할합니다 — 네트워크(거의 안 바뀜)/데이터(위험)/애플리케이션(자주 바뀜)을 별도 state로 두고, 계층 간 참조는 output을 데이터 소스로 읽어 연결합니다.
하나 더 — 코드를 정리(리소스 이름 변경·모듈로 묶기)하면 Terraform은 다른 자원으로 인식해 삭제+재생성을 계획합니다. 실제 자원은 그대로 두고 state 안의 주소만 바꾸는 도구가 moved 블록(또는 terraform state mv)입니다. 리팩터링 plan에서 destroy가 보이면 이것부터 의심합니다.
상황: 정리 목적의 리팩터링 PR. 동작 변경이 없다고 생각해 가볍게 apply하려다, plan에서 운영 DB 서브넷 그룹·보안그룹까지 삭제 대상에 올라온 것을 발견했습니다.
원인: Terraform은 자원을 '코드상 주소'(예: aws_instance.web → module.web.aws_instance.this)로 추적합니다. 이름을 바꾸거나 모듈로 옮기면 기존 주소의 자원은 '삭제할 것', 새 주소는 '만들 것'으로 계산합니다. 실제 인프라는 그대로인데 state의 주소만 어긋난 상태입니다.
진단: plan의 destroy와 add 목록을 짝지어 봅니다 — 같은 자원이 옛 주소에서 지워지고 새 주소로 생성되면 리팩터링발 오탐입니다. terraform state list로 현재 state의 주소 목록을 확인합니다.
해결: 코드에 moved 블록으로 '옛 주소 → 새 주소'를 선언하면(구버전은 terraform state mv) plan이 0 to destroy로 바뀝니다. 그리고 리팩터링 PR은 plan 결과가 무변경(no changes)임을 확인한 뒤에만 머지하는 것을 규칙으로 삼습니다 — CI가 plan을 PR에 코멘트로 붙여주면(CI/CD 파이프라인) 리뷰어가 바로 잡아냅니다.
IaC는 현대 인프라 엔지니어의 필수 역량입니다. 면접에서 "인프라를 어떻게 관리하나요?"에 "콘솔에서 직접"이라 답하면 감점, "Terraform으로 코드화하고 PR 리뷰 후 CI에서 plan/apply, state는 원격+잠금"이면 합격선입니다.
실무 성숙도: ① 모듈화(재사용 가능한 VPC/EKS 모듈), ② 환경별 분리(workspace/디렉터리), ③ CI에서 plan을 PR에 코멘트로 보여주고 머지 시 apply(CI/CD 파이프라인), ④ 드리프트 정기 점검. 이 트랙에서 배운 VPC(VPC와 서브넷)·DB(관리형 데이터베이스(RDS))·컨테이너(매니지드 컨테이너)를 전부 코드로 선언하면 환경 재현이 버튼 하나가 됩니다.
관련 모듈로 더 깊이:
- 클라우드 비용 최적화 — IaC로 선언한 자원이 만들어내는 비용을 모델·최적화하는 법
- 매니지드 컨테이너 — IaC로 함께 선언·배포하는 컨테이너 오케스트레이션
- Well-Architected — 재현 가능한 인프라가 떠받치는 운영 우수성·안정성 원칙
다음 모듈에서는 이렇게 만든 자원들이 새어 나가는 돈 — 클라우드 비용 모델과 최적화 를 다룹니다.