Cloud Run 유휴 비용이 웹 서빙의 3배 나온 이유: GCP 8월 청구서 SKU 해부

26년 09월 01일

결론부터 말하면

“Cloud Run이 비싸다”는 체감은 틀렸다. 월 ₩27,221의 Cloud Run 청구를 SKU 단위로 분해했더니, 순수 웹 서빙은 26만 요청에 약 ₩3,900이었다. 나머지는 요청 없이도 24시간 켜 둔 유휴 인스턴스와 리전 이전 잔재였다.

결국 Cloud Run은 안 비쌌고, 내 설정이 비싸게 나온 것이었다.

청구서가 갑자기 42배가 됐다

법원경매 분석 서비스를 GCP 위에서 운영하는데, 8월 GCP 청구가 ₩58,734로 나왔다. 7월에는 ₩1,388이었다. 42배다.

7월까지 인프라가 사실상 미가동 상태였고 8월에 본격 가동했으니 증가 자체는 예상했지만, 감사 없이 다음 달에도 이 금액이 나온다는 보장은 없었다. 결제 콘솔의 청구 보고서를 SKU 그룹 기준으로 직접 추출해서 서비스별로 분해해봤다.

서비스순비용비고
Cloud Run₩19,643무료 한도 상쇄 후
Cloud Domains₩17,421도메인 연납, 연 1회 선불
Cloud Storage₩11,437버킷 마이그레이션 과도기 포함
Secret Manager₩4,031전월 대비 +191%
Compute Engine₩3,107매일 Spot 크롤 VM
Cloud Build₩1,874GitHub 장애기 수동 배포분
Artifact Registry₩954이미지 113개 누적

여기서 9월에도 계속 나올 것과 이번 달만 나올 것을 나눠야 다음 달 청구를 예측할 수 있다. Domains 연납, Storage 마이그레이션 과도기, Cloud Build 수동 배포분은 일회성이라 9월엔 사라진다. 지속분은 월 ₩25,000~33,000 선이다.

웹 서빙의 3배를 내는 유휴 인스턴스

Cloud Run ₩27,221을 다시 열어보니 구성이 이랬다.

  • 유휴 Min Instance 4개 SKU: ₩13,331 (49%)
  • 그중 도쿄 리전(Tier 2)분: ₩8,123, 리전 이전 전의 잔재
  • Jobs CPU: 크레딧 후 ₩2,208, 이미 Batch로 전환해서 9월부턴 소멸
  • 대륙간 이그레스: ₩2,003, 도쿄 서빙 시절의 일회성 트래픽
  • 순수 웹 서빙: 약 ₩3,900

핵심 아이러니는 웹 서빙 본체보다 유휴비가 3배라는 점이다. Cloud Run의 저렴함은 “요청이 올 때만 과금”에서 나오는데, minScale=1은 콜드스타트를 없애려고 그 원칙을 일부러 깨는 옵션이다. 여기에 리전 이전 전에 운영하던 도쿄 리전의 잔재까지 더해졌다.

더 문제인 건 유령 리비전이었다.

트래픽 0%인 리비전이 왜 돈을 내고 있나

nakchalai-web-00028-rp8 리비전을 확인했더니 트래픽은 100% 최신 리비전(00052)에 몰려 있고 00028은 0%다. 그런데 이 리비전에 prev 태그가 붙어 있었고, minScale=1 설정이 남아 있어서 인스턴스 1대가 24/7 기동 중이었다.

Cloud Run에서 트래픽 태그는 리비전을 핀으로 고정한다. min-instances 과금은 “트래픽을 받는 리비전”이 아니라 “활성 리비전”에 붙는다. 태그된 구 리비전은 트래픽이 0%여도 GC되지 않고, 유휴 인스턴스 1대분(월 약 ₩4,600)을 계속 청구한다.

태그를 만든 코드는 CI에도 스크립트에도 없었다. 과거 수동 명령의 잔재였다.

제거 전에 롤백 절차가 이 태그를 쓰는지부터 확인했다. rollback.yml을 grep해보니 롤백은 SHA 도커 이미지 태그로 이전 버전을 선택하지, Cloud Run 트래픽 태그를 쓰지 않았다. 그래서 제거해도 안전하다고 판단했다.

gcloud run services update-traffic nakchalai-web \
  --region=asia-northeast3 --remove-tags=prev

제거 후 status.traffic이 단일 구성으로 정리됐고, minScale=0 전환과 합쳐서 이 리비전의 유휴비는 월 ₩9,200에서 0이 됐다.

Secret Manager는 회전할수록 비싸진다

Secret Manager 청구 ₩4,031은 전월 대비 191% 증가였다. 원인은 과금 구조였다.

무료 한도는 활성 버전 6개다. 초과분은 버전당 월 약 $0.06. 나는 시크릿 27개에 활성 버전이 총 34개였다. 34에서 6을 빼면 28개가 과금 대상인데, 8월엔 DB URL과 API 키 만료 작업으로 버전이 계속 쌓이다 일부 정리되는 중이라 월평균 버전 수가 말일 기준보다 더 컸다.

여기서 알게 된 함정이 하나 있다. 시크릿 회전(rotate)은 새 버전을 만들 뿐 이전 버전을 지우지 않는다. 회전이 잦은 인프라일수록 이전 버전 정리 정책(destroy)이 없으면 비용이 조용히 인상된다. 절감 레버는 이전 버전 destroy와 web/batch/agents에 3중복된 시크릿 통합이다.

감사하다가 삭제 권고를 철회한 이야기

이번 감사에서 방법론적으로 더 중요했던 건 오탐이었다.

초기 스캔에서 “orphan 디스크”로 분류한 30GB 디스크가 있었다. 삭제 권고를 넣기 직전, 디스크 상세를 조회해봤더니 살아있는 Batch VM에 붙어 있었다. 전날 저녁에 생성된 진행 중 크롤의 작업 디스크였다. 확인 없이 삭제했다면 진행 중인 크롤을 통째로 파괴할 뻔했다.

-local로 끝나는 시크릿 6개도 유령 시크릿으로 의심했는데, 환경 변수 동기화 스크립트가 실제로 사용 중인 로컬 오버라이드였다. 유지 판정.

결국 규칙을 하나 새웠다. 비용 청소 권고는 리소스가 존재한다는 사실만으로 내면 안 되고, 부착 관계·스크립트·CI 사용 여부를 역방향으로 추적한 뒤에 내야 한다. 삭제는 되돌릴 수 없으니 권고와 실행 사이에 확인 단계를 반드시 둔다.

Storage 청구 10배의 정체는 용량이 아니라 횟수였다

Cloud Storage 청구 ₩11,437도 의심스러워서 열어봤다. 정체는 용량이 아니라 Class A(쓰기 계열) 작업 142만 회였다.

여기서 구별해야 할 것은 매일 도는 대량 쓰기와 마이그레이션 단발이다. 전자면 구조적 누수라 다음 달에도 반복 청구되고, 후자면 다음 달에 소멸한다. 일별 API 요청 지표를 버킷별로 집계하니 8/28 하루에 250만 건 이상이 몰려 있었고, 누적 상위 method가 Rewrite와 Delete였다. 객체 복사 후 원본 삭제, 즉 버킷 마이그레이션의 서명이었다. 다음 날 구버킷 삭제 후 일평균 요청이 3만 건 이하로 떨어져서 단발로 확정했다.

남은 것

이번 감사로 9월 예상 청구는 월 ₩25,000~33,000으로 정리됐다. 그중 유휴비가 절반 가까이 차지하는데, Vercel Pro(월 $20 고정)와 비교해도 튜닝 후 Cloud Run이 절반에서 3분의 1 수준이라 인프라 선택 자체는 유지하기로 했다. 애초에 수십 분 도는 크롤러 배치를 Vercel Functions는 올릴 수 없다.

아직 남은 과제는 두 가지다. 도쿄 리전 잔재 정리는 리비전 재배포가 수반돼서 미뤄뒀고, Secret Manager destroy는 되돌릴 수 없는 삭제라 롤백 필요성 확인부터 해야 한다. 비용 감사의 마지막 단계가 삭제라면, 그 마지막 단계가 제일 느리게 가야 하는 것 같다.