지난주에 좀 크게 데였다. dev/staging/prod 순으로 조용히 굴러가야 할 ApplicationSet의 롤아웃이, 왜인지 prod 클러스터 4대에 동시에 꽂혔다. 그날 새벽 3시에 오라이 슬랙 알람이 뜨는 순간 침대에서 튕겨나왔다. 결론부터 말하면 Progressive Sync 설정을 내가 잘못 이해하고 있었던 거였는데, 정리해두면 나중에 나 같은 사람이 안 나올 것 같아서 남긴다.
상황
우리 팀은 EKS 클러스터를 리전별로 6개 굴린다. dev 1개, staging 1개, prod 4개(한/일/미/유). Helm 차트를 하나 만들어서 ApplicationSet의 Cluster generator로 뿌리는 구조. 이번에 백엔드 팀에서 auth 미들웨어를 바꾸면서 차트 값이 하나 바뀌었고, "이건 위험하니까 dev → staging → prod 순으로 최소 30분씩 텀 두고 나가게 해달라"는 요청이 들어왔다.
ApplicationSet의 Progressive Sync가 딱 그거라고 문서에서 봤던 게 기억나서 자신있게 "네" 했다. 근데 자신감이 근거가 없었다.
뭘 잘못했나
내가 처음 짠 설정은 이렇게 생겼었다.
spec:
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: env
operator: In
values: [dev]
- matchExpressions:
- key: env
operator: In
values: [staging]
- matchExpressions:
- key: env
operator: In
values: [prod]
generators:
- clusters:
selector:
matchLabels:
managed-by: argocd
문제 1. Progressive Sync는 컨트롤러 레벨 feature flag를 켜야 동작한다. ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS=true. 이걸 안 켜면 위 strategy 블록은 그냥 무시된다. YAML validation에도 안 걸리고 조용히 지나간다. 이게 나를 첫 번째로 태웠다. 새벽에 로그 뒤지면서 "왜 step이 안 찍히지?" 하다가 컨트롤러 env 확인하고 나서야 알았다.
문제 2. 이건 더 어이가 없는데, 클러스터에 env 라벨이 없었다. Cluster secret에 라벨을 붙여야 하는데 우리는 environment=prod 이렇게 다른 키를 쓰고 있었다. matchExpressions는 매칭 실패해도 에러가 아니라 "해당 step에는 대상 없음"으로 처리된다. 그래서 step 1, 2가 즉시 완료 처리되고, step 3에서 prod 4대가 한꺼번에 튀어나갔다. 이게 새벽 3시 사건의 실체.
문제 3. 이건 나중에 알게 된 건데, RollingSync는 아직 Beta다. "generally stable"이라고는 하지만, 각 step 완료 판정을 Application의 health status로 하는데, 이 health가 어떻게 계산되는지에 대해 팀 내에서 합의가 안 돼 있었다. Deployment가 Progressing 상태로 오래 머무는 워크로드가 하나 있었고, 이게 step을 무한히 blocking시키는 케이스도 있었다.
어떻게 고쳤나
일단 feature flag부터 켜고, 클러스터 secret의 라벨을 정리했다.
apiVersion: v1
kind: Secret
metadata:
name: cluster-prod-kr
labels:
argocd.argoproj.io/secret-type: cluster
env: prod
region: kr
tier: production
type: Opaque
라벨 스키마를 팀에서 다시 정의했다. env는 dev/staging/prod, region은 kr/jp/us/eu, tier는 sandbox/production. 이렇게 정리하니까 step matching이 훨씬 명확해졌다.
Prod 안에서도 한 번에 다 안 나가도록 리전 단위로 쪼갰다.
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- {key: env, operator: In, values: [dev]}
maxUpdate: 100%
- matchExpressions:
- {key: env, operator: In, values: [staging]}
maxUpdate: 100%
- matchExpressions:
- {key: env, operator: In, values: [prod]}
- {key: region, operator: In, values: [kr]}
maxUpdate: 1
- matchExpressions:
- {key: env, operator: In, values: [prod]}
- {key: region, operator: In, values: [jp, us, eu]}
maxUpdate: 1
kr에 먼저 나가고 30분 관찰, 그 다음 나머지 3개 리전은 하나씩 순차. maxUpdate가 있어서 한 step 안에서도 병렬을 제어할 수 있다.
아직 안 풀린 것
Step 간 대기시간(soak time) 설정은 아직 네이티브로 없다. 우린 그냥 sync를 사람이 승인하는 방식(syncPolicy.automated를 안 켠 상태)으로 우회하는데, 그러면 자동화의 의미가 반감된다. 커뮤니티에서 논의는 계속 있는 것 같지만 2026년 9월 기준으로 안정 버전에 들어온 건 아직 없다. 우리는 임시로 GitHub Actions로 값 커밋 → 알람 → 슬랙 승인 → 다음 step 라벨 붙이기 흐름을 만들었는데, 솔직히 좀 못생겼다. 더 나은 방법 쓰시는 분 있으면 알려주세요.
그리고 health 판정 이슈. 각 Application에 명시적 health check(예: argocd.argoproj.io/hook으로 Job 하나 걸어서 smoke test)를 넣는 걸 검토 중이다. 아직 검증 중이라 이건 다음에 정리해서 올려보려고 한다.
교훈
Progressive Sync는 켜져 있다고 착각하기 쉬운 기능이다. feature flag, 라벨 스키마, health 정의 이 세 개가 어긋나면 자동화가 오히려 사고를 키운다. 새 기능 도입할 때는 "prod에 4대가 동시에 나가면 어떻게 되는가"를 먼저 시뮬레이션해봐야 한다는 걸 다시 배웠다. 아 그리고, 새벽 알람은 정말 싫다.
'IT > CI CD' 카테고리의 다른 글
| GitHub Actions Reusable Workflow vs Composite Action, 뭘 쓸까 (0) | 2026.09.02 |
|---|---|
| Argo Rollouts vs Flagger, 뭘 쓸까 (0) | 2026.08.28 |
| ARC 0.14.0로 self-hosted runner 셋업하기, multilabel까지 (0) | 2026.08.25 |
| Renovate vs Dependabot, 컨테이너 이미지 업데이트 관점에서 (0) | 2026.08.22 |
| ArgoCD ApplicationSet Progressive Sync 실무 가이드 (0) | 2026.08.22 |