Search
📊

git-flow와 github-flow 비교체험기

작성일
2022/08/07 15:00
수정일
카테고리
아키텍처
태그
git
협업

들어가며

맘시터 기술블로그에 Git Flow에서 트렁크 기반 개발으로 나아가기 라는 글을 읽었습니다.
git flow와 trunk base flow을 비교하고, 각 flow의 장단점에 대해 비교하며, trunk 기반 개발을 한 경험을 소개해준 좋은 포스팅이었습니다!
이 포스팅에 영감을 받아 최근 회사에서 개발하며 개발 당시엔 github-flow(trunk base)를, 런칭 이후엔 변형된 git flow를 사용한 경험을 공유하려 글을 작성합니다.
해당 flow를 채택한 이유와, 각 flow에서 어떤 장단점을 느꼈는지를 중심으로 작성하겠습니다. 다양한 개발자의 의견을 수렴한게 아닌, 저 개인의 경험을 토대로 작성한 글이니 참고해주세요.

바쁘다 바빠 현대사회

바쁜 현대인을 위한 두괄식입니다.
구분
git flow
github flow
개요
다섯 개의 브랜치를 중심으로 다양한 시나리오에 대처 가능한 브랜칭 전략
main 브랜치와 feature 브랜치만을 두고, 항상 배포되는 main 브랜치를 중심으로 한 브랜칭 전략
언제 필요했어?
서로 다른 배포주기를 가진 기능들이 있을 때 단순한 flow로 인해 다양한 상황에 대처하기 어려웠을 때
기능이 빠르게 베이스 브랜치에 병합될 필요가 있을 때 최신 기능이 항상 배포되어도 되는 환경일 때 CI / CD가 잘 갖춰져 있어 버그에 대한 대비가 잘 되어있을 때
장점
다양한 시나리오(롤백, 핫픽스, 운영배포 등)에 쉽게 대처 가능 서로 다른 배포시기를 가진 코드가 잘 못 딸려나갈 걱정이 없음
항상 코드가 최신 상태라는 심리적 안정감 기능 구현 시나리오가 단순하여 편리함
단점
별도의 배포관리자를 두어야 함 다양한 브랜치를 관리하며 쓸데 없는 머지 커밋이 많이 생김
배포 시기가 다른 코드가 딸려나가는 경우가 생김 배포가 자동화 되어 있지 않으면 자주 배포해야 하여 번거로움

프로젝트 환경

진행한 프로젝트의 환경은 다음과 같습니다.
구분
도구
배포 관리 시스템
쿠버네티스
배포 도구
젠킨스
프로젝트 아키텍처
도메인 별로 module을 두어 MSA 구축
버전 컨트롤
gitlab 사용, 모든 코드를 한 개의 레포지토리에서 관리
배포 환경
개발 - 프론트엔드와 함께 확인할 수 있는 개발 환경 베타 - 사내 다른 모듈도 연동하여 QA를 진행 가능한 베타 환경 운영 - 소비자와 직접 접촉하는 프로덕션 환경
저희 프로젝트는 쿠버네티스를 통해 개발, 베타, 운영환경을 조성하였고 젠킨스를 활용해 각 모듈별로, 환경 별로 배포할 수 있는 환경입니다.
3분 내외의 짧은 시간에 특정 코드를 배포할 수 있는 환경으로, 만약 배포시간이 길거나 배포과정이 복잡했다면 배포를 자주 해야하는 github flow를 선택하기 어려웠을 것 같아요.
젠킨스 : 특정 브랜치를 선택하여 배포 가능
젠킨스 : 배포하고자 하는 모듈을 선택

왜 github-flow 였는가?

github flow는 단순합니다. 개발 당시에는 바쁜 일정에 맞추기 위해 간소한 git 프로세스가 필요했는데요.
아주 바빴던.. 개발 기간..
1.
별도의 배포 주기를 가지지 않고
2.
각 기능이 짧은 주기로 Merge Request(github의 Pull Request와 동일)를 거치고 나면 빠르게 병합되며
3.
장애 상황에 대해 가장 빠른 롤백 시나리오가 필요하지 않은(운영 배포되지 않았으므로)
github flow가 제격이었습니다. 저희 팀은 추가적으로 3way merge가 아닌 rebase 후 merge하는 rule을 추가하였어요.

팀에서 정한 github-flow rule

rebase merge 하기

PR을 보내기 전, master 브랜치를 반드시 rebase해야 합니다.
gitlab 설정을 통해 Merge Request가 target branch를 rebase하지 않으면 merge가 불가능 하도록 설정할 수 있습니다.
이는 깃 히스토리 그래프를 단순하게 만들기 위한 작업으로, 향후 단순히 시간 순서의 커밋이 아니라 기능 단위로 커밋 단위를 나눠 볼 수 있도록 해줍니다. 아래는 우아한형제들 기술블로그에서 가져온, 일반 merge(아래)와 rebase merge를 할 때(위)의 차이점입니다.
Pull Request를 일반 merge할 경우
Pull Request를 rebase merge할 경우 (feat. squash merge)
위 블로그 예시에선 1 PR = 1 티켓 으로 관리했기 때문에 squash merge하지만, 저희 팀은 각 기능의 수정사항이 많아 하나의 commit에 담기엔 너무 커다란 경우가 많았으므로, 따로 squash merge하진 않았습니다.
그 결과 아래와 같은 git graph를 가지게 되었습니다.
master branch의 git graph.

rebase merge의 장점

1.
시간 순서 단위가 아닌 기능 단위로 history가 쌓이므로, 롤백 해야할 상황이나 새로운 기능 브랜치를 생성해야 할 때, 어떤 기능까지 포함하여 브랜치를 분화할 것인지 쉽게 판단할 수 있습니다.
2.
만약 master 브랜치로부터 분화된지 오래된 경우, master 브랜치와 병합할 때 그동안의 변경사항으로 인해 버그가 발생할 수 있습니다. 베이스 브랜치를 A, 기능 브랜치를 B로 예를 들겠습니다.
A1 브랜치에서 생성한 feature 브랜치인 B 브랜치는 기능을 완성하고 PR을 보냅니다. 이 때 B 브랜치에선 A2 커밋의 변경사항에 대해선 알지 못 합니다. B 브랜치에선 정상 동작하던 코드가, A2의 변경사항과 합쳐진 A3 커밋에서도 정상 동작할 것이라는 보장이 없기 떄문에, rebase 후 CI를 거쳐 머지한다면 위 상황에서 발생할 수 있는 버그를 방지할 수 있습니다.

rebase merge의 단점

1.
conflict가 발생하면 한꺼번에 모든 conflict를 보여주는 3way merge와 달리 매 커밋마다 충돌난 부분을 해결해야 하기에 conflict 해결이 좀 더 힘듭니다… master 브랜치를 checkout 한 후, conflict 나지 않는 커밋만 cherry pick하고, conflict난 부분은 다시 작성하는 방식을 많이 사용했습니다.
2.
Pull Request를 올려놓고 바로 병합되지 않고 시간이 흘러 master 브랜치에 변경사항이 발생하면, 로컬에서 master 브랜치로 rebase 후 push 하려할 때 force push를 해야합니다. 나 혼자 작업한 기능이라면 크게 상관없지만… force push가 주는 생리적 거부감…

github flow를 사용하며 느낀 장점

기능 구현 시나리오가 단순하여 (main에서 feature 브랜치 생성 → 기능 완료후 master으로 merge) 큰 고민이 필요없고, 타 개발자의 기능 구현이 빠르게 반영되어 master 브랜치가 가장 최신의 브랜치라는 심리적 안정감이 컸습니다.
또한 배포 시 특정한 브랜치를 target으로 배포할 수 있는 배포 시스템을 구축해놨기에, merge 전에 잠시 dev 환경이나 beta 환경에 배포해보고 테스트 할 수 있어서 staging이 없다는 github-flow의 단점도 어느정도 상쇄할 수 있었습니다.

왜 git flow로 전환하였는가?

실제 프로젝트 런칭일이 다가오자, 운영환경과 동일하게 구성한 테스트가 자주 진행되었습니다. 이 때 안정적인 QA를 위해서 아무때나 배포하던 배포 시기를 하루 중 정해진 시간(ex. 오후 2시)이나 QA를 불가능하게 하는 긴급한 수정사항일 때만 배포하도록 변경되었는데요, 이 때문에 문제가 발생했습니다.
1.
MSA 프로젝트 였기에 각 모듈별로 다른 버전의 master 브랜치가 배포되어있었는데요, 여러 모듈이 공통으로 쓰는 common module의 버저닝에서 문제가 발생했습니다.
새로운 기능이 master에 머지될 때 마다 따로 배포되는 모듈들.
문제 상황 -
상품 서버를 개발하는 개발자 A는 정해진 배포시간인 오후 2시에 master 브랜치의 c1 커밋을 배포하였습니다. 이후 추가 기능을 구현하며 c2 에서 공통 모듈을 수정하고, 머지해놓았습니다. A는 해당 기능이 내일 배포시간인 오후 2시에 배포되길 기대했습니다.
주문 서버를 개발하는 개발자 B는 긴급한 수정사항이 생겨 c3 커밋을 생성해 주문 서버에 배포하였습니다. 주문쪽 코드에만 변경사항이 있었기에 주문 모듈만 배포합니다.
이 때 상품 에서 내일 배포되길 기대했던 공통모듈의 수정사항이 주문 서버로 인해 딸려나갔습니다. 상품 서버도 같이 배포해주었다면 문제가 없었겠지만, 주문 서버 개발자는 그 사실을 몰랐기에 공통 모듈의 코드가 서로 달라 에러가 발생합니다.
문제 상황 예시
이 문제를 해결하려면 배포시에 모든 모듈을 한꺼번에 배포하거나 기능을 개발하는 개발자가 본인이 원하는 시기인 배포시간에 맞추어 코드를 머지해야합니다. 각 기능이 서로 다른 배포 시기를 가질 때, github flow에선 신경써야할 요소가 많았습니다.
이 때문에 주기에 맞는 운영배포 시나리오(기능개발, 운영배포, 핫픽스, 롤백)를 브랜치 전략을 통해 관리할 수 있는 git flow로 이전하게 되었습니다.

git flow로 전환하고 나서

git flow로 전환하고 난 뒤엔 release 브랜치에서 특정 주기에 배포되어야 할 코드들이 병합되고, 배포 관리자가 영향 범위를 파악하고 있어 의도하지 않은 시기에 코드가 딸려나갈 걱정이 사라졌습니다.
여러 브랜치를 관리해야 하고 배포 시간에 대해 배포 관리자를 두어야 하지만, 브랜치를 중심으로 일관되게 여러 시나리오를 대처할 수 있는 장점이 있었습니다.
몇주간 develop 브랜치와 main 브랜치를 별도로 운영 했었는데, release 브랜치로 배포하는 저희 팀의 특성상 develop 브랜치의 역할이 모호하다 느껴져 develop 브랜치를 삭제하고 main 브랜치 하나만 유지하는 방식으로 변경하였습니다.
저희 팀에선 아래 시나리오처럼 관리하는데, 이런 방법도 있구나 참고해주세요.

개요

* 삭제되지 않는 브랜치
설명
main
​코드 베이스가 되는 브랜치. release 브랜치 및 hotfix 브랜치의 원본 브랜치 역할을 한다. 
-
-
* 삭제되는 브랜치
설명
생성 시기 및 베이스 브랜치
머지 시기 및 머지 대상 브랜치
​release-yyyyMMdd
​다음 버전의 기능이 추가되는 브랜치. develop으로부터 생성하며, 배포 나갈 일정에 맞추어 yyyyMMdd를 붙인다. 배포날 해당 브랜치를 대상으로 배포하며, 안정적이라고 판단되면 main으로 merge한다.
​다음 배포 시기가 결정되었을 때, develop으로부터 생성
배포 후 안정적이라고 판단되었을 때, master로 머지
feature/기능명
기능을 개발하는 브랜치. 머지할 때 release를 rebase하여 git history를 관리한다.
기능 개발이 필요한 때, release가 있다면 release 브랜치, 없다면 develop 브랜치로부터 생성
기능 개발이 완료된 시점에, 배포 시기에 맞는 release로 머지
hotfix-yyyyMMdd
배포버전에 오류가 있어 빠른 대응이 필요할 때 생성하는 브랜치.
핫픽스가 필요한 때, master 브랜치로부터 생성
hotfix를 배포한 뒤 안정적이라고 판단되었을 때, master로 머지(현재 생성되어있는 release 브랜치는 hotfix 수정 내용을 rebase)

기능 개발 시나리오

1.
feature 브랜치를 생성합니다. 이때 이름은 'feature/티켓명' 혹은 'feature/기능명' 으로 작성합니다.
a.
만약 release 브랜치가 있다면 : 해당 릴리즈 브랜치로부터 생성합니다.
b.
만약 release 브랜치가 없다면 : main 브랜치로부터 생성합니다.
2.
해당 브랜치에서 기능을 구현합니다. 만약 기능 테스트가 필요하다면, dev에 해당 브랜치를 기준으로 배포하여 기능을 확인할 수 있습니다.
3.
기능 구현이 완료되었다면, 배포 버전을 확인하여 알맞은 브랜치에 머지할 준비를 합니다.
a.
만약 release 브랜치가 있다면 : 해당 릴리즈 브랜치를 rebase합니다.
b.
만약 release 브랜치가 없다면 : develop을 기준으로 release브랜치를 생성합니다.
i.
remote/develop을 release-yyyyMMdd (yyyyMMdd는 배포일 기준)으로 생성합니다.
ii.
작업한 feature브랜치는 새롭게 생성한 release 브랜치를 rebase합니다.
4.
release 브랜치에 담당자, 리뷰어 등을 적절히 지정하여 merge request를 생성합니다.
5.
리뷰를 반영하고 MR이 approve되면 머지합니다.

운영 배포 시나리오

1.
배포일이 되면, release-yyyyMMdd 브랜치를 운영환경에 배포합니다.
2.
운영 배포가 모니터링, 운영환경 테스트를 통해 안정적이라고 판단되면 백포팅을 준비합니다.
Backporting은 이전 소프트웨어를 최신 버전으로 이식하는 것을 나타내는 말로, 저희 팀에선 배포된 release 브랜치를 main에 반영하는 용어로 사용합니다.
3.
(백포팅) release 브랜치를 main 브랜치로 머지합니다.
a.
이후 master의 최신 커밋에 배포한 날짜를 기준으로 tag를 생성합니다. 태그의 이름은 platform-yyyyMMdd 양식을 따릅니다.

롤백 시나리오

1.
release 브랜치를 배포하고 난 뒤 문제가 발생하여 서버 롤백이 필요한 상황입니다.
a.
백포팅 전이라면 main 브랜치로 배포합니다.
b.
백포팅 후라면 직전 배포 버전에 해당하는 tag를 찾아 배포합니다.

핫픽스 시나리오

1.
배포버전에 중대한 결함이 발견되어 빠르게 수정이 필요한 상황입니다.
2.
main 브랜치로부터 배포버전에 해당하는 태그를 찾아 'hotfix-yyyyMMdd' 브랜치를 생성합니다.
3.
기능을 수정합니다.
4.
이상이 없다고 판단되면 hotfix 브랜치를 배포합니다.
5.
이후 안정되었다면, master 및 develop 브랜치로 백포팅을 준비합니다.
6.
(백포팅) hotfix 브랜치를 main 브랜치로 머지합니다.
a.
이후 master의 최신 커밋에 배포한 날짜를 기준으로 tag를 생성합니다. 태그의 이름은 platform-yyyyMMdd 양식을 따릅니다.
7.
현재 생성되어있는 release 브랜치는 develop을 rebase해 hotfix 변경사항을 반영합니다.

git flow의 단점

1.
다양한 브랜치를 관리하여야 하여야 합니다.
2.
배포 상황을 총괄할 배포 관리자가 필요합니다.

마치며

은탄환은 없다는 말처럼, 세상에 존재하는 모든 팀별로 각자 필요한 브랜칭 전략이 다를 거란 생각이 듭니다. 각자 상황에 맞게 브랜칭 전략을 도입하고, 문제 상황을 맞닥뜨리면 나만의 전략을 도입해 지혜롭게 해결하시길 바랍니다. conflict 없는 하루 되세요.