CI/CD 스터디 6주차에 『예제로 배우는 Argo CD』 5장의 클러스터 부트스트랩을 따라가며, AWS EKS 대신 로컬 kind로 관리 클러스터와 두 workload cluster를 구성했다. 목표는 Terraform 코드를 흉내 내는 것이 아니라 다음 흐름을 직접 확인하는 것이었다.
mgmt cluster
└─ Argo CD
├─ dev cluster
└─ prd cluster
Git repository
├─ root applications
└─ ApplicationSet templates
원문 실습에서 사용한 kindest/node:v1.32.8과 argo-cd Helm chart 9.0.5는 2025년 당시의 재현값이다. 지금 실행할 때는 kind·Kubernetes 호환표와 chart release note를 확인해 검증한 버전을 명시적으로 고정해야 한다. 이 글은 실제 학습 흐름은 보존하되, 깨진 manifest와 평문 비밀번호 예제는 제거했다.

이 실습에서 확인하려는 것
- Argo CD가 설치된 mgmt cluster와 배포 대상 cluster의 역할 분리
- 외부 cluster 등록 시 생성되는 credential과 RBAC 범위
- root Application이 여러 child Application을 관리하는 App of Apps
- cluster label에 따라 Application을 생성하는 ApplicationSet
- 삭제 후 Git 선언으로 다시 구성할 수 있는지 확인하는 절차
Argo CD 설치 자체가 목적이라면 Argo CD 시작하기를 먼저 보고, 여기서는 멀티 클러스터 경계에 집중한다.
시작 전에 버전과 context를 기록한다
docker version
kind version
kubectl version --client
helm version
argocd version --client
kubectl config get-contexts -o name
실습 중 가장 위험한 실수는 다른 cluster context에 명령을 실행하는 것이다. 명령마다 --context를 붙이거나 현재 context를 출력해 확인한다. production credential이 섞인 기본 kubeconfig 대신 실습용 kubeconfig를 분리하면 더 안전하다.
export KUBECONFIG="$PWD/kubeconfig-lab"
kind가 첫 cluster를 만들면 이 파일도 생성된다. 빈 파일을 미리 만들지 않고, 생성 후 권한을 조정한다. 파일에는 cluster credential이 들어가므로 저장소에 commit하지 않는다.
mgmt·dev·prd cluster를 만든다
실습 당시 고정한 node image를 변수로 둔다.
export KIND_NODE_IMAGE='kindest/node:v1.32.8'
kind create cluster --name mgmt --image "$KIND_NODE_IMAGE"
chmod 600 "$KUBECONFIG"
kind create cluster --name dev --image "$KIND_NODE_IMAGE"
kind create cluster --name prd --image "$KIND_NODE_IMAGE"
세 context가 각각 동작하는지 확인한다.
kubectl --context kind-mgmt get nodes
kubectl --context kind-dev get nodes
kubectl --context kind-prd get nodes
EKS에서는 control plane, VPC, subnet, security group, IAM, node group까지 설계해야 한다. kind의 세 container는 그 보안·가용성 특성을 재현하지 않는다. 여기서 같은 것은 “관리 cluster가 workload cluster를 GitOps 대상으로 등록한다”는 논리 구조뿐이다.
kind의 설치와 networking 옵션은 kind Quick Start를 기준으로 확인한다.
Argo CD는 mgmt cluster에만 설치한다
Helm repository를 갱신하고 사용할 chart version을 먼저 확인한다.
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm search repo argo/argo-cd --versions | head
실습 재현에는 당시 값을 사용한다. 새 환경에서는 검증한 버전으로 ARGO_CD_CHART_VERSION을 바꾼다.
export ARGO_CD_CHART_VERSION='9.0.5'
helm upgrade --install argocd argo/argo-cd \
--version "$ARGO_CD_CHART_VERSION" \
--namespace argocd \
--create-namespace \
--kube-context kind-mgmt \
--wait
로컬 실습에서 ingress, self-signed certificate, /etc/hosts 변경까지 한 번에 넣으면 문제 경계가 늘어난다. 먼저 port-forward로 Argo CD API와 UI를 확인한다.
kubectl --context kind-mgmt \
-n argocd port-forward svc/argocd-server 8080:443
다른 terminal에서 초기 비밀번호를 일시적으로 읽어 로그인한다. 화면 공유와 shell history에 값을 남기지 않는다.
ARGO_INITIAL_PASSWORD="$(
kubectl --context kind-mgmt -n argocd \
get secret argocd-initial-admin-secret \
-o jsonpath='{.data.password}' | base64 -d
)"
argocd login localhost:8080 \
--username admin \
--password "$ARGO_INITIAL_PASSWORD" \
--insecure
unset ARGO_INITIAL_PASSWORD
argocd account update-password
--insecure는 localhost port-forward의 local lab에 한정한다. 외부 ingress에서는 신뢰할 수 있는 인증서와 hostname 검증을 구성한다. 초기 admin secret은 비밀번호를 바꾸고 불필요해진 뒤 공식 절차에 따라 제거한다. 기본 설치 흐름은 Argo CD Getting Started를 따른다.
dev와 prd cluster를 등록한다
argocd cluster add는 kubeconfig context를 읽어 대상 cluster에 service account와 RBAC를 만들고, Argo CD가 사용할 credential을 mgmt cluster에 저장한다.
argocd cluster add kind-dev --name dev-k8s
argocd cluster add kind-prd --name prd-k8s
argocd cluster list
공식 Getting Started 문서의 기본 동작은 대상에 admin 수준 ClusterRole을 binding한다. 실제 환경에서는 관리할 namespace와 resource에 맞게 권한을 줄여야 한다. 자동화 편의 때문에 cluster-admin을 그대로 두지 않는다.
또 하나의 조건이 있다. kubeconfig의 API server 주소는 명령을 실행하는 host뿐 아니라 mgmt cluster 안의 Argo CD controller에서도 도달 가능해야 한다. kind와 Docker Desktop의 내부 주소는 Linux와 macOS에서 접근 방식이 다를 수 있다. 임의의 container IP를 kubeconfig에 덮어쓰기 전에 다음을 확인한다.
argocd cluster get dev-k8s -o wide
argocd cluster get prd-k8s -o wide
kubectl --context kind-mgmt -n argocd get secrets \
-l argocd.argoproj.io/secret-type=cluster
Unknown이나 connection error가 나오면 certificate 검증을 꺼서 숨기지 말고, 저장된 server endpoint의 DNS·route·TLS SAN을 함께 고친다. cluster 등록과 credential 구조는 Argo CD Cluster Management를 기준으로 한다.
App of Apps는 작은 정적 묶음을 이해하기 좋았다
App of Apps는 root Application의 source 경로에 child Application manifest를 두는 패턴이다.
bootstrap-repo/
├─ root/
│ └─ applications.yaml
├─ ingress-nginx/
├─ monitoring/
└─ sample-app/
root Application 하나를 sync하면 child Application이 생성되고, 각 child가 자신의 resource를 동기화한다. 공통 addon이 적고 목록이 안정적인 lab에서는 구조가 눈에 잘 들어왔다.
하지만 root repository를 수정할 수 있는 사람은 사실상 여러 namespace와 cluster에 Application을 만들 권한을 갖는다. 공식 Cluster Bootstrapping 문서도 일반적인 cluster bootstrap에는 ApplicationSet과 cluster generator를 우선 살펴보라고 안내한다.
App of Apps를 쓸 때는 최소한 다음 경계를 둔다.
- AppProject로 허용 repository, destination cluster·namespace, resource kind를 제한한다.
- root repository에는 branch protection과 review를 적용한다.
- child Application의
project: default를 관성적으로 사용하지 않는다. - automated prune이 삭제할 범위를 review한다.
- root Application 삭제와 child finalizer의 동작을 lab에서 검증한다.
Helm 기반 child application 구성은 Helm chart 작업 흐름과 함께 보면 source와 rendered manifest의 경계가 명확해진다.
여러 cluster에는 ApplicationSet이 더 자연스러웠다
ApplicationSet은 generator가 parameter를 만들고 template에 대입해 여러 Application을 생성한다. Cluster generator는 Argo CD에 등록된 cluster secret과 label을 읽는다.
먼저 cluster에 환경 label을 붙인다.
argocd cluster set dev-k8s --label env=dev
argocd cluster set prd-k8s --label env=prd
다음은 env=dev인 cluster에만 sample app을 만드는 뼈대다. repository URL과 path는 자신의 저장소로 바꾸고, main에는 보호 규칙과 review를 적용한다.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: sample-dev
namespace: argocd
spec:
goTemplate: true
goTemplateOptions:
- missingkey=error
generators:
- clusters:
selector:
matchLabels:
env: dev
template:
metadata:
name: '{{.nameNormalized}}-sample'
spec:
project: platform
source:
repoURL: https://github.com/your-org/bootstrap-repo.git
targetRevision: main
path: apps/sample
destination:
server: '{{.server}}'
namespace: sample
syncPolicy:
syncOptions:
- CreateNamespace=true
nameNormalized를 쓰면 cluster name에 Kubernetes resource name으로 허용되지 않는 문자가 있을 때의 실패를 줄일 수 있다. missingkey=error는 template 변수가 빠졌을 때 빈 값으로 조용히 진행되는 일을 막는다.
적용 전후에는 생성될 범위를 확인한다.
kubectl --context kind-mgmt apply -f applicationset.yaml
kubectl --context kind-mgmt -n argocd get applicationsets
kubectl --context kind-mgmt -n argocd get applications -o wide
argocd app list
ApplicationSet Cluster Generator 문서에 따르면 이 generator는 이미 등록된 cluster를 대상으로 parameter를 만든다. cluster 자체를 생성하거나 등록해 주지는 않는다.
List와 Cluster generator의 선택 기준
| 상황 | List generator | Cluster generator |
|---|---|---|
| 대상이 적고 고정됨 | 명시적이라 이해하기 쉬움 | 과할 수 있음 |
| cluster 추가·삭제가 잦음 | 목록을 수동 변경 | 등록 정보와 label로 자동 반영 |
| 환경별 선택 | element에 값을 직접 기입 | cluster secret label selector |
| 잘못된 대량 배포 위험 | 목록 review로 제한 | label 하나가 여러 Application을 만들 수 있음 |
자동 생성이 편한 만큼 label 변경의 blast radius가 커진다. ApplicationSet을 만들 수 있는 주체와 template에서 선택할 수 있는 project, repository, destination을 제한해야 한다. 공식 문서도 multi-tenant 환경에서 ApplicationSet의 보안 영향을 먼저 검토하라고 경고한다.
삭제와 재생성도 bootstrap의 일부다
실습을 끝낼 때는 무작정 kind cluster부터 지우지 않았다. 관리 resource가 어떤 child를 삭제하는지 먼저 확인한다.
ApplicationSet의 생성 대상 확인
→ automated prune·finalizer 범위 확인
→ ApplicationSet·root Application 제거
→ remote cluster credential 제거
→ workload cluster 제거
→ mgmt cluster 제거
삭제 직전에는 argocd app list와 kubectl -n argocd get applications,applicationsets 결과를 남긴다. Git repository와 secret에는 실제 credential이 없는지도 확인한다. production에서는 teardown이 곧 disaster recovery가 아니므로, backup·restore와 외부 secret·DNS·storage 복구 절차를 별도로 검증해야 한다.
이번 실습에서 남은 기준
- kind 세 개는 multi-cluster control flow를 익히는 데 충분했지만 EKS의 network·IAM·가용성을 재현하지는 않는다.
- 외부 cluster 등록은 이름 하나를 추가하는 일이 아니라 credential, endpoint, RBAC를 추가하는 일이다.
- 정적이고 작은 child 목록은 App of Apps로 이해하기 쉽다.
- cluster가 늘고 label 기반 배포가 필요하면 ApplicationSet Cluster generator가 더 자연스럽다.
- bootstrap이 성공했다는 말은 설치뿐 아니라 삭제 범위와 재생성 경로를 확인했다는 뜻이어야 한다.
실습에서 가장 크게 바뀐 생각은 “root app 하나면 편하다”에서 멈추지 않게 된 점이다. 누가 root와 label을 바꿀 수 있는지, 그 변경이 어느 cluster까지 번지는지까지 함께 설계해야 GitOps가 운영 도구가 된다.
참고 자료
'배움과 성장 > DevOps·클라우드' 카테고리의 다른 글
| HashiCorp Vault 시크릿 관리: 안전한 실습에서 운영 설계까지 (0) | 2025.11.29 |
|---|---|
| Jenkins·Argo CD·Keycloak·OpenLDAP SSO와 RBAC: 안전한 구성 기준 (0) | 2025.11.22 |
| 『웹 엔지니어가 알아야 할 인프라의 기본』 1장: RAS·CIA를 설계 질문으로 바꾸기 (1) | 2025.11.19 |
| Argo CD 접근 제어 설계: 최소 권한 RBAC·AppProject·Keycloak SSO (1) | 2025.11.15 |
| SLO·에러 버짓·RTO·MTTR: 신뢰성 지표를 섞지 않는 법 (1) | 2025.11.09 |
댓글