테라폼 운영 강좌 #2 drift와 state 수술: 정기 감지, import 블록, removed 블록
#1의 워크플로로 코드를 거치는 변경은 전부 관문을 통과하게 됐습니다. 남은 문제는 관문을 거치지 않는 변경입니다. 장애 대응 중의 콘솔 수정, 다른 팀이 붙인 태그, 보안팀 도구가 바꾼 설정. 기초 #5에서 이것을 드리프트(drift)라고 불렀고, “테라폼 리소스는 테라폼으로만"이 원칙이라고 했습니다. 운영의 현실은 그 원칙이 깨지는 날이 반드시 온다는 것이고, 이번 편은 깨졌을 때의 대응 체계입니다.
감지: 정기 plan과 exit code #
드리프트의 무서움은 발생이 아니라 모른 채 쌓이는 것입니다. 다음 배포 날 plan에 정체불명의 변경이 잔뜩 나타나면, 내 변경과 남의 드리프트가 섞여 리뷰가 불가능해집니다. 답은 아무 변경이 없어도 주기적으로 plan을 돌려 보는 것입니다. 이때 쓰라고 있는 플래그가 -detailed-exitcode입니다.
terraform plan -detailed-exitcode
# exit 0: 변경 없음
# exit 1: 에러
# exit 2: 변경 있음 = 드리프트exit code 2를 실패로 취급하는 야간 워크플로를 하나 두면 드리프트 감지기가 됩니다.
# .github/workflows/drift-check.yml (핵심부)
on:
schedule:
- cron: "0 21 * * *" # 매일 06:00 KST
jobs:
drift:
steps:
# ... init까지 #1과 동일 ...
- run: terraform -chdir=envs/prod plan -detailed-exitcode
# exit 2면 잡이 실패하고, 실패 알림이 드리프트 알림이 됩니다드리프트가 하루 안에 발견되면 “어제 무슨 일이 있었나"로 좁혀지므로 원인 추적도 쉬워집니다.
대응: 세 갈래의 선택 기준 #
드리프트를 발견하면 선택지는 셋뿐입니다.
- 현실을 코드에 흡수: 콘솔에서 한 변경이 옳았던 경우입니다(장애 대응으로 늘린 타임아웃 등). 그 값을 코드에 반영해 PR로 올리면 plan이 “변경 없음"이 되며 수렴합니다. 긴급 대응 자체를 막을 수는 없으니, 대응 후 코드 반영까지가 대응 절차라고 팀 규약에 넣는 것이 현실적입니다.
- apply로 현실을 되돌림: 변경이 실수였거나 무단이었던 경우입니다. 코드가 진실이므로 apply 한 번으로 원상 복구됩니다. 되돌리기 전에 그 변경이 정말 불필요한지 확인하는 것만 잊지 않습니다.
- ignore_changes로 관할 이관: 그 속성을 다른 시스템이 계속 바꾸는 것이 정상이라면, 기초 #7에서 본 대로 테라폼의 관할에서 빼는 것이 맞습니다.
import 블록: 콘솔에서 태어난 리소스 편입하기 #
드리프트의 확장판으로, 리소스 자체가 코드 밖에서 태어난 경우가 있습니다. 테라폼 도입 전부터 있던 리소스나 급해서 콘솔로 만든 리소스를 코드 관리로 가져오는 도구가 import입니다. 예전에는 terraform import 명령으로 한 개씩 처리했지만, 지금은 import 블록(테라폼 1.5 이상)이 표준입니다. 코드로 선언하니 PR 리뷰에 남고, plan에서 미리 결과를 볼 수 있습니다.
import {
to = aws_s3_bucket.legacy_logs
id = "myapp-legacy-logs" # 리소스 타입별 import ID
}
resource "aws_s3_bucket" "legacy_logs" {
bucket = "myapp-legacy-logs"
# 실제 설정과 일치해야 함
}편입 절차에서 힘든 부분은 import 자체가 아니라 기존 설정과 정확히 일치하는 리소스 블록을 쓰는 일입니다. 여기에 보조 도구가 있습니다.
terraform plan -generate-config-out=generated.tfimport 블록만 있고 resource 블록이 없는 상태에서 이 플래그를 주면, 실제 리소스를 읽어 코드 초안을 생성해 줍니다. 생성된 코드는 장황해서 그대로 쓸 물건은 아니지만, 속성값을 눈으로 옮겨 적는 노동을 없애 줍니다. 다듬어서 리소스 블록으로 확정하고, plan이 “변경 없음”(import만 1건)이 되면 편입 완료입니다. apply 후 import 블록은 지웁니다.
removed 블록: 지우지 않고 내보내기 #
반대 방향도 있습니다. 리소스를 실제로는 남기고 테라폼 관리에서만 빼는 경우입니다(다른 팀·다른 스택으로 이관, 수동 관리 전환). 기초 #5에서 terraform state rm 명령을 소개했는데, 이것 역시 선언형 짝이 생겼습니다. removed 블록(테라폼 1.7 이상)입니다.
removed {
from = aws_s3_bucket.legacy_logs
lifecycle {
destroy = false # 리소스는 남기고 state에서만 제거
}
}리소스 블록을 지우면서 이 블록을 함께 두면, plan이 “forget”(파괴 없는 제거)을 보여 줍니다. destroy = false를 빼먹으면 실제 삭제가 되므로 이 한 줄이 이 블록의 전부라고 해도 과언이 아닙니다. moved(실전 #9), import, removed까지 갖춰지면서, state 수술 세 종이 모두 CLI 명령이 아니라 리뷰 가능한 코드가 된 셈입니다. 명령형 도구(state mv, state rm, import 명령)는 여전히 동작하지만, 팀 운영에서는 코드 쪽을 기본으로 삼는 것을 권합니다.
정리 #
이번 글에서 다룬 내용입니다.
- 드리프트는 발생보다 축적이 문제입니다.
-detailed-exitcode를 쓴 야간 plan 워크플로가 감지기가 됩니다 - 대응은 셋뿐입니다. 옳은 변경은 코드로 흡수, 잘못된 변경은 apply로 원복, 정상적인 외부 변경은 ignore_changes로 이관
- 긴급 콘솔 대응은 막는 것이 아니라 “코드 반영까지가 절차"로 규약화합니다
- 기존 리소스 편입은 import 블록 +
-generate-config-out초안 생성이 현행 표준입니다. 완료 기준은 “변경 없음” plan입니다 - 관리 제외는 removed 블록에
destroy = false입니다. moved·import·removed로 state 수술이 전부 코드화됐습니다
다음 글(#3 코드 품질과 테스트)에서는 사람 리뷰 앞단의 자동 검사를 쌓습니다. tflint와 pre-commit, 그리고 테라폼 내장 테스트 프레임워크인 terraform test입니다.