terraform plan -refresh=false, CI 시간 절반으로 줄인 한 줄
이거 모르는 분 꽤 많더라. 우리 팀 모듈 plan에 8분 넘게 걸리던 게 4분으로 줄었다. 코드 한 줄도 안 고쳤다.
뭐가 문제였냐면
terraform plan 돌리면 기본적으로 state에 있는 모든 리소스를 클라우드 API에 한 번씩 조회한다(refresh). 리소스 200개짜리 모듈이면 200번 API 콜이 도는데, AWS provider 같이 throttling 걸리면 더 느려진다.
CI에서 PR마다 plan을 돌리는 입장에서는, 매번 fresh한 state가 필요한 것도 아닌데 이 refresh 때문에 시간을 다 까먹는다. 특히 우리는 monorepo에 모듈 30개 있어서 PR 하나 올리면 plan job 30개가 동시에 돌고, 그러면 AWS API throttling까지 같이 터진다.
그래서 이렇게 했다
terraform plan -refresh=false -out=tfplan
-refresh=false는 state를 신뢰하고 refresh를 스킵한다. CI에서는 일반적으로 state가 최근 apply 이후 그대로 유지되니까, drift만 걱정 안 하면 안전하다.
GitHub Actions step으로 쓰면 이런 식:
- name: Terraform Plan
run: |
terraform plan \
-refresh=false \
-lock-timeout=60s \
-out=tfplan \
-no-color
working-directory: ${{ matrix.module }}
근데 drift는?
이게 좀 애매한데. PR 단계 plan에서 refresh 안 하니까 외부에서 손댄 리소스가 있어도 못 잡는다. 우리 팀은 이렇게 갈랐다.
PR 시점의 plan은 -refresh=false로 빠르게. apply 직전 plan(메인 머지 후)은 refresh 켜고 정상 실행. 그리고 매일 새벽 한 번 terraform plan -refresh-only만 별도 cron job으로 돌려서 drift 감지하고 슬랙으로 쏜다.
terraform plan -refresh-only -detailed-exitcode
# exit code 2 = drift 있음
-refresh-only는 state만 업데이트하고 리소스 변경은 제안하지 않는 모드다. 1.1부터 standalone terraform refresh를 대체하고 들어왔는데, 의외로 안 쓰는 팀이 많다.
실제 효과
우리 monorepo 기준으로:
| 단계 | 이전 | 이후 |
|---|---|---|
| PR plan (모듈 30개 병렬) | 8~12분 | 3~5분 |
| AWS API throttle 에러 | 주 2-3건 | 0건 |
| Apply 시점 plan | 변화 없음 | 변화 없음 |
CI 비용도 줄었다. GitHub Actions 분당 과금 생각하면 한 달에 꽤 차이 난다.
주의할 것
다 좋은데 함정도 있다. -refresh=false로 plan 떠서 본 결과가 실제 인프라와 다를 수 있다는 점. 누가 콘솔에서 보안 그룹 룰 하나 지웠는데 plan에는 안 보일 수 있다. 그래서 apply 직전 plan은 꼭 refresh를 켜라.
그리고 처음 도입할 때 한 가지 더 — 기존에 state가 오래된 모듈이면 일단 한 번은 refresh 켜서 plan 떠보고 drift를 정리한 다음에 -refresh=false로 전환하는 게 안전하다. drift 쌓여있는 상태에서 바로 끄면 나중에 봐도 뭐가 진짜 변경인지 구분 안 된다.
혹시 더 좋은 패턴 쓰시는 분 있으면 댓글로 알려주세요.