Git 저장소의 릴리스 이력을 추적하는 더 나은 방법일지도 모릅니다

Maybe a better way to track the release history of a Git repo

blog.plover.com ▲ 59 댓글 15 evakhoury

요약

배포할 때마다 태그를 새로 만들지 말고, 배포 대상마다 ref 하나에 커밋을 이어 붙이자는 제안입니다.

프로그래머 마크 도미너스가 집리크루터에서 겪은 문제를 바탕으로 쓴 글입니다. 모노레포(여러 프로젝트를 한 저장소에 담는 방식)에 배포 기록용 태그가 수천 개 쌓이자 Git의 ref(브랜치·태그 같은 이름표) 조회가 눈에 띄게 느려졌습니다.

왜 중요한가

  • ref 수가 배포 횟수가 아니라 단계와 앱 조합 수로 고정되어, 배포가 쌓여도 저장소가 느려지지 않습니다.
  • 배포 이력이 평범한 커밋 이력이 되므로 별도 도구 없이 git log와 git diff로 조회할 수 있습니다.
  • 태그 이름 규칙에만 숨어 있던 배포 정보를 커밋 메시지와 트리에 구조적으로 담을 수 있습니다.

핵심 내용

  • 단계와 앱 조합마다 ref 하나를 두고, 배포할 때마다 그 ref에 메타정보 커밋을 하나씩 추가합니다.
  • 메타정보 커밋의 첫 번째 부모는 직전 메타정보 커밋이고, 두 번째 부모는 실제로 배포한 커밋입니다.
  • "운영 환경에 마지막으로 배포한 때"를 알려고 태그 수천 개를 걸러 정렬하던 일이 git log 한 번으로 줄어듭니다.
  • 롤백은 예전 릴리스 커밋을 가리키는 새 메타정보 커밋을 하나 추가하면 끝납니다.
  • 저자는 이 제안이 실제로 채택되지 않았고, 예상하지 못한 단점이 있을 수 있다고 밝혔습니다.

HN 반응

  • 배포 기록은 Git이 아니라 배포 도구에 남겨야 한다는 반론이 많았고, 롤백이나 환경이 늘면 금방 꼬인다는 경험담도 나왔습니다.
  • Git에 남긴 기록은 배포 요청일 뿐 실제 배포 완료가 아니라는 지적과, 환경별 버전을 파일로 관리하는 Flux·ArgoCD식 대안이 제시됐습니다.

댓글

15개 표시 · 전체 15개
  1. duesabati HN

    릴리스는 배포와 분리해야 합니다. 프로덕션에서 도는 코드와 다른 환경에서 도는 코드는 사실상 차이가 없어야 하고요. 환경마다 릴리스를 따로 만들 게 아니라, 코드에 읽기 쉬운 라벨(예: 버전 번호)로 태그를 붙이고 그 버전의 코드로 아티팩트를 만든 다음, 배포할 때 어떤 버전을 쓸지 (수동이든 자동 로직이든) 정하면 됩니다. 이상적으로는 프로덕션과 다른 환경 사이의 런타임 "차이"는 말 그대로 런타임에, 즉 환경 변수로 정의해야 합니다. 보통 빌드 시점에 처리하는 프론트엔드 쪽 설정도 런타임에 처리하는 방법이 있어야 합니다. 그러면 동작을 훨씬 예측하기 쉽고 추론하기도 쉬워집니다. 빌드 파이프라인과 배포 파이프라인을 물리적으로 분리할 수 있으니 필요에 따라 무엇으로든 훨씬 민첩하게 바꿀 수 있고, 사람 머리(그리고 LLM)가 단계를 명확하게 파악해서 CI 파이프라인 문제를 고치기도 훨씬 수월합니다.

    빌드 시점의 차이가 정말 필요하다면, 그 차이를 "이 코드가 돌 것이라고 가정한 환경"으로 구분하지 말고 실제로 무엇이 다른지를 기준으로 구분해야 합니다. 명시적인 것이 암묵적인 것보다 언제나 낫고, LLM과 신입 개발자에게는 특히 그렇기 때문입니다.

  2. aleksiy123 HN

    동의합니다. 저는 이렇게 생각해요.

    런타임 설정은 환경에 대한 순수 함수다.

    버전이 매겨진 바이너리/아티팩트는 환경이 달라도 동일해야 한다.

    릴리스 단계는 환경의 속성이다. (로컬/개발 -> 프리뷰/스테이징 -> 프로덕션)

    환경에 따라 동작이나 값이 달라지는 것은 코드가 아니라 설정에 속한다.

  3. turboponyy HN

    배포 이력을 저장소에 기록하는 것이 합리적으로 보였다

    저에게는 합리적으로 보이지 않습니다. 소스 코드 관리와 직접 관련 없는 영속성 문제에 Git을 쓰는 셈이니까요.

    "X 환경에 1970-01-01T00:00:00Z에 커밋 a1b2c3d가 배포되었다"만 저장하면 충분한데, 그걸 왜 굳이 저장소 안에 넣으려는지 모르겠습니다. 커밋 a1b2c3d는 어디에 언제 배포되든 상관없이 존재합니다.

    그 데이터는 배포를 담당하는 쪽에 저장하세요.

  4. WorldMaker HN

    저는 배포 과정에서 산출물 파일에 git describe --always --dirty > VERSION 같은 간단한 스크립트를 넣는 방식을 아주 좋아합니다. 아니면 번들이나 실행 파일 메타데이터에 포함시켜도 됩니다. (예를 들어 형식을 살짝 바꿔서 dotnet build -p:Version $GITDESCRIBEISH에 넘기거나, 웹 서비스가 정적으로 서빙하는 .json 파일에 넣는 식입니다.) 마찬가지로 git describe --always --dirty의 출력을 CI/CD 로그에 남겨 두면 아주 요긴할 수 있습니다.

    git describe의 출력으로 커밋을 정확하게 지칭할 수 있습니다. 어노테이티드 태그로 semver 릴리스나 marver(마케팅 버전) 릴리스를 태그할 수도 있는데, semver나 marver를 신경 쓰는 사용자에게 git describe 결과를 보여 주면 태그와 정확히 일치하는 것을 배포했는지, 아니면 그 이후의 핫픽스 같은 것을 배포했는지가 드러나서 배포 시스템에 신뢰를 갖게 됩니다. 하위 환경에서도 git describe는 유용합니다. 태그 바로 위가 아닐 때도 의미 있는 결과가 나오니까요.

    (추가: 모노레포라면 git describe --match subproject/로 태그를 트리 구조로 쓰면서 semver/marver 어노테이티드 태그를 그대로 쓸 수 있습니다.)

  5. n_e HN

    전적으로 동의합니다. 처음에는 단순해서 좋은 생각 같지만, 배포의 관심사가 소스 코드 관리의 관심사와 맞지 않아서 금방 무너집니다.

    예를 들어 배포 순서가 커밋 순서와 일치할 때는 읽기 편하지만, 배포 버전을 이전 버전으로 롤백하는 경우처럼 순서가 어긋나면 문제가 됩니다.

    환경이 늘어나면 지저분해지고, 고객별 배포까지 관리해야 하면 상황은 더 나빠집니다.

  6. deathanatos HN

    저는 버전 태그(또는 그 비슷한 것)에 환경을 넣는 게 좋은 생각이라고 생각해 본 적이 없습니다. 그런데 놀랄 만큼 흔한 방식이죠.

    그렇게 하면 정의상 dev, staging, prod(또는 프로덕션 이전 환경을 뭐라 부르든)에 서로 다른 태그를 배포하게 됩니다. 이 태그들이 모두 같은 커밋을 가리키기를 바랄 수밖에 없는데, 제발¹ 그러길 빕니다. (더 심한 건, 세상에는 같은 커밋을 절대 지정하지 못하게 강제하는 방식도 있다는 겁니다. TFA의 방식은 아니지만요(!) 그러면 "서로 다른 커밋이 같은 트리를 가리키기를 기도하자"로 전락합니다.)

    그냥 버전 번호를 태그로 다세요. 버전 번호가 떠오르지 않으면 날짜를 쓰세요. 그리고 별도로, dev/stage/prod 등 각 환경에 어떤 태그/리비전을 올릴지 적은 파일을 관리하는 겁니다. 이건 TFA가 제시한 방식과 대략 같은 구조입니다. 다만 특별할 게 없죠. 다른 모든 것과 마찬가지로 저장소²에 있는 텍스트 파일일 뿐입니다. 별도 저장소에 두어도 되고(저는 이쪽을 권합니다), 같은 저장소에 두어도 됩니다(권하지는 않지만 동작은 합니다).

    대표적인 질문은 이겁니다.

    www.our-app 앱을 프로덕션에 마지막으로 배포한 게 언제였지?

    이건 그 앱이 배포되어야 할 해시를 담은 파일의 git log를 보면 됩니다.

    다만 SRE로서 짚고 싶은 게 있습니다. 이런 방식은 전부 gitops 기반 배포인데, 여기에 남는 시점은 배포가 요청된 시점이지 실제로 롤아웃된 시점이 아닙니다. 실패했거나 외부 요인에 막힌 배포는 배포를 지시하는 git 커밋이 만들어진 뒤 한참 지나서야 수렴할 수 있습니다. 또 SRE로서 덧붙이자면, 현실적인 시나리오에서 배포는 어느 한 시점이 아닙니다. 앱의 첫 번째 레플리카를 띄우기 시작할 때 시작해서, 이전 배포의 마지막 레플리카가 내려갈 때 끝납니다. 군더더기가 얼마나 있느냐에 따라 꽤 긴 시간이 될 수도 있습니다.

    (¹TFA의 방식에는 트리 sha에 대한 텍스트 참조가 들어 있는 것 같은데, 그게 실제로 검증되나요?)

    ²Flux나 ArgoCD를 써 보셨다면 거의 확실히 지금 제가 설명한 것과 똑같이 하고 계실 겁니다. 물론 이런 게 가능한 시스템이 이 둘뿐인 것은 아닙니다.

  7. wrxd HN

    우리 Git 모노레포에 성가신 문제가 생기고 있었다

    모노레포를 쓰는 사람들한테서 이런 문장이 너무 자주 나와서, 모노레포는 해결하는 문제보다 만들어 내는 문제가 더 많다고 확신하게 됐어요.

  8. aleksiy123 HN

    분명 트레이드오프이긴 해요. 하지만 문제가 조금 다른 방식으로 나타날 뿐이라고 봐요.

    멀티 레포는 하는 일마다 작은 마찰이 꾸준히 따라붙는 식이고요.

    모노레포는 처음엔 모든 게 훌륭하게 돌아가요. 그러다 앞으로 나아가려면 반드시 풀어야 하는 큰 문제를 만나는 거죠. 그래서 마찰이 들쭉날쭉 튀는 모양이에요.

    수정: 생각을 하나 더 보태면, 멀티 레포는 프로세스 중심이고 모노레포는 그걸 툴링으로 해결하려는 쪽인 것 같아요. 개발자들은 대체로 그쪽에 대해 더 많이 글을 쓰게 되고요.

  9. bvrmn HN

    제가 뭔가 놓친 건가요? git 태그는 실제 배포에 대해 아무것도 말해 주지 않잖아요. 기껏해야 배포를 시도했다는 뜻일 뿐이고요. 실제 배포를 추적해서 어떤 커밋이 배포되었는지, 누가 어디에 했는지 같은 관련 정보를 기록해 주는 CD를 쓰면 뭐가 문제인가요?

  10. bita_nidir HN

    이렇게 해서 무엇을 어디에 언제 배포했는지 완전한 기록이 만들어졌다.

    저라면 이 정보를 git의 ref로 저장하지는 않을 겁니다.

    그래도 꼭 해야 한다면 하위 디렉터리 아래에 저장하겠습니다. "git checkout -b "host/person/version/diet/cookies/bug-fix-for-joe""를 해 보세요.

    .git/refs 아래에 디렉터리 구조로 만들어집니다.

  11. shiandow HN

    ref를 트라이(trie)에 저장하는 건 비교적 쉬운 변경일 것 같지만, 하위 호환성이 골치 아프겠네요.

    아니면 ref 이름을 정렬해 두는 방법도 있겠고요.

  12. WorldMaker HN

    기본값은 아니지만, ref가 많은 저장소에서 선택해 쓸 수 있는 packed ref 포맷이 git에 꽤 오래전부터 있습니다. 3.0 기본값 검토 과정에서 새 기본값으로 고려 중인 것 가운데 하나이기도 합니다.

    https://git-scm.com/docs/git-pack-refs

  13. ZiiS HN

    제가 git을 실제로 얼마나 조금만 쓰는지 또 한 번 깨닫게 되네요.

  14. IshKebab HN

    [플래그 처리됨]

  15. tomhow HN

    HN에 이런 비아냥 섞인 상투적인 글은 올리지 말아 주세요. 가이드라인에 우리가 더 나은 것을 지향한다고 분명히 나와 있습니다. https://news.ycombinator.com/newsguidelines.html

Hacker News에서 보기 ↗