
3줄 요약
- Artifact Registry에서 오래된 이미지를 지울 때 생성 시각만 보면, 현재 Pod나 이전 Deployment revision이 참조하는 이미지까지 삭제할 수 있다.
- 정리 전에 Pod·Deployment·ReplicaSet·Job 계열의 이미지 참조를 모으고 tag와 digest를 실제 Registry 버전에 대조해 보호했다.
- Kubernetes 조회가 실패하면 삭제를 계속하지 않는 fail-closed 원칙과 dry-run 리포트가 자동 정리보다 먼저 필요하다.
배경: 이미지 정리는 왜 위험한가
컨테이너 이미지는 계속 쌓인다. 빌드마다 새 digest가 만들어지고 개발·운영 저장소, 여러 애플리케이션 패키지를 합치면 사람이 하나씩 정리하기 어렵다. 그래서 “이미지 이름별 최신 한 개만 남긴다” 같은 자동화가 자연스럽게 등장한다.
문제는 최신 이미지와 필요한 이미지가 항상 같지 않다는 점이다. 지금 실행 중인 Pod가 이전 digest를 사용하고 있을 수 있고, Deployment의 ReplicaSet history가 롤백용 이미지를 참조할 수도 있다. Job이나 CronJob은 실행 간격이 길어 오래된 tag를 계속 사용할 수 있다.
실행 중인 Pod는 노드에 이미지 layer가 남아 있어 당장 멀쩡해 보일 수 있다. 하지만 노드 교체, Pod 재스케줄링, scale-out 또는 rollback 때 Registry에서 다시 pull하면 삭제 사실이 드러난다. 정리 시점과 장애 시점이 멀리 떨어지는 것이 이 문제를 더 위험하게 만든다.
처음 정책의 빈틈
초기 정리 기준은 단순했다.
패키지별 이미지를 생성 시각 내림차순으로 정렬
→ 첫 번째 버전 보존
→ 나머지는 삭제 후보
저장 공간은 빠르게 줄일 수 있지만 배포 시스템의 참조 상태를 보지 않는다. “오래됐다”는 Registry 메타데이터이고 “사용 중이다”는 Kubernetes 상태다. 서로 다른 두 정보원을 합치지 않으면 안전한 삭제 후보를 계산할 수 없다.
tag만 비교하는 것도 충분하지 않다. Kubernetes manifest는 app:v1처럼 tag를 쓰기도 하고 app@sha256:...처럼 digest를 고정하기도 한다. Registry에서는 같은 digest에 여러 tag가 연결될 수도 있다. 문자열 전체가 같은지만 보면 실제로 동일한 이미지 버전을 놓친다.
참조 이미지는 어디에서 모을까
정리 전에 cluster 전체 namespace에서 다음 workload를 조회했다.
- 현재 실행 상태를 보여주는 Pod
- 원하는 상태의 정본인 Deployment, StatefulSet, DaemonSet
- 배포 이력과 rollback 후보를 담는 ReplicaSet
- 나중에 다시 실행될 수 있는 Job과 CronJob
- 각 리소스의 일반 container와 init container
리소스마다 Pod spec의 위치가 다르다. CronJob은 jobTemplate.spec.template.spec, Pod는 자체 spec, 나머지는 보통 spec.template.spec을 읽는다. 수집 결과는 중복을 제거한 뒤 Registry의 package, tag, digest 형태로 나눈다.
registry.example.com/team/app:v17
→ package: registry.example.com/team/app
→ tag: v17
registry.example.com/team/app@sha256:abc...
→ package: registry.example.com/team/app
→ digest: sha256:abc...
tag와 digest를 함께 비교한 이유
digest 참조는 비교가 명확하다. Kubernetes가 참조한 digest와 Registry version digest가 같으면 보호한다.
tag 참조는 한 단계 더 필요하다. Registry에서 각 version에 연결된 tag 목록을 가져와 Kubernetes가 사용한 tag가 포함돼 있는지 확인한다. 이 방식은 문자열 모양이 다른 tag 참조와 digest version을 연결한다.
다만 mutable tag에는 본질적인 한계가 있다. tag가 다른 digest로 이동했다면 현재 manifest의 문자열만으로 “실행 중인 Pod가 실제로 pull했던 digest”를 완전히 재구성하기 어렵다. 중요한 배포와 rollback 정확성이 필요할수록 manifest를 digest로 고정하는 편이 낫다. 정리 스크립트의 보호는 배포 불변성을 대신하는 장치가 아니다.
적용한 분류 규칙
각 이미지 package 안에서 버전을 최신순으로 정렬한 뒤 세 부류로 나눴다.
- 가장 최신 version은 기본 보존한다.
- 최신이 아니어도 Kubernetes의 tag 또는 digest 참조와 일치하면 보호한다.
- 둘 다 아니면 삭제 후보로 기록한다.
실제 삭제는 항상 digest URI를 대상으로 했다. tag 문자열만 지웠다가 동일 version의 다른 tag 관계를 오해하지 않도록, 어떤 version을 제거하는지 명시적으로 만든 것이다. tag가 붙은 version을 삭제할 때는 그 tag도 함께 제거된다는 점을 리포트와 실행 모드에서 분명히 했다.
여기서 “최신 한 개”는 당시 저장소 정리 정책일 뿐 모든 팀에 맞는 권장 보존 개수가 아니다. 배포 빈도, rollback 기간, 보안 패치 방식에 따라 최신 N개 또는 일정 기간을 함께 보존할 수 있다. 중요한 것은 어떤 보존 정책이든 활성 참조 보호 조건보다 먼저 적용돼서는 안 된다는 점이다.
가장 중요한 안전장치: 조회 실패 시 중단
Kubernetes API 조회는 권한, context, 네트워크 문제로 실패할 수 있다. 이때 빈 목록으로 간주하고 정리를 계속하면 모든 이전 version이 “참조 없음”으로 분류된다. 그래서 참조 수집 실패는 삭제 대상 0건이 아니라 작업 전체 실패로 처리했다.
Kubernetes 참조 조회 성공 → 보호 목록 계산 → Registry 분류
Kubernetes 참조 조회 실패 → 즉시 중단
보호를 생략하는 옵션은 자동 fallback이 아니라 운영자가 명시적으로 선택해야만 사용할 수 있게 했다. 안전 정보를 못 얻은 상태에서 편의상 삭제를 계속하지 않는 전형적인 fail-closed 설계다.
Dry-run을 실행 계약으로 만들기
기본 모드는 삭제가 아니라 리포트 생성이다. 저장소와 package별로 최신 보존, Kubernetes 참조 보존, 삭제 후보를 나누고 전체 합계를 보여준다. 실제 삭제 모드는 별도 인자와 최종 확인을 요구한다.
Dry-run에서 봐야 할 것은 단순한 개수가 아니다.
- 현재 배포와 ReplicaSet history의 digest가 보호됐는가
- 장기 주기의 CronJob 이미지가 보호됐는가
- 같은 digest에 연결된 tag가 예상대로 해석됐는가
- 삭제 후보에 최근 rollback 대상이 섞이지 않았는가
- 조회 대상 cluster와 Registry project가 의도한 환경인가
정리 자동화는 삭제 명령보다 “왜 이 버전이 보존 또는 삭제 후보인지 설명하는 리포트”가 먼저 완성돼야 한다.
남아 있는 경계
이 방식도 모든 참조를 안다고 주장할 수는 없다. Git에만 있는 미배포 manifest, 외부 cluster, 중지된 환경, 사람의 수동 rollback 계획은 현재 Kubernetes API에서 보이지 않는다. Helm release나 GitOps 도구의 이력까지 rollback 계약에 포함한다면 별도 참조원으로 추가해야 한다.
또한 Registry cleanup policy를 사용하더라도 실행 중인 workload 참조를 자동으로 이해한다고 가정하면 안 된다. 보존 tag, version prefix, 최근 업로드 기간 같은 조건과 실제 배포 참조는 서로 다른 문제다.
지금 다시 한다면
이미지 생성 단계부터 immutable digest를 배포 정본으로 삼고, tag는 사람이 읽기 쉬운 별칭으로만 사용한다. 배포 성공 시점에는 현재 digest뿐 아니라 승인된 rollback window의 digest도 별도 메타데이터로 남긴다.
정리 작업은 먼저 참조 snapshot과 삭제 계획을 파일로 만들고, 검토된 동일 계획을 apply하는 두 단계로 나눈다. 수집 이후 Registry가 바뀌는 시간차까지 엄격히 막아야 한다면 계획에 version digest 집합과 생성 시각을 넣어 적용 직전에 다시 대조한다.
핵심은 오래된 것을 잘 지우는 기술이 아니다. 다시 필요할 수 있는 것을 증명 가능하게 보호한 뒤, 남은 것만 지우는 것이다.