클라우드‐WHY‐CI - 100-hours-a-week/16-Hot6-wiki GitHub Wiki
목차
CI
왜 CI 과정이 필요한가요?
CI는 코드의 변경 사항을 자동으로 통합하는 과정입니다. 단순히 코드의 형상을 일치시키기 위한 목적이 아니라, 린팅, 테스팅 등을 포함하여 "정말 사용할 수 있는 코드" 상태로 통합하는 것을 말합니다.
1. 빅뱅 배포 실험에서 마주한 문제
프로젝트 초기, 저희 팀은 자동화된 빌드·배포 파이프라인 없이, EC2 인스턴스에 접속하여 git pull
→ 빌드
→ 실행
을 직접 수행하는 수작업(Big Bang) 배포 방식을 선택했습니다.
이 방식은 학습 부담이 없고, 별도의 도구 없이 빠르게 결과를 확인할 수 있다는 장점이 있었습니다. 그러나 다음과 같은 실제 실패 경험을 통해 한계가 명확히 드러났습니다.
-
문제 상황:
- 백엔드 서버는 정상 동작 중이었으나, 배포 중 프론트엔드(React)
npm run build
과정에서 **메모리 부족(OOM)**이 발생해 빌드가 실패함 - 서비스 운영 자체는 수백 MB로 가능했지만, 빌드에는 2~4GB 이상의 메모리가 필요했던 것
- EC2 t2.micro 환경에서는 이를 감당할 수 없어, 결국 swap 메모리를 수동 설정해 해결해야 했음
- 백엔드 서버는 정상 동작 중이었으나, 배포 중 프론트엔드(React)
-
그 외 겪었거나 예상되는 어려움:
- 로컬(macOS)에서는 문제 없이 빌드되던 코드가, 서버(Ubuntu)에서는 패키지 호환성 문제로 실패
- 팀원이 늘어나면서 누가, 어떤 환경에서, 어떤 명령어로 배포했는지 이력을 추적하기 어려워짐
- 실수로 테스트하지 않은 상태에서 배포해, 프로덕션 장애가 발생할 가능성도 높아짐
이러한 경험은 단순히 "수동 배포가 귀찮다"는 수준의 문제가 아니라, 서비스 안정성과 팀 협업의 리스크로 이어질 수 있는 구조적 문제임을 보여주었습니다.
2. CI 도입이 왜 해답인가?
CI는 다음과 같은 방식으로 위 문제들을 해소해줍니다.
빅뱅 배포에서 겪은 문제 | CI를 통한 해결 |
---|---|
빌드 시 서버 메모리 부족 | GitHub Actions에서 외부 빌드 → EC2에는 실행만 |
환경 간 패키지 호환 문제 | 통일된 OS/버전에서의 자동 빌드 및 테스트 수행 |
테스트 없이 배포되는 문제 | push 시 자동 유닛 테스트 수행, 실패 시 배포 불가 |
배포 방식 일관성 부족 | 모든 빌드/테스트 작업이 기록되고 공유됨 |
실수 방지 어려움 | Step-by-step으로 검증된 자동화 파이프라인 구성 가능 |
특히, EC2 인스턴스는 서비스를 실행하는 데 필요한 최소 자원만 확보하면 되며, 빌드는 GitHub Actions 같은 외부에서 처리되므로 운영 안정성과 비용 측면 모두에서 효과적인 구조가 됩니다.
결론
"CI가 좋아서 도입했다"가 아니라, "CI 없이 실제로 해봤더니 한계가 명확했고, 직접 겪은 문제를 해결하려고 도입했다"*는 것이 저희 팀의 진짜 이유입니다.
왜 브랜치 전략을 그렇게 수립했나요?
비교 분석
전략 | 특징 | 장점 | 단점 |
---|---|---|---|
GitHub Flow | main 브랜치 기준으로 feature → PR → merge 방식 |
간단하고 빠름 | 운영 코드와 개발 코드 분리가 어려움 |
Git Flow | main , develop , feature , release , hotfix 구분 |
명확한 역할 분담, 안정적인 릴리즈 관리 가능 | 브랜치가 많아지고 관리가 복잡해질 수 있음 |
현재 전략 | main , dev , feat/* , hotfix/* 로 구성 |
단순성과 안전성 절충, CI/CD 흐름과 맞음 | 브랜치 병합 규칙을 초기에 명확히 해야 함 |
GitHub Flow를 단독으로 사용하지 않은 이유
→ 기능 개발과 운영 코드의 분리가 어렵고, 긴급 수정 대응에도 제약이 있기 때문.
Git Flow 전체를 도입하지 않은 이유
→ release
브랜치까지 운용하는 구조는 팀 규모나 프로젝트 단계에 비해 과도하게 복잡하다고 판단.
현재 전략을 채택한 이유
→ GitHub Flow
의 간결함과 Git Flow
의 안정성을 절충하여, 운영과 개발 모두를 유연하게 관리할 수 있기 때문.
기본 구조
이 프로젝트에서는 다음과 같은 브랜치 전략을 사용합니다.
브랜치 | 용도 |
---|---|
main |
운영용 브랜치. 항상 배포 가능한 안정 상태를 유지합니다. |
dev |
개발 통합 브랜치. 여러 기능을 병합하여 테스트하는 공간입니다. |
feat/* |
기능 단위 작업 브랜치. 단일 기능 개발을 위한 브랜치입니다. |
hotfix/* |
운영 중 긴급 수정 브랜치. main 에 바로 반영되며 이후 dev 에 병합됩니다. |
브랜치 흐름도
feat/* push
→ 개인 기능 개발
1. - 목적: 개인 기능 개발 중 임시 저장(push)
- 테스트:
- 기본적으로 단위 테스트만 실행
- 커밋에
[no-ci]
태그가 있으면 테스트 생략 가능
- 이유:
- 개발 중이라 빠르게 반복(push)해야 하는 경우가 많아 불필요한 CI 낭비를 줄이고, 본인이 작성한 코드에 대해서만 빠르게 피드백 받기 위해.
feat/* → dev PR
→ 개발 브랜치 병합 요청
2. - 목적: 팀 개발 브랜치(dev)에 기능 병합 요청
- 테스트:
- 단위 테스트 고정.
- 통합 테스트는 백그라운드에서 참고용으로만 실행
- 이유:
dev
는 여러 기능을 합치는 중간 단계니까
테스트는 하되, 통합 테스트 실패해도 병합 가능- 대신 백그라운드에서 통합 테스트를 돌려서
나중에 미리 문제를 감지할 수 있도록 함
dev → main PR
→ 정식 배포 준비
3. - 목적: 실제 제품(main)에 반영하기 전 마지막 검증
- 테스트:
- 단위 + 통합 테스트 모두 필수
- 실패하면 병합 차단
- 이유:
- 이건 서비스에 직접 반영되는 배포 전 단계
- 무조건 성공한 코드만 main에 들어가야 안정적이기 때문에
가장 강한 검증을 적용함
hotfix/* → main PR
→ 긴급 수정
4. - 목적: main 브랜치에서 발생한 버그를 빠르게 수정
- 테스트:
- 단위 테스트는 필수
- 통합 테스트는 병합 후 백그라운드 실행
- 이유:
- 긴급성이 중요하므로
일단 빨리 반영해서 사용자 불편 최소화 - 다만 나중에라도 통합 테스트를 돌려서
새로운 문제가 생겼는지는 꼭 확인
- 긴급성이 중요하므로
[feat/기능A] → [dev] → [main]
- 기능 개발은
feat/*
브랜치에서 시작됩니다. - 기능이 완료되면
dev
브랜치에 병합되어 통합 테스트를 진행합니다. dev
브랜치가 충분히 안정적일 경우main
에 병합하여 배포합니다.
[hotfix/버그수정] → [main] → [dev]
- 운영 중 발생한 긴급 이슈는
hotfix/*
브랜치에서 빠르게 수정하여main
에 먼저 반영합니다. - 이후
main
브랜치의 변경사항을dev
에 병합하여 일관성을 유지합니다.
왜 이러한 전략을 사용했는가?
프로젝트 초기에는 단순한 GitHub Flow만으로 충분했지만, 다음과 같은 요구사항이 추가되면서 전략을 확장할 필요가 있었습니다:
-
개발 중인 기능과 운영 코드의 분리 필요
main
과dev
브랜치를 구분함으로써, 배포 가능한 안정 코드와 통합 개발 코드를 나눌 수 있습니다. -
기능별 분리 작업 및 병렬 진행
feat/*
브랜치를 통해 팀원들이 동시에 여러 기능을 개발하고, 독립적으로 코드 리뷰를 받을 수 있습니다. -
긴급한 운영 이슈 대응
hotfix/*
브랜치를 통해 기능 개발 진행과 무관하게 빠르게main
에 반영하고, 이후dev
에도 되돌려 병합함으로써 코드 이력의 일관성을 유지할 수 있습니다. -
풀리퀘스트 기준 병합 구조 정립
feat/*
→dev
: 기능 개발 완료 후 병합dev
→main
: 릴리즈 시 병합hotfix/*
→main
→dev
: 운영 긴급 수정 후 병합
결론
이 전략은 GitHub Flow의 간결함과 Git Flow의 안전성을 절충한 구조로, 기능 개발의 유연성과 운영 안정성, 긴급 대응력을 모두 확보할 수 있는 실용적인 브랜치 전략입니다.