왜 태그 기반 배포가 위험한가
myapp:1.4.0 같은 태그는 사람이 붙이는 라벨일 뿐, 특정 이미지를 영구히 가리키지 않는다. 같은 태그를 다시 push하면 레지스트리의 태그 포인터가 새 이미지로 옮겨간다. 그 결과 어제 통과한 latest나 1.4.0이 오늘 다른 바이너리를 배포하는 상황이 생긴다. 스테이징에서 검증한 이미지와 프로덕션에 실제로 뜬 이미지가 달라지고, 롤백을 해도 "그때 그 이미지"로 돌아간다는 보장이 없다.
해결책은 다이제스트 고정이다. 다이제스트는 이미지 매니페스트의 SHA-256 해시라서 내용이 1비트라도 바뀌면 값이 달라진다. myapp@sha256:abc... 형태로 참조하면 언제 어디서 pull하든 동일한 이미지가 보장된다.
태그와 다이제스트 참조 비교
| 구분 | 태그 참조 | 다이제스트 참조 |
|---|---|---|
| 가변성 | 덮어쓰기 가능 | 불변(content-addressable) |
| 재현성 | 보장 안 됨 | 완전 보장 |
| 가독성 | 높음 | 낮음(해시) |
| 롤백 신뢰도 | 불확실 | 정확한 이미지 복원 |
빌드 시점에 다이제스트 확보하기
핵심은 CI에서 push 직후 다이제스트를 캡처해 이후 단계로 넘기는 것이다. Docker의 --push와 buildx는 push 결과에서 다이제스트를 뽑아낼 수 있다.
#!/usr/bin/env bash
set -euo pipefail
IMAGE="registry.example.com/myapp"
TAG="1.4.0"
# 빌드 후 push, 결과 메타데이터를 파일로 출력
docker buildx build \
--tag "${IMAGE}:${TAG}" \
--metadata-file build-meta.json \
--push .
# 매니페스트 다이제스트 추출
DIGEST=$(jq -r '."containerimage.digest"' build-meta.json)
echo "${IMAGE}@${DIGEST}" | tee image-ref.txt
image-ref.txt에 담긴 registry.example.com/myapp@sha256:...가 이후 배포 전 과정에서 유일한 진실이 된다. 태그는 사람이 보기 위한 별칭으로만 남긴다.
배포 매니페스트에 주입하기
Kubernetes 매니페스트에 다이제스트를 직접 박아 넣는다. Kustomize의 images 변환기를 쓰면 태그 문자열을 다이제스트로 치환할 수 있다.
# kustomization.yaml (CI가 digest 값을 채워 넣음)
images:
- name: registry.example.com/myapp
digest: sha256:9b2c... # image-ref.txt에서 주입
---
# deployment.yaml
spec:
template:
spec:
containers:
- name: myapp
image: registry.example.com/myapp # kustomize가 @digest로 확정
# CI 파이프라인에서 치환 실행
DIGEST=$(cut -d@ -f2 image-ref.txt)
kustomize edit set image \
"registry.example.com/myapp=registry.example.com/myapp@${DIGEST}"
kubectl apply -k .
배포 후 실제로 뜬 이미지 검증하기
매니페스트에 다이제스트를 넣었다고 끝이 아니다. 파드가 실제로 그 이미지로 떴는지 확인해야 신뢰할 수 있다. 런타임의 imageID를 검증한다.
EXPECTED=$(cut -d@ -f2 image-ref.txt)
ACTUAL=$(kubectl get pods -l app=myapp \
-o jsonpath='{.items[*].status.containerStatuses[*].imageID}' \
| grep -oE 'sha256:[a-f0-9]+' | sort -u)
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "digest mismatch: expected $EXPECTED got $ACTUAL" >&2
exit 1
fi
echo "verified: $ACTUAL"
주의점과 운영 고려사항
- 멀티아키텍처 매니페스트: 다이제스트에는 매니페스트 리스트(인덱스) 다이제스트와 플랫폼별 이미지 다이제스트 두 종류가 있다. 배포에는 인덱스 다이제스트를 쓰고, 노드가 pull 시 자신의 아키텍처를 고른다. 검증 시
imageID가 플랫폼 다이제스트로 나올 수 있어 비교 로직에 주의한다. - 가비지 컬렉션: 태그가 옮겨간 뒤 원래 다이제스트가 어떤 태그에도 안 걸리면 레지스트리 GC 대상이 된다. 롤백용 이미지가 사라질 수 있으니 릴리스마다 불변 태그(예: 커밋 SHA)를 함께 붙여 참조를 유지한다.
- 서명 연계: 다이제스트 고정은 무결성만 보장하고 출처는 보장하지 않는다. cosign 등으로 다이제스트에 서명하고 admission 단계에서 검증하면 공급망 신뢰까지 확장된다.
- 가독성 보완: 다이제스트는 사람이 읽기 어렵다. 배포 로그와 릴리스 노트에
태그 → 다이제스트매핑을 항상 남겨 추적성을 확보한다.
정리
태그는 배포의 편의를 위한 별칭으로만 두고, 실제 배포·검증·롤백의 기준은 다이제스트로 통일한다. 빌드 시점에 다이제스트를 캡처하고, 매니페스트에 주입하고, 런타임에서 재검증하는 세 단계를 파이프라인에 넣으면 "검증한 것과 배포한 것이 같다"는 재현성을 코드로 보장할 수 있다.