infra
Platform

모듈 맵

[Infra Ops] Git/GitLab 브랜치 전략과 릴리즈 관리

0 / 52 완료

펼치기
0 / 52 완료0%

인프라 운영 & SRE · 34 / 52

[Infra Ops] Git/GitLab 브랜치 전략과 릴리즈 관리

Git 브랜치 전략, tag 기반 릴리즈, merge 충돌 해결, 이슈-커밋 연결까지 — 인프라 엔지니어가 알아야 할 형상관리 실무

🚨INCIDENT ALERT
HIGH

인프라 팀에서 배포 후 문제가 생겼습니다. 어젯밤 누군가 main 브랜치에 직접 push한 커밋이 있었고, 오늘 오전 배포에서 그 코드가 올라갔습니다. 롤백 요청이 왔는데 팀원들은 "revert해야 하나 reset해야 하나"로 논쟁 중입니다. 릴리즈 태그도 없어서 "정확히 어느 버전까지 돌아가야 하나"도 불분명합니다.

이 모듈은 그런 상황에서 당황하지 않도록, Git을 형상관리 도구로 제대로 쓰는 방법을 다룹니다. 브랜치 전략, 태그 릴리즈, 안전한 롤백 절차를 실무 중심으로 정리합니다.

이번 챕터에서 배울 것
  • 1GitFlow 브랜치 전략(main/develop/feature/release/hotfix)의 흐름을 설명할 수 있다
  • 2git tag로 릴리즈 기준점을 만들고 배포 이력을 추적할 수 있다
  • 3커밋 메시지에 이슈 번호를 포함해 GitLab에서 이슈-커밋 연결을 활용할 수 있다
  • 4긴급 hotfix 절차(main merge → develop merge)를 순서대로 실행할 수 있다
  • 5잘못된 커밋에 git revert를 안전하게 적용하고 reset과의 차이를 설명할 수 있다

브랜치 전략 — GitFlow

💡개념

GitFlow 브랜치 구조

브랜치 전략은 팀이 코드를 어떻게 나누고 합치는가의 규칙입니다. 규칙이 없으면 누가 어디에 무엇을 push했는지 파악이 어렵고, 긴급 수정이 필요할 때 경로가 없어 혼란이 생깁니다.

브랜치 전략 개요 — 팀이 코드를 나누고 합치는 규칙. main(운영 배포)·develop(통합)·feature(기능)·hotfix(긴급 수정) 브랜치를 역할별로 두어 누가 어디에 push하는지 명확히 하고, 긴급 수정 경로를 확보. 규칙이 없으면 추적·긴급 대응이 혼란스러움확대

운영 장애가 났습니다. 누군가 main에 직접 push한 커밋이 테스트 없이 배포됐고, 롤백하려니 "어느 커밋으로 돌아가야 하나"부터 막힙니다. 릴리즈 태그도 없고, 누가 무엇을 바꿨는지 추적할 방법이 없습니다. 브랜치 전략이 없는 팀은 긴급 상황에서 이렇게 무너집니다. 규칙 없이 모두가 한 브랜치에 push하면, 운영 코드와 개발 코드가 뒤섞이고 장애 원인을 찾는 데 배포 복구보다 더 많은 시간이 걸립니다. GitFlow는 소규모~중규모 팀에서 검증된 전략입니다. 브랜치별 역할이 명확하게 분리됩니다.

GitFlow 브랜치 전략 — main(운영 배포)·develop(통합 개발)·feature(기능 개발)로 분리, feature는 develop에 머지·release와 hotfix는 main에 머지확대

브랜치 역할 요약:

브랜치역할직접 push 여부
main운영 배포 기준. 항상 배포 가능 상태금지 (MR only)
develop다음 릴리즈 통합 브랜치금지 (MR only)
feature/*기능 단위 개발개인 작업 가능
release/*릴리즈 준비 (QA, 버그픽스)제한적
hotfix/*운영 긴급 수정main에서 분기
로컬 터미널
# feature 브랜치 생성 및 작업
git checkout develop
git pull origin develop
git checkout -b feature/user-logout

# 작업 후 커밋
git add .
git commit -m "feat: 사용자 로그아웃 기능 구현 (#45)"

# develop으로 MR(Merge Request) 요청
git push origin feature/user-logout
# GitLab에서 develop 대상 MR 생성
💡개념

Trunk-Based Development — GitFlow의 대안

팀 규모가 작거나 배포 주기가 짧은 경우, 더 단순한 전략이 유리할 수 있습니다.

비교 항목GitFlowTrunk-Based
브랜치 수많음 (5종)적음 (main + 단기 feature)
릴리즈 주기주/월 단위수시(하루 여러 번 가능)
팀 규모중~대규모소~중규모
적합한 상황명확한 버전 관리 필요CI/CD 자동화가 잘 된 팀

이 모듈에서는 인프라 운영 팀에서 더 자주 보이는 GitFlow를 기준으로 다룹니다.

Git Workflow — 브랜치 전략과 운영 배포 흐름확대

태그 기반 릴리즈

💡개념

git tag 로 배포 기준점 만들기

배포 기준점이 없으면 "어제 배포본으로 롤백해줘"라는 요청을 받았을 때 정확히 어느 커밋인지 찾기가 어렵습니다. git tag는 특정 커밋에 이름을 붙이는 방법입니다. 한 번 붙이면 브랜치와 달리 이동하지 않습니다.

로컬 터미널
# annotated tag 생성 (메시지 포함, 배포 이력에 남김)
git tag -a v1.2.0 -m "Release v1.2.0 — 로그아웃 기능 추가, 성능 개선"

# 원격 저장소에 push
git push origin v1.2.0

# 전체 태그 목록 확인
git tag -l

# 특정 버전 두 태그 사이의 커밋 목록
git log --oneline v1.1.0..v1.2.0
OUTPUT
a3f9c21 feat: 사용자 로그아웃 기능 구현 (#45)
b82e1d3 fix: 세션 만료 시 리다이렉트 오류 수정 (#43)
c11a027 perf: 쿼리 캐시 레이어 추가 (#40)

배포 이력 추적 루틴:

로컬 터미널
# 가장 최근 태그 확인
git describe --tags --abbrev=0

# 특정 커밋이 어느 태그 기준인지 확인
git describe --tags a3f9c21

# 날짜 기준 배포 이력
git log --oneline --since="2026-01-01" --until="2026-06-01" --decorate | grep "tag:"

실습

1릴리즈 태그 생성 및 푸시

태그 생성은 현재 브랜치의 최신 커밋에 붙습니다. main 브랜치에서 실행해야 운영 기준점으로 사용할 수 있습니다. 먼저 main 브랜치에 있는지 확인합니다.

로컬 터미널
# main 브랜치 확인
git branch --show-current
# 또는
git status | head -1

# main이 아니라면 이동
git checkout main
git pull origin main

# 태그 생성 및 push
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
git tag -a v1.0.0 -m 'Release v1.0.0' && git push origin v1.0.0
🔍실행 후 확인할 것
  • git log --oneline --decorate | head -5로 HEAD 위치를 먼저 확인 — HEAD -> main과 tag: v1.0.0이 같은 커밋 해시에 있어야 정상. 다른 커밋에 있으면 태그가 잘못된 커밋을 가리키는 것
  • git show v1.0.0 출력에서 태그 타입 확인 — tag 오브젝트(Annotated)면 Tagger와 Date가 나옴. commit 오브젝트면 lightweight 태그로 메시지가 없어 릴리즈 추적에 부적합
  • git push origin v1.0.0 후 GitLab Tags 탭에서 해당 태그가 보이지 않으면 — 원격에 push된 브랜치가 달라 태그 참조 커밋이 원격에 없는 경우. git push origin main 먼저 실행 후 태그 push
2배포 기준 커밋 범위 확인

두 태그 사이의 변경 내역을 확인합니다. 릴리즈 노트 작성이나 롤백 범위 파악에 사용합니다. 이전 태그(v0.9.0)가 없는 경우 첫 커밋부터 확인하려면 git log --oneline v1.0.0을 씁니다.

로컬 터미널
# 이전 릴리즈 이후 커밋 목록
git log --oneline v0.9.0..v1.0.0

# 변경된 파일 목록
git diff --name-only v0.9.0..v1.0.0

# 작성자별 기여 요약
git shortlog v0.9.0..v1.0.0
OUTPUT
a3f9c21 feat: 사용자 로그아웃 기능 구현 (#45)
b82e1d3 fix: 세션 만료 시 리다이렉트 오류 수정 (#43)
c11a027 perf: 쿼리 캐시 레이어 추가 (#40)
git log --oneline v0.9.0..v1.0.0
🔍실행 후 확인할 것
  • git log --oneline v0.9.0..v1.0.0 커밋 수를 먼저 확인 — 0개이면 두 태그가 같은 커밋을 가리키거나 태그 순서가 역전된 것. 50개 이상이면 릴리즈 단위가 너무 커서 롤백 범위 특정이 어려움
  • 커밋 메시지에 feat/fix/perf prefix가 없으면 릴리즈 노트 자동 생성 불가 — 최소 50% 이상의 커밋이 컨벤션을 따라야 changelog 도구 활용 가능. 이슈 번호(#N) 없으면 추적 단절
  • 커밋 목록이 맞는데 git diff --name-only 변경 파일이 예상보다 많으면 — 다른 브랜치의 변경이 병합된 것. merge commit이 포함됐는지 git log --merges로 확인

이슈-커밋 연결

💡개념

커밋 메시지 컨벤션과 이슈 연결

커밋 메시지를 일관된 형식으로 작성하면, 나중에 "이 변경이 왜 생겼나"를 추적하기 쉬워집니다. GitLab은 커밋 메시지의 #이슈번호를 자동으로 해당 이슈와 연결합니다.

로컬 터미널
# 커밋 메시지 형식 권장
# <type>: <요약> (#이슈번호)
#
# type 종류:
#   feat  — 새 기능
#   fix   — 버그 수정
#   docs  — 문서
#   refac — 리팩터링
#   chore — 빌드/배포/설정 변경

# 예시
git commit -m "fix: 로그인 후 세션 쿠키 누락 수정 (#78)"
git commit -m "feat: Prometheus 메트릭 엔드포인트 추가 (#82)"
git commit -m "chore: application-prod.yml 로그 레벨 WARN으로 변경"
로컬 터미널
# 특정 이슈 번호가 언급된 커밋 찾기
git log --oneline --all | grep "#78"

# 최근 30개 커밋 확인
git log --oneline -30

# 특정 작성자 커밋만 확인
git log --oneline --author="minjun" --since="1 week ago"

# 특정 파일이 언제 마지막으로 변경됐는지
git log --oneline -5 -- src/main/resources/application-prod.yml

트러블슈팅

상황: 장애를 일으키는 커밋이 main에 merge됐고, 빠르게 되돌려야 합니다. git revertgit reset 중 어느 것을 써야 하는지 판단 기준이 핵심입니다.

로컬 터미널
# 문제 커밋 SHA 확인
git log --oneline main | head -10
# 예: d9a3f1c feat: 캐시 로직 변경 (#91) ← 이 커밋이 문제

# 방법 1: git revert (공유 브랜치에서 항상 이 방법 사용)
# 문제 커밋을 취소하는 새 커밋을 만든다 — 이력 보존
git revert d9a3f1c
# 에디터가 열리면 커밋 메시지 확인 후 저장
git push origin main

# 방법 2: git reset --hard (주의: 공유 브랜치에서 사용 금지)
# 이 방법은 혼자 쓰는 feature 브랜치에서만
git reset --hard d9a3f1c~1  # 해당 커밋 이전으로 되돌림
# force push 필요 → 다른 팀원 이력 꼬임 발생 가능

판단 기준:

상황사용할 명령이유
main/develop 등 공유 브랜치git revert이력 보존, force push 불필요
혼자 쓰는 feature 브랜치git reset 가능아직 다른 사람이 pull하지 않음
이미 force push한 상황팀에 알리고 revert이력 복구 시도보다 revert가 안전

원인: feature 브랜치와 develop 브랜치가 동일한 파일의 같은 부분을 각자 수정했습니다. Git이 자동으로 합칠 수 없어 충돌 마커를 삽입하고 멈춥니다.

로컬 터미널
# 충돌 파일 목록 확인
git status | grep "both modified"

# 충돌 마커 확인 (파일 안에 삽입됨)
grep -n "<<<<<<" src/main/resources/application.yml

# 충돌 마커 해석:
# <<<<<<< HEAD          ← 현재 브랜치(develop)의 내용
# spring.datasource.url=jdbc:mysql://develop-db:3306/mydb
# =======
# spring.datasource.url=jdbc:mysql://localhost:3306/mydb_dev
# >>>>>>> feature/new-db-config   ← merge하려는 브랜치 내용

# 직접 편집해서 올바른 내용만 남김 (마커 줄 포함 삭제)
vi src/main/resources/application.yml

# 수정 후 해결 완료 표시
git add src/main/resources/application.yml

# merge 완료
git commit
# (merge 커밋 메시지가 자동으로 채워짐)

# 충돌이 많으면 GUI 도구 활용
git mergetool    # vimdiff, VS Code 등 설정에 따라 실행

충돌 예방 습관:

  • feature 브랜치 작업이 길어지면 주기적으로 git merge develop으로 최신화
  • 설정 파일 같이 충돌 잦은 파일은 MR 전 팀에 알리고 작업

심화 — 이력은 지워지지 않는다, 그리고 저장소 속의 저장소

💡개념

심화: git은 스냅샷 이력을 영구 보관한다 — 대용량·비밀이 특별한 이유

"파일을 커밋했다가 다음 커밋에서 지웠으니 사라졌겠지"는 git에서 통하지 않습니다. git이 데이터를 어떻게 저장하는지 알면, 왜 대용량 파일과 비밀값을 특별히 다뤄야 하는지 보입니다.

  • 모든 커밋은 완전한 스냅샷입니다: git은 diff가 아니라 각 커밋 시점의 파일 전체(blob)를 저장하고, 커밋들이 그 blob을 가리킵니다. 파일을 나중에 지워도 그 blob은 과거 커밋에서 여전히 도달 가능해 이력에 남습니다. 그래서 한 번 커밋된 100MB 덤프는 이후 삭제해도 모든 클론의 .git을 계속 무겁게 만듭니다.
  • 비밀도 마찬가지입니다: 비밀번호·키를 커밋했다가 다음 커밋에서 지워도 git log -p·git show로 과거 커밋에서 그대로 꺼낼 수 있습니다. 삭제 커밋은 "새 커밋을 추가"할 뿐 과거를 바꾸지 않습니다. 유출된 비밀은 이력 재작성과 별개로 반드시 회전(교체) 해야 합니다.
  • 대용량은 LFS로 분리합니다: 이미지·바이너리·빌드 산출물처럼 큰 파일은 Git LFS로 관리하면, 저장소엔 작은 포인터만 들어가고 실제 blob은 LFS 서버에 둡니다. 애초에 빌드 산출물은 커밋하지 않는 것이 원칙입니다.
  • 한계/되돌리기: 이미 이력에 박힌 대용량·비밀을 진짜로 지우려면 git filter-repo(또는 BFG)로 이력을 재작성하고 강제 push한 뒤, 모두가 다시 클론해야 합니다 — 공유 브랜치 이력을 갈아엎는 무거운 작업이라, 처음부터 안 올리는 것이 훨씬 쌉니다.

그래서 .gitignore와 커밋 리뷰는 미관이 아니라, "영구 보관되는 것에 무엇을 넣을지"를 지키는 안전장치입니다.

상황: 저장소를 클론했더니 특정 디렉터리(공통 라이브러리)가 비어 있어 컴파일이 실패합니다. 혹은 로컬은 되는데 CI만 옛 버전으로 빌드돼, 이미 고친 버그가 되살아납니다.

원인: 그 디렉터리가 Git 서브모듈입니다. 서브모듈은 부모 저장소가 자식 저장소의 특정 커밋 SHA를 가리키는 포인터만 담고, 자식의 실제 파일은 담지 않습니다. 그냥 git clone만 하면 서브모듈 폴더는 비어 있고, git submodule update --init --recursive(또는 git clone --recursive)를 해야 그 SHA로 체크아웃됩니다. CI가 옛 버전으로 빌드되는 것은, 서브모듈 안에서 최신 커밋을 만들었지만 부모가 가리키는 SHA를 갱신·커밋하지 않아 부모가 여전히 옛 SHA를 가리키기 때문입니다.

진단: git submodule status로 부모가 가리키는 SHA와 실제 체크아웃된 SHA를 비교합니다. .gitmodules에서 경로·URL을 확인하고, CI 로그에서 서브모듈 초기화 단계가 도는지, --recursive가 빠졌는지 봅니다.

해결: 클론 측은 git submodule update --init --recursive로 폴더를 채웁니다. 버전을 올릴 땐 서브모듈 안에서 원하는 커밋으로 이동한 뒤 부모에서 그 변경(새 SHA 포인터)을 git add·커밋해 포인터를 갱신합니다. CI에는 체크아웃 단계에서 서브모듈을 재귀 초기화하도록 명시해(예: 체크아웃 옵션의 submodules recursive) 재발을 막습니다.

💼
실무 맥락
현업 패턴

실제 업무에서 이 지식이 쓰이는 상황:

인프라/운영 엔지니어에게 형상관리가 실질적으로 필요한 세 장면입니다.

1. 긴급 핫픽스 절차 (운영 버그 발생 시):

로컬 터미널
# main 기반으로 hotfix 브랜치 생성
git checkout main
git pull origin main
git checkout -b hotfix/session-null-pointer

# 수정 후 커밋
git add .
git commit -m "fix: 세션 null 시 NPE 수정 (#101)"

# main에 merge (운영 배포)
git checkout main
git merge hotfix/session-null-pointer
git tag -a v1.2.1 -m "Hotfix v1.2.1 — 세션 NPE 긴급 수정"
git push origin main
git push origin v1.2.1

# develop에도 반영 (재발 방지)
git checkout develop
git merge hotfix/session-null-pointer
git push origin develop

# hotfix 브랜치 삭제
git branch -d hotfix/session-null-pointer
git push origin --delete hotfix/session-null-pointer

2. 배포 이력 추적:

로컬 터미널
# "3주 전 배포본으로 돌아가야 한다" 요청이 오면
git log --oneline --decorate | grep "tag: v"
# 릴리즈 태그가 있어야 정확한 기준점으로 배포 가능

# 특정 날짜 이전 마지막 태그 확인
git log --tags --simplify-by-decoration --pretty="format:%d %ai" | grep "v1\." | head -5

3. 설정 파일 변경 추적: 운영 서버 설정이 언제, 누가, 왜 바뀌었는지 조회합니다.

로컬 터미널
git log --oneline --follow -- config/application-prod.yml
git show <커밋SHA>:config/application-prod.yml

형상관리가 잘 된 팀은 장애 발생 시 "어떤 버전에서 문제가 생겼나"를 5분 안에 파악합니다.


명령어·단축키 빠른 참조

이 모듈에서 다룬 Git 브랜치·태그·롤백·서브모듈 명령을 실전 옵션과 함께 모았습니다.

명령어/단축키용도자주 쓰는 예
git tag -a릴리즈 기준점(annotated) 생성git tag -a v1.2.0 -m "Release v1.2.0"
git push origin <태그>태그 원격 반영브랜치 먼저 push 후 태그 push
git describe --tags현재 커밋의 최근 태그 확인git describe --tags --abbrev=0
git log --oneline v0.9.0..v1.0.0릴리즈 간 변경·롤백 범위git diff --name-only로 파일까지
git revert <SHA>공유 브랜치 안전 롤백(이력 보존)main/develop은 항상 revert
git reset --hard <SHA>로컬 브랜치 되돌리기(주의)혼자 쓰는 feature 브랜치만
git merge브랜치 통합충돌 시 git mergetool·마커 편집
git status | grep "both modified"충돌 파일 식별이후 git add로 해결 표시
git commit -m "<type>: 요약 (#N)"컨벤션 커밋·이슈 연결fix: 세션 쿠키 누락 (#78)
git log --author --since작성자·기간별 이력 추적git log --oneline --author="minjun"
git submodule update --init --recursive빈 서브모듈 폴더 채우기클론 후 라이브러리 비면 실행
git submodule status부모가 가리키는 SHA 대조옛 SHA면 부모에서 add·커밋
git log --follow -- <파일>설정 파일 변경 이력 조회git show <SHA>:config/application-prod.yml

관련 모듈로 더 깊이:

다음 모듈에서는 이 코드를 실제 배포 산출물로 만드는 빌드 도구 실무를 다룹니다.

지식 확인

퀴즈 — 8문제

Q1

배포 후 장애가 발생해 롤백이 필요합니다. 이전 버전 코드로 정확히 돌아가야 하는데 릴리즈 기준 커밋을 찾기 어렵습니다. 다음 번 배포부터 이런 상황을 방지하기 위해 사용해야 하는 Git 기능은?

Q2

hotfix 브랜치를 main과 develop 양쪽에 merge하는 이유는?

Q3

git revert와 git reset의 차이에서 운영 환경에서 revert를 선호하는 이유는?

Q4

커밋 메시지에 이슈 번호를 포함하는 이유는?

Q5

여러 장수(long-lived) 브랜치를 두는 GitFlow와, main 하나에 짧은 브랜치로 자주 통합하는 Trunk-Based Development는 어떤 기준으로 고르나?

Q6

운영에서 '언제 어떤 변경이 들어가 버그가 생겼는지' 추적하기 쉬우려면 커밋을 어떻게 남기는 게 좋은가?

Q7

[심화] 실수로 100MB 데이터 덤프를 커밋했다가 다음 커밋에서 삭제했는데도, 팀원들이 클론할 때 저장소가 여전히 무겁다. 근본 이유는?

Q8

[심화] git clone한 저장소에서 공통 라이브러리 폴더가 텅 비어 빌드가 깨진다. .gitmodules 파일은 존재한다. 가장 유력한 원인과 조치는?

0 / 8 답변

🧪 실습으로 확인하기

Nginx 설치 및 기동

초급

Linux 서버에 Nginx를 설치하고 systemd 서비스로 등록하여 80포트에서 응답하는 상태까지 만든다.

40📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요