Skip to content
Published on

ArgoCD Sync 엔진 분석: 동기화 메커니즘의 모든 것

Authors

1. Sync 엔진 개요

ArgoCD의 Sync 엔진은 Git 저장소에 정의된 원하는 상태(Desired State)를 Kubernetes 클러스터에 적용하는 핵심 모듈입니다. 단순한 kubectl apply를 넘어 Hook, Wave, Health Check, Retry 등 정교한 메커니즘을 제공합니다.

Sync 상태 머신

Pending --> Running --> Succeeded
                |
                +--> Failed --> (Retry or Manual)

Sync 작업은 다음 상태를 가집니다:

상태설명
PendingSync가 대기열에 있음
RunningSync가 실행 중
Succeeded모든 리소스가 성공적으로 동기화됨
FailedSync 중 오류 발생

2. Sync Phases (동기화 단계)

ArgoCD는 Sync를 여러 단계(Phase)로 나누어 실행합니다. 각 단계에서 특정 유형의 리소스를 처리합니다.

Phase 실행 순서

PreSync --> Sync --> PostSync
              |
              +--> SyncFail (Sync 실패 시에만)

PreSync Phase

PreSync는 메인 동기화 전에 실행되는 단계입니다:

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: myapp/migration:latest
          command: ['./migrate', 'up']
      restartPolicy: Never

PreSync 사용 사례:

  • 데이터베이스 스키마 마이그레이션
  • 설정 사전 검증
  • 외부 시스템 상태 확인
  • 백업 생성

Sync Phase

메인 동기화 단계에서는 실제 Kubernetes 리소스를 클러스터에 적용합니다:

1. 모든 Sync Phase 리소스를 Wave 순서로 정렬
2.Wave 그룹별로:
   a. 리소스 타입 순서에 따라 적용
   b.  리소스에 대해 kubectl apply 동등 작업 수행
   c. 해당 Wave의 모든 리소스가 Healthy가 될 때까지 대기
3. 다음 Wave로 진행

PostSync Phase

PostSync는 메인 동기화가 성공한 후에만 실행됩니다:

apiVersion: batch/v1
kind: Job
metadata:
  name: notification
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
        - name: notify
          image: curlimages/curl:latest
          command:
            - curl
            - -X
            - POST
            - https://hooks.slack.com/services/XXX
            - -d
            - '{"text":"Deployment successful"}'
      restartPolicy: Never

PostSync 사용 사례:

  • 배포 완료 알림 전송
  • 스모크 테스트 실행
  • CDN 캐시 무효화
  • 외부 시스템 동기화 트리거

SyncFail Phase

SyncFail은 Sync가 실패했을 때만 실행됩니다:

apiVersion: batch/v1
kind: Job
metadata:
  name: failure-notification
  annotations:
    argocd.argoproj.io/hook: SyncFail
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
        - name: notify-failure
          image: curlimages/curl:latest
          command:
            - curl
            - -X
            - POST
            - https://hooks.slack.com/services/XXX
            - -d
            - '{"text":"Deployment FAILED"}'
      restartPolicy: Never

3. Resource Hooks 상세

Hook 어노테이션

metadata:
  annotations:
    # Hook 유형 지정
    argocd.argoproj.io/hook: PreSync|Sync|PostSync|SyncFail|Skip
    # Hook 리소스 삭제 정책
    argocd.argoproj.io/hook-delete-policy: HookSucceeded|HookFailed|BeforeHookCreation

Hook Delete Policy

정책설명
HookSucceededHook이 성공하면 리소스를 삭제
HookFailedHook이 실패하면 리소스를 삭제
BeforeHookCreation다음 Sync에서 Hook을 생성하기 전에 기존 리소스를 삭제

BeforeHookCreation이 기본값이며, 가장 많이 사용됩니다. 이 정책은 다음 Sync 시 이전 Hook 리소스를 먼저 삭제한 후 새로 생성합니다.

Hook 실행 메커니즘

1. Application Controller가 Sync 시작
2. 현재 Phase에 해당하는 Hook 리소스 식별
3. Hook 리소스를 Wave 순서로 정렬
4.Hook 리소스를 클러스터에 적용 (Job, Pod)
5. Hook이 완료(성공/실패)될 때까지 대기
6. 성공 시 다음 단계 진행, 실패 시 Sync 중단 또는 SyncFail Phase로 전환
7. Delete Policy에 따라 Hook 리소스 정리

4. Sync Waves와 순서 제어

Sync Wave 개념

Sync Wave는 리소스 적용 순서를 세밀하게 제어하는 메커니즘입니다:

# Wave -1: 인프라 리소스 (먼저 생성)
apiVersion: v1
kind: Namespace
metadata:
  name: my-app
  annotations:
    argocd.argoproj.io/sync-wave: '-1'

---
# Wave 0: 설정 리소스 (기본값)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  annotations:
    argocd.argoproj.io/sync-wave: '0'

---
# Wave 1: 애플리케이션 리소스
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  annotations:
    argocd.argoproj.io/sync-wave: '1'

---
# Wave 2: 외부 접근 리소스
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    argocd.argoproj.io/sync-wave: '2'

Wave 실행 로직

1. 모든 리소스를 Wave 번호로 그룹화
2. 가장 낮은 Wave부터 순서대로 실행
3.Wave 내에서는 리소스 타입 기본 순서 적용
4. 현재 Wave의 모든 리소스가 Healthy가 될 때까지 대기
5. 다음 Wave로 진행
6. 어느 Wave에서든 실패 시 전체 Sync 중단

리소스 타입 기본 순서

동일 Wave 내에서 리소스는 다음 순서로 적용됩니다:

Phase 1: 네임스페이스와 기본 설정
  1. Namespace
  2. NetworkPolicy
  3. ResourceQuota
  4. LimitRange
  5. PodSecurityPolicy
  6. ServiceAccount
  7. Secret
  8. SecretList
  9. ConfigMap

Phase 2: RBAC
  10. ClusterRole
  11. ClusterRoleBinding
  12. Role
  13. RoleBinding

Phase 3: CRD
  14. CustomResourceDefinition

Phase 4: 스토리지와 볼륨
  15. PersistentVolume
  16. PersistentVolumeClaim
  17. StorageClass

Phase 5: 서비스
  18. Service
  19. Endpoints

Phase 6: 워크로드
  20. DaemonSet
  21. Deployment
  22. ReplicaSet
  23. StatefulSet
  24. Job
  25. CronJob

Phase 7: 라우팅
  26. Ingress
  27. IngressClass
  28. APIService

5. Resource Tracking (리소스 추적)

Tracking 방식

ArgoCD는 자신이 관리하는 리소스를 추적하기 위해 두 가지 방식을 제공합니다:

Annotation 방식 (기본값, 권장):

metadata:
  annotations:
    argocd.argoproj.io/tracking-id: 'my-app:apps/Deployment:default/nginx'

Label 방식 (레거시):

metadata:
  labels:
    app.kubernetes.io/instance: my-app

Tracking ID 구조

APP_NAME:GROUP/KIND:NAMESPACE/NAME

예시:
  my-app:apps/Deployment:default/nginx
  my-app:/Service:default/nginx-svc
  my-app:networking.k8s.io/Ingress:default/nginx-ingress

Tracking 방식 설정

argocd-cm ConfigMap에서 설정합니다:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  application.resourceTrackingMethod: annotation # annotation | label | annotation+label

6. Diff 엔진 상세 분석

3-Way Diff

ArgoCD는 세 가지 상태를 비교합니다:

1. Desired State: Git에서 생성된 매니페스트 (목표 상태)
2. Live State: 클러스터에서 실행 중인 실제 상태
3. Last Applied: 마지막으로 적용된 설정 (annotation에 기록)

Structured Merge Diff

ArgoCD는 Kubernetes의 Server-Side Apply에서 사용하는 Structured Merge Diff 라이브러리를 활용합니다:

// Diff 수행 로직 (간소화)
func diff(desired, live *unstructured.Unstructured) (*DiffResult, error) {
    // 1. 정규화
    normalizedDesired := normalize(desired)
    normalizedLive := normalize(live)

    // 2. 무시 필드 제거
    removeIgnoredFields(normalizedDesired)
    removeIgnoredFields(normalizedLive)

    // 3. 구조적 비교
    result := structuredMergeDiff(normalizedDesired, normalizedLive)

    return result, nil
}

Normalization (정규화) 상세

정규화는 불필요한 diff를 제거하기 위한 핵심 과정입니다:

제거 대상 필드:

  • metadata.resourceVersion
  • metadata.uid
  • metadata.generation
  • metadata.creationTimestamp
  • metadata.managedFields
  • status (대부분의 리소스)

정규화 규칙 예시:

  • Container의 imagePullPolicy가 생략되었지만 이미지 태그가 latest면 Kubernetes가 Always를 자동 설정 -> diff에서 무시
  • Service의 clusterIP가 생략되면 Kubernetes가 자동 할당 -> diff에서 무시
  • 빈 필드("", [], null)와 미설정 필드의 동등 처리

Diff 결과 해석

결과의미
NoDiff원하는 상태와 실제 상태가 동일 (Synced)
Diff차이 존재 (OutOfSync)
Modified필드 값이 변경됨
Added새 필드가 추가됨
Removed필드가 제거됨

Diff Customization

특정 필드를 diff에서 무시하도록 설정할 수 있습니다:

# Application 리소스에서 설정
spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas # HPA가 관리하는 replica 수 무시
    - group: ''
      kind: ConfigMap
      jqPathExpressions:
        - .data.generated-field # 자동 생성 필드 무시

글로벌 설정은 argocd-cm ConfigMap에서 가능합니다:

data:
  resource.customizations.ignoreDifferences.all: |
    managedFieldsManagers:
      - kube-controller-manager
      - kube-scheduler

7. Health Assessment (건강 상태 평가)

Built-in Health Check

ArgoCD는 주요 Kubernetes 리소스에 대해 내장 Health Check를 제공합니다:

Deployment:

Healthy: 모든 replica가 Ready이고 업데이트 완료
Progressing: 롤아웃이 진행  (ReplicaSet 생성 중)
Degraded: replica가 Ready 상태에 도달하지 못함

StatefulSet:

Healthy: 모든 replica가 Ready이고 currentRevision == updateRevision
Progressing: 업데이트가 진행 중
Degraded: replica가 Ready가 아님

Pod:

Healthy: Running 상태이고 모든 컨테이너가 Ready
Progressing: Pending 또는 ContainerCreating 상태
Degraded: CrashLoopBackOff, ImagePullBackOff

Service:

Healthy: Endpoints가 존재하고 하나 이상의 Ready 주소가 있음
Progressing: LoadBalancer 타입에서 외부 IP 할당 대기 중

Ingress:

Healthy: LoadBalancer 주소가 할당됨
Progressing: 주소 할당 대기 중

Job:

Healthy: 성공적으로 완료됨 (Completed)
Progressing: 실행  (Active)
Degraded: 실패 (Failed)

Custom Lua Health Check

기본 Health Check로 충분하지 않은 경우, Lua 스크립트로 커스텀 로직을 작성합니다:

# argocd-cm ConfigMap
data:
  resource.customizations.health.cert-manager.io_Certificate: |
    hs = {}
    if obj.status ~= nil then
      if obj.status.conditions ~= nil then
        for _, condition in ipairs(obj.status.conditions) do
          if condition.type == "Ready" then
            if condition.status == "True" then
              hs.status = "Healthy"
              hs.message = "Certificate is ready"
            else
              hs.status = "Degraded"
              hs.message = condition.message
            end
            return hs
          end
        end
      end
    end
    hs.status = "Progressing"
    hs.message = "Waiting for certificate"
    return hs

Health Check 대상 CRD 예시

CRD프로젝트Health Check 기준
Certificatecert-managerReady condition
VirtualServiceIstio항상 Healthy (상태 없음)
RolloutArgo Rolloutsphase 필드 기반
HelmReleaseFluxReady condition
KustomizationFluxReady condition

8. Pruning (리소스 정리) 상세

Prune 동작 원리

1. Git 매니페스트에서 모든 리소스 목록 생성
2. 클러스터에서 ArgoCD가 관리하는 리소스 조회 (tracking label/annotation)
3. 클러스터에 존재하지만 Git에 없는 리소스 식별 (= Prune 대상)
4. 삭제 정책에 따라 리소스 삭제

삭제 전략

Cascade 삭제 (기본값):

- 소유 리소스를 재귀적으로 삭제
- Deployment를 삭제하면 ReplicaSet과 Pod도 함께 삭제
- Kubernetes의 garbage collection 메커니즘 사용

Foreground 삭제:

- 소유 리소스가 먼저 삭제된 후 부모 리소스 삭제
- 순서가 보장되어야 하는 경우 사용
- finalizer를 통한 순서 제어

Prune 보호

의도하지 않은 삭제를 방지하기 위한 보호 메커니즘:

# 리소스에 Prune 방지 어노테이션 추가
metadata:
  annotations:
    argocd.argoproj.io/sync-options: Prune=false

# Application 수준에서 Prune 비활성화
spec:
  syncPolicy:
    automated:
      prune: false # 자동 Prune 비활성화

Orphan 리소스 모니터링

ArgoCD는 관리 대상이 아닌 "고아(Orphan)" 리소스도 감지할 수 있습니다:

# AppProject에서 Orphan 리소스 모니터링 활성화
spec:
  orphanedResources:
    warn: true # 경고만 표시
    # ignore:   # 무시할 리소스 패턴
    #   - group: ""
    #     kind: ConfigMap
    #     name: "auto-*"

9. Retry 전략과 Backoff

Auto-Sync Retry

Sync가 실패하면 자동으로 재시도할 수 있습니다:

spec:
  syncPolicy:
    automated:
      selfHeal: true
    retry:
      limit: 5 # 최대 재시도 횟수
      backoff:
        duration: 5s # 초기 대기 시간
        factor: 2 # 대기 시간 증가 배수
        maxDuration: 3m # 최대 대기 시간

Backoff 계산 예시

재시도 1: 5초 후
재시도 2: 10 (5s * 2)
재시도 3: 20 (10s * 2)
재시도 4: 40 (20s * 2)
재시도 5: 80 (40s * 2) -> 최대 3분으로 제한

Retry 트리거 조건

다음 상황에서 Retry가 트리거됩니다:

  • 리소스 적용 실패 (API 서버 오류, 유효성 검사 실패 등)
  • Health Check 타임아웃 (리소스가 시간 내에 Healthy가 되지 않음)
  • Hook 실행 실패 (PreSync/PostSync Job 실패)
  • 네트워크 일시적 오류

10. Sync 옵션

Application 수준 Sync 옵션

spec:
  syncPolicy:
    automated:
      prune: true # Git에서 삭제된 리소스 자동 정리
      selfHeal: true # 수동 변경 자동 교정
      allowEmpty: false # 빈 매니페스트 허용 여부
    syncOptions:
      - CreateNamespace=true # 네임스페이스 자동 생성
      - PrunePropagationPolicy=foreground # 삭제 전파 정책
      - PruneLast=true # 다른 리소스 동기화 후 Prune
      - Replace=false # apply 대신 replace 사용 여부
      - ServerSideApply=true # Server-Side Apply 사용
      - ApplyOutOfSyncOnly=true # OutOfSync 리소스만 적용
      - Validate=true # 매니페스트 유효성 검사
      - RespectIgnoreDifferences=true # ignoreDifferences 설정 존중

리소스 수준 Sync 옵션

개별 리소스에 어노테이션으로 설정할 수 있습니다:

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

Server-Side Apply

Server-Side Apply는 Kubernetes 1.22+에서 권장되는 적용 방식입니다:

장점:
  - 필드 소유권(Field Ownership) 추적
  - 여러 컨트롤러 간 충돌 방지
  - 더 정확한 3-way merge
  - 대규모 리소스에서 성능 향상

설정:
  syncOptions:
    - ServerSideApply=true

11. Sync Window (동기화 시간대)

Sync Window 개념

AppProject에서 동기화를 허용하거나 금지하는 시간대를 설정할 수 있습니다:

spec:
  syncWindows:
    # 평일 업무시간에만 Sync 허용
    - kind: allow
      schedule: '0 9 * * 1-5' # 월-금 09:00
      duration: 9h # 9시간 동안
      applications:
        - '*'
      namespaces:
        - 'production'
    # 주말에는 Sync 금지
    - kind: deny
      schedule: '0 0 * * 0,6' # 토,일 00:00
      duration: 24h
      applications:
        - '*'
    # 수동 Sync만 허용하는 시간대
    - kind: allow
      schedule: '0 18 * * 1-5' # 월-금 18:00
      duration: 15h
      manualSync: true
      applications:
        - 'critical-*'

Window 유형

유형설명
allow지정된 시간에만 Sync를 허용
deny지정된 시간에는 Sync를 금지

우선순위 규칙

1. deny가 allow보다 우선
2. 동일 우선순위면 더 구체적인 규칙이 우선
3. manualSync=true면 수동 Sync만 허용

12. 실전 Sync 전략

안전한 프로덕션 배포 전략

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: production-app
spec:
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - PruneLast=true
      - ServerSideApply=true
      - ApplyOutOfSyncOnly=true
    retry:
      limit: 3
      backoff:
        duration: 10s
        factor: 2
        maxDuration: 5m
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas # HPA 관리
    - group: autoscaling
      kind: HorizontalPodAutoscaler
      jqPathExpressions:
        - .status

Hook을 활용한 완전한 배포 파이프라인

# Wave -2: PreSync - DB 백업
apiVersion: batch/v1
kind: Job
metadata:
  name: db-backup
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: '-2'
spec:
  template:
    spec:
      containers:
        - name: backup
          image: backup-tool:latest
          command: ['./backup.sh']
      restartPolicy: Never

---
# Wave -1: PreSync - DB 마이그레이션
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: '-1'
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: myapp/migration:v2
          command: ['./migrate', 'up']
      restartPolicy: Never

---
# Wave 0: Sync - 메인 애플리케이션
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  annotations:
    argocd.argoproj.io/sync-wave: '0'
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:v2

---
# Wave 1: PostSync - 스모크 테스트
apiVersion: batch/v1
kind: Job
metadata:
  name: smoke-test
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
    argocd.argoproj.io/sync-wave: '1'
spec:
  template:
    spec:
      containers:
        - name: test
          image: curlimages/curl:latest
          command: ['curl', '-f', 'http://myapp:8080/health']
      restartPolicy: Never

13. 정리

ArgoCD Sync 엔진의 핵심 요소를 정리합니다:

  1. Phase: PreSync, Sync, PostSync, SyncFail로 배포 단계를 구조화
  2. Hook: Job이나 Pod로 각 Phase에서 커스텀 작업 실행
  3. Wave: 리소스 간 적용 순서를 세밀하게 제어
  4. Diff Engine: 3-Way Merge 기반의 정규화된 상태 비교
  5. Health Check: 내장 + Lua 커스텀으로 리소스 건강 상태 평가
  6. Pruning: Git에서 제거된 리소스의 안전한 정리
  7. Retry: 지수 백오프를 통한 자동 재시도
  8. Sync Window: 시간대 기반 동기화 제어

이러한 메커니즘을 적절히 조합하면 안전하고 예측 가능한 GitOps 배포 파이프라인을 구축할 수 있습니다.