Skip to content

Split View: ArgoCD GitOps 완벽 가이드: ApplicationSet·Sync Waves·Hook으로 구현하는 Kubernetes 선언적 배포

✨ Learn with Quiz
|

ArgoCD GitOps 완벽 가이드: ApplicationSet·Sync Waves·Hook으로 구현하는 Kubernetes 선언적 배포

ArgoCD GitOps 완벽 가이드

들어가며

GitOps는 Git을 Single Source of Truth로 사용하여 인프라와 애플리케이션의 원하는 상태를 선언적으로 관리하는 운영 패러다임입니다. ArgoCD는 Kubernetes 환경에서 GitOps를 구현하는 가장 널리 사용되는 도구로, CNCF Graduated 프로젝트로서 프로덕션 검증이 완료되었습니다.

이 글에서는 ArgoCD의 핵심 기능인 ApplicationSet을 통한 멀티 클러스터/환경 배포, Sync Waves와 Hook을 통한 배포 순서 제어, RBAC과 시크릿 관리까지 프로덕션 환경에서 필요한 모든 설정을 실전 코드와 함께 다룹니다.

ArgoCD 아키텍처 개요

ArgoCD는 다음과 같은 핵심 컴포넌트로 구성됩니다.

컴포넌트역할주요 기능
API Server웹 UI, CLI, CI/CD 연동gRPC/REST API 제공, RBAC 처리
Repo ServerGit 저장소 관리매니페스트 생성(Helm, Kustomize, Plain YAML)
Application Controller핵심 조정 루프클러스터 상태 감시, 동기화 실행
ApplicationSet Controller다중 Application 생성템플릿 기반 자동 Application 생성
Redis캐시매니페스트 캐싱, 클러스터 상태 캐싱
Dex인증SSO, OIDC, LDAP 등 외부 인증 연동

ArgoCD 설치

# Namespace 생성 및 ArgoCD 설치
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 또는 Helm으로 설치 (프로덕션 권장)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  --set server.service.type=LoadBalancer \
  --set configs.params."server\.insecure"=true \
  --set controller.replicas=2 \
  --set repoServer.replicas=2 \
  --set redis-ha.enabled=true

# 초기 admin 비밀번호 확인
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d

Application 리소스 기본 구성

Application 스펙 상세

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-application
  namespace: argocd
  # Finalizer: Application 삭제 시 클러스터 리소스도 함께 삭제
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: production
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: apps/my-app/overlays/production
    # Kustomize 옵션
    kustomize:
      namePrefix: prod-
      commonLabels:
        env: production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true # Git에서 삭제된 리소스를 클러스터에서도 삭제
      selfHeal: true # 수동 변경 시 자동 복구
      allowEmpty: false # 빈 매니페스트 배포 방지
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
      - ServerSideApply=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas # HPA 관리 대상이므로 무시

Helm 소스 설정

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus-stack
  namespace: argocd
spec:
  project: monitoring
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 65.1.0
    helm:
      releaseName: prometheus
      valuesObject:
        grafana:
          enabled: true
          adminPassword: vault:secret/grafana#password
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  storageClassName: gp3
                  resources:
                    requests:
                      storage: 100Gi
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring

AppProject로 RBAC 구현

프로젝트 정의

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  description: 'Production environment project'
  # 허용된 소스 저장소
  sourceRepos:
    - 'https://github.com/myorg/k8s-manifests.git'
    - 'https://github.com/myorg/helm-charts.git'
  # 허용된 배포 대상
  destinations:
    - namespace: 'production'
      server: 'https://kubernetes.default.svc'
    - namespace: 'production-*'
      server: 'https://kubernetes.default.svc'
  # 허용된 클러스터 리소스
  clusterResourceWhitelist:
    - group: ''
      kind: Namespace
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRole
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRoleBinding
  # 차단된 네임스페이스 리소스
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota
    - group: ''
      kind: LimitRange
  # RBAC 역할 정의
  roles:
    - name: deployer
      description: 'Can sync and manage applications'
      policies:
        - p, proj:production:deployer, applications, get, production/*, allow
        - p, proj:production:deployer, applications, sync, production/*, allow
        - p, proj:production:deployer, applications, action/*, production/*, allow
      groups:
        - platform-team
    - name: viewer
      description: 'Read-only access'
      policies:
        - p, proj:production:viewer, applications, get, production/*, allow
      groups:
        - dev-team

ApplicationSet 제너레이터

Git Directory 제너레이터

Git 저장소의 디렉토리 구조를 기반으로 Application을 자동 생성합니다.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: app-of-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - git:
        repoURL: https://github.com/myorg/k8s-manifests.git
        revision: main
        directories:
          - path: 'apps/*/overlays/production'
          - path: 'apps/deprecated-*'
            exclude: true
  template:
    metadata:
      name: '{{.path.basename}}'
      namespace: argocd
      labels:
        app.kubernetes.io/managed-by: applicationset
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Cluster 제너레이터

등록된 클러스터를 기반으로 멀티 클러스터 배포를 자동화합니다.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-cluster-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - clusters:
        selector:
          matchLabels:
            env: production
          matchExpressions:
            - key: region
              operator: In
              values:
                - ap-northeast-2
                - us-west-2
                - eu-west-1
  template:
    metadata:
      name: 'app-{{.name}}'
      namespace: argocd
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: 'apps/my-app/overlays/{{.metadata.labels.env}}'
        kustomize:
          commonLabels:
            cluster: '{{.name}}'
            region: '{{.metadata.labels.region}}'
      destination:
        server: '{{.server}}'
        namespace: production

Matrix 제너레이터 (복합 조합)

두 제너레이터를 조합하여 모든 조합을 생성합니다.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: matrix-deployment
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - matrix:
        generators:
          # 제너레이터 1: 클러스터 목록
          - clusters:
              selector:
                matchLabels:
                  env: production
          # 제너레이터 2: Git 디렉토리에서 앱 목록
          - git:
              repoURL: https://github.com/myorg/k8s-manifests.git
              revision: main
              directories:
                - path: 'apps/*'
  template:
    metadata:
      name: '{{.path.basename}}-{{.name}}'
      namespace: argocd
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: '{{.path.path}}/overlays/production'
      destination:
        server: '{{.server}}'
        namespace: '{{.path.basename}}'

List 제너레이터

정적 값 목록을 기반으로 Application을 생성합니다.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: environment-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - list:
        elements:
          - env: dev
            cluster: https://dev-k8s.example.com
            revision: develop
            replicas: '1'
          - env: staging
            cluster: https://staging-k8s.example.com
            revision: release/v2.5
            replicas: '2'
          - env: production
            cluster: https://kubernetes.default.svc
            revision: main
            replicas: '3'
  template:
    metadata:
      name: 'my-app-{{.env}}'
      namespace: argocd
    spec:
      project: '{{.env}}'
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: '{{.revision}}'
        path: 'apps/my-app/overlays/{{.env}}'
      destination:
        server: '{{.cluster}}'
        namespace: 'my-app-{{.env}}'

Sync Waves를 통한 배포 순서 제어

Sync Wave 기본 개념

Sync Wave는 argocd.argoproj.io/sync-wave 어노테이션으로 정의하며, 정수 값을 사용하여 낮은 값부터 순차적으로 배포합니다. 기본값은 0입니다.

# Wave -2: 네임스페이스와 RBAC (전제 조건)
apiVersion: v1
kind: Namespace
metadata:
  name: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-service-account
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
---
# Wave -1: ConfigMap과 Secret (설정)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-1'
data:
  APP_ENV: production
  LOG_LEVEL: info
---
# Wave 0: 핵심 애플리케이션 (기본값)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '0'
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: app-service-account
      containers:
        - name: app
          image: myorg/my-app:v2.5.0
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: app-config
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '0'
spec:
  selector:
    app: my-app
  ports:
    - port: 8080
      targetPort: 8080
---
# Wave 1: 의존 리소스 (Ingress, HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '1'
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
---
# Wave 2: 모니터링
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app-monitor
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '2'
spec:
  selector:
    matchLabels:
      app: my-app
  endpoints:
    - port: http
      interval: 15s

Sync Hook 활용

PreSync Hook: 배포 전 데이터베이스 마이그레이션

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  namespace: production
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: '-1'
spec:
  backoffLimit: 3
  template:
    spec:
      containers:
        - name: migrate
          image: myorg/db-migrator:v2.5.0
          command: ['./migrate', 'up']
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
      restartPolicy: Never

PostSync Hook: 배포 후 검증

apiVersion: batch/v1
kind: Job
metadata:
  name: smoke-test
  namespace: production
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 1
  template:
    spec:
      containers:
        - name: smoke-test
          image: myorg/smoke-tester:latest
          command:
            - /bin/sh
            - -c
            - |
              echo "Running smoke tests..."
              curl -sf http://my-app.production.svc:8080/healthz || exit 1
              curl -sf http://my-app.production.svc:8080/api/v1/status || exit 1
              echo "All smoke tests passed!"
      restartPolicy: Never

SyncFail Hook: 동기화 실패 시 알림

apiVersion: batch/v1
kind: Job
metadata:
  name: sync-fail-notification
  namespace: production
  annotations:
    argocd.argoproj.io/hook: SyncFail
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 1
  template:
    spec:
      containers:
        - name: notify
          image: curlimages/curl:latest
          command:
            - /bin/sh
            - -c
            - |
              curl -X POST "$SLACK_WEBHOOK_URL" \
                -H 'Content-Type: application/json' \
                -d '{"text":"ArgoCD Sync FAILED for my-app in production!"}'
          env:
            - name: SLACK_WEBHOOK_URL
              valueFrom:
                secretKeyRef:
                  name: slack-webhook
                  key: url
      restartPolicy: Never

시크릿 관리

Sealed Secrets 연동

# Sealed Secrets 컨트롤러 설치
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace kube-system

# kubeseal CLI로 시크릿 암호화
kubectl create secret generic db-credentials \
  --namespace production \
  --from-literal=url='postgresql://user:pass@db:5432/mydb' \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-db-credentials.yaml
# Git에 커밋 가능한 SealedSecret
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
spec:
  encryptedData:
    url: AgA2X5N0Q...encrypted...base64==
  template:
    metadata:
      name: db-credentials
      namespace: production
    type: Opaque

External Secrets Operator 연동

# ExternalSecret: AWS Secrets Manager에서 시크릿 동기화
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: url
      remoteRef:
        key: production/database
        property: connection_url
    - secretKey: password
      remoteRef:
        key: production/database
        property: password
---
# ClusterSecretStore 정의
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmanager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-2
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

멀티 클러스터 배포

클러스터 등록

# 대상 클러스터 등록 (kubeconfig 컨텍스트 기반)
argocd cluster add staging-cluster \
  --name staging \
  --label env=staging \
  --label region=ap-northeast-2

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

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

클러스터별 설정 분리

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-services
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchLabels:
                  env: production
          - list:
              elements:
                - app: cert-manager
                  namespace: cert-manager
                  chart: cert-manager
                  repoURL: https://charts.jetstack.io
                  targetRevision: v1.16.0
                - app: external-secrets
                  namespace: external-secrets
                  chart: external-secrets
                  repoURL: https://charts.external-secrets.io
                  targetRevision: 0.12.0
                - app: metrics-server
                  namespace: kube-system
                  chart: metrics-server
                  repoURL: https://kubernetes-sigs.github.io/metrics-server
                  targetRevision: 3.12.0
  template:
    metadata:
      name: '{{.app}}-{{.name}}'
      namespace: argocd
    spec:
      project: platform
      source:
        repoURL: '{{.repoURL}}'
        chart: '{{.chart}}'
        targetRevision: '{{.targetRevision}}'
        helm:
          releaseName: '{{.app}}'
      destination:
        server: '{{.server}}'
        namespace: '{{.namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

롤백 전략

자동 롤백 설정

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: apps/my-app/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 3
      backoff:
        duration: 10s
        factor: 2
        maxDuration: 5m

CLI를 통한 수동 롤백

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

# 특정 리비전으로 롤백
argocd app rollback my-app 5

# 또는 특정 Git 커밋으로 동기화
argocd app sync my-app --revision abc123def

# 동기화 상태 확인
argocd app get my-app
argocd app wait my-app --health

모니터링 구성

ArgoCD Prometheus 메트릭

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-metrics
  namespace: argocd
spec:
  selector:
    matchLabels:
      app.kubernetes.io/part-of: argocd
  endpoints:
    - port: metrics
      interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: argocd-alerts
  namespace: argocd
spec:
  groups:
    - name: argocd
      rules:
        - alert: ArgoCDAppOutOfSync
          expr: |
            argocd_app_info{sync_status="OutOfSync"} == 1
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: 'Application {{ "{{" }} $labels.name {{ "}}" }} is OutOfSync for more than 10 minutes'
        - alert: ArgoCDAppDegraded
          expr: |
            argocd_app_info{health_status="Degraded"} == 1
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: 'Application {{ "{{" }} $labels.name {{ "}}" }} is Degraded'
        - alert: ArgoCDSyncFailed
          expr: |
            increase(argocd_app_sync_total{phase="Failed"}[10m]) > 0
          labels:
            severity: critical
          annotations:
            summary: 'ArgoCD sync failed for {{ "{{" }} $labels.name {{ "}}" }}'

Slack 알림 설정

# argocd-notifications-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  trigger.on-sync-failed: |
    - when: app.status.operationState.phase in ['Error', 'Failed']
      send: [app-sync-failed]
  trigger.on-health-degraded: |
    - when: app.status.health.status == 'Degraded'
      send: [app-health-degraded]
  trigger.on-sync-succeeded: |
    - when: app.status.operationState.phase in ['Succeeded']
      oncePer: app.status.sync.revision
      send: [app-sync-succeeded]
  template.app-sync-failed: |
    slack:
      attachments: |
        [{
          "color": "#E96D76",
          "title": "Sync Failed: {{.app.metadata.name}}",
          "text": "Application {{.app.metadata.name}} sync failed.\nRevision: {{.app.status.sync.revision}}\nMessage: {{.app.status.operationState.message}}"
        }]
  template.app-health-degraded: |
    slack:
      attachments: |
        [{
          "color": "#f4c030",
          "title": "Health Degraded: {{.app.metadata.name}}",
          "text": "Application {{.app.metadata.name}} health is degraded."
        }]
  template.app-sync-succeeded: |
    slack:
      attachments: |
        [{
          "color": "#18BE52",
          "title": "Sync Succeeded: {{.app.metadata.name}}",
          "text": "Application {{.app.metadata.name}} synced successfully.\nRevision: {{.app.status.sync.revision}}"
        }]

운영 시 주의사항

1. ApplicationSet 보안

ApplicationSet의 project 필드가 템플릿화된 경우, 개발자가 과도한 권한을 가진 프로젝트에 Application을 생성할 수 있습니다. 항상 관리자가 제어하는 소스에서만 프로젝트 필드를 참조하도록 설정해야 합니다.

2. 자동 동기화 주의점

automated.prune: true는 Git에서 제거된 리소스를 클러스터에서 삭제합니다. 실수로 매니페스트를 삭제하면 프로덕션 리소스가 즉시 삭제될 수 있으므로, 중요 리소스에는 argocd.argoproj.io/sync-options: Prune=false 어노테이션을 추가하는 것을 권장합니다.

3. Webhook 보안

ArgoCD가 공개적으로 접근 가능한 경우, 반드시 Webhook 시크릿을 설정하여 DDoS 공격을 방지해야 합니다.

4. Repo Server 리소스

대규모 저장소나 Helm 차트를 처리할 때 Repo Server의 메모리 사용량이 급증할 수 있습니다. 적절한 리소스 제한과 레플리카 수를 설정해야 합니다.

5. 추상화 계층 제한

Application of Applications 패턴 사용 시 3단계 이상의 추상화를 피해야 합니다. 4-5단계의 중첩은 디버깅을 극도로 어렵게 만듭니다.

장애 사례 및 복구 절차

사례 1: Sync 무한 루프

# 증상: Application이 계속 Syncing 상태를 반복
# 원인: ignoreDifferences 미설정으로 인한 drift 감지

# 진단
argocd app diff my-app
argocd app get my-app --show-operation

# 복구: ignoreDifferences 추가
kubectl patch application my-app -n argocd --type merge -p '{
  "spec": {
    "ignoreDifferences": [
      {
        "group": "apps",
        "kind": "Deployment",
        "jsonPointers": ["/spec/replicas"]
      }
    ]
  }
}'

사례 2: Repo Server OOM

# 증상: Application이 Unknown 상태, 매니페스트 생성 실패
# 진단
kubectl logs -n argocd deploy/argocd-repo-server --previous
kubectl top pods -n argocd

# 복구: 리소스 제한 증가
kubectl patch deployment argocd-repo-server -n argocd --type json -p '[
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/resources/limits/memory",
    "value": "4Gi"
  }
]'

# Repo Server 재시작
kubectl rollout restart deployment/argocd-repo-server -n argocd

사례 3: 시크릿 동기화 실패 (External Secrets)

# 증상: Application은 Healthy이지만 Pod가 CrashLoopBackOff
# 원인: ExternalSecret이 아직 동기화되지 않음

# 진단
kubectl get externalsecret -n production
kubectl describe externalsecret db-credentials -n production

# 복구: ExternalSecret 강제 동기화
kubectl annotate externalsecret db-credentials \
  -n production \
  force-sync=$(date +%s) --overwrite

# 또는 Sync Wave로 순서 보장
# ExternalSecret: sync-wave=-2, Deployment: sync-wave=0

사례 4: ApplicationSet 의도치 않은 삭제

# 증상: ApplicationSet 수정 후 기존 Application이 삭제됨
# 원인: 제너레이터 설정 오류로 매칭되는 항목 감소

# 예방: preserveResourcesOnDeletion 설정
kubectl patch applicationset my-appset -n argocd --type merge -p '{
  "spec": {
    "syncPolicy": {
      "preserveResourcesOnDeletion": true
    }
  }
}'

# 복구: Git 히스토리에서 이전 ApplicationSet 설정 복원
git log --oneline -- applicationsets/my-appset.yaml
git checkout HEAD~1 -- applicationsets/my-appset.yaml
git commit -m "Revert ApplicationSet to restore applications"
git push

마치며

ArgoCD는 GitOps의 핵심 도구로서, ApplicationSet을 통한 멀티 클러스터 자동화, Sync Waves와 Hook을 통한 정밀한 배포 순서 제어, 그리고 RBAC과 시크릿 관리를 통한 보안 강화까지 프로덕션 환경에서 필요한 모든 기능을 제공합니다.

핵심은 Git을 Single Source of Truth로 유지하면서, 적절한 자동화 수준을 설정하는 것입니다. 모든 환경에 자동 동기화를 적용하기보다는, 개발/스테이징에는 자동 동기화를, 프로덕션에는 수동 동기화를 적용하는 점진적 접근을 권장합니다. 모니터링과 알림을 반드시 함께 구성하여, 동기화 실패나 헬스 체크 이상을 신속하게 감지하고 대응할 수 있도록 하시기 바랍니다.

참고자료

ArgoCD GitOps Complete Guide: Declarative Kubernetes Deployment with ApplicationSet, Sync Waves, and Hooks

ArgoCD GitOps Complete Guide

Introduction

GitOps is an operational paradigm that uses Git as the Single Source of Truth for declaratively managing the desired state of infrastructure and applications. ArgoCD is the most widely adopted tool for implementing GitOps in Kubernetes environments, and as a CNCF Graduated project, it is production-proven at scale.

This guide covers all production essentials with practical code: multi-cluster/environment deployment through ApplicationSet, deployment order control through Sync Waves and Hooks, RBAC, and secrets management.

ArgoCD Architecture Overview

ArgoCD consists of the following core components.

ComponentRoleKey Functions
API ServerWeb UI, CLI, CI/CD integrationgRPC/REST API, RBAC processing
Repo ServerGit repository managementManifest generation (Helm, Kustomize, Plain YAML)
Application ControllerCore reconciliation loopCluster state monitoring, sync execution
ApplicationSet ControllerMulti-Application generationTemplate-based automatic Application creation
RedisCacheManifest caching, cluster state caching
DexAuthenticationSSO, OIDC, LDAP external auth integration

ArgoCD Installation

# Create namespace and install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Or install via Helm (recommended for production)
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

helm install argocd argo/argo-cd \
  --namespace argocd \
  --create-namespace \
  --set server.service.type=LoadBalancer \
  --set configs.params."server\.insecure"=true \
  --set controller.replicas=2 \
  --set repoServer.replicas=2 \
  --set redis-ha.enabled=true

# Retrieve initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath='{.data.password}' | base64 -d

Application Resource Basics

Application Spec Details

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-application
  namespace: argocd
  # Finalizer: delete cluster resources when Application is deleted
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: production
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: apps/my-app/overlays/production
    # Kustomize options
    kustomize:
      namePrefix: prod-
      commonLabels:
        env: production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true # Delete cluster resources removed from Git
      selfHeal: true # Auto-revert manual changes
      allowEmpty: false # Prevent deploying empty manifests
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
      - ServerSideApply=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas # Ignore since HPA manages this

Helm Source Configuration

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus-stack
  namespace: argocd
spec:
  project: monitoring
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: kube-prometheus-stack
    targetRevision: 65.1.0
    helm:
      releaseName: prometheus
      valuesObject:
        grafana:
          enabled: true
          adminPassword: vault:secret/grafana#password
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  storageClassName: gp3
                  resources:
                    requests:
                      storage: 100Gi
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring

RBAC with AppProject

Project Definition

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  description: 'Production environment project'
  # Allowed source repositories
  sourceRepos:
    - 'https://github.com/myorg/k8s-manifests.git'
    - 'https://github.com/myorg/helm-charts.git'
  # Allowed deployment targets
  destinations:
    - namespace: 'production'
      server: 'https://kubernetes.default.svc'
    - namespace: 'production-*'
      server: 'https://kubernetes.default.svc'
  # Allowed cluster resources
  clusterResourceWhitelist:
    - group: ''
      kind: Namespace
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRole
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRoleBinding
  # Blocked namespace resources
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota
    - group: ''
      kind: LimitRange
  # RBAC role definitions
  roles:
    - name: deployer
      description: 'Can sync and manage applications'
      policies:
        - p, proj:production:deployer, applications, get, production/*, allow
        - p, proj:production:deployer, applications, sync, production/*, allow
        - p, proj:production:deployer, applications, action/*, production/*, allow
      groups:
        - platform-team
    - name: viewer
      description: 'Read-only access'
      policies:
        - p, proj:production:viewer, applications, get, production/*, allow
      groups:
        - dev-team

ApplicationSet Generators

Git Directory Generator

Automatically generates Applications based on the Git repository directory structure.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: app-of-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - git:
        repoURL: https://github.com/myorg/k8s-manifests.git
        revision: main
        directories:
          - path: 'apps/*/overlays/production'
          - path: 'apps/deprecated-*'
            exclude: true
  template:
    metadata:
      name: '{{.path.basename}}'
      namespace: argocd
      labels:
        app.kubernetes.io/managed-by: applicationset
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{.path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Cluster Generator

Automates multi-cluster deployment based on registered clusters.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-cluster-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - clusters:
        selector:
          matchLabels:
            env: production
          matchExpressions:
            - key: region
              operator: In
              values:
                - ap-northeast-2
                - us-west-2
                - eu-west-1
  template:
    metadata:
      name: 'app-{{.name}}'
      namespace: argocd
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: 'apps/my-app/overlays/{{.metadata.labels.env}}'
        kustomize:
          commonLabels:
            cluster: '{{.name}}'
            region: '{{.metadata.labels.region}}'
      destination:
        server: '{{.server}}'
        namespace: production

Matrix Generator (Composite Combinations)

Combines two generators to produce all possible combinations.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: matrix-deployment
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - matrix:
        generators:
          # Generator 1: Cluster list
          - clusters:
              selector:
                matchLabels:
                  env: production
          # Generator 2: App list from Git directories
          - git:
              repoURL: https://github.com/myorg/k8s-manifests.git
              revision: main
              directories:
                - path: 'apps/*'
  template:
    metadata:
      name: '{{.path.basename}}-{{.name}}'
      namespace: argocd
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: main
        path: '{{.path.path}}/overlays/production'
      destination:
        server: '{{.server}}'
        namespace: '{{.path.basename}}'

List Generator

Generates Applications from a static list of values.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: environment-apps
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - list:
        elements:
          - env: dev
            cluster: https://dev-k8s.example.com
            revision: develop
            replicas: '1'
          - env: staging
            cluster: https://staging-k8s.example.com
            revision: release/v2.5
            replicas: '2'
          - env: production
            cluster: https://kubernetes.default.svc
            revision: main
            replicas: '3'
  template:
    metadata:
      name: 'my-app-{{.env}}'
      namespace: argocd
    spec:
      project: '{{.env}}'
      source:
        repoURL: https://github.com/myorg/k8s-manifests.git
        targetRevision: '{{.revision}}'
        path: 'apps/my-app/overlays/{{.env}}'
      destination:
        server: '{{.cluster}}'
        namespace: 'my-app-{{.env}}'

Deployment Order Control with Sync Waves

Sync Wave Fundamentals

Sync Waves are defined using the argocd.argoproj.io/sync-wave annotation with integer values, deploying sequentially from the lowest value. The default value is 0.

# Wave -2: Namespace and RBAC (prerequisites)
apiVersion: v1
kind: Namespace
metadata:
  name: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-service-account
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
---
# Wave -1: ConfigMap and Secret (configuration)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-1'
data:
  APP_ENV: production
  LOG_LEVEL: info
---
# Wave 0: Core application (default)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '0'
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: app-service-account
      containers:
        - name: app
          image: myorg/my-app:v2.5.0
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef:
                name: app-config
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '0'
spec:
  selector:
    app: my-app
  ports:
    - port: 8080
      targetPort: 8080
---
# Wave 1: Dependent resources (Ingress, HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '1'
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
---
# Wave 2: Monitoring
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app-monitor
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '2'
spec:
  selector:
    matchLabels:
      app: my-app
  endpoints:
    - port: http
      interval: 15s

Using Sync Hooks

PreSync Hook: Database Migration Before Deployment

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  namespace: production
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: '-1'
spec:
  backoffLimit: 3
  template:
    spec:
      containers:
        - name: migrate
          image: myorg/db-migrator:v2.5.0
          command: ['./migrate', 'up']
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
      restartPolicy: Never

PostSync Hook: Post-Deployment Verification

apiVersion: batch/v1
kind: Job
metadata:
  name: smoke-test
  namespace: production
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 1
  template:
    spec:
      containers:
        - name: smoke-test
          image: myorg/smoke-tester:latest
          command:
            - /bin/sh
            - -c
            - |
              echo "Running smoke tests..."
              curl -sf http://my-app.production.svc:8080/healthz || exit 1
              curl -sf http://my-app.production.svc:8080/api/v1/status || exit 1
              echo "All smoke tests passed!"
      restartPolicy: Never

SyncFail Hook: Notification on Sync Failure

apiVersion: batch/v1
kind: Job
metadata:
  name: sync-fail-notification
  namespace: production
  annotations:
    argocd.argoproj.io/hook: SyncFail
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 1
  template:
    spec:
      containers:
        - name: notify
          image: curlimages/curl:latest
          command:
            - /bin/sh
            - -c
            - |
              curl -X POST "$SLACK_WEBHOOK_URL" \
                -H 'Content-Type: application/json' \
                -d '{"text":"ArgoCD Sync FAILED for my-app in production!"}'
          env:
            - name: SLACK_WEBHOOK_URL
              valueFrom:
                secretKeyRef:
                  name: slack-webhook
                  key: url
      restartPolicy: Never

Secrets Management

Sealed Secrets Integration

# Install Sealed Secrets controller
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace kube-system

# Encrypt secret with kubeseal CLI
kubectl create secret generic db-credentials \
  --namespace production \
  --from-literal=url='postgresql://user:pass@db:5432/mydb' \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-db-credentials.yaml
# SealedSecret safe to commit to Git
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
spec:
  encryptedData:
    url: AgA2X5N0Q...encrypted...base64==
  template:
    metadata:
      name: db-credentials
      namespace: production
    type: Opaque

External Secrets Operator Integration

# ExternalSecret: Sync secrets from AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
  annotations:
    argocd.argoproj.io/sync-wave: '-2'
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: url
      remoteRef:
        key: production/database
        property: connection_url
    - secretKey: password
      remoteRef:
        key: production/database
        property: password
---
# ClusterSecretStore definition
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmanager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-2
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

Multi-Cluster Deployment

Cluster Registration

# Register target clusters (kubeconfig context-based)
argocd cluster add staging-cluster \
  --name staging \
  --label env=staging \
  --label region=ap-northeast-2

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

# Verify registered clusters
argocd cluster list

Per-Cluster Configuration Separation

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-services
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ['missingkey=error']
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchLabels:
                  env: production
          - list:
              elements:
                - app: cert-manager
                  namespace: cert-manager
                  chart: cert-manager
                  repoURL: https://charts.jetstack.io
                  targetRevision: v1.16.0
                - app: external-secrets
                  namespace: external-secrets
                  chart: external-secrets
                  repoURL: https://charts.external-secrets.io
                  targetRevision: 0.12.0
                - app: metrics-server
                  namespace: kube-system
                  chart: metrics-server
                  repoURL: https://kubernetes-sigs.github.io/metrics-server
                  targetRevision: 3.12.0
  template:
    metadata:
      name: '{{.app}}-{{.name}}'
      namespace: argocd
    spec:
      project: platform
      source:
        repoURL: '{{.repoURL}}'
        chart: '{{.chart}}'
        targetRevision: '{{.targetRevision}}'
        helm:
          releaseName: '{{.app}}'
      destination:
        server: '{{.server}}'
        namespace: '{{.namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

Rollback Strategies

Automatic Rollback Configuration

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://github.com/myorg/k8s-manifests.git
    targetRevision: main
    path: apps/my-app/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 3
      backoff:
        duration: 10s
        factor: 2
        maxDuration: 5m

Manual Rollback via CLI

# Check deployment history
argocd app history my-app

# Rollback to a specific revision
argocd app rollback my-app 5

# Or sync to a specific Git commit
argocd app sync my-app --revision abc123def

# Verify sync status
argocd app get my-app
argocd app wait my-app --health

Monitoring Setup

ArgoCD Prometheus Metrics

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-metrics
  namespace: argocd
spec:
  selector:
    matchLabels:
      app.kubernetes.io/part-of: argocd
  endpoints:
    - port: metrics
      interval: 30s
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: argocd-alerts
  namespace: argocd
spec:
  groups:
    - name: argocd
      rules:
        - alert: ArgoCDAppOutOfSync
          expr: |
            argocd_app_info{sync_status="OutOfSync"} == 1
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: 'Application {{ "{{" }} $labels.name {{ "}}" }} is OutOfSync for more than 10 minutes'
        - alert: ArgoCDAppDegraded
          expr: |
            argocd_app_info{health_status="Degraded"} == 1
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: 'Application {{ "{{" }} $labels.name {{ "}}" }} is Degraded'
        - alert: ArgoCDSyncFailed
          expr: |
            increase(argocd_app_sync_total{phase="Failed"}[10m]) > 0
          labels:
            severity: critical
          annotations:
            summary: 'ArgoCD sync failed for {{ "{{" }} $labels.name {{ "}}" }}'

Slack Notification Configuration

# argocd-notifications-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  trigger.on-sync-failed: |
    - when: app.status.operationState.phase in ['Error', 'Failed']
      send: [app-sync-failed]
  trigger.on-health-degraded: |
    - when: app.status.health.status == 'Degraded'
      send: [app-health-degraded]
  trigger.on-sync-succeeded: |
    - when: app.status.operationState.phase in ['Succeeded']
      oncePer: app.status.sync.revision
      send: [app-sync-succeeded]
  template.app-sync-failed: |
    slack:
      attachments: |
        [{
          "color": "#E96D76",
          "title": "Sync Failed: {{.app.metadata.name}}",
          "text": "Application {{.app.metadata.name}} sync failed.\nRevision: {{.app.status.sync.revision}}\nMessage: {{.app.status.operationState.message}}"
        }]
  template.app-health-degraded: |
    slack:
      attachments: |
        [{
          "color": "#f4c030",
          "title": "Health Degraded: {{.app.metadata.name}}",
          "text": "Application {{.app.metadata.name}} health is degraded."
        }]
  template.app-sync-succeeded: |
    slack:
      attachments: |
        [{
          "color": "#18BE52",
          "title": "Sync Succeeded: {{.app.metadata.name}}",
          "text": "Application {{.app.metadata.name}} synced successfully.\nRevision: {{.app.status.sync.revision}}"
        }]

Operational Notes

1. ApplicationSet Security

If the project field in your ApplicationSet is templated, developers may be able to create Applications under Projects with excessive permissions. Always ensure the project field references a source controlled by administrators only.

2. Auto-Sync Caution

automated.prune: true deletes cluster resources that have been removed from Git. If you accidentally delete a manifest, production resources will be immediately removed. For critical resources, add the argocd.argoproj.io/sync-options: Prune=false annotation.

3. Webhook Security

If ArgoCD is publicly accessible, you must configure webhook secrets to prevent DDoS attacks.

4. Repo Server Resources

When processing large repositories or Helm charts, Repo Server memory usage can spike significantly. Configure appropriate resource limits and replica counts.

5. Abstraction Layer Limits

When using the Application of Applications pattern, avoid more than 3 levels of abstraction. Nesting 4-5 levels makes debugging extremely difficult.

Failure Cases and Recovery Procedures

Case 1: Sync Infinite Loop

# Symptom: Application keeps cycling through Syncing state
# Cause: Drift detection due to missing ignoreDifferences

# Diagnosis
argocd app diff my-app
argocd app get my-app --show-operation

# Recovery: Add ignoreDifferences
kubectl patch application my-app -n argocd --type merge -p '{
  "spec": {
    "ignoreDifferences": [
      {
        "group": "apps",
        "kind": "Deployment",
        "jsonPointers": ["/spec/replicas"]
      }
    ]
  }
}'

Case 2: Repo Server OOM

# Symptom: Application in Unknown state, manifest generation fails
# Diagnosis
kubectl logs -n argocd deploy/argocd-repo-server --previous
kubectl top pods -n argocd

# Recovery: Increase resource limits
kubectl patch deployment argocd-repo-server -n argocd --type json -p '[
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/resources/limits/memory",
    "value": "4Gi"
  }
]'

# Restart Repo Server
kubectl rollout restart deployment/argocd-repo-server -n argocd

Case 3: Secret Sync Failure (External Secrets)

# Symptom: Application is Healthy but Pod is in CrashLoopBackOff
# Cause: ExternalSecret has not yet synchronized

# Diagnosis
kubectl get externalsecret -n production
kubectl describe externalsecret db-credentials -n production

# Recovery: Force ExternalSecret sync
kubectl annotate externalsecret db-credentials \
  -n production \
  force-sync=$(date +%s) --overwrite

# Or ensure ordering with Sync Waves
# ExternalSecret: sync-wave=-2, Deployment: sync-wave=0

Case 4: Unintended ApplicationSet Deletion

# Symptom: Existing Applications deleted after ApplicationSet modification
# Cause: Generator config error reducing matched items

# Prevention: Set preserveResourcesOnDeletion
kubectl patch applicationset my-appset -n argocd --type merge -p '{
  "spec": {
    "syncPolicy": {
      "preserveResourcesOnDeletion": true
    }
  }
}'

# Recovery: Restore previous ApplicationSet config from Git history
git log --oneline -- applicationsets/my-appset.yaml
git checkout HEAD~1 -- applicationsets/my-appset.yaml
git commit -m "Revert ApplicationSet to restore applications"
git push

Conclusion

ArgoCD serves as the cornerstone of GitOps, providing everything needed for production environments: multi-cluster automation through ApplicationSet, precise deployment order control through Sync Waves and Hooks, and security hardening through RBAC and secrets management.

The key is maintaining Git as the Single Source of Truth while configuring the appropriate level of automation. Rather than applying auto-sync to all environments, a graduated approach is recommended: auto-sync for dev/staging and manual sync for production. Always configure monitoring and alerting together to quickly detect and respond to sync failures or health check anomalies.

References