Argo CD ApplicationSet Matrix Generator로 멀티 클러스터 단계적 배포 자동화하기
쿠버네티스 클러스터가 두 개를 넘어서는 순간부터 배포 관리가 슬슬 골치 아파지기 시작합니다. dev, staging, prod 각각에 대해 Application YAML을 따로 관리하다 보면 어느 순간 복붙의 지옥에 빠지고, 신규 클러스터가 하나 추가될 때마다 누군가 YAML 파일을 손으로 추가해줘야 하는 상황이 반복됩니다. 저도 한동안 이 구조로 버텼는데, 어느 날 스테이징에만 반영된 리소스 변경이 프로덕션에서 빠진 채로 배포된 걸 뒤늦게 발견하고 나서야 제대로 된 해결책을 찾아봤습니다.
이 글에서는 CPU/메모리 requests·limits를 조정하는 리소스 최적화 PR을 Matrix Generator와 RollingSync 전략으로 dev → staging → prod 순서로 자동 배포하는 전체 구성을 구체적인 예시와 함께 풀어드립니다. ApplicationSet 하나로 클러스터 수만큼 Application이 자동 생성되고, dev가 정상이어야만 staging으로 넘어가는 단계적 파이프라인을 선언적으로 구성하는 방법입니다.
ApplicationSet을 처음 접하신다면 기초 개념부터, 이미 써보셨다면 Matrix Generator와 Progressive Sync 조합 부분만 참고하셔도 됩니다.
핵심 개념
ApplicationSet — 반복 YAML을 없애주는 컨트롤러
Argo CD를 처음 쓸 때는 Application 하나씩 직접 만들죠. 그런데 클러스터가 늘고 서비스가 늘어나면 Application YAML이 수십, 수백 개가 됩니다. ApplicationSet은 이 반복을 없애줍니다. Generator가 파라미터 목록을 만들어내면, 컨트롤러가 그 파라미터를 template에 주입해 Application을 자동으로 생성·관리해줍니다.
ApplicationSet: 단일 YAML 정의로 여러 Argo CD Application 리소스를 자동 생성·관리하는 컨트롤러입니다. Argo CD v2.3부터 코어에 통합되었고, v2.6 이상에서 Progressive Sync가 안정화됐습니다.
Cluster Generator — 클러스터 목록을 파라미터로
Cluster Generator는 Argo CD에 등록된 클러스터 시크릿을 스캔해서 각 클러스터의 메타데이터를 파라미터로 뽑아줍니다. 핵심은 레이블 기반 필터링입니다. 클러스터 시크릿에 environment: dev 같은 레이블만 붙여두면, 신규 클러스터를 프로비저닝하는 순간 Argo CD가 자동으로 감지해 Application을 생성합니다.
generators:
- clusters:
selector:
matchLabels:
environment: production # 이 레이블이 있는 클러스터만 타겟{{name}}, {{server}}, {{metadata.labels.environment}} 같은 변수로 클러스터 정보를 template에 그대로 주입할 수 있어서, 클러스터가 늘어도 YAML을 건드릴 필요가 없습니다.
Matrix Generator — 두 Generator의 조합
Matrix Generator는 두 하위 Generator가 생성하는 파라미터의 카르테시안 곱을 만들어냅니다. 3개 클러스터 × 4개 서비스 = 12개 Application이 자동으로 생기는 구조입니다.
generators:
- matrix:
generators:
- clusters: # ← 첫 번째 Generator: 클러스터 목록
selector:
matchExpressions:
- key: environment
operator: In
values: [dev, staging, prod]
- git: # ← 두 번째 Generator: 앱 디렉터리 목록
repoURL: https://github.com/org/k8s-manifests
revision: HEAD
directories:
- path: services/*한 가지 주의할 점이 있는데, 같은 클러스터에 동일 서비스를 여러 네임스페이스로 배포하는 경우 Application 이름({{name}}-{{path.basename}} 패턴)이 충돌할 수 있습니다. 이런 시나리오라면 네임스페이스나 고유 식별자를 이름에 함께 포함시키는 게 좋습니다.
주의: Matrix의 직접 하위 Generator는 공식 스펙상 2개까지입니다. 3개 이상 조합이 필요하면 Matrix를 중첩해서 사용해야 합니다.
Progressive Sync (RollingSync) — 단계별 배포 제어
솔직히 이 기능이 없었다면 ApplicationSet이 이렇게까지 매력적이지 않았을 것 같습니다. RollingSync를 쓰면 생성된 Application들을 단계별 그룹으로 묶어 순차 배포할 수 있습니다.
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: environment
operator: In
values: [dev] # 1단계: dev 전체
- matchExpressions:
- key: environment
operator: In
values: [staging] # 2단계: dev 완료 확인 후
- matchExpressions:
- key: environment
operator: In
values: [prod] # 3단계: staging 완료 확인 후각 단계의 모든 Application이 OutOfSync 상태가 없어지고 Healthy 상태가 될 때까지 다음 단계가 블로킹됩니다. Healthy만 확인하면 된다고 생각하기 쉬운데, 실제로는 OutOfSync 상태인 Application이 남아있으면 다음 단계로 진행되지 않습니다. dev 배포에서 OOM이 발생하거나 파드가 CrashLoopBackOff 상태가 되면, staging과 prod는 자동으로 차단됩니다.
Progressive Sync 활성화: 기본적으로 비활성화 상태입니다. Argo CD 서버 실행 시
--enable-progressive-syncs플래그를 추가해야 합니다. Helm으로 설치했다면server.extraArgs, Operator 기반이라면ArgoCDCR의server.extraCommandArgs에 추가하는 방식이 다릅니다.
실전 적용
예시 1: ApplicationSet YAML 전체 구성
CPU/메모리 requests·limits를 조정하는 PR을 dev → staging → prod 순서로 배포하는 전체 흐름입니다.
클러스터 레이블 설정
먼저 Argo CD에 등록된 클러스터 시크릿에 환경 레이블을 붙여줍니다. 이미 Argo CD에 등록된 클러스터라면 argocd.argoproj.io/secret-type=cluster 레이블은 이미 붙어있을 테니, 환경 레이블만 추가하시면 됩니다.
# 기존 등록 클러스터에 환경 레이블 추가
kubectl label secret dev-cluster \
environment=dev \
-n argocd
kubectl label secret stg-cluster \
environment=staging \
-n argocd
kubectl label secret prod-cluster \
environment=prod \
-n argocd새로 등록하는 클러스터라면 argocd.argoproj.io/secret-type=cluster 레이블도 함께 지정해야 Argo CD가 클러스터 시크릿으로 인식합니다.
ApplicationSet YAML 작성
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: resource-optimization-rollout
namespace: argocd
spec:
generators:
- matrix:
generators:
- clusters:
selector:
matchExpressions:
- key: environment
operator: In
values: [dev, staging, prod]
- git:
repoURL: https://github.com/org/k8s-manifests
# 운영 환경에서는 특정 태그나 커밋 SHA 사용 권장 (예: v1.2.3)
revision: HEAD
directories:
- path: services/*
template:
metadata:
# {{name}}은 클러스터명, {{path.basename}}은 서비스 디렉터리명 (api, worker 등)
name: '{{name}}-{{path.basename}}'
labels:
environment: '{{metadata.labels.environment}}'
spec:
project: default
source:
repoURL: https://github.com/org/k8s-manifests
targetRevision: HEAD
path: '{{path}}/overlays/{{metadata.labels.environment}}'
destination:
server: '{{server}}'
# 서비스명(api, worker 등)을 네임스페이스로 사용하는 전제
namespace: '{{path.basename}}'
syncPolicy:
automated:
prune: true
# selfHeal: true는 수동 변경을 자동으로 되돌립니다.
# 핫픽스 시나리오(파드 크래시 → 직접 패치 시도)에서 충돌 위험이 있으므로
# 운영 환경 적용 전 팀 내 핫픽스 프로세스를 먼저 정의하는 것을 권장합니다.
selfHeal: true
# automated syncPolicy와 RollingSync를 함께 쓰면, 자동 싱크 트리거 시에도
# RollingSync에 정의된 단계 순서가 유지됩니다.
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: environment
operator: In
values: [dev]
- matchExpressions:
- key: environment
operator: In
values: [staging]
- matchExpressions:
- key: environment
operator: In
values: [prod]| 필드 | 역할 | 예시 값 |
|---|---|---|
{{name}} |
클러스터 이름 | dev-cluster, prod-cluster |
{{path.basename}} |
Git 디렉터리명 (서비스명) | api, worker |
{{metadata.labels.environment}} |
클러스터 레이블에서 뽑은 환경 값 | dev, staging, prod |
{{server}} |
클러스터 API 서버 URL | https://1.2.3.4:6443 |
예시 2: Kustomize 오버레이 디렉터리 구조
Kustomize를 처음 접하신다면, base 디렉터리에 공통 리소스를 두고 overlays에서 환경별로 값을 덮어쓰는 패턴입니다. 자세한 내용은 Kustomize 공식 문서를 참고하시면 좋습니다.
리포지터리 구조를 아래처럼 잡으면 환경마다 다른 리소스 값을 깔끔하게 관리할 수 있습니다.
services/
api/
base/
deployment.yaml # 공통 템플릿
kustomization.yaml
overlays/
dev/
kustomization.yaml # cpu: 100m, memory: 128Mi
staging/
kustomization.yaml # cpu: 500m, memory: 512Mi
prod/
kustomization.yaml # cpu: 2000m, memory: 2Gi
worker/
base/
...
overlays/
dev/ staging/ prod/# services/api/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- patch: |-
# JSON Patch 형식: 특정 경로(path)의 값을 지정한 value로 교체합니다
- op: replace
path: /spec/template/spec/containers/0/resources
value:
requests:
cpu: "2000m"
memory: "2Gi"
limits:
cpu: "4000m"
memory: "4Gi"
target:
kind: DeploymentMatrix Generator가 services/* 디렉터리(api, worker, ...)와 클러스터(dev, staging, prod) 조합을 전부 만들어내고, {{path}}/overlays/{{metadata.labels.environment}} 경로로 각 환경에 맞는 오버레이를 자동으로 참조합니다. 서비스가 늘어나도 ApplicationSet YAML은 그대로입니다.
장단점 분석
진짜 주의해야 할 두 가지만 먼저 꼽자면, selfHeal: true와 Progressive Sync 플래그 누락입니다. 나머지는 운영 규모에 따라 선택적으로 고려하시면 되는데, 아래 표에 전체 내용을 정리해 두었습니다.
장점
| 항목 | 내용 |
|---|---|
| 선언적 관리 | 수백 개 Application을 단일 YAML로 정의, GitOps 원칙 완벽 준수 |
| 자동 클러스터 감지 | 레이블 기반으로 신규 클러스터 추가 시 자동 배포 대상 편입 |
| 안전한 단계적 배포 | RollingSync로 장애가 상위 환경으로 전파되지 않음 |
| Dry-run 가능 | --dry-run 플래그로 실제 배포 전 생성될 Application 목록 확인 가능 |
| 환경별 설정 분리 | Kustomize/Helm 오버레이로 환경마다 다른 리소스 값 적용 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| Matrix 2개 Generator 제한 | 직접 하위 Generator는 2개까지 | 3개 이상 필요 시 Matrix 중첩 사용 |
| Progressive Sync 별도 활성화 | 기본 비활성화, 플래그 추가 필수 | Helm: server.extraArgs, Operator: server.extraCommandArgs에 --enable-progressive-syncs 추가 |
| automated + RollingSync 동작 확인 | 자동 싱크 트리거 시 RollingSync 순서가 유지되는지 환경 검증 필요 | 충분한 스테이징 검증 후 운영 적용 권장 |
| 대규모 클러스터 성능 | 수백 개 클러스터에서 컨트롤러 메모리 사용량 급증 가능 | argocd-agent 아키텍처 전환 검토 (아래 각주 참조) |
| 레이블 오염 위험 | 레이블 누락·오타 시 배포 누락 | CI 파이프라인에서 레이블 유효성 검증 추가 |
| selfHeal 주의 | selfHeal: true 상태에서 수동 패치 시 자동으로 되돌아감 |
핫픽스 프로세스(파드 크래시 → 직접 패치 시나리오) 사전 정의 필요 |
argocd-agent: 원격 클러스터에 경량 에이전트를 배포하고 중앙 허브에 역방향 연결하는 아키텍처입니다. 수백 개 클러스터 규모에서 기존 중앙 집중형의 성능·보안 병목을 해소하기 위해 Red Hat과 Argo 커뮤니티가 도입 중인 모델입니다.
실무에서 가장 흔한 실수
-
Progressive Sync 플래그 빠뜨리기: ApplicationSet은 정상 적용됐는데 RollingSync가 동작하지 않는다면, 십중팔구
--enable-progressive-syncs플래그가 없는 겁니다. 팀에 새로 합류한 분들이 이 설정을 모르고 "ApplicationSet이 이상하다"고 하는 경우를 몇 번 봤습니다. -
클러스터 레이블 없이 Cluster Generator 사용: 레이블 셀렉터를 지정했는데 아무 Application도 생성되지 않는다면, 클러스터 시크릿에 레이블이 실제로 붙어있는지 먼저 확인해보시는 것을 권장합니다.
kubectl get secret -n argocd \
-l argocd.argoproj.io/secret-type=cluster --show-labels- Kustomize 경로 오타로 일부 환경만 배포 실패:
{{path}}/overlays/{{metadata.labels.environment}}경로에서 오타가 나면 해당 환경의 Application만 Sync 실패가 납니다. 아래 소개할--dry-run으로 미리 생성될 Application 목록과 경로를 확인해보시면 이런 실수를 사전에 잡을 수 있습니다.
마치며
ApplicationSet의 Matrix Generator + Cluster Generator + RollingSync 조합은 멀티 클러스터 환경에서 반복적인 YAML 관리와 불안전한 수동 배포 순서 제어 문제를 선언적으로 해결해주는 가장 실용적인 접근 방식입니다. 처음에는 YAML 구조가 낯설게 느껴질 수 있는데, 한 번 세팅해두면 서비스가 늘고 클러스터가 늘어도 ApplicationSet 하나로 모든 걸 커버할 수 있어 꽤 편합니다.
지금 바로 시작해볼 수 있는 3단계:
-
클러스터 레이블 확인 및 추가:
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=cluster --show-labels로 현재 등록된 클러스터 시크릿을 확인하고,environment=dev/staging/prod레이블이 없다면 위의kubectl label명령어로 붙여볼 수 있습니다. -
dry-run으로 생성될 Application 목록 미리 확인: ApplicationSet을 실제 적용하기 전에
kubectl apply --dry-run=client -f applicationset.yaml로 생성될 Application 수와 경로를 먼저 검증해보시는 것을 권장합니다. 경로 오타나 레이블 누락을 이 단계에서 잡을 수 있습니다. -
Progressive Sync 플래그 추가 후 RollingSync 적용:
argocd-server에--enable-progressive-syncs를 추가하고 ApplicationSet에strategy.type: RollingSync를 적용하면 dev Healthy 이후 staging이 진행되는 단계적 배포 흐름을 바로 확인해볼 수 있습니다. Argo CD UI의 ApplicationSet > Applications 탭에서 각 Application의 단계별 진행 상태를 실시간으로 모니터링할 수 있습니다.
다음 글에서는 Pull Request Generator와 Matrix Generator를 조합해 PR이 열릴 때 자동으로 미리보기 환경을 생성하고 병합 시 자동 삭제하는 PR Preview 환경 구축 방법을 다뤄볼 예정입니다.
참고 자료
- Matrix Generator 공식 문서 | Argo CD
- Cluster Generator 공식 문서 | Argo CD
- Progressive Syncs 공식 문서 | Argo CD
- ArgoCD ApplicationSet: Multi-Cluster Deployment Made Easy | Codefresh
- Set It and Forget It: Auto-Rolling Dev, Staging, and Prod with Argo CD | Medium
- Multi-cluster, multi-apps, multi-value deployments using Argo CD Application Sets | GitOpsCon NA 2025
- Enhance Kubernetes deployment efficiency with Argo CD and ApplicationSet | Red Hat Developer
- ApplicationSet with Matrix Generator Ep.13 | blog.stderr.at
- Set Up ArgoCD ApplicationSet Matrix Generator for Cross-Product Deployments | OneUptime
- Multi-cluster GitOps with the Argo CD Agent | Red Hat Blog
- Progressive Sync in OpenShift GitOps | Red Hat Documentation
- Best practices for promotion between clusters | GitHub Discussion