왜 다중 클러스터에서 배포 관리가 무너지는가
클러스터가 dev, staging, prod-us, prod-eu로 늘어나면 각 클러스터마다 Application 리소스를 따로 만들어 관리하게 된다. 이때 두 가지 문제가 반복된다. 첫째, 동일한 앱을 클러스터마다 복붙하면서 값 하나가 어긋나는 드리프트가 생긴다. 둘째, 새 서비스를 추가할 때 어느 클러스터에 무엇이 배포됐는지 전체를 추적할 단일 진실 원천(single source of truth)이 없다. ArgoCD의 App-of-Apps 패턴과 ApplicationSet은 이 문제를 "Application을 정의하는 상위 Application" 구조로 풀어낸다.
App-of-Apps의 핵심 구조
루트 Application 하나가 Git 저장소의 특정 디렉터리를 바라보고, 그 안에 자식 Application 매니페스트들을 담는다. 루트를 sync하면 자식 Application들이 생성되고, 각 자식이 실제 워크로드를 각 클러스터로 배포한다. 즉 사람이 관리하는 대상은 루트 하나뿐이다.
# root-app.yaml — 이 하나만 수동으로 apply 한다
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: default
source:
repoURL: https://git.example.com/platform/gitops.git
targetRevision: main
path: apps # 자식 Application 매니페스트가 모인 디렉터리
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
ApplicationSet으로 클러스터별 팬아웃
자식 Application을 클러스터 수만큼 손으로 쓰는 건 여전히 복붙이다. ApplicationSet의 generator를 쓰면 등록된 클러스터 목록을 순회하며 Application을 자동 생성한다. 아래는 특정 라벨을 가진 클러스터에만 배포하는 예시다.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: payment-svc
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: prod # prod 라벨 클러스터만 대상
template:
metadata:
name: 'payment-{{name}}'
spec:
project: default
source:
repoURL: https://git.example.com/platform/gitops.git
targetRevision: main
path: charts/payment
helm:
valueFiles:
- 'values-{{metadata.labels.region}}.yaml' # 리전별 값 분리
destination:
server: '{{server}}'
namespace: payment
syncPolicy:
automated:
prune: true
selfHeal: true
클러스터를 새로 등록하고 env=prod 라벨만 붙이면 payment-svc가 자동으로 그 클러스터에 배포된다. 클러스터를 빼면 자식 Application도 사라진다.
클러스터 등록과 자격 증명
ApplicationSet의 cluster generator는 argocd에 등록된 클러스터 Secret을 기준으로 동작한다. 등록 시점에 라벨을 함께 부여하는 것이 핵심이다.
# 클러스터 등록 후 라벨 부여
argocd cluster add prod-eu-context --name prod-eu
kubectl label secret -n argocd \
cluster-prod-eu env=prod region=eu
단일 Application vs App-of-Apps vs ApplicationSet
| 방식 | 관리 단위 | 클러스터 확장 | 적합 상황 |
|---|---|---|---|
| 개별 Application | 앱×클러스터 전부 | 수동 복제 | 1~2개 클러스터 |
| App-of-Apps | 루트 1개 | 자식 파일 추가 | 앱 목록을 Git으로 통제 |
| ApplicationSet | 템플릿 1개 | 라벨만 부여 | 클러스터 수가 유동적 |
실무에서 걸리는 주의점
- prune와 selfHeal의 위험. 루트에 automated prune을 켠 상태에서 실수로 자식 매니페스트를 삭제하면 해당 워크로드가 전 클러스터에서 사라진다. prod 대상 루트는 prune을 신중히 검토하고, 리뷰 없는 main 병합을 막아야 한다.
- 동시 롤아웃 폭발 반경. 하나의 커밋이 모든 prod 클러스터에 즉시 반영된다. ApplicationSet의
strategy: RollingSync로 클러스터를 단계적으로 배포하고, 앞 단계 실패 시 멈추도록 구성하는 편이 안전하다. - 값 파일 존재 여부.
values-{{region}}.yaml이 없는 리전이 섞이면 sync가 실패한다. 라벨과 값 파일의 정합성을 CI에서 미리 검증하는 게 좋다. - 권한 경계. 다중 클러스터를 한 ArgoCD가 관리하면 그 컨트롤러가 모든 클러스터의 admin 자격을 쥔다. AppProject로 배포 가능한 대상 클러스터·네임스페이스·리소스 종류를 제한해 폭발 반경을 좁혀야 한다.
정리
App-of-Apps는 "배포 목록"을 Git 하나로 통제하고, ApplicationSet은 "클러스터 확장"을 라벨 기반으로 자동화한다. 둘을 함께 쓰면 루트 Application 한 개와 라벨 규칙만으로 다중 클러스터 상태를 선언적으로 관리할 수 있다. 다만 자동화가 강해질수록 한 번의 실수가 미치는 범위도 넓어지므로, prune 정책·단계적 롤아웃·AppProject 권한 제한을 함께 설계해야 실전에서 버틴다.