Skip to content
Published on

GitOps와 ArgoCD 기반 지속적 배포 파이프라인 구축 가이드

Authors
  • Name
    Twitter
GitOps와 ArgoCD 기반 지속적 배포 파이프라인

들어가며

쿠버네티스 기반 인프라를 운영하다 보면 **"누가, 언제, 무엇을 배포했는가?"**라는 질문에 명확하게 답하기 어려운 상황을 자주 만나게 된다. kubectl apply를 직접 실행하거나 CI 파이프라인에서 배포 스크립트를 호출하는 전통적인 방식은 감사 추적이 불완전하고, 장애 시 롤백이 번거로우며, 여러 클러스터에 일관된 배포를 보장하기 힘들다.

GitOps는 이러한 문제를 해결하기 위해 Git 저장소를 단일 진실 공급원(Single Source of Truth)으로 삼고, 선언적 인프라 정의와 자동 동기화를 통해 배포 프로세스를 혁신하는 패러다임이다. 그리고 ArgoCD는 CNCF Graduated 프로젝트로서, 2025년 CNCF End User Survey 기준 GitOps 도구 시장의 약 60%를 차지하는 사실상의 표준이 되었다. 2025년 4월에 발표된 v3.0을 시작으로, 2026년 2월 GA된 v3.3에 이르기까지 OCI 레지스트리 네이티브 지원, Source Hydrator, CLI 플러그인 등 핵심 기능이 대거 추가되면서 성숙도가 한층 높아졌다.

이 글에서는 GitOps의 핵심 원칙부터 ArgoCD 설치, Application 정의, Kustomize/Helm 통합, 멀티 클러스터 전략, CI/CD 파이프라인 통합, 그리고 실무에서 마주치는 장애 사례와 롤백 절차까지 실전 중심으로 다룬다.


GitOps 핵심 원칙과 아키텍처

GitOps의 4가지 원칙

GitOps는 OpenGitOps 프로젝트에서 정의한 4가지 핵심 원칙을 따른다.

  1. 선언적(Declarative): 시스템의 원하는 상태를 선언적으로 정의한다. 쿠버네티스 매니페스트, Helm 차트, Kustomize 오버레이 등이 이에 해당한다.
  2. 버전 관리(Versioned and Immutable): Git에 저장된 원하는 상태는 버전 관리되고 불변이다. 모든 변경은 커밋으로 추적된다.
  3. 자동 적용(Pulled Automatically): 승인된 변경사항은 자동으로 클러스터에 적용된다. Push 모델이 아닌 Pull 모델이 핵심이다.
  4. 지속적 조정(Continuously Reconciled): 에이전트가 실제 상태와 원하는 상태의 차이를 지속적으로 감시하고 자동으로 조정한다.

Push 모델 vs Pull 모델

전통적인 CI/CD 파이프라인은 Push 모델로, CI 서버가 클러스터에 직접 배포 명령을 푸시한다. 이 방식은 CI 서버에 클러스터 접근 권한이 필요하고, 보안 취약점이 될 수 있다.

Push 모델 (전통적 CI/CD)
========================
[개발자] --> [Git Push] --> [CI 서버] --kubectl apply--> [K8s 클러스터]
                                  |
                          (클러스터 자격증명 보유)

Pull 모델 (GitOps)
========================
[개발자] --> [Git Push] --> [Git 저장소]
                                  |
                          [ArgoCD Agent] <--polling/webhook-- [Git 저장소]
                                  |
                          --reconcile--> [K8s 클러스터]

Pull 모델에서는 클러스터 내부의 에이전트(ArgoCD)가 Git 저장소를 주기적으로 폴링하거나 웹훅을 통해 변경을 감지한 후, 클러스터 상태를 Git의 선언적 정의와 일치시킨다. CI 서버에 클러스터 자격증명을 노출하지 않아도 되므로 보안이 크게 향상된다.

저장소 전략: 모노레포 vs 멀티레포

GitOps 구현 시 저장소 전략도 중요한 아키텍처 결정이다.

전략장점단점적합한 경우
모노레포변경사항 한눈에 파악, 의존성 관리 용이규모 커지면 관리 복잡, 권한 분리 어려움소규모 팀, 밀접한 서비스
멀티레포 (앱/인프라 분리)관심사 분리 명확, 세밀한 접근 제어저장소 간 동기화 필요중대규모 조직, 보안 중시
환경별 브랜치직관적 환경 구분브랜치 간 드리프트, 머지 충돌비권장 (안티패턴)
환경별 디렉토리단일 저장소로 환경 관리, 비교 용이모든 환경이 한 곳에가장 권장되는 패턴

실무에서는 애플리케이션 소스 코드 저장소배포 매니페스트 저장소를 분리하고, 매니페스트 저장소 내에서 환경별 디렉토리 구조를 사용하는 패턴이 가장 널리 채택된다.

gitops-manifests/
├── apps/
│   ├── frontend/
│   │   ├── base/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── overlays/
│   │       ├── dev/
│   │       │   └── kustomization.yaml
│   │       ├── staging/
│   │       │   └── kustomization.yaml
│   │       └── prod/
│   │           └── kustomization.yaml
│   └── backend/
│       ├── base/
│       └── overlays/
├── infrastructure/
│   ├── cert-manager/
│   ├── ingress-nginx/
│   └── monitoring/
└── clusters/
    ├── dev-cluster/
    ├── staging-cluster/
    └── prod-cluster/

ArgoCD 설치와 기본 구성

Helm을 이용한 설치

ArgoCD 공식 Helm 차트를 사용하면 프로덕션 수준의 설치를 쉽게 구성할 수 있다. 2026년 3월 현재 Helm 차트 버전은 9.4.x이며, ArgoCD v3.3 계열을 설치한다.

# ArgoCD Helm 저장소 추가
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# 네임스페이스 생성
kubectl create namespace argocd

# HA 모드로 ArgoCD 설치 (프로덕션 권장)
helm install argocd argo/argo-cd \
  --namespace argocd \
  --set redis-ha.enabled=true \
  --set controller.replicas=2 \
  --set server.replicas=2 \
  --set repoServer.replicas=2 \
  --set applicationSet.replicas=2 \
  --set server.service.type=LoadBalancer \
  --wait

# 초기 admin 패스워드 확인
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d && echo

# ArgoCD CLI 로그인
argocd login <ARGOCD_SERVER> --username admin --password <PASSWORD>

# admin 패스워드 변경 (필수)
argocd account update-password

values.yaml 커스터마이징

프로덕션 환경에서는 values.yaml 파일을 통해 세부 설정을 관리하는 것이 좋다.

# argocd-values.yaml
global:
  image:
    tag: 'v3.3.3'

server:
  replicas: 2
  ingress:
    enabled: true
    ingressClassName: nginx
    hosts:
      - argocd.example.com
    tls:
      - secretName: argocd-tls
        hosts:
          - argocd.example.com

  config:
    # OIDC 연동 (SSO)
    url: https://argocd.example.com
    oidc.config: |
      name: Keycloak
      issuer: https://keycloak.example.com/realms/devops
      clientID: argocd
      clientSecret: $oidc.keycloak.clientSecret
      requestedScopes:
        - openid
        - profile
        - email
        - groups

  rbacConfig:
    policy.csv: |
      p, role:developer, applications, get, */*, allow
      p, role:developer, applications, sync, */dev-*, allow
      p, role:admin, applications, *, */*, allow
      g, dev-team, role:developer
      g, platform-team, role:admin
    policy.default: role:readonly

controller:
  replicas: 2
  resources:
    requests:
      cpu: 500m
      memory: 1Gi
    limits:
      cpu: 2000m
      memory: 4Gi

repoServer:
  replicas: 2
  resources:
    requests:
      cpu: 250m
      memory: 512Mi
    limits:
      cpu: 1000m
      memory: 2Gi

redis-ha:
  enabled: true

applicationSet:
  replicas: 2
# 커스텀 values로 설치/업그레이드
helm upgrade --install argocd argo/argo-cd \
  --namespace argocd \
  -f argocd-values.yaml \
  --wait

Application 정의와 Sync 전략

Application CRD 기본 구조

ArgoCD에서 배포 단위는 Application CRD이다. 하나의 Application은 하나의 Git 저장소 경로와 하나의 클러스터 네임스페이스를 연결한다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: frontend-app
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/gitops-manifests.git
    targetRevision: main
    path: apps/frontend/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: frontend
  syncPolicy:
    automated:
      prune: true # Git에서 삭제된 리소스를 클러스터에서도 삭제
      selfHeal: true # 수동 변경을 자동으로 원복
      allowEmpty: false # 빈 리소스 목록 배포 방지
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m0s
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas # HPA가 관리하는 필드는 무시

Sync 전략과 옵션

ArgoCD의 Sync 전략은 운영 안정성에 직결되므로 신중하게 선택해야 한다.

자동 Sync (automated): Git 변경 감지 시 자동 배포된다. 개발 환경에 적합하다.

  • prune: true - Git에서 제거된 리소스를 클러스터에서도 삭제한다.
  • selfHeal: true - 클러스터 상태가 Git과 다르면 자동으로 원복한다.

수동 Sync: 프로덕션 환경에서는 수동 Sync를 권장한다. Git에 변경이 반영되면 ArgoCD UI에서 OutOfSync 상태를 확인한 후 수동으로 Sync를 트리거한다.

Sync Wave와 Hook: 배포 순서가 중요한 경우 Wave 어노테이션과 Hook을 사용한다.

# 데이터베이스 마이그레이션을 먼저 실행
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: my-org/db-migrator:v1.2.0
          command: ['./migrate', 'up']
      restartPolicy: Never
---
# Wave 0: ConfigMap, Secret
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  annotations:
    argocd.argoproj.io/sync-wave: '0'
data:
  DATABASE_HOST: 'postgres.db.svc.cluster.local'
---
# Wave 1: Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  annotations:
    argocd.argoproj.io/sync-wave: '1'
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: my-org/backend:v2.1.0
---
# Wave 2: Service, Ingress
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
  annotations:
    argocd.argoproj.io/sync-wave: '2'
spec:
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080

Kustomize와 Helm 통합

Kustomize 기반 환경별 구성

Kustomize는 base와 overlay 패턴으로 환경별 차이만 관리할 수 있어 GitOps에 매우 적합하다.

# apps/backend/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: backend
          image: my-org/backend:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
# apps/backend/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - hpa.yaml
commonLabels:
  app.kubernetes.io/managed-by: argocd
# apps/backend/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: backend-prod
resources:
  - ../../base
patches:
  - target:
      kind: Deployment
      name: backend
    patch: |
      - op: replace
        path: /spec/replicas
        value: 5
      - op: replace
        path: /spec/template/spec/containers/0/resources/requests/cpu
        value: "500m"
      - op: replace
        path: /spec/template/spec/containers/0/resources/requests/memory
        value: "512Mi"
      - op: replace
        path: /spec/template/spec/containers/0/resources/limits/cpu
        value: "2000m"
      - op: replace
        path: /spec/template/spec/containers/0/resources/limits/memory
        value: "2Gi"
images:
  - name: my-org/backend
    newTag: v2.1.0

Helm 차트 기반 Application

ArgoCD는 Helm 차트도 네이티브로 지원한다. source 섹션에서 Helm 관련 설정을 명시하면 된다.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus-stack
  namespace: argocd
spec:
  project: infrastructure
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 65.1.0
    helm:
      releaseName: prometheus
      valuesObject:
        grafana:
          enabled: true
          ingress:
            enabled: true
            hosts:
              - grafana.example.com
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  accessModes:
                    - ReadWriteOnce
                  resources:
                    requests:
                      storage: 100Gi
        alertmanager:
          alertmanagerSpec:
            replicas: 2
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

멀티 클러스터 배포 전략

ApplicationSet을 활용한 멀티 클러스터 관리

대규모 조직에서는 개발, 스테이징, 프로덕션 등 여러 클러스터에 동일한 애플리케이션을 배포해야 한다. ArgoCD의 ApplicationSet 컨트롤러는 이를 자동화한다.

클러스터 등록

# 외부 클러스터 등록
argocd cluster add dev-cluster-context --name dev-cluster \
  --label env=dev --label region=ap-northeast-2

argocd cluster add staging-cluster-context --name staging-cluster \
  --label env=staging --label region=ap-northeast-2

argocd cluster add prod-cluster-context --name prod-cluster \
  --label env=prod --label region=ap-northeast-2

# 등록된 클러스터 확인
argocd cluster list

List Generator 기반 ApplicationSet

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: backend-appset
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - list:
        elements:
          - cluster: dev-cluster
            url: https://dev-k8s.example.com
            namespace: backend-dev
            replicas: '1'
          - cluster: staging-cluster
            url: https://staging-k8s.example.com
            namespace: backend-staging
            replicas: '2'
          - cluster: prod-cluster
            url: https://prod-k8s.example.com
            namespace: backend-prod
            replicas: '5'
  template:
    metadata:
      name: 'backend-{{.cluster}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/gitops-manifests.git
        targetRevision: main
        path: 'apps/backend/overlays/{{.cluster}}'
      destination:
        server: '{{.url}}'
        namespace: '{{.namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

Cluster Generator 기반 자동 탐지

ArgoCD에 등록된 클러스터의 레이블을 기반으로 자동 탐지하는 방식도 가능하다.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: infra-monitoring
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - clusters:
        selector:
          matchLabels:
            env: prod
        values:
          revision: main
  template:
    metadata:
      name: 'monitoring-{{.name}}'
    spec:
      project: infrastructure
      source:
        repoURL: https://github.com/my-org/gitops-manifests.git
        targetRevision: '{{.values.revision}}'
        path: infrastructure/monitoring
      destination:
        server: '{{.server}}'
        namespace: monitoring
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Hub-and-Spoke vs Standalone 아키텍처

아키텍처설명장점단점
Hub-and-Spoke중앙 관리 클러스터에서 모든 워크로드 클러스터 관리단일 대시보드, 중앙 집중 관리단일 장애점, 네트워크 의존성
Standalone각 클러스터마다 독립적인 ArgoCD 인스턴스장애 격리, 독립적 운영일관성 유지 어려움, 관리 오버헤드
HybridHub에서 인프라 관리, 각 클러스터에서 앱 관리관심사 분리, 유연성구성 복잡도 증가

프로덕션에서는 Hybrid 아키텍처가 가장 널리 사용된다. 플랫폼 팀이 중앙 Hub에서 인프라 컴포넌트(모니터링, 인그레스, cert-manager 등)를 관리하고, 각 팀이 자체 클러스터의 ArgoCD에서 애플리케이션을 관리하는 구조이다.


GitOps 도구 비교 (ArgoCD vs FluxCD vs Jenkins X)

주요 기능 비교

항목ArgoCDFluxCDJenkins X
CNCF 단계GraduatedGraduated- (별도 프로젝트)
아키텍처중앙 집중형 서버분산형 컨트롤러통합 CI/CD 플랫폼
Web UI내장 (풍부한 시각화)없음 (Weave GitOps 별도)제한적
CLI 도구argocd CLIflux CLIjx CLI
멀티 클러스터ApplicationSet 네이티브 지원Kustomization Controller내장 지원
Helm 지원네이티브HelmRelease CRD네이티브
Kustomize 지원네이티브Kustomization CRD지원
OCI 레지스트리v3.1부터 네이티브 지원지원미지원
RBAC프로젝트 기반 세밀한 제어쿠버네티스 RBAC 활용팀 기반
SSO 연동OIDC, SAML, LDAP 등쿠버네티스 인증 위임OAuth 기반
알림Notification ControllerAlert/Provider CRD웹훅
Diff/PreviewUI에서 시각적 DiffCLI 기반PR Preview 환경
커뮤니티 규모매우 활발 (GitHub Stars 18k+)활발 (GitHub Stars 7k+)보통
기업 지원Akuity, Codefresh 등Weaveworks 폐쇄 후 커뮤니티CloudBees
시장 점유율 (2025)약 60%약 25%약 5% 미만
학습 곡선중간높음 (K8s 심층 이해 필요)높음

선택 가이드

ArgoCD를 선택해야 할 때:

  • 직관적인 Web UI가 필요한 경우
  • 다수의 개발팀이 셀프서비스로 배포해야 하는 경우
  • 프로젝트 기반 세밀한 RBAC이 필요한 경우
  • 엔터프라이즈 지원이 중요한 경우

FluxCD를 선택해야 할 때:

  • 플랫폼 엔지니어링 팀이 인프라 자동화를 구축하는 경우
  • 쿠버네티스 네이티브 접근을 선호하는 경우
  • 모듈화된 컨트롤러 아키텍처를 원하는 경우
  • 가벼운 풋프린트가 중요한 경우

Jenkins X를 선택해야 할 때:

  • CI/CD 전체 파이프라인을 하나의 도구로 통합하고 싶은 경우
  • PR 기반 Preview 환경 자동 생성이 핵심 요구사항인 경우
  • Tekton 기반 파이프라인에 익숙한 경우

2024년 Weaveworks(FluxCD의 주요 후원사)가 폐쇄된 이후, FluxCD는 CNCF 커뮤니티 주도로 유지되고 있다. 프로젝트 자체는 건재하지만, 상업적 지원 생태계 측면에서 ArgoCD가 더 안정적인 선택지가 되었다.


CI/CD 파이프라인 통합 (GitHub Actions + ArgoCD)

GitOps에서 CI와 CD는 명확하게 분리된다. GitHub Actions가 CI(빌드, 테스트, 이미지 푸시)를 담당하고, ArgoCD가 CD(클러스터 배포)를 담당한다. 핵심은 GitHub Actions가 쿠버네티스에 직접 배포하지 않고, Git 저장소의 매니페스트만 업데이트한다는 점이다.

CI 워크플로우: 이미지 빌드와 매니페스트 업데이트

# .github/workflows/ci-cd.yaml
name: CI - Build and Update Manifests

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'Dockerfile'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: my-org/backend
  GITOPS_REPO: my-org/gitops-manifests

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}

    steps:
      - name: Checkout source code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/my-org/backend
          tags: |
            type=sha,prefix=
            type=ref,event=branch

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

      - name: Run Trivy vulnerability scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'ghcr.io/my-org/backend:${{ steps.meta.outputs.version }}'
          format: 'sarif'
          output: 'trivy-results.sarif'

  update-manifests:
    needs: build-and-push
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      - name: Checkout GitOps manifests repo
        uses: actions/checkout@v4
        with:
          repository: my-org/gitops-manifests
          token: ${{ secrets.GITOPS_PAT }}
          path: gitops-manifests

      - name: Update image tag in Kustomize
        working-directory: gitops-manifests
        run: |
          cd apps/backend/overlays/staging
          kustomize edit set image \
            my-org/backend=ghcr.io/my-org/backend:${{ needs.build-and-push.outputs.image_tag }}

      - name: Commit and push changes
        working-directory: gitops-manifests
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add .
          git diff --staged --quiet || \
            git commit -m "chore: update backend image to ${{ needs.build-and-push.outputs.image_tag }}"
          git push

ArgoCD Notification 설정

배포 결과를 Slack으로 알림받는 설정도 중요하다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  trigger.on-sync-succeeded: |
    - when: app.status.operationState.phase in ['Succeeded']
      send: [app-sync-succeeded]
  trigger.on-sync-failed: |
    - when: app.status.operationState.phase in ['Error', 'Failed']
      send: [app-sync-failed]
  template.app-sync-succeeded: |
    slack:
      attachments: |
        [{
          "color": "#18be52",
          "title": "{{.app.metadata.name}} 배포 성공",
          "text": "Revision: {{.app.status.sync.revision}}\nEnvironment: {{.app.spec.destination.namespace}}"
        }]
  template.app-sync-failed: |
    slack:
      attachments: |
        [{
          "color": "#E96D76",
          "title": "{{.app.metadata.name}} 배포 실패",
          "text": "Revision: {{.app.status.sync.revision}}\nMessage: {{.app.status.operationState.message}}"
        }]

운영 시 주의사항과 트러블슈팅

핵심 운영 주의사항

1. Secret 관리: Git에 평문 Secret을 저장하면 안 된다. 다음 도구 중 하나를 사용하라.

  • Sealed Secrets: 쿠버네티스 컨트롤러 기반 암호화. 클러스터별 키 관리 필요.
  • External Secrets Operator: AWS Secrets Manager, HashiCorp Vault 등 외부 저장소 연동.
  • SOPS (Mozilla): Age/PGP 키로 YAML 파일 내 특정 필드만 암호화.
# External Secrets Operator 예시
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: backend-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: backend-secrets
  data:
    - secretKey: DATABASE_PASSWORD
      remoteRef:
        key: prod/backend/database
        property: password
    - secretKey: API_KEY
      remoteRef:
        key: prod/backend/api-keys
        property: main-key

2. Resource Hook과 Finalizer 주의: Application 삭제 시 resources-finalizer.argocd.argoproj.io Finalizer가 설정되어 있으면 연결된 모든 쿠버네티스 리소스가 함께 삭제된다. 프로덕션 데이터베이스가 연결된 경우 각별히 주의해야 한다.

3. ignoreDifferences 설정: HPA가 관리하는 replicas 필드, 어드미션 컨트롤러가 추가하는 어노테이션 등은 ArgoCD가 무시하도록 설정하지 않으면 끊임없이 OutOfSync 상태가 된다.

4. Repo Server 리소스: 대규모 Helm 차트나 많은 Application을 관리하면 Repo Server의 메모리 사용량이 급증한다. 리소스 제한을 넉넉하게 설정하고 모니터링해야 한다.

자주 발생하는 문제와 해결 방법

문제 1: ComparisonError - 매니페스트 렌더링 실패

# Repo Server 로그 확인
kubectl logs -n argocd deployment/argocd-repo-server --tail=100

# 매니페스트를 로컬에서 미리 렌더링하여 검증
kustomize build apps/backend/overlays/prod | kubectl apply --dry-run=server -f -

# Helm 차트의 경우
helm template my-release ./chart -f values-prod.yaml | kubectl apply --dry-run=server -f -

문제 2: Sync 상태가 Unknown으로 표시

# Application Controller 로그 확인
kubectl logs -n argocd deployment/argocd-application-controller --tail=100

# 캐시 초기화
argocd app get frontend-app --hard-refresh

# Application 상태 강제 새로고침
argocd app get frontend-app --refresh

문제 3: Git 저장소 연결 실패

# 저장소 연결 상태 확인
argocd repo list

# SSH 키 문제 시 재등록
argocd repo add git@github.com:my-org/gitops-manifests.git \
  --ssh-private-key-path ~/.ssh/argocd_deploy_key

# HTTPS 인증 문제 시
argocd repo add https://github.com/my-org/gitops-manifests.git \
  --username git --password <PERSONAL_ACCESS_TOKEN>

문제 4: 리소스 쿼터 초과로 Sync 실패

# 네임스페이스 리소스 쿼터 확인
kubectl describe resourcequota -n backend-prod

# Application 이벤트 확인
kubectl describe application frontend-app -n argocd

실패 사례와 롤백 절차

실패 사례 1: 자동 Sync + Prune로 인한 서비스 장애

한 팀에서 실수로 프로덕션 Kustomize 오버레이의 리소스 목록에서 Service 파일을 제거하고 커밋했다. 자동 Sync와 prune가 활성화되어 있었기 때문에, ArgoCD가 즉시 Service 리소스를 삭제하여 전체 서비스가 중단되었다.

교훈: 프로덕션 환경에서는 automated.prune: false로 설정하거나, 최소한 syncOptionsPruneLast=true를 추가하라. 중요 리소스에는 다음 어노테이션을 적용한다.

metadata:
  annotations:
    argocd.argoproj.io/sync-options: Prune=false

실패 사례 2: Helm values 충돌로 인한 롤백 불가

Helm 차트 업그레이드 시 새 버전에서 values 스키마가 변경되었는데, 이전 values.yaml과 호환되지 않아 롤백 시에도 렌더링이 실패하는 상황이 발생했다.

교훈: Helm 차트 메이저 버전 업그레이드 전에 반드시 스테이징에서 검증하고, 롤백 가능 여부도 함께 테스트하라.

롤백 절차

ArgoCD에서의 롤백은 Git Revert가 정석이다. ArgoCD UI/CLI의 Rollback 기능도 있지만, GitOps 원칙에 따르면 Git이 진실 공급원이므로 Git에서 되돌리는 것이 올바른 방법이다.

방법 1: Git Revert (권장)

# 문제가 된 커밋 확인
git log --oneline -10

# 특정 커밋 되돌리기
git revert <COMMIT_SHA> --no-edit
git push origin main

# ArgoCD가 자동 Sync하거나 수동 Sync 트리거
argocd app sync frontend-app

방법 2: ArgoCD CLI를 이용한 롤백 (긴급 시)

# 배포 히스토리 확인
argocd app history frontend-app

# 특정 리비전으로 롤백
argocd app rollback frontend-app <REVISION_NUMBER>

# 주의: 이 방법은 Git과 클러스터 상태가 불일치하게 된다
# 반드시 이후에 Git에서도 해당 변경을 반영해야 한다

방법 3: 이미지 태그 즉시 변경 (가장 빠른 복구)

# GitOps 매니페스트 저장소에서 이미지 태그를 이전 버전으로 변경
cd gitops-manifests/apps/backend/overlays/prod
kustomize edit set image my-org/backend=ghcr.io/my-org/backend:<PREVIOUS_TAG>

git add .
git commit -m "hotfix: rollback backend to <PREVIOUS_TAG> due to OOM issue"
git push origin main

# ArgoCD Sync 대기 또는 수동 트리거
argocd app sync backend-prod-cluster --prune=false

롤백 체크리스트

  1. 장애 인지 및 영향 범위 파악
  2. ArgoCD UI에서 Application 상태 확인 (Healthy/Degraded/Missing)
  3. 롤백 대상 리비전 결정 (argocd app history 활용)
  4. Git Revert 또는 이미지 태그 롤백 실행
  5. Sync 완료 확인 및 서비스 정상 동작 검증
  6. 포스트모텀 작성 및 재발 방지 대책 수립

마치며

GitOps는 단순한 배포 자동화를 넘어, 인프라와 애플리케이션의 선언적 관리, 감사 추적, 자동 복구를 통해 운영 효율성을 근본적으로 개선하는 패러다임이다. ArgoCD는 직관적인 UI, 풍부한 생태계, 활발한 커뮤니티를 바탕으로 GitOps 구현의 사실상 표준으로 자리잡았다.

이 글에서 다룬 내용을 정리하면 다음과 같다.

  • GitOps 원칙: 선언적 정의, 버전 관리, 자동 적용, 지속적 조정의 4원칙
  • ArgoCD 구성: Helm 기반 HA 설치, RBAC/SSO 설정, Application CRD 정의
  • Sync 전략: 자동/수동 Sync, Wave 기반 순서 제어, Hook을 이용한 사전/사후 작업
  • 환경 관리: Kustomize overlay와 Helm values를 통한 환경별 차이 관리
  • 멀티 클러스터: ApplicationSet의 다양한 Generator를 활용한 자동 배포
  • CI/CD 통합: GitHub Actions(CI) + ArgoCD(CD)의 명확한 역할 분리
  • 운영 노하우: Secret 관리, 트러블슈팅, 롤백 절차

GitOps 도입은 하루아침에 완성되지 않는다. 개발 환경부터 시작하여 점진적으로 스테이징, 프로덕션으로 확장하고, 팀의 역량과 프로세스 성숙도에 맞춰 자동화 수준을 높여가는 것이 성공의 열쇠이다.


참고자료