IT/CI CD

ArgoCD ApplicationSet Progressive Sync 실무 가이드

gfrog 2026. 8. 22. 12:18

ArgoCD ApplicationSet Progressive Sync 실무 가이드

멀티 클러스터로 ArgoCD를 굴리다 보면 한 번은 겪는 상황이 있다. 앱 매니페스트를 하나 바꿨는데 그게 20개 클러스터에 동시에 동기화되면서, 하필 그 변경이 문제였을 때 20개가 같이 뻗어버리는 케이스. 우리 팀도 지난 분기에 한 번 크게 데었고, 그 이후로 ApplicationSet Progressive Sync를 프로덕션에 도입했다.

이 글은 도입하면서 정리한 내용이다. 문서만 보고 따라 하면 놓치기 쉬운 부분 위주로 적었다. Progressive Sync 자체는 2026년 현재 Beta로 승격됐는데, 여전히 experimental 플래그로 활성화해야 한다는 점을 먼저 짚어둔다.

Progressive Sync가 뭘 하는가

기본 ApplicationSet은 generator가 뽑은 모든 대상 클러스터/앱을 병렬로 동기화한다. 편하긴 한데, 나쁜 매니페스트를 만들면 그 병렬성이 그대로 사고 반경이 된다.

Progressive Sync는 이 동기화 순서를 단계별로 쪼갠다. 예를 들어 stage-1은 dev 클러스터 하나, stage-2는 staging 3개, stage-3는 prod 전체 이런 식이다. 각 stage는 maxUpdate로 몇 개까지 동시에 진행할지 정할 수 있고, 다음 stage로 넘어가려면 이번 stage의 앱들이 전부 Healthy가 되어야 한다.

stage 하나가 실패하면 그 시점에서 전체가 멈춘다. 사고 반경이 stage 단위로 잘리는 게 핵심이다.

활성화가 먼저다

이 부분에서 처음에 삽질했다. Progressive Sync는 controller 옵션을 켜야 한다. RollingSync 전략을 매니페스트에 써놔도 플래그가 꺼져 있으면 그냥 무시된다. 에러도 안 뜬다.

세 가지 방법이 있는데, 우리는 argocd-cmd-params-cm ConfigMap을 쓰는 방식으로 갔다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cmd-params-cm
  namespace: argocd
data:
  applicationsetcontroller.enable.progressive.syncs: "true"

ConfigMap을 바꾼 뒤에는 applicationset-controller Pod를 재시작해야 반영된다. 이거 안 하고 왜 안 되지 하면서 30분 날렸다.

RollingSync 전략의 실제 모양

가장 흔한 시나리오는 클러스터 라벨을 기준으로 rollout order를 만드는 것이다. 우리는 cluster secret에 env=dev|staging|prod 라벨을 붙여놓고 이걸로 stage를 나눴다.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: platform-agent
  namespace: argocd
spec:
  generators:
  - clusters:
      selector:
        matchLabels:
          managed-by: platform-team
  strategy:
    type: RollingSync
    rollingSync:
      steps:
      - matchExpressions:
        - key: env
          operator: In
          values: [dev]
        maxUpdate: 100%
      - matchExpressions:
        - key: env
          operator: In
          values: [staging]
        maxUpdate: 50%
      - matchExpressions:
        - key: env
          operator: In
          values: [prod]
        maxUpdate: 1
  template:
    metadata:
      name: '{{name}}-platform-agent'
    spec:
      project: platform
      source:
        repoURL: https://github.com/example/platform-agent
        targetRevision: main
        path: manifests
      destination:
        server: '{{server}}'
        namespace: platform
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

여기서 maxUpdate는 stage 내부의 동시성이다. prod stage의 maxUpdate: 1은 클러스터 한 개씩만 순차 진행한다는 뜻이고, dev는 어차피 하나뿐이라 100%로 뒀다. staging은 3개를 반반씩 굴리려고 50%로 했다.

Healthy 판정을 뭐로 볼 것인가

Progressive Sync의 progress 판정은 앱의 health status에 의존한다. 그래서 각 앱의 health check가 정확하지 않으면 다음 stage로 넘어가는 시점이 이상해진다.

우리가 겪은 문제 하나: Deployment는 rollout이 끝났는데 그 뒤에 실행되는 Job이 아직 돌고 있는 경우. Argo 기본 health는 Deployment만 보고 Healthy로 판정하고 다음 stage로 넘어간다. Job이 실패하면 이미 다음 stage가 진행되고 있어서 롤백 타이밍을 놓친다.

이건 argocd-cm에 custom health check를 넣어서 해결했다. Job이 있는 앱의 경우 Job의 succeed까지 봐야 Healthy가 되도록.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  resource.customizations.health.batch_Job: |
    hs = {}
    if obj.status ~= nil then
      if obj.status.succeeded ~= nil and obj.status.succeeded > 0 then
        hs.status = "Healthy"
        hs.message = "Job completed"
        return hs
      end
      if obj.status.failed ~= nil and obj.status.failed > 0 then
        hs.status = "Degraded"
        hs.message = "Job failed"
        return hs
      end
    end
    hs.status = "Progressing"
    hs.message = "Job in progress"
    return hs

Lua로 짜야 해서 처음 봤을 때 좀 낯설다. 다만 이건 이미 있는 사람도 있을 거라 우리 것과 비교해서 다르면 그대로 쓰지 말고 팀 컨벤션을 맞춰라.

실패 시 어떻게 되는가

stage 하나가 Degraded로 떨어지면 그 stage에서 멈춘다. 그런데 여기서 오해하기 쉬운 게 있다. 자동 롤백은 안 된다. progressive sync는 "나쁜 변경의 확산"만 막지, 이미 배포된 stage를 되돌리진 않는다.

즉 dev와 staging까지는 나간 상태에서 prod에서 멈추면, dev/staging은 여전히 새 버전이다. 롤백은 Git 되돌리기(revert commit) + 다시 sync가 정답이다.

우리는 이거 처음에 몰라서 "stage 실패했으니까 다 자동으로 이전 커밋 상태로 돌아가는 거 아니야?" 라고 착각했다. Argo Rollouts와 헶갈리면 안 된다. Argo Rollouts는 앱 내부 배포 전략(canary/blue-green)이고, ApplicationSet Progressive Sync는 여러 앱/클러스터를 stage로 나누는 오케스트레이션이다. 둘은 레벨이 다르다.

모니터링해야 할 것

Progressive Sync 도입 후 우리가 알림을 추가한 지표는 두 가지다.

  • argocd_appset_labels와 함께 각 앱의 sync/health 상태를 카운팅해서, 특정 stage에서 오래 멈춰 있으면 알림. 이건 사고 반경이 잘렸다는 좋은 신호일 수도, 그냥 health check가 이상한 신호일 수도 있어서 사람이 봐야 한다.
  • ApplicationSet controller의 reconcile latency. Progressive Sync를 켠 뒤로 reconcile이 무거워지는 케이스가 있었다. 대규모 클러스터셋에서는 controller resource limit도 다시 봐야 한다.

Grafana 대시보드는 argocd 공식 대시보드 그대로 쓰기엔 stage별 뷰가 없어서, ApplicationSet label을 축으로 하는 패널을 몇 개 추가했다.

도입 순서 추천

한 번에 다 갈아엎지 말고 아래 순서로 갔다.

  1. Non-critical 워크로드(관측 에이전트, 로그 수집기 등) 하나만 골라서 Progressive Sync 켬
  2. 실제로 나쁜 매니페스트를 dev에 일부러 넣어 stage-1에서 멈추는지 확인
  3. health check 커스터마이즈가 필요한 워크로드 식별
  4. 프로덕션 크리티컬 워크로드로 확대

특히 2번을 꼭 해봐라. 안 켜져 있는데 켜졌다고 착각한 채로 프로덕션에 도입하면 아무 방어막 없이 굴리는 셈이다.

마무리

솔직히 도입 자체는 오래 걸리지 않는다. 반나절이면 세팅은 끝난다. 그런데 health check를 신뢰할 수 있게 만드는 작업이 진짜다. 그게 안 되면 progressive sync는 그냥 stage 나누는 흉내만 낸다.

우리 팀은 이번 분기 목표 중 하나가 "health check 정확도 100%"인데, 지금 절반쯤 왔다. 이건 좀 더 굴려보고 다음에 정리해서 쓰겠다. 혹시 health check Lua 짤 때 참고할 만한 사내 컨벤션 있으신 분 계시면 댓글 남겨주시면 감사하겠다.