IT/CI CD

ArgoCD ApplicationSet으로 12개 클러스터 한 번에 날린 이야기

gfrog 2026. 6. 16. 15:12
SMALL

ArgoCD ApplicationSet으로 12개 클러스터 한 번에 날린 이야기

지난 금요일 저녁이었다. 정확히는 금요일 저녁 8시 47분. 슬랙이 한 번에 12번 울렸다. PagerDuty도 같이 울렸다. 12개 리전 prod 클러스터의 핵심 워크로드가 동시에 CrashLoopBackOff에 빠진 거였다.

원인은 단순했다. ArgoCD ApplicationSet의 matrix generator에 새 컨테이너 이미지 tag를 commit했고, 그게 모든 클러스터에 동시에 sync된 거다. 캐너리도 없고, 단계적 롤아웃도 없었다. 그냥 한 방에 전부.

이 글은 그날 밤 11시 40분까지 이어진 복구 과정과, 그 후에 progressive sync를 도입하면서 배운 것들에 대한 회고다.

우리가 어쩌다 12개를 한 번에 띄우게 됐는가

배경부터 말하자면 우리 팀은 작년 11월에 ArgoCD ApplicationSet으로 마이그레이션을 끝냈다. 그 전에는 클러스터마다 Application 리소스를 하나씩 직접 만들고 있었는데, 클러스터 수가 12개로 늘면서 더는 감당이 안 됐다.

matrix generator는 깔끔했다. cluster generator로 12개 클러스터를 자동 탐지하고, list generator로 각 마이크로서비스를 정의해서 곱셈. 결과적으로 약 540개 Application이 자동으로 만들어졌다. 사람이 손댈 일이 거의 없어졌다.

너무 깔끔해서 다들 만족했다. 문제는 "변경이 일어났을 때"였는데, 그건 그때까지 진지하게 고민하지 않았다. dev/staging에서 충분히 테스트하면 prod는 안전하다고 막연히 생각했다. 솔직히 말하면 그냥 안 터졌으니까 그렇게 믿었던 거지.

그날 무슨 일이 있었나

문제의 PR은 인증 서비스 이미지를 v2.14.3에서 v2.15.0으로 올리는 것이었다. staging에서 1주일 돌렸고, 카나리아 환경에서도 문제없었다. 머지하고 퇴근 준비 중이었다.

# applicationset.yaml의 일부
spec:
  generators:
  - matrix:
      generators:
      - clusters:
          selector:
            matchLabels:
              env: production
      - list:
          elements:
          - service: auth-service
            image: company/auth:v2.15.0  # 이게 문제

ApplicationSet controller는 정직하게 일했다. 12개 클러스터의 540개 Application 중 auth-service에 해당하는 12개를 동시에 sync 시작. auth-service는 모든 서비스가 의존하는 인증 게이트웨이였고, 시작 시점에 새로 추가된 환경변수를 읽으려고 시도했다. 그 환경변수는 ConfigMap에 있어야 했는데, ConfigMap 변경은 다른 PR로 분리되어 있었다.

그렇다. Helm chart 의존성 순서 문제였다. ConfigMap이 먼저 배포되어야 새 이미지가 정상 부팅된다. 근데 ApplicationSet은 그걸 알 리가 없었다.

12개 클러스터에서 동시에 auth-service가 CrashLoopBackOff. 모든 사용자 로그인 실패. P0.

복구는 정공법밖에 없었다

처음 5분은 멘탈이 나갔다. 어디부터 손대야 할지 감이 안 왔다. 12개 클러스터를 다 만져야 하는데 ArgoCD UI는 이미 알람 폭격으로 느려진 상태였다.

결국 한 가지로 정리했다. ConfigMap 변경 PR을 즉시 머지하고 sync하는 것. 다행히 그건 별도로 준비되어 있었다. 머지하고 ArgoCD CLI로 12개 클러스터에 동시 sync:

for cluster in prod-{ap1,ap2,us1,us2,us3,us4,eu1,eu2,eu3,eu4,sa1,af1}; do
  argocd app sync auth-config-${cluster} --prune &
done
wait

3분 후 모든 클러스터에서 auth-service가 정상화. 약 8분 동안 전체 장애. 사고 보고서를 쓰면서 손이 떨렸다.

progressive sync 도입은 생각보다 깐깐했다

다음 주 월요일 회고에서 결론은 명확했다. ApplicationSet의 progressive sync를 켜야 한다. 근데 도입은 생각보다 깐깐했다.

먼저 feature flag가 필요했다. ApplicationSet controller에 --enable-progressive-syncs 플래그를 켜지 않으면 strategy 블록을 써도 그냥 무시된다. 이거 모르고 첫 번째 시도에서 한참 헤맸다. strategy를 다 적었는데 여전히 한 번에 sync되길래 "왜?" 한참 봤다.

flag를 켠 다음 strategy를 작성했다:

spec:
  strategy:
    type: RollingSync
    rollingSync:
      steps:
      - matchExpressions:
        - key: env
          operator: In
          values: [production]
        - key: tier
          operator: In
          values: [canary]
        maxUpdate: 100%
      - matchExpressions:
        - key: env
          operator: In
          values: [production]
        - key: tier
          operator: In
          values: [low-traffic]
        maxUpdate: 50%
      - matchExpressions:
        - key: env
          operator: In
          values: [production]
        - key: tier
          operator: In
          values: [high-traffic]
        maxUpdate: 25%

각 클러스터에 tier 레이블을 미리 붙여놨다. canary 1개, low-traffic 3개, high-traffic 8개. 첫 단계에서 카나리아만 sync되고 모든 Application이 Healthy가 되어야 다음 단계로 넘어간다. low-traffic은 50%씩 두 배치, high-traffic은 25%씩 네 배치.

여기서 또 하나 배운 것. maxUpdate는 동시에 시작하는 Application 수가 아니라, "이 단계에서 동시에 sync 중일 수 있는 최대 수"다. 단계 안에서 차례차례 진행되는 게 아니라 그 비율만큼 병렬로 진행된다. 처음엔 차례차례 가는 줄 알고 "왜 이렇게 빠르지?" 했다.

그 후 6개월

도입 후 6개월이 지났다. 이번에 KubeCon EU 2026에서도 ApplicationSet progressive sync는 안정화 단계라는 발표가 있었고, RollingSync 외에 다른 전략들도 실험적으로 들어오고 있다.

운영하면서 느낀 건 progressive sync가 만능은 아니라는 거다. canary 단계의 health check가 충분히 정교하지 않으면 결국 다 통과하고 high-traffic에서 터진다. 우리는 첫 단계에서 5분 베이크 타임 + Prometheus alerting rule 통과를 PostSync hook으로 검증하도록 추가했다.

또 한 가지. RollingSync는 sync 시점만 통제한다. 이미 sync된 클러스터에서 점진적으로 다시 망가지는 경우(예: 메모리 누수)는 잡지 못한다. 그건 별도의 모니터링과 자동 롤백 시스템이 필요하다.

그래서 결론

ApplicationSet은 강력하지만, "한 commit으로 540개 Application이 동시에 움직일 수 있다"는 점을 늘 의식해야 한다. matrix generator 같은 거 쓸 때는 더더욱. progressive sync는 그 동시성을 통제하는 안전벨트인데, 안전벨트는 차에 타기 전에 매야지 사고 난 후에 매봐야 의미 없다.

우리는 사고를 한 번 겪고 나서야 매기 시작했다. 이 글을 읽으시는 분들은 매도 안 되는 거 매야 한다.

혹시 ApplicationSet으로 멀티 클러스터 운영하시는 분들 중에 다른 progressive 전략 쓰시는 분 있으면 공유 부탁드립니다. 우리도 아직 검증 중인 부분이 많습니다.

BIG