
처음에는 개발과 운영 환경만 나눈 Artifact Registry 저장소에 모든 컨테이너 이미지를 넣었다. GKE workload와 Cloud Run 변환 서비스가 한 목록에 섞였다. 배포는 가능했지만 시간이 지날수록 “누가 이 이미지를 사용하고, 언제 지워도 되는가”를 판단하기 어려워졌다.
환경만으로는 이미지의 운영 수명주기를 설명하기 부족했다. 우리는 저장소를 GKE와 Cloud Run이라는 배포 런타임 기준으로 한 번 더 분리했다.
같은 컨테이너 이미지라도 소비자가 다르다
GKE 이미지는 Deployment, ReplicaSet과 Pod가 참조한다. rollback 가능성을 확인하려면 현재 Pod뿐 아니라 배포 이력에 남은 digest까지 살펴야 한다.
Cloud Run 이미지는 service revision이 참조한다. 서비스마다 배포 빈도와 scale-to-zero 특성이 다르고, 공통 변환기처럼 개발·운영이 같은 image를 소비하는 경우도 있다.
두 런타임을 한 저장소에 두면 다음 질문의 답이 섞인다.
- 어느 identity가 image를 pull해야 하는가?
- 누가 push하거나 삭제할 수 있는가?
- 어떤 revision과 Kubernetes object가 rollback을 위해 image를 참조하는가?
- 오래된 image를 정리할 때 어떤 keep 정책이 필요한가?
- 취약점과 저장 비용의 소유 팀은 누구인가?
repository 이름은 단순한 폴더가 아니라 IAM과 cleanup policy를 적용하는 리소스 경계다.
선택한 경계
구조는 세 축으로 정리했다.
- GKE image는 개발과 운영 저장소를 분리한다.
- 환경별 Cloud Run 서비스는 개발과 운영 저장소를 분리한다.
- 동일 image를 여러 환경이 소비하는 공통 변환기는 별도 공통 저장소를 사용한다.
build
├─ GKE / dev
├─ GKE / prod
├─ Cloud Run / dev
├─ Cloud Run / prod
└─ Cloud Run / common
이 구조 덕분에 운영 GKE node는 운영 workload 저장소만 읽고, Cloud Run service agent는 필요한 변환기 저장소만 읽도록 권한 범위를 좁힐 수 있다. 개발 pipeline이 운영 image를 삭제할 이유도 사라진다.
cleanup 정책의 blast radius를 줄인다
Artifact Registry cleanup policy는 오래된 version을 지우고 특정 version을 보존하도록 구성할 수 있다. 그러나 나이만 보고 삭제하면 사용 중인 image나 rollback 후보까지 지울 수 있다.
저장소를 나누면 정책 하나가 영향을 주는 대상이 줄어든다.
- GKE 저장소는 Kubernetes 참조와 최근 배포 version을 보존한다.
- Cloud Run 저장소는 활성 revision과 traffic이 남은 revision을 보존한다.
- 공통 변환기 저장소는 여러 환경의 참조를 합쳐 판단한다.
- 개발 저장소는 운영보다 짧은 보존 기준을 적용할 수 있다.
분리 자체가 안전한 삭제를 보장하지는 않는다. keep 조건과 실제 runtime 참조를 대조하는 절차는 여전히 필요하다. cleanup은 먼저 dry-run으로 후보를 확인하고, 참조 조회에 실패하면 삭제를 중단해야 한다.
탐색과 장애 대응도 단순해졌다
통합 저장소에서는 비슷한 서비스 이름과 환경 tag가 한 목록에 쌓였다. 배포 실패 때 image가 존재하는지, tag가 올바른 runtime의 repository를 가리키는지부터 확인해야 했다.
런타임별 경계를 두자 image URL 자체가 배포 대상을 설명했다. Cloud Build 설정, 배포 script와 runtime identity를 한 묶음으로 검토할 수 있었고, 저장량과 취약점도 책임 범위별로 집계하기 쉬워졌다.
반대급부도 있다. repository 수가 늘고 build script의 경로 관리가 필요하다. 공통 image를 어느 경계에 둘지도 정해야 한다. 그래서 서비스마다 임의의 저장소를 만들지 않고 “배포 런타임 × 환경”이라는 제한된 분류만 사용했다.
migration은 한 번에 끝나지 않는다
저장소를 만든 뒤에도 오래된 build script나 배포 도구가 이전 경로를 가리킬 수 있다. 새 경로와 기존 경로가 섞인 기간에는 저장소 이름만 보고 분리가 완료됐다고 말할 수 없다.
우리는 다음을 migration 완료 조건으로 본다.
- 모든 활성 build script가 새 repository로 push한다.
- 현재 Deployment와 Cloud Run revision이 새 digest를 참조한다.
- runtime identity가 새 repository를 pull할 수 있다.
- 이전 repository를 참조하는 rollback 후보를 확인했다.
- cleanup과 취약점 조회가 새 경계를 사용한다.
현재도 일부 레거시 배포 경로는 별도 정리가 필요한 대상으로 남아 있다. 아키텍처 결정과 migration 완료를 구분해야 운영 문서가 실제 상태보다 앞서가지 않는다.
지금 다시 한다면
첫 repository를 만들 때 image 이름보다 먼저 소비자와 수명주기를 표로 만들 것이다. 같은 IAM, cleanup, rollback 계약을 갖는 image만 한 repository에 둔다.
핵심은 저장소를 많이 만드는 것이 아니다. image를 누가 배포하고 누가 실행하며 어떤 기준으로 보존하는지가 다르면, Artifact Registry 경계도 그 차이를 표현해야 한다는 점이다.