Application을 한두 개 쓸 때는 앞 글의 방식으로 충분합니다. 하지만 서비스가 수십 개로 늘어나고, dev·stg·prod 3벌씩 구성하면 금방 100개가 넘는 Application 리소스를 수작업으로 관리하게 됩니다. 여기서 등장하는 게 ApplicationSet입니다. “Application을 만드는 템플릿”을 선언적으로 정의하면 generator가 알아서 필요한 수만큼 찍어냅니다
왜 ApplicationSet인가
간단한 비교입니다. 10개 서비스를 3개 환경에 배포하면 Application이 30개 필요한데, 이걸 YAML 30장으로 관리하면 변경이 생길 때마다 지옥입니다
| 방식 | 리소스 개수 | 변경 전파 |
|---|---|---|
| Application 개별 작성 | 30개 파일 | 변경마다 30곳 수정 |
| ApplicationSet 1개 | 1개 템플릿 + generator | 템플릿만 수정하면 자동 전파 |
Generator: 어디서 목록을 가져올까
ApplicationSet의 심장은 Generator입니다. “몇 개의 Application을 어떤 파라미터로 만들지”를 결정하는 공급원입니다. 대표적인 타입만 추려봤습니다
| Generator | 입력 | 전형적 용도 |
|---|---|---|
| List | 하드코딩된 리스트 | 소수의 명시적 환경 |
| Cluster | ArgoCD에 등록된 클러스터 | 동일 앱을 여러 클러스터에 배포 |
| Git (Directory) | Git 리포지토리의 디렉토리 구조 | 디렉토리 하나 = 서비스 하나 |
| Git (File) | Git의 설정 파일 목록 | 서비스 메타데이터를 YAML로 관리 |
| Matrix | 두 generator 조합 | “N 서비스 × M 클러스터” |
| Pull Request | GitHub·GitLab PR 목록 | PR마다 preview 환경 생성 |
Git Directory Generator: 가장 많이 쓰는 패턴
리포지토리를 이렇게 구성했다고 하겠습니다
infra-repo/
├── services/
│ ├── user-api/
│ │ ├── deployment.yaml
│ │ └── service.yaml
│ ├── order-api/
│ ├── payment-api/
│ └── ...
ApplicationSet 하나로 각 디렉토리를 자동 감지해서 Application을 생성합니다
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: all-services
spec:
generators:
- git:
repoURL: https://github.com/org/infra-repo
revision: main
directories:
- path: services/*
template:
metadata:
name: ''
spec:
source:
repoURL: https://github.com/org/infra-repo
path: ''
destination:
server: https://kubernetes.default.svc
namespace: ''
syncPolicy:
automated: { selfHeal: true }
새 서비스를 추가하려면 디렉토리만 만들면 됩니다. ApplicationSet controller가 감지해서 Application을 자동 생성합니다
Matrix Generator: 환경 × 서비스 조합
“서비스 × 클러스터” 조합을 전부 만들어야 할 때 Matrix가 유용합니다
flowchart LR
subgraph services ["Git Directory<br/>(services/*)"]
S1["user-api"]
S2["order-api"]
S3["payment-api"]
end
subgraph clusters ["Cluster Generator"]
C1["cluster-dev"]
C2["cluster-stg"]
C3["cluster-prod"]
end
MATRIX["Matrix Generator"]
services --> MATRIX
clusters --> MATRIX
APPS["9개 Application<br/>user-api-dev<br/>user-api-stg<br/>...<br/>payment-api-prod"]
MATRIX --> APPS
classDef primary fill:#2563eb,stroke:#1e40af,color:#ffffff
classDef info fill:#0891b2,stroke:#0e7490,color:#ffffff
classDef success fill:#059669,stroke:#047857,color:#ffffff
class S1,S2,S3 primary
class C1,C2,C3 info
class MATRIX success
class APPS primary
3개 서비스 × 3개 클러스터 = 9개 Application이 템플릿 하나로 생성됩니다. 서비스가 10개로 늘어도 템플릿은 그대로입니다
멀티 클러스터 등록
ApplicationSet이 여러 클러스터에 배포하려면 먼저 ArgoCD에 그 클러스터들이 등록돼 있어야 합니다. 클러스터는 Secret으로 표현됩니다
apiVersion: v1
kind: Secret
metadata:
name: cluster-prod
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
env: prod
region: ap-northeast-2
type: Opaque
stringData:
name: cluster-prod
server: https://prod.k8s.example.com
config: |
{"bearerToken": "..."}
라벨이 중요한 이유는 Cluster Generator에서 필터링에 쓰이기 때문입니다
generators:
- clusters:
selector:
matchLabels:
env: prod
이렇게 하면 “prod 라벨이 붙은 클러스터들에만 배포”가 선언 한 줄로 해결됩니다
Hub-and-Spoke vs Stand-Alone
멀티 클러스터 환경에서 ArgoCD를 어떻게 배치할지는 두 가지 전략이 있습니다
flowchart TB
subgraph hub ["Hub-and-Spoke"]
H_ARGO["ArgoCD<br/>(Hub Cluster)"]
H_C1[("Cluster A")]
H_C2[("Cluster B")]
H_C3[("Cluster C")]
H_ARGO --> H_C1
H_ARGO --> H_C2
H_ARGO --> H_C3
end
subgraph standalone ["Stand-Alone"]
SA_C1[("Cluster A<br/>+ ArgoCD")]
SA_C2[("Cluster B<br/>+ ArgoCD")]
SA_C3[("Cluster C<br/>+ ArgoCD")]
end
classDef primary fill:#2563eb,stroke:#1e40af,color:#ffffff
classDef success fill:#059669,stroke:#047857,color:#ffffff
class H_ARGO,SA_C1,SA_C2,SA_C3 success
class H_C1,H_C2,H_C3 primary
| 전략 | 장점 | 단점 |
|---|---|---|
| Hub-and-Spoke | 단일 UI·단일 RBAC, 관리 단순 | Hub 장애 시 전체 배포 마비, 네트워크 지연 |
| Stand-Alone | 각 클러스터 독립, 지역 격리 | UI·정책·업그레이드 N중 관리 |
실무에서는 Hub-and-Spoke가 기본이고, 보안·규제로 클러스터 간 통신이 막히거나 대륙이 다른 경우에만 Stand-Alone으로 쪼개게 됩니다
AppProject: 멀티 팀 RBAC
ApplicationSet으로 Application을 대량 생성하면 이제 “누가 어떤 Application을 건드릴 수 있는가”가 문제가 됩니다. 기본 default Project는 모든 권한이 열려있어서, 프로덕션 환경에서는 팀·환경별 Project를 반드시 만듭니다
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-payment
spec:
sourceRepos:
- https://github.com/org/payment-*
destinations:
- namespace: 'payment-*'
server: '*'
clusterResourceWhitelist:
- group: ''
kind: Namespace
roles:
- name: developer
policies:
- p, proj:team-payment:developer, applications, sync, team-payment/*, allow
groups:
- payment-team
| 필드 | 역할 |
|---|---|
sourceRepos |
이 Project에서 허용되는 Git 리포 (다른 팀 리포로 spoof 방지) |
destinations |
배포 가능한 네임스페이스·클러스터 |
clusterResourceWhitelist |
클러스터 전역 리소스 생성 권한 (대부분 차단) |
roles |
팀 그룹별 세분화된 RBAC |
운영 팁
대규모 환경에서 자주 부딪히는 이슈와 대응법입니다
| 증상 | 원인 | 대응 |
|---|---|---|
| Sync가 전부 “Progressing”에 멈춤 | controller sharding 없이 Application 수 폭증 | controller.sharding.replicas 증가 |
| UI가 매우 느림 | Redis 단일 노드 부하 | Redis HA 모드 전환 |
| repo-server OOM | 대형 Helm 차트 병렬 렌더링 | replicas 증가 + parallelismLimit 조정 |
| Git fetch 타임아웃 빈발 | 사설 레포 미러 없음 | webhook으로 polling 주기 늘림 + on-demand fetch |
시리즈 마무리
4편에 걸쳐 GitOps 철학에서 시작해 ArgoCD 아키텍처, Application 단위의 동기화 전략, 그리고 ApplicationSet·멀티 클러스터 운영까지 다뤘습니다. 핵심 메시지를 한 줄로 요약하면 다음과 같습니다
“클러스터에 직접 명령하지 말고, Git에 쓰고 컨트롤러가 따라오게 하라.”
- 01: Push → Pull 모델 전환과 GitOps 4대 원칙
- 02: server·repo-server·controller·dex·redis 5개 컴포넌트 분리
- 03: Application 리소스와 sync policy·hook·wave
- 04: ApplicationSet과 Hub-and-Spoke 멀티 클러스터, AppProject RBAC
다음 시리즈에서는 ArgoCD가 배포하는 컨테이너 자체를 어떻게 효율적으로 만들지 — Docker 이미지 설계부터 깊이 있게 파고들어 보겠습니다