Skip to content
Published on

GitOps + Git Workflow 총정리 — 브랜치 전략, PR 규칙, 배포 파이프라인

Authors
  • Name
    Twitter

들어가며

"코드가 곧 인프라다." GitOps는 이 한 문장을 운영 현실로 만든 패러다임이다. Git 저장소를 Single Source of Truth(SSOT)로 삼고, 선언적 설정 파일의 변경 이력 하나하나가 곧 인프라와 애플리케이션의 상태가 된다. 더 이상 수동으로 kubectl apply를 실행하거나, 슬랙에서 "배포 해주세요"라고 요청할 필요가 없다.

하지만 GitOps를 도입한다고 해서 자동으로 모든 것이 정리되지는 않는다. 브랜치 전략이 엉망이면 배포 파이프라인도 엉망이 되고, PR 규칙이 없으면 코드 품질도 보장되지 않으며, 커밋 메시지가 중구난방이면 변경 이력 추적이 불가능해진다. GitOps의 진정한 힘은 Git 워크플로우 전체가 체계적으로 설계되었을 때 비로소 발휘된다.

이 글에서는 GitOps의 핵심 원칙부터 브랜치 전략 비교, PR 규칙 설계, 커밋 컨벤션, ArgoCD 파이프라인 구성, 환경별 배포 전략, 그리고 운영 체크리스트까지 팀에서 바로 적용할 수 있는 수준으로 총정리한다.


1. GitOps 핵심 원칙

GitOps는 Weaveworks가 2017년에 제안한 개념으로, 다음 4가지 원칙을 기반으로 한다.

1.1 선언적(Declarative)

모든 시스템 상태를 선언적으로 정의한다. "이 명령을 실행하라"가 아니라 **"이 상태가 되어야 한다"**를 기술한다.

# 선언적 상태 정의 예시 - Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api-server
          image: ghcr.io/myorg/api-server:v2.4.1
          ports:
            - containerPort: 8080

1.2 버전 관리(Versioned and Immutable)

모든 변경은 Git에 커밋으로 기록된다. 누가, 언제, 왜, 무엇을 변경했는지 완벽한 감사 로그(Audit Log)가 자동으로 남는다. 롤백은 git revert로 끝난다.

1.3 자동 적용(Pulled Automatically)

승인된 변경 사항은 자동으로 시스템에 적용된다. Push 방식(CI가 직접 배포)이 아닌 **Pull 방식(에이전트가 Git을 감시하고 변경을 감지하여 적용)**을 사용한다.

1.4 자가 치유(Continuously Reconciled)

실제 시스템 상태가 Git에 정의된 상태와 다르면 자동으로 교정한다. 누군가 kubectl로 직접 변경해도 GitOps 에이전트가 원래 상태로 되돌린다.

원칙핵심 질문실패 시 증상
선언적원하는 상태가 코드로 정의되어 있는가?설정 드리프트, 재현 불가
버전 관리모든 변경이 Git에 기록되는가?변경 추적 불가, 책임 소재 불명확
자동 적용변경이 수동 개입 없이 적용되는가?배포 지연, 휴먼 에러
자가 치유드리프트가 자동으로 교정되는가?환경 불일치, 장애 확산

2. 브랜치 전략 비교

브랜치 전략은 팀의 크기, 릴리스 주기, 제품 특성에 따라 선택해야 한다. 세 가지 주요 전략을 비교한다.

2.1 Git Flow

Vincent Driessen이 2010년에 제안한 클래식 모델이다. main, develop, feature/*, release/*, hotfix/* 5가지 브랜치 타입을 사용한다.

main ────●─────────────●──────●── (프로덕션)
          \           / \    /
release    \    ●────●   \  /
            \  /          \/
develop ─────●──●──●──●───●──●── (통합)
              \  \      /
feature        ●──●────●

적합한 경우: 정기 릴리스 주기가 있는 팀, 모바일 앱, 패키지 라이브러리

2.2 GitHub Flow

main 브랜치와 feature 브랜치만 사용하는 단순한 모델이다. feature 브랜치에서 작업하고 PR을 통해 main에 병합한다.

main ────●──●──●──●──●── (항상 배포 가능)
          \    /  \  /
feature    ●──●    ●──●

적합한 경우: 지속적 배포(CD)를 실천하는 SaaS 팀, 소규모 팀

2.3 Trunk-Based Development (TBD)

모든 개발자가 trunk(main)에 직접 커밋하거나, 매우 짧은 수명의 브랜치(1일 이내)를 사용한다.

main ────●──●──●──●──●──●──●── (모든 커밋이 배포 후보)
          \/ \/ \/
short     ●  ●    (1일 이내 병합)

적합한 경우: 대규모 엔지니어링 조직(Google, Meta), Feature Flag 기반 릴리스

비교 표

항목Git FlowGitHub FlowTrunk-Based
브랜치 수5종 이상2종1~2종
병합 빈도주 1~2회일 1~3회일 5~10회+
릴리스 주기격주/월간수시수시
복잡도높음낮음낮음
Feature Flag 필요아니오선택필수
CI 요구 수준중간높음매우 높음
롤백 용이성release 태그커밋 단위커밋/플래그
GitOps 궁합보통좋음매우 좋음

권장: GitOps 환경에서는 GitHub Flow 또는 Trunk-Based Development를 추천한다. Git Flow의 release 브랜치는 GitOps의 자동 동기화 패턴과 마찰이 크다.

3. PR(Pull Request) 규칙

PR은 단순한 코드 리뷰 도구가 아니다. 변경의 의도를 문서화하고, 품질 게이트를 통과시키며, 팀 지식을 공유하는 핵심 프로세스다.

3.1 PR 템플릿

.github/PULL_REQUEST_TEMPLATE.md 파일을 저장소 루트에 추가한다.

## 변경 사항

<!-- 무엇을 왜 변경했는지 간결하게 설명 -->

## 변경 유형

- [ ] 기능 추가 (feature)
- [ ] 버그 수정 (bugfix)
- [ ] 리팩토링 (refactor)
- [ ] 인프라/설정 변경 (infra)
- [ ] 문서 업데이트 (docs)

## 테스트

- [ ] 단위 테스트 추가/수정
- [ ] 통합 테스트 통과
- [ ] 로컬 환경에서 수동 검증

## 체크리스트

- [ ] 커밋 메시지가 Conventional Commits 형식을 따르는가?
- [ ] 불필요한 파일(로그, 임시 파일)이 포함되지 않았는가?
- [ ] 기존 API와 하위 호환되는가?
- [ ] 보안 민감 정보(시크릿, 키)가 포함되지 않았는가?

## 관련 이슈

Closes #

## 스크린샷 (UI 변경 시)

3.2 리뷰 체크리스트

리뷰어가 확인해야 할 핵심 항목:

  1. 기능 정확성 — 변경이 의도한 대로 동작하는가?
  2. 에지 케이스 — 빈 값, 대용량, 동시 요청 등 예외 상황을 처리하는가?
  3. 보안 — SQL Injection, XSS, 시크릿 노출 위험이 없는가?
  4. 성능 — N+1 쿼리, 불필요한 연산, 메모리 누수가 없는가?
  5. 테스트 커버리지 — 변경된 로직에 대한 테스트가 있는가?
  6. 명명 규칙 — 변수, 함수, 클래스 이름이 의미를 잘 전달하는가?

3.3 Merge 전략

전략커밋 이력적합한 상황
Squash Mergefeature의 모든 커밋을 하나로 합침main 이력을 깔끔하게 유지하고 싶을 때
Rebase Mergefeature 커밋을 main 위에 재배치커밋 단위가 논리적으로 잘 나뉘어 있을 때
Merge Commitmerge 커밋을 생성feature 브랜치의 컨텍스트를 보존하고 싶을 때

팀 권장 설정: Squash Merge를 기본으로 설정하고, main 브랜치에 직접 push를 금지한다. GitHub Settings > Branches > Branch protection rules에서 "Require a pull request before merging"을 활성화한다.

4. 커밋 컨벤션

4.1 Conventional Commits 형식

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

type 목록:

type설명예시
feat새로운 기능feat(auth): 소셜 로그인 추가
fix버그 수정fix(api): 페이지네이션 오프셋 오류 수정
docs문서 변경docs: API 스펙 업데이트
style포맷팅, 세미콜론 등style: ESLint 규칙 적용
refactor리팩토링refactor(db): 쿼리 최적화
test테스트 추가/수정test: 회원가입 E2E 테스트 추가
chore빌드, 도구 설정chore: Node.js 20으로 업그레이드
ciCI 설정 변경ci: GitHub Actions 캐시 최적화
perf성능 개선perf(api): 응답 캐싱 적용

4.2 commitlint 설정

# 설치
npm install --save-dev @commitlint/cli @commitlint/config-conventional

# commitlint.config.js
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

# husky로 Git hook 연동
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg

commitlint.config.js에서 팀 규칙을 커스터마이즈할 수 있다.

// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'ci', 'perf', 'revert'],
    ],
    'subject-max-length': [2, 'always', 72],
    'body-max-line-length': [2, 'always', 100],
  },
}

5. ArgoCD를 활용한 GitOps 파이프라인

5.1 ArgoCD 아키텍처

ArgoCD는 Kubernetes-native한 GitOps 배포 도구로, 다음 컴포넌트로 구성된다.

  • API Server — Web UI, CLI, gRPC/REST API 제공
  • Repository Server — Git 저장소에서 매니페스트를 렌더링
  • Application Controller — 실제 상태와 목표 상태를 지속적으로 비교하고 동기화

5.2 Application 정의

# argocd/applications/api-server.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-server
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: apps/api-server/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true # Git에서 삭제된 리소스 자동 제거
      selfHeal: true # 드리프트 자동 교정
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

5.3 Sync 정책 옵션

옵션설명권장
automated.pruneGit에서 삭제된 리소스 자동 제거프로덕션: true
automated.selfHeal수동 변경 시 자동 교정프로덕션: true
syncOptions.CreateNamespace네임스페이스 자동 생성개발: true, 프로덕션: false
retry.limit동기화 실패 시 재시도 횟수3~5

5.4 Git 저장소 디렉토리 구조

k8s-manifests/
├── apps/
│   ├── api-server/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   ├── hpa.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       │   ├── kustomization.yaml
│   │       │   └── patches/
│   │       │       └── replicas.yaml
│   │       ├── staging/
│   │       │   ├── kustomization.yaml
│   │       │   └── patches/
│   │       │       └── replicas.yaml
│   │       └── production/
│   │           ├── kustomization.yaml
│   │           └── patches/
│   │               ├── replicas.yaml
│   │               └── resources.yaml
│   └── web-frontend/
│       ├── base/
│       └── overlays/
├── argocd/
│   ├── applications/
│   │   ├── api-server.yaml
│   │   └── web-frontend.yaml
│   └── projects/
│       └── default.yaml
└── README.md

6. CI/CD 파이프라인 설계

6.1 전체 흐름

graph LR
    A[개발자 Push] --> B[GitHub Actions CI]
    B --> C{테스트 통과?}
    C -->|Yes| D[컨테이너 이미지 빌드]
    C -->|No| E[실패 알림]
    D --> F[이미지 Push - GHCR]
    F --> G[매니페스트 저장소 PR 생성]
    G --> H[PR 자동 병합 / 수동 승인]
    H --> I[ArgoCD 감지]
    I --> J[Kubernetes 동기화]
    J --> K{Health Check}
    K -->|Healthy| L[배포 완료]
    K -->|Degraded| M[자동 롤백]

6.2 GitHub Actions 워크플로우

# .github/workflows/ci-cd.yaml
name: CI/CD Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Unit tests
        run: npm run test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build-and-push:
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image-tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  update-manifest:
    needs: build-and-push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout manifest repo
        uses: actions/checkout@v4
        with:
          repository: myorg/k8s-manifests
          token: ${{ secrets.MANIFEST_REPO_TOKEN }}

      - name: Update image tag
        run: |
          cd apps/api-server/base
          kustomize edit set image \
            ghcr.io/myorg/api-server=ghcr.io/myorg/api-server:${{ needs.build-and-push.outputs.image-tag }}

      - name: Create PR
        uses: peter-evans/create-pull-request@v6
        with:
          token: ${{ secrets.MANIFEST_REPO_TOKEN }}
          commit-message: 'chore: update api-server image to ${{ needs.build-and-push.outputs.image-tag }}'
          title: 'deploy: api-server ${{ needs.build-and-push.outputs.image-tag }}'
          body: |
            자동 생성된 배포 PR입니다.
            - 소스 커밋: ${{ github.sha }}
            - 이미지 태그: ${{ needs.build-and-push.outputs.image-tag }}
          branch: deploy/api-server-${{ needs.build-and-push.outputs.image-tag }}
          base: main

7. 환경별 배포 전략

7.1 환경 분리 원칙

환경목적동기화 방식승인 필요
dev기능 개발, 통합 테스트자동 (Auto Sync)아니오
stagingQA, 성능 테스트, UAT자동 (Auto Sync)아니오
production실 서비스수동 (Manual Sync)

7.2 Kustomize Overlay 구성

base/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - hpa.yaml
commonLabels:
  app: api-server

overlays/dev/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
namePrefix: dev-
namespace: dev
patches:
  - path: patches/replicas.yaml
images:
  - name: ghcr.io/myorg/api-server
    newTag: latest

overlays/dev/patches/replicas.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: api-server
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 200m
              memory: 256Mi

overlays/production/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
namespace: production
patches:
  - path: patches/replicas.yaml
  - path: patches/resources.yaml
images:
  - name: ghcr.io/myorg/api-server
    newTag: v2.4.1 # 프로덕션은 명시적 태그 사용

overlays/production/patches/replicas.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 5
  template:
    spec:
      containers:
        - name: api-server
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: '1'
              memory: 1Gi

7.3 Helm Values per Environment

Helm을 사용하는 경우 환경별 values 파일로 분리한다.

helm-charts/
├── api-server/
│   ├── Chart.yaml
│   ├── templates/
│   ├── values.yaml            # 기본값
│   ├── values-dev.yaml        # dev 환경
│   ├── values-staging.yaml    # staging 환경
│   └── values-prod.yaml       # production 환경
# values-prod.yaml
replicaCount: 5
image:
  repository: ghcr.io/myorg/api-server
  tag: v2.4.1
resources:
  requests:
    cpu: 500m
    memory: 512Mi
  limits:
    cpu: '1'
    memory: 1Gi
autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 20
  targetCPUUtilization: 70
ingress:
  enabled: true
  hosts:
    - host: api.myservice.com
      paths:
        - path: /
          pathType: Prefix

8. 운영 체크리스트

팀에서 GitOps 워크플로우를 운영할 때 반드시 확인해야 할 항목들이다.

카테고리항목상태
브랜치 보호main 브랜치 직접 push 금지[ ]
브랜치 보호PR 없이 merge 금지[ ]
브랜치 보호Force push 금지[ ]
코드 리뷰최소 1인 리뷰 승인 필수[ ]
코드 리뷰CODEOWNERS 파일 설정[ ]
코드 리뷰Stale review 자동 해제 설정[ ]
CI 게이트PR에 CI 통과 필수 설정 (required status checks)[ ]
CI 게이트테스트 커버리지 임계값 설정[ ]
CI 게이트린트/포맷 검사 통과 필수[ ]
커밋commitlint + husky 설정[ ]
커밋Conventional Commits 가이드 문서화[ ]
보안시크릿 스캐닝 활성화 (GitHub Secret Scanning)[ ]
보안.gitignore에 민감 파일 패턴 추가[ ]
보안Dependabot / Renovate 활성화[ ]
ArgoCDApplication 리소스 Git 관리[ ]
ArgoCDSync 알림 연동 (Slack/Teams)[ ]
ArgoCDRBAC 설정 (팀별 프로젝트 권한)[ ]
ArgoCD프로덕션 수동 Sync 설정[ ]

CODEOWNERS 파일 예시:

# .github/CODEOWNERS
# 기본 소유자
*                       @myorg/platform-team

# 인프라 매니페스트
/apps/                  @myorg/sre-team
/argocd/                @myorg/sre-team

# 프론트엔드
/apps/web-frontend/     @myorg/frontend-team

# 백엔드 API
/apps/api-server/       @myorg/backend-team

9. 흔한 실수

GitOps와 Git 워크플로우를 도입할 때 팀들이 자주 범하는 실수 목록이다.

  1. 앱 코드와 매니페스트를 같은 저장소에 관리 — 앱 CI가 돌 때마다 ArgoCD 동기화가 트리거된다. 매니페스트는 반드시 별도 저장소로 분리하라.

  2. 프로덕션에 Auto Sync + Auto Prune 설정 — 실수로 merge된 삭제가 즉시 프로덕션에 반영된다. 프로덕션은 Manual Sync를 사용하거나 최소한 selfHeal만 활성화하라.

  3. latest 태그를 프로덕션에 사용 — 어떤 버전이 배포되었는지 추적이 불가능하다. 반드시 immutable 태그(SHA, 시맨틱 버전)를 사용하라.

  4. PR 리뷰 없이 자동 병합 — 매니페스트 변경 PR도 반드시 리뷰를 거쳐야 한다. 자동 병합은 dev 환경에만 허용하라.

  5. RBAC 설정 없이 ArgoCD 운영 — 모든 팀원이 모든 애플리케이션을 Sync/Delete할 수 있으면 사고가 난다. Project + RBAC으로 권한을 최소화하라.

  6. 커밋 메시지에 의미 없는 내용 작성fix, update, test 같은 커밋 메시지는 3개월 후 아무도 이해할 수 없다. Conventional Commits를 강제하라.

  7. Feature 브랜치를 오래 유지 — 3일 이상 살아있는 feature 브랜치는 merge conflict의 온상이다. 작은 PR로 자주 병합하라.

  8. Secret을 Git에 커밋 — 한 번이라도 Git에 올라간 시크릿은 히스토리에 영원히 남는다. Sealed Secrets, External Secrets Operator, 또는 Vault를 사용하라.

  9. 환경별 차이를 하드코딩 — dev/staging/prod의 차이를 별도 YAML 파일로 만들지 말고, Kustomize overlays나 Helm values로 관리하라.

  10. 롤백 절차를 문서화하지 않음 — 장애 상황에서 "git revert 후 ArgoCD sync"라는 절차를 모든 팀원이 알고 있어야 한다. Runbook을 만들어라.

10. 요약

영역핵심 포인트
GitOps 원칙선언적, 버전 관리, 자동 적용, 자가 치유 — 4가지를 모두 만족해야 진정한 GitOps
브랜치 전략GitOps에는 GitHub Flow 또는 Trunk-Based가 최적. 팀 규모와 릴리스 주기에 맞춰 선택
PR 규칙템플릿 + 체크리스트 + 최소 1인 리뷰 + CI 게이트 = 품질의 마지막 방어선
커밋 컨벤션Conventional Commits + commitlint으로 기계적 강제. 사람에게 의지하지 마라
ArgoCDApplication YAML, syncPolicy, 디렉토리 구조를 표준화하고 Git으로 관리
CI/CDApp Repo → CI(빌드/테스트/푸시) → Manifest Repo PR → ArgoCD Sync의 흐름을 자동화
환경 분리Kustomize overlays 또는 Helm values-env.yaml로 환경별 차이를 관리
운영체크리스트를 팀 온보딩 문서에 포함시키고 정기적으로 점검

GitOps는 도구가 아니라 문화다. ArgoCD를 설치하는 것이 끝이 아니라, 팀 전체가 Git 중심의 워크플로우를 체화하고 규칙을 지키는 것이 핵심이다. 이 글에서 다룬 내용을 하나씩 적용하면서 팀에 맞는 워크플로우를 만들어가길 바란다.

퀴즈

Q1: GitOps의 4가지 핵심 원칙은 무엇인가? 선언적(Declarative), 버전 관리(Versioned and Immutable), 자동 적용(Pulled Automatically), 자가 치유(Continuously Reconciled)이다. 이 4가지 원칙이 모두 충족되어야 진정한 GitOps라 할 수 있으며, 하나라도 빠지면 단순한 "Git 기반 배포"에 불과하다.

Q2: Git Flow, GitHub Flow, Trunk-Based Development 중 GitOps 환경에 가장 적합한 브랜치 전략은?

GitHub Flow 또는 Trunk-Based Development가 GitOps 환경에 가장 적합하다. Git Flow의 release 브랜치는 GitOps의 자동 동기화 패턴과 마찰이 크며, 간결한 브랜치 구조가 선언적 상태 관리와 더 잘 어울린다. 특히 Trunk-Based Development는 Feature Flag와 함께 사용하면 가장 빠른 배포 주기를 달성할 수 있다.

Q3: Squash Merge, Rebase Merge, Merge Commit의 차이점과 각각 적합한 상황은? Squash Merge는 feature 브랜치의 모든 커밋을 하나로 합쳐 main에 깔끔한 이력을 유지하고 싶을 때 적합하다. Rebase Merge는 커밋 단위가 논리적으로 잘 나뉘어 있어 각 커밋을 보존하고 싶을 때 사용한다. Merge Commit은 feature 브랜치의 컨텍스트(언제 시작되고 병합되었는지)를 보존하고 싶을 때 적합하다. 대부분의 팀에서는 Squash Merge를 기본값으로 권장한다.

Q4: ArgoCD의 selfHeal 옵션은 무엇이며 왜 중요한가? selfHeal은 실제 Kubernetes 클러스터의 상태가 Git에 정의된 상태와 다를 때 자동으로 교정하는 기능이다. 예를 들어 누군가 kubectl로 직접 Deployment의 replicas를 변경하면, ArgoCD가 이를 감지하고 Git에 정의된 값으로 되돌린다. 이는 GitOps의 "자가 치유" 원칙을 구현하며, 설정 드리프트를 방지하고 Git을 유일한 진실의 원천(SSOT)으로 유지하는 데 핵심적인 역할을 한다.

Q5: 앱 코드 저장소와 매니페스트 저장소를 분리해야 하는 이유는?앱 코드와 매니페스트를 같은 저장소에 두면, 앱 CI가 실행될 때마다 ArgoCD가 불필요한 동기화를 트리거한다. 또한 앱 개발자와 인프라 담당자의 접근 권한을 분리하기 어렵고, 매니페스트 변경에 대한 독립적인 리뷰 프로세스를 운영하기 어렵다. 저장소를 분리하면 관심사 분리, 권한 관리, 배포 추적이 훨씬 명확해진다.

Q6: Conventional Commits에서 feat, fix, chore의 의미는 무엇인가? feat는 사용자에게 제공되는 새로운 기능을 추가할 때, fix는 기존 기능의 버그를 수정할 때, chore는 사용자 기능에 직접적인 영향을 주지 않는 빌드 시스템이나 도구 설정 변경 시에 사용한다. 이 규칙은 commitlint로 자동 검증할 수 있으며, semantic-release와 연동하면 커밋 타입에 따라 자동으로 버전을 올릴 수 있다.

Q7: Kustomize overlays로 환경별 배포를 관리하는 방법은? base 디렉토리에 공통 매니페스트를 두고, overlays/{dev, staging, production} 디렉토리에 환경별 차이를 patches로 정의한다. 예를 들어 base에는 Deployment, Service, HPA 등 공통 리소스를 두고, dev overlay에서는 replicas를 1로, production overlay에서는 5로 패치한다. ArgoCD Application의 source.path를 환경별 overlay 디렉토리로 지정하면 환경별 배포가 완성된다.

Q8: GitOps 환경에서 Secret을 안전하게 관리하는 방법은? Secret을 절대 Git에 평문으로 커밋하면 안 된다. 대안으로 Sealed Secrets(Bitnami)는 공개키로 암호화하여 Git에 저장하고 클러스터 내에서만 복호화한다. External Secrets Operator는 AWS Secrets Manager, HashiCorp Vault 등 외부 시크릿 관리 서비스와 연동하여 런타임에 Secret을 주입한다. SOPS(Mozilla)는 KMS 키로 YAML 파일 값을 암호화하여 Git에 저장할 수 있게 한다. 팀 규모와 인프라에 따라 적절한 도구를 선택하면 된다.