Terraform 실전 · 라이프사이클과 드리프트 · 이론
라이프사이클과 드리프트 — 어느 쪽으로 맞출 것인가
한 줄 요약
드리프트 대응의 핵심 질문은 "어떻게 고치나"가 아니라 "코드를 실물에 맞출 것인가, 실물을 코드에 맞출 것인가" 다.
왜 이게 필요했나
새벽 세 시, 트래픽이 몰려 서비스가 죽는다. 담당자가 콘솔에 들어가 인스턴스 타입을 키우고, 오토스케일링 최대치를 올리고, 디버깅용 포트를 잠깐 연다. 서비스는 살아난다. 여기까지는 옳은 판단이다.
문제는 그다음이다. 이 세 가지 변경은 어디에도 기록되지 않았다. 코드는 여전히 예전 값을 말하고 있고, 상태 파일도 마찬가지다. 며칠 뒤 누군가 전혀 다른 이유로 apply 를 돌리면 도구는 성실하게 새벽의 변경을 전부 되돌린다. 장애가 재현되고, 아무도 원인을 모른다.
이것이 드리프트다. 코드(있어야 할 상태), 상태 파일(마지막으로 알던 상태), 실물(지금 있는 상태) 셋 사이의 불일치. 드리프트 자체는 죄가 아니다. 감지되지 않은 드리프트가 죄다.
어떻게 동작하나
감지의 기본 도구는 plan 이다. 계획은 실행 전에 실물을 새로고침해 상태와 비교하고, 그다음 상태와 코드를 비교한다. 사람이 읽기에는 충분하지만 자동화에는 부족하다. 출력 문자열을 파싱하는 방식은 버전이 바뀌면 깨진다.
그래서 -detailed-exitcode 가 있다.
| 종료 코드 | 의미 |
| --- | --- |
| 0 | 변경 없음 — 코드·상태·실물이 일치 |
| 1 | 오류 |
| 2 | 변경 있음 — 드리프트 또는 미적용 변경 |
이 세 값만 있으면 드리프트 감지를 크론이나 CI 에 그대로 얹을 수 있다. 여기서 자주 걸리는 함정이 set -e 다. 종료 코드 2 를 실패로 보고 스크립트가 죽어 버리므로, 판정 명령은 반드시 예외 처리를 하고 실행해야 한다.
더 정밀하게 보려면 plan -refresh-only 로 계획을 저장한 뒤 show -json 으로 뽑는다. JSON 에는 resource_drift 배열이 따로 있어서 코드 변경으로 생긴 차이와 코드 밖에서 생긴 차이를 구분할 수 있다. 알림에 "누가 콘솔에서 만졌다"와 "아직 배포 안 된 코드가 있다"를 섞어 보내면 사람들은 곧 알림을 무시한다.
lifecycle 블록은 이 흐름에 개입하는 네 개의 손잡이다.
| 인자 | 하는 일 | 쓰는 자리 |
| --- | --- | --- |
| create_before_destroy | 새 것을 먼저 만들고 옛 것을 지움 | 교체 중 중단을 피해야 할 때 |
| prevent_destroy | 파괴 계획 자체를 오류로 막음 | 상태 저장소, 운영 DB |
| ignore_changes | 특정 속성의 차이를 계획에서 제외 | 외부 시스템이 관리하는 속성 |
| replace_triggered_by | 다른 자원이 바뀌면 이 자원을 재생성 | 갱신 수단이 없는 자원의 강제 교체 |
ignore_changes 는 특히 중요하다. 오토스케일러가 관리하는 복제 수, 콘솔이 자동으로 붙이는 태그처럼 의도적으로 코드 밖에서 변하는 값까지 드리프트로 잡으면 거짓 경보가 넘쳐나고, 넘쳐나는 경보는 곧 무시되는 경보가 된다. 다만 무시 목록은 정확히 그 속성만 담아야 한다. 통째로 무시하면 진짜 사고도 함께 조용해진다.
prevent_destroy 는 계획 단계에서 오류를 내며 멈춘다. 실수로 리소스 블록을 지우거나 이름을 바꿨을 때 파괴가 승인되기 전에 브레이크가 걸린다는 뜻이다. 대신 의도적으로 지울 때는 코드에서 이 줄을 먼저 지워야 하므로, 삭제가 반드시 리뷰를 거치게 만드는 장치이기도 하다.
현장에서 만나는 모습
첫째, 대응 방향은 세 가지다. (1) apply 로 실물을 코드에 맞춰 되돌리기, (2) 변경이 의도된 것이었다면 코드를 실물에 맞춰 고치기, (3) 실물이 정답이고 코드도 이미 맞다면 apply -refresh-only 로 상태만 갱신하기. 새벽의 응급 조치가 옳은 판단이었다면 되돌리는 것이 오히려 사고다. 판단 없이 자동 복구를 돌리는 것이 가장 위험하다.
둘째, 심각도로 나눠 자동화한다. 태그 하나 어긋난 것은 자동으로 되돌려도 되지만, 보안 그룹 규칙이나 인스턴스 타입은 사람이 봐야 한다. 실무의 표준 패턴은 낮은 심각도만 자동 복구하고 높은 심각도는 알림 후 멈추는 선택적 복구다.
셋째, 감지 자체가 비용이다. 계획은 관리 중인 모든 자원에 대해 프로바이더 API 를 호출하므로 대규모 인프라에서 자주 돌리면 API 제한에 걸린다. 감지 중에는 상태 읽기 잠금도 걸려 동시에 도는 배포와 충돌할 수 있다. 그리고 계획 출력에는 비밀번호 같은 민감한 값이 섞일 수 있으니 로그로 남길 때 반드시 걸러야 한다.
넷째, 프로바이더 업그레이드 뒤의 대량 드리프트. 새 버전이 속성 기본값을 바꾸면 아무도 아무것도 안 했는데 수백 건이 드리프트로 뜬다. 이때는 실물이 아니라 프로바이더가 바뀐 것이므로 버전 고정과 변경 로그 확인이 먼저다.
다음 실습에서 할 것
create_before_destroy 와 prevent_destroy 를 실제로 걸어 삭제가 막히는 것을 확인하고, ignore_changes 로 코드 값을 바꿔도 계획이 비는 것을 본다. replace_triggered_by 로 다른 자원의 변경이 재생성을 유발하게 만든 뒤, 코드를 거치지 않고 실물을 손으로 고쳐 드리프트를 만든다. 그것을 -refresh-only 계획과 종료 코드 2 로 감지하고, 한 번은 코드 기준으로 되돌리고 한 번은 실물을 코드에 흡수하는 반대 방향으로 대응한다. 마지막에는 감지 결과를 JSON 으로 뱉는 스크립트를 만들어 자동화의 형태를 갖춘다.