가끔 배포 파이프라인을 보다가 이런 질문을 만난다.

"이 버전 태그, 대체 언제 붙는 거야?"

처음엔 쉬워 보인다.
publish-image.yaml이라는 이름의 워크플로우가 있다.
이름만 보면 딱 감이 온다.

아, publish 할 때 버전이 붙겠구나.

그런데 배포 파이프라인은 종종 편의점 택배보다 복잡하다.
택배 상자에 송장 붙이는 사람, 물류센터에 넘기는 사람, 최종 배송하는 사람이 다르듯이, 컨테이너 이미지도 "만드는 단계", "승인하는 단계", "외부에 내보내는 단계"가 나뉜다.

오늘의 주인공은 바로 이 이름표다.

10.2.0
10.2.0-6cc1a0d
v10.2.0

비슷해 보이지만 역할이 다르다.
잘못 이해하면 "publish가 버전을 만들었다"고 착각하기 쉽다.

하지만 결론부터 말하면 이렇다.

Publish는 작명가가 아니다. 배송 기사에 가깝다.

이미지에게도 출생신고와 졸업장이 있다

컨테이너 이미지를 사람처럼 생각해보자.

이미지가 태어나는 순간은 빌드 단계다.
이때 이미지는 처음 이름표를 받는다.

Build Container Image

여기서 VERSION이 계산된다.
브랜치가 release/10.2.0이면 버전은 대체로 10.2.0처럼 읽힌다.
develop이라면 현재 release 흐름을 보고 다음 minor 버전을 계산할 수 있다.

그리고 AIO 이미지에는 commit 기반 꼬리표도 붙는다.

TAG_AIO = 현재 commit SHA 앞 7자리

예를 들어 commit이 6cc1a0d...라면 이런 태그가 생긴다.

ireg.querypie.io/ci/querypie:10.2.0
ireg.querypie.io/ci/querypie:10.2.0-6cc1a0d

여기서 10.2.0은 버전 이름이고,
10.2.0-6cc1a0d는 "이 버전 후보 중 정확히 이 commit에서 나온 아이"라는 출생신고서에 가깝다.

버전만 있으면 같은 버전 안에서 어떤 빌드인지 헷갈릴 수 있다.
commit 꼬리표가 있으면 훨씬 정확하다.

CI 이미지는 연습생이다

CI registry에 올라간 이미지는 아직 연습생이다.

ireg.querypie.io/ci/querypie:10.2.0-6cc1a0d

빌드는 됐다.
태그도 있다.
하지만 아직 "정식 릴리스입니다"라고 말하기엔 이르다.

왜냐하면 릴리스에는 기술적인 성공 말고도 한 단계가 더 있기 때문이다.

이 빌드를 정말 릴리스로 인정할 것인가?

이 단계가 Sign Off다.

Sign Off는 졸업식이다

Sign Off workflow는 두 가지를 입력받는다.

TAG     = 10.2.0-6cc1a0d
VERSION = 10.2.0

여기서 TAG는 "어떤 CI 이미지를 졸업시킬 것인가"를 가리킨다.
VERSION은 "졸업장에 어떤 공식 이름을 적을 것인가"를 말한다.

Sign Off는 먼저 CI 이미지를 release registry로 옮긴다.

ireg.querypie.io/ci/querypie:10.2.0-6cc1a0d
  -> ireg.querypie.io/release/querypie:10.2.0-6cc1a0d

그리고 나서 정식 버전 태그를 붙인다.

ireg.querypie.io/release/querypie:10.2.0-6cc1a0d
  -> ireg.querypie.io/release/querypie:10.2.0

이게 중요하다.

정식 release image tag는 publish 단계가 아니라 Sign Off 단계에서 생긴다.

그러니까 이미지 세계에서 Sign Off는 졸업식이다.
졸업장이 발급되고, 공식 이름이 생기고, GitHub Release도 만들어진다.

v10.2.0

v10.2.0도 Publish가 아니라 Sign Off의 산출물이다.

Publish는 택배 기사다

그럼 publish-image.yaml은 뭘 할까?

이름이 Publish라서 왠지 뭔가 거창해 보인다.
하지만 역할은 훨씬 단순하고 명확하다.

ireg.querypie.io/release/querypie:10.2.0
  -> harbor.chequer.io/querypie/querypie:10.2.0

이미 Sign Off가 끝난 이미지를 외부 registry로 복사한다.

말하자면 이런 일이다.

졸업장이 이미 발급된 이미지를 고객이 받을 수 있는 창고로 옮긴다.

그래서 Publish는 작명가가 아니다.
Publish는 이미 이름이 붙은 상자를 배송하는 사람이다.

이 차이를 모르면 장애 대응 때 엉뚱한 문을 두드리게 된다.

"Harbor에 10.2.0이 없어요."
그러면 먼저 Publish가 성공했는지 본다.

그런데 Publish가 실패했다면 다음 질문은 이거다.

"release registry에는 10.2.0이 있었나요?"

만약 release registry에도 없다면 Publish 문제가 아니다.
Sign Off가 안 됐거나, Sign Off 입력이 잘못됐거나, 애초에 CI 빌드 태그를 잘못 골랐을 수 있다.

세 개의 이름표를 구분하자

이 파이프라인에는 이름표가 세 종류 나온다.

1. VERSION

10.2.0

제품 버전이다.
사람이 보기 좋고, 릴리스 노트에 들어가고, 고객도 이해할 수 있는 이름이다.

2. VERSION-TAG

10.2.0-6cc1a0d

빌드 후보를 정확히 가리키는 이름이다.
같은 버전 후보라도 commit이 다르면 다른 아이일 수 있으니, commit 꼬리표가 붙는다.

3. vVERSION

v10.2.0

GitHub Release 이름이다.
이미지 registry의 태그와 연결되지만, 같은 물건은 아니다.

간단히 말하면 이렇다.

VERSION      = 제품 이름
VERSION-TAG  = 제품 이름 + 출생증명서
vVERSION     = GitHub 졸업앨범

왜 이게 중요할까

릴리스 파이프라인에서 가장 무서운 건 실패 자체가 아니다.

실패는 로그를 남긴다.
워크플로우도 빨갛게 뜬다.
누가 봐도 "어, 뭔가 터졌네"라고 알 수 있다.

진짜 무서운 건 헷갈리는 성공이다.

  • CI 빌드는 성공했다.
  • Sign Off는 했는지 안 했는지 애매하다.
  • Publish는 실행했는데 Harbor에 없다.
  • GitHub Release는 있는데 외부 registry에는 없다.

이럴 때 단계별 역할을 모르면 같은 질문을 계속 반복하게 된다.

"버전은 어디서 붙지?"
"이 태그는 누가 만들었지?"
"publish를 다시 누르면 해결되나?"
"아니면 Sign Off부터 다시 해야 하나?"

파이프라인을 이해한다는 건 결국 지도 한 장을 갖는 일이다.

Build Container
  -> VERSION 계산
  -> CI 이미지 태그 생성

Sign Off
  -> release registry로 복사
  -> 정식 VERSION 태그 부여
  -> GitHub Release 생성

Publish Image
  -> Sign Off 완료 이미지를 Harbor로 복사

지도만 있으면 당황하지 않는다.

운영할 때의 작은 체크리스트

다음에 "이미지가 없어요"라는 이야기를 들으면 이렇게 보면 된다.

  1. Harbor에 없는가?

    • publish-image.yaml 실행 결과를 본다.
  2. release registry에는 있는가?

    • ireg.querypie.io/release/*:<VERSION> 존재 여부를 본다.
  3. release registry에도 없는가?

    • sign-off.yml이 성공했는지 본다.
    • 입력한 TAG, VERSION이 맞는지 본다.
  4. Sign Off에 넣을 TAG를 모르겠는가?

    • CI 빌드 summary의 VERSION-TAG_AIO를 본다.
  5. VERSION 자체가 이상한가?

    • scripts/inspect-version과 release branch 이름을 본다.

이 정도만 기억해도 "publish를 다시 눌러야 하나요?"라는 질문에 훨씬 침착하게 답할 수 있다.

오늘의 교훈

배포 파이프라인은 이름이 비슷한 단계가 많다.

Build, Release, Sign Off, Publish.
다들 뭔가 내보내는 것 같고, 다들 태그를 만지는 것 같다.

하지만 역할을 나누면 단순해진다.

  • Build는 아이를 낳는다.
  • CI tag는 출생신고를 한다.
  • Sign Off는 졸업장을 준다.
  • Publish는 졸업생을 세상 밖 창고로 배송한다.

그러니 다음에 publish-image.yaml을 보고 "여기서 버전이 생기나?"라는 생각이 들면 이렇게 기억하면 된다.

Publish는 이름을 짓지 않는다. 이미 이름 붙은 아이를 배송한다.

작은 차이지만, 운영할 때는 이 작은 차이가 삽질 한 시간을 아껴준다.