TestForge Blog
← 전체 포스트

Kubernetes 개발·운영 실전 6편 — 멀티 환경 배포 전략 (Kustomize + ArgoCD)

Kustomize overlay로 dev/staging/prod 환경 설정을 분리하고 ArgoCD App of Apps 패턴으로 전체 클러스터를 GitOps로 관리하는 실전 가이드.

TestForge Team ·

멀티 환경 관리의 핵심 문제

dev, staging, prod 세 환경에서 같은 애플리케이션을 운영합니다. 환경마다 다른 것들이 있습니다.

  • 이미지 태그 (dev: latest, prod: 1.2.3)
  • replicas (dev: 1, prod: 3)
  • 도메인 (dev.example.com vs example.com)
  • 리소스 요청/제한
  • 외부 서비스 연결 정보 (DB URL, Redis URL)

이걸 파일 복사로 관리하면 한 환경에서 고친 것을 다른 환경에 반영하는 것을 잊어 드리프트가 생깁니다. Kustomize는 공통 기반(base) + 환경별 오버라이드(overlay) 구조로 이 문제를 해결합니다.


1. Kustomize 디렉터리 구조

k8s/
├── base/                   # 공통 기반 매니페스트
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── patch-deployment.yaml
    ├── staging/
    │   ├── kustomization.yaml
    │   └── patch-deployment.yaml
    └── prod/
        ├── kustomization.yaml
        ├── patch-deployment.yaml
        └── patch-hpa.yaml

2. base 구성

base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

commonLabels:
  app: my-app
  managed-by: kustomize

base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: my-registry/my-app:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          envFrom:
            - configMapRef:
                name: my-app-config

3. overlay 구성

overlays/dev/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: development

bases:
  - ../../base

patches:
  - path: patch-deployment.yaml

images:
  - name: my-registry/my-app
    newTag: dev-latest

configMapGenerator:
  - name: my-app-config
    behavior: merge
    literals:
      - LOG_LEVEL=debug
      - DATABASE_URL=postgres://dev-db:5432/myapp

overlays/prod/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

bases:
  - ../../base

patches:
  - path: patch-deployment.yaml
  - path: patch-hpa.yaml

images:
  - name: my-registry/my-app
    newTag: "1.2.3"

configMapGenerator:
  - name: my-app-config
    behavior: merge
    literals:
      - LOG_LEVEL=info
      - DATABASE_URL=postgres://prod-db:5432/myapp

overlays/prod/patch-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: my-app
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 2Gi
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 10

overlays/prod/patch-hpa.yaml

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

배포 및 미리보기

# 렌더링 결과 확인 (배포 없음)
kubectl kustomize overlays/prod

# 배포
kubectl apply -k overlays/prod

# dev 배포
kubectl apply -k overlays/dev

4. ArgoCD App of Apps 패턴

단일 ArgoCD Application으로 전체 클러스터의 모든 앱을 관리합니다.

Git 저장소 구조

gitops-repo/
├── apps/                    # App of Apps 루트
│   ├── kustomization.yaml
│   ├── my-app.yaml
│   ├── api-gateway.yaml
│   └── monitoring.yaml
└── manifests/
    ├── my-app/
    │   └── overlays/prod/
    ├── api-gateway/
    │   └── overlays/prod/
    └── monitoring/
        └── overlays/prod/

루트 Application (App of Apps)

# bootstrap/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-repo
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

개별 Application 정의

# apps/my-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: production
  source:
    repoURL: https://github.com/myorg/gitops-repo
    targetRevision: main
    path: manifests/my-app/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true      # Git에서 삭제된 리소스 클러스터에서도 삭제
      selfHeal: true   # 수동 변경 감지 시 자동 복구
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas  # HPA가 관리하는 replicas 무시

5. CI/CD 파이프라인 연결

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and push image
        run: |
          IMAGE_TAG=${GITHUB_SHA::8}
          docker build -t $REGISTRY/my-app:$IMAGE_TAG .
          docker push $REGISTRY/my-app:$IMAGE_TAG
          echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV

      - name: Update image tag in gitops repo
        run: |
          git clone https://github.com/myorg/gitops-repo
          cd gitops-repo

          # Kustomize로 이미지 태그 업데이트
          cd manifests/my-app/overlays/prod
          kustomize edit set image my-registry/my-app:$IMAGE_TAG

          git config user.email "ci@github.com"
          git config user.name "GitHub Actions"
          git add .
          git commit -m "chore: update my-app image to $IMAGE_TAG"
          git push

ArgoCD가 Git 변경을 감지해 자동으로 클러스터에 반영합니다.


6. 환경 프로모션 전략

[개발자 PR]
    ↓ merge to main
[CI: 이미지 빌드 + dev 태그 업데이트]
    ↓ ArgoCD auto sync
[dev 환경 배포]
    ↓ 테스트 통과 후 수동 프로모션
[staging 태그 업데이트 PR]
    ↓ merge
[staging 환경 배포]
    ↓ QA 승인
[prod 태그 업데이트 PR + 리뷰]
    ↓ merge
[prod 환경 배포]
# 프로모션 스크립트 — staging → prod
#!/bin/bash
CURRENT_TAG=$(kustomize cfg grep --path manifests/my-app/overlays/staging | grep newTag | awk '{print $2}')

cd manifests/my-app/overlays/prod
kustomize edit set image my-registry/my-app:$CURRENT_TAG

git add . && git commit -m "chore: promote my-app $CURRENT_TAG to prod"
git push origin -u promote/my-app-$CURRENT_TAG

7. 드리프트 감지

# ArgoCD CLI로 동기화 상태 확인
argocd app list
argocd app diff my-app

# 클러스터와 Git 상태 비교
argocd app sync my-app --dry-run

# 수동 변경 감지 및 복구
argocd app sync my-app --force

시리즈 마무리 — 전체 아키텍처

이 시리즈에서 구성한 Kubernetes 개발·운영 체계를 정리합니다.

[개발자 로컬]
  Kind 클러스터 + Skaffold/Tilt → 빠른 개발 루프

[코드 구조]
  Namespace + RBAC → 팀·환경 격리
  WorkloadType 선택 → 서비스 특성에 맞는 배포 단위

[네트워크]
  ClusterIP + Ingress + NetworkPolicy → 명시적 트래픽 제어

[배포 관리]
  Helm Chart → 템플릿화 + 버전 관리
  Kustomize overlay → 환경별 설정 분리

[GitOps]
  ArgoCD App of Apps → Git이 Single Source of Truth
  CI/CD 이미지 태그 업데이트 → 자동 배포 파이프라인

각 편에서 다룬 내용이 하나의 흐름으로 연결됩니다. 로컬에서 개발한 코드가 Git을 거쳐 프로덕션까지 일관된 방식으로 배포되는 구조입니다.