본문으로 건너뛰기
Previous
Next
Git submodule 서브모듈 실무 | 추가·업데이트·CI·모노레포 대안

Git submodule 서브모듈 실무 | 추가·업데이트·CI·모노레포 대안

Git submodule 서브모듈 실무 | 추가·업데이트·CI·모노레포 대안

이 글의 핵심

Git submodule로 서브레포를 끌어오는 법, 초기화·업데이트·삭제, CI 캐시·흔한 오류, submodule 대신 모노레포를 쓰는 기준까지 정리합니다.

들어가며

Git submodule은 한 저장소 안에 다른 Git 저장소를 디렉터리로 포함시키는 방식입니다. 상위 저장소(슈퍼프로젝트)는 하위 저장소의 특정 커밋 SHA만 기록하므로, “의존 라이브러리를 소스로 포함하되 버전을 엄격히 핀한다”는 요구에 맞습니다. 반면 클론 한 번에 모든 것이 끝나지 않고, CI·Docker 빌드에서 초기화 단계를 빼먹기 쉬워 운영 난이도가 올라갑니다. 이 글은 Git submodule 서브모듈 실무 키워드에 맞춰 추가·업데이트·삭제, CI 설정, 흔한 오류, 모노레포 대안까지 정리합니다.

실전 경험에서 배운 교훈

이 기술을 실무 프로젝트에 처음 도입했을 때, 공식 문서만으로는 알 수 없었던 많은 함정들이 있었습니다. 특히 프로덕션 환경에서 발생하는 엣지 케이스들은 로컬 개발 환경에서는 재현조차 되지 않았죠.

가장 어려웠던 점은 성능 최적화였습니다. 처음엔 “동작만 하면 되겠지”라고 생각했지만, 실제 사용자 트래픽이 몰리면서 병목 지점들이 하나씩 드러났습니다. 특히 데이터베이스 쿼리 최적화, 캐싱 전략, 에러 핸들링 구조 등은 여러 번의 장애를 겪으면서 개선해 나갔습니다.

이 글에서는 그런 시행착오를 통해 얻은 실전 노하우와, “이렇게 하면 안 된다”는 교훈들을 함께 정리했습니다. 특히 트러블슈팅 섹션은 실제 장애 대응 경험을 바탕으로 작성했으니, 비슷한 문제를 마주했을 때 참고하시면 도움이 될 것입니다.

  • 상위 저장소의 .gitmodules경로·URL·브랜치 추적 설정이 기록됩니다.
  • Git은 하위 디렉터리를 일반 파일이 아니라 gitlink(커밋 포인터)로 취급합니다.
  • 따라서 “하위 저장소의 최신 main을 자동으로 따라간다”가 기본은 아닙니다. 상위에 기록된 SHA가 진실입니다.

실전: 추가·클론·업데이트·삭제

서브모듈 추가

기본 추가:

# 서브모듈 추가
git submodule add https://github.com/org/shared-lib.git vendor/shared-lib
# 상태 확인
git status
# Changes to be committed:
#   new file:   .gitmodules
#   new file:   vendor/shared-lib (커밋 SHA)
# 커밋
git commit -m "chore: add shared-lib submodule"
git push

특정 브랜치 추적:

# develop 브랜치 추적
git submodule add -b develop https://github.com/org/shared-lib.git vendor/shared-lib
# .gitmodules 확인
cat .gitmodules
# [submodule "vendor/shared-lib"]
#     path = vendor/shared-lib
#     url = https://github.com/org/shared-lib.git
#     branch = develop

.gitmodules 파일 구조:

[submodule "vendor/shared-lib"]
    path = vendor/shared-lib
    url = https://github.com/org/shared-lib.git
    branch = main
    
[submodule "vendor/utils"]
    path = vendor/utils
    url = https://github.com/org/utils.git
    branch = stable

처음 클론할 때

방법 1: 클론 시 서브모듈 포함

# 모든 서브모듈 함께 클론
git clone --recurse-submodules https://github.com/org/main-app.git
# 또는 (동일)
git clone --recursive https://github.com/org/main-app.git

방법 2: 클론 후 서브모듈 초기화

# 먼저 메인 저장소 클론
git clone https://github.com/org/main-app.git
cd main-app
# 서브모듈 초기화 및 가져오기
git submodule init
git submodule update
# 또는 한 번에
git submodule update --init --recursive

방법 3: 병렬 클론 (빠름)

# 서브모듈을 병렬로 클론
git clone --recurse-submodules --jobs 4 https://github.com/org/main-app.git

서브모듈 상태 확인

# 서브모듈 목록 및 상태
git submodule status
#  abc1234 vendor/shared-lib (v1.2.3)
# +def5678 vendor/utils (v2.0.0-5-gdef5678)
# -ghi9012 vendor/legacy (heads/main)
# 기호 의미:
# (공백): 정상 (상위가 가리키는 커밋과 일치)
# +: 서브모듈이 다른 커밋을 체크아웃 (변경됨)
# -: 서브모듈이 초기화되지 않음
# U: 병합 충돌
# 상세 정보
git submodule foreach 'echo $name: $(git rev-parse HEAD)'

하위 저장소를 최신으로 올리기

방법 1: 수동 업데이트

# 서브모듈 디렉터리로 이동
cd vendor/shared-lib
# 최신 코드 가져오기
git fetch origin
git checkout main
git pull origin main
# 상위 저장소로 돌아와서 변경 기록
cd ../..
git add vendor/shared-lib
git commit -m "chore: update shared-lib to v1.3.0"
git push

방법 2: 자동 업데이트

# 모든 서브모듈을 원격 브랜치 최신으로
git submodule update --remote
# 특정 서브모듈만
git submodule update --remote vendor/shared-lib
# 변경사항 커밋
git add .gitmodules vendor/shared-lib
git commit -m "chore: update submodules to latest"

방법 3: 특정 태그로 고정

cd vendor/shared-lib
git fetch --tags
git checkout v1.3.0
cd ../..
git add vendor/shared-lib
git commit -m "chore: pin shared-lib to v1.3.0"

팀원이 변경사항 받기

# 상위 저장소 업데이트
git pull
# 서브모듈도 업데이트
git submodule update --init --recursive
# 또는 한 번에
git pull --recurse-submodules

자동화 (Git 설정):

# pull 시 항상 서브모듈 업데이트
git config submodule.recurse true
# 이제 git pull만 해도 서브모듈 자동 업데이트
git pull

서브모듈 제거

완전 제거 절차:

# 1. 서브모듈 등록 해제
git submodule deinit -f vendor/shared-lib
# 2. Git에서 제거
git rm -f vendor/shared-lib
# 3. .git/modules 정리 (선택)
rm -rf .git/modules/vendor/shared-lib
# 4. 커밋
git commit -m "chore: remove shared-lib submodule"
git push

부분 제거 (나중에 다시 추가 가능):

# 등록만 해제 (디렉터리는 유지)
git submodule deinit vendor/shared-lib
# 다시 활성화
git submodule update --init vendor/shared-lib


고급: shallow·포크·대체 URL

CI에서 빠른 fetch

얕은 클론으로 시간 단축:

# 히스토리 1개만 가져오기
git submodule update --init --recursive --depth 1
# 또는 클론 시
git clone --recurse-submodules --shallow-submodules https://github.com/org/main-app.git

GitHub Actions 예제:

name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout with submodules
        uses: actions/checkout@v4
        with:
          submodules: recursive
          
      - name: Build
        run: |
          make build

GitLab CI 예제:

variables:
  GIT_SUBMODULE_STRATEGY: recursive
build:
  script:
    - git submodule sync
    - git submodule update --init --recursive --depth 1
    - make build

Docker 빌드:

FROM node:18
WORKDIR /app
# Git 설치 (서브모듈 클론에 필요)
RUN apt-get update && apt-get install -y git
# 저장소 클론
COPY . .
# 서브모듈 초기화
RUN git submodule update --init --recursive --depth 1
# 빌드
RUN npm install && npm run build
CMD ["npm", "start"]

URL 재매핑 (엔터프라이즈 미러)

로컬 설정:

# 특정 서브모듈 URL 변경
git config submodule.vendor/shared-lib.url https://git.internal.corp/org/shared-lib.git
# 모든 GitHub URL을 내부 미러로
git config --global url."https://git.internal.corp/".insteadOf "https://github.com/"

SSH ↔ HTTPS 전환:

# HTTPS를 SSH로
git config --global url."[email protected]:".insteadOf "https://github.com/"
# SSH를 HTTPS로 (CI에서 유용)
git config --global url."https://github.com/".insteadOf "[email protected]:"

서브모듈에서 작업하기

브랜치 생성 및 푸시:

# 서브모듈로 이동
cd vendor/shared-lib
# 현재 상태 확인 (detached HEAD일 가능성 높음)
git status
# HEAD detached at abc1234
# 브랜치 생성
git checkout -b feature/my-change
# 작업 후 커밋
git add .
git commit -m "feat: add new feature"
# 푸시 (upstream 설정)
git push -u origin feature/my-change
# PR 생성 후 머지되면 상위 저장소 업데이트
cd ../..
git submodule update --remote vendor/shared-lib
git add vendor/shared-lib
git commit -m "chore: update shared-lib with new feature"

서브모듈 포크 사용

원본 대신 포크 사용:

# 1. 원본 서브모듈 제거
git submodule deinit -f vendor/shared-lib
git rm -f vendor/shared-lib
# 2. 포크 추가
git submodule add https://github.com/myorg/shared-lib.git vendor/shared-lib
# 3. 원본을 upstream으로 추가
cd vendor/shared-lib
git remote add upstream https://github.com/org/shared-lib.git
git fetch upstream
# 4. 원본 변경사항 가져오기
git merge upstream/main
git push origin main
cd ../..
git add vendor/shared-lib
git commit -m "chore: switch to forked shared-lib"


비교: submodule vs subtree vs 패키지 vs 모노레포

상세 비교표

항목SubmoduleSubtree패키지 (npm/pip)모노레포
클론 복잡도추가 명령 필요단순 (git clone)단순 (패키지 설치)단순
버전 관리커밋 SHA커밋 히스토리시맨틱 버전단일 버전
업데이트수동 (명시적)수동 (git subtree pull)자동 (패키지 매니저)즉시 반영
독립 개발✅ 쉬움⚠️ 복잡✅ 쉬움❌ 어려움
권한 관리레포별 분리레포별 분리레지스트리 권한단일 레포
CI 복잡도높음중간낮음낮음
학습 곡선가파름가파름낮음중간

Submodule 예제

# 구조
main-app/
├── src/
├── vendor/
   ├── shared-lib/  (서브모듈)
   └── utils/       (서브모듈)
└── .gitmodules
# 클론
git clone --recurse-submodules https://github.com/org/main-app.git
# 업데이트
cd vendor/shared-lib
git pull origin main
cd ../..
git add vendor/shared-lib
git commit -m "update"

Subtree 예제

# 추가
git subtree add --prefix=vendor/shared-lib https://github.com/org/shared-lib.git main --squash
# 구조 (일반 디렉터리처럼 보임)
main-app/
├── src/
└── vendor/
    └── shared-lib/  (일반 디렉터리, 히스토리 포함)
# 업데이트
git subtree pull --prefix=vendor/shared-lib https://github.com/org/shared-lib.git main --squash
# 변경사항 업스트림에 푸시
git subtree push --prefix=vendor/shared-lib https://github.com/org/shared-lib.git feature-branch

패키지 예제 (npm)

# 구조
main-app/
├── src/
├── node_modules/
   └── @org/shared-lib/  (npm 패키지)
└── package.json
# 설치
npm install @org/shared-lib
# 업데이트
npm update @org/shared-lib
# package.json
{
  "dependencies": {
    "@org/shared-lib": "^1.2.3"
  }
}

모노레포 예제 (Turborepo)

# 구조
monorepo/
├── apps/
   ├── web/
   └── api/
├── packages/
   ├── shared-lib/
   └── utils/
└── turbo.json
# 클론 (한 번에 모든 것)
git clone https://github.com/org/monorepo.git
# 빌드 (의존성 자동 해결)
npm install
npm run build
# 변경사항 (원자적 커밋)
git add packages/shared-lib apps/web
git commit -m "feat: update shared-lib and use in web"

선택 가이드

Submodule 선택:

  • ✅ 독립적인 릴리스 주기
  • ✅ 레포별 권한 분리 필요
  • ✅ 여러 프로젝트에서 동일 라이브러리 사용
  • ❌ 팀이 Git 초보 패키지 선택:
  • ✅ 버전 관리 중요
  • ✅ 퍼블릭 또는 프라이빗 레지스트리 사용
  • ✅ 자동 업데이트 원함
  • ❌ 소스 코드 직접 수정 필요 모노레포 선택:
  • ✅ 여러 패키지 동시 수정 빈번
  • ✅ 원자적 변경 중요
  • ✅ 공통 CI/CD 파이프라인
  • ❌ 레포 크기 제한 (수십 GB)


실무 사례

1. 공유 프로토콜 버퍼 레포

구조:

api-gateway/
├── src/
└── proto/  (서브모듈 → shared-proto-repo)
user-service/
├── src/
└── proto/  (서브모듈 → shared-proto-repo)
order-service/
├── src/
└── proto/  (서브모듈 → shared-proto-repo)

워크플로우:

# 1. shared-proto-repo에서 스키마 변경
cd shared-proto-repo
git checkout -b feature/add-user-field
# user.proto 수정
git commit -m "feat: add email field to User"
git push origin feature/add-user-field
# 2. PR 머지 후 각 서비스 업데이트
cd api-gateway
git submodule update --remote proto
git add proto
git commit -m "chore: update proto to include email field"

2. 공유 UI 컴포넌트 라이브러리

구조:

web-app/
├── src/
└── components/  (서브모듈 → shared-components)
admin-app/
├── src/
└── components/  (서브모듈 → shared-components)

버전 고정 전략:

# 안정 버전 사용 (태그)
cd components
git fetch --tags
git checkout v2.1.0
cd ..
git add components
git commit -m "chore: pin components to v2.1.0"
# 최신 개발 버전 사용 (브랜치)
cd components
git checkout develop
git pull origin develop
cd ..
git add components
git commit -m "chore: update components to latest develop"

3. 문서 레포 분리

구조:

website/
├── src/
├── public/
└── docs/  (서브모듈 → documentation-repo)

자동 업데이트 (GitHub Actions):

name: Update Docs
on:
  repository_dispatch:
    types: [docs-updated]
jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: true
          token: ${{ secrets.PAT }}
      
      - name: Update docs submodule
        run: |
          git submodule update --remote docs
          git config user.name "GitHub Actions"
          git config user.email "[email protected]"
          git add docs
          git commit -m "chore: update docs" || exit 0
          git push

4. CI 캐싱 전략

GitHub Actions:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      
      - name: Cache submodules
        uses: actions/cache@v3
        with:
          path: |
            .git/modules
            vendor/shared-lib
          key: submodules-${{ hashFiles('.gitmodules') }}-${{ hashFiles('vendor/shared-lib/.git/HEAD') }}
      
      - name: Build
        run: make build

GitLab CI:

build:
  cache:
    key: 
      files:
        - .gitmodules
    paths:
      - .git/modules
      - vendor/
  
  script:
    - git submodule sync
    - git submodule update --init --recursive
    - make build


트러블슈팅

증상원인대응
빈 디렉터리submodule 미초기화submodule update --init --recursive
detached HEADsubmodule은 포인터만 따라감작업 시 브랜치 생성·명시적 push
권한 오류CI 토큰에 서브레포 접근 없음machine user·deploy key·조직 SSO
충돌 merge상위·하위 동시 변경하위에서 먼저 정리 후 상위 SHA 갱신
버전 불일치일부만 pull문서에 “항상 이 명령” 고정
“서브모듈 안에서 실수로 push” 방지: 하위 저장소에 branch protection을 두고, 상위 bump는 PR로만 받습니다.

마무리

Git submodule멀티레포 의존성을 Git 네이티브로 표현하는 도구이며, 성공하려면 클론·업데이트·CI를 레포 README에 한 줄이라도 표준 명령으로 박아 두는 것이 중요합니다. 원격 협업과 Actions 패턴은 Git push pull·원격 협업, 충돌 해결 사고례는 Git merge conflict 실전과 함께 보면 좋습니다.

내부 동작과 핵심 메커니즘

이 글의 주제는 「Git submodule 서브모듈 실무 | 추가·업데이트·CI·모노레포 대안」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.

처리 파이프라인(개념도)

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]

경계에서의 지연·실패(시퀀스 관점)

sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(프로세스·런타임·게이트웨이)
  participant D as 의존성(외부 API·DB·큐)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)

알고리즘·프로토콜·리소스 관점 체크포인트

  • 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.

프로덕션 운영 패턴

실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가
용량피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.


확장 예시: 엔드투엔드 미니 시나리오

「Git submodule 서브모듈 실무 | 추가·업데이트·CI·모노레포 대안」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.

의사코드 스케치(프레임워크 무관)

handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)        // 경계에서 거절
  authorize(validated, ctx)                  // 권한·테넌트
  result = domainCore(validated)             // 순수에 가까운 규칙
  persistOrEmit(result, idempotentKey)       // I/O: 멱등·재시도 정책
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성 불안정, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정이 로컬과 다름프로필·시크릿·기본값, 지역 리전단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Git submodule로 서브레포를 끌어오는 법, 초기화·업데이트·삭제, CI 캐시·흔한 오류, submodule 대신 모노레포를 쓰는 기준까지 정리합니다. 실전 예제와 코드로 개념부터 활용까지 정리합니다. Git… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Git, Submodule, 멀티레포, 의존성 관리, CI, 협업 등으로 검색하시면 이 글이 도움이 됩니다.