ArgoCD ApplicationSet Progressive Sync 실무 가이드
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을 축으로 하는 패널을 몇 개 추가했다.
도입 순서 추천
한 번에 다 갈아엎지 말고 아래 순서로 갔다.
- Non-critical 워크로드(관측 에이전트, 로그 수집기 등) 하나만 골라서 Progressive Sync 켬
- 실제로 나쁜 매니페스트를 dev에 일부러 넣어 stage-1에서 멈추는지 확인
- health check 커스터마이즈가 필요한 워크로드 식별
- 프로덕션 크리티컬 워크로드로 확대
특히 2번을 꼭 해봐라. 안 켜져 있는데 켜졌다고 착각한 채로 프로덕션에 도입하면 아무 방어막 없이 굴리는 셈이다.
마무리
솔직히 도입 자체는 오래 걸리지 않는다. 반나절이면 세팅은 끝난다. 그런데 health check를 신뢰할 수 있게 만드는 작업이 진짜다. 그게 안 되면 progressive sync는 그냥 stage 나누는 흉내만 낸다.
우리 팀은 이번 분기 목표 중 하나가 "health check 정확도 100%"인데, 지금 절반쯤 왔다. 이건 좀 더 굴려보고 다음에 정리해서 쓰겠다. 혹시 health check Lua 짤 때 참고할 만한 사내 컨벤션 있으신 분 계시면 댓글 남겨주시면 감사하겠다.