← 전체 글로 돌아가기

Docker

Docker 이미지 태그를 latest로만 배포하다 롤백이 느려진 회고

latest 태그 하나로 배포하다 장애 때 이전 버전을 찾느라 시간을 쓴 뒤, immutable 태그로 바꾼 회고.

장애 때 “어제 이미지”를 바로 고르지 못했다

작은 서비스 배포에서 이미지를 계속 latest로 밀었다. 평소에는 docker compose pull && docker compose up -d 한 줄이면 됐고, 편해 보였다. 하지만 새 배포 뒤 로그인 화면만 500이 난 날, 레지스트리의 latest는 이미 문제 버전을 가리키고 있었다.

로컬에 남은 이미지 ID와 CI 로그의 커밋 SHA를 대조해 이전 이미지를 찾는 데 시간을 썼다. 롤백 명령보다 “무엇으로 돌아갈지”를 정하는 일이 더 느렸다.

배포 단위를 커밋 태그로 만들었다

CI에서 짧은 커밋 SHA를 이미지 태그로 붙이고, Compose에는 배포할 태그를 변수로 전달하도록 바꿨다.

export IMAGE_TAG=$(git rev-parse --short HEAD)
docker build -t ghcr.io/team/app:$IMAGE_TAG .
docker push ghcr.io/team/app:$IMAGE_TAG
services:
  app:
    image: ghcr.io/team/app:${IMAGE_TAG}
    env_file: .env

서버에서는 배포 기록 파일에 태그를 남긴 뒤 실행했다.

printf '%s
' "$IMAGE_TAG" | sudo tee /opt/app/DEPLOYED_TAG
docker compose pull
docker compose up -d --remove-orphans

latest도 사람이 현재 버전을 빠르게 확인하는 용도로 남길 수는 있다. 다만 실행 명령이 참조하는 값으로는 쓰지 않기로 했다. 같은 태그가 다른 이미지를 가리키면, 재현과 롤백이 모두 불확실해진다.

롤백은 실제로 한 번 해 봤다

배포 직후 헬스 체크가 실패했다는 가정으로 이전 태그를 넣어 보았다.

export IMAGE_TAG=$(cat /opt/app/PREVIOUS_TAG)
docker compose pull && docker compose up -d
curl -fsS http://127.0.0.1:3000/api/health

이 과정에서 데이터베이스 마이그레이션은 이미지 롤백만으로 되돌아가지 않는다는 점도 확인했다. 파괴적인 migration은 호환 기간을 두거나 별도 복구 계획이 필요하다.

회고: 편한 태그와 안전한 배포는 달랐다

앞으로 배포 확인에는 세 가지를 적는다.

  • 실행 중인 이미지의 태그와 커밋이 연결되는가
  • 직전 태그가 별도 기록으로 남아 있는가
  • 코드 롤백과 DB 롤백을 같은 일로 착각하지 않는가

배포 자동화의 첫 목표는 명령을 줄이는 것이 아니라, 문제가 생겼을 때 같은 상태로 돌아갈 수 있게 만드는 것이라고 배웠다.