
가시다님께서 진행하신 CI/CD 스터디 6주차 주제인 Argo CD를 활용한 멀티 클러스터 부트스트랩 실습 내용을 기반으로 작성하였다. 이번 글에서는 예제로 배우는 Argo CD 5장을 따라가며, 로컬 kind 환경에서 mgmt/dev/prd 클러스터를 구성하고, Argo CD·App of Apps 패턴·ApplicationSet을 활용해 멀티 클러스터 GitOps 부트스트랩 과정을 단계별로 정리한다.
기술 요구 사항
이번 장에서 사용하는 도구/환경은 아래와 같다.
- 로컬 환경
- Docker
- kubectl
- kind
- Helm
- argocd CLI
- 유틸리티
- jq, yq
- krew + kubectl-neat (k neat)
- k9s (선택)
- openssl, curl
- Git
- GitHub (또는 개인 Git 서버)
- 브라우저
- Argo CD UI 접속용
※ 실전에서 책 목차 그대로 따라가려면 Terraform, AWS CLI, EKS, Route53, ACM 등이 추가로 필요하다. 여기서는 Terraform+EKS 대신 kind 멀티클러스터 구성으로 같은 개념을 연습한다.
테라폼을 통한 아마존 EKS 클러스터 생성
아마존 EKS와 친해지기
책 기준으로는:
- 관리형 컨트롤 플레인(EKS)
- 워커 노드 그룹(Managed Node Group)
- VPC / Subnet / Security Group / IAM 등
을 Terraform으로 선언하고, 코드로 클러스터를 구성한다.
이번 실습에서는 이를 단순화해서 kind 클러스터 3개를 만든다.
- mgmt : Argo CD + ingress-nginx가 올라가는 관리용 클러스터
- dev : 개발용 워크로드 클러스터
- prd : 운영용 워크로드 클러스터
※ 개념적으로는 mgmt에 EKS mgmt/infra cluster, dev/prd에 workload EKS cluster를 만든다고 생각하면 된다.
EKS 인프라 설계하기
EKS라면 보통 이런 구성을 고민한다.
- VPC, 서브넷 구조 (Public / Private)
- AZ 분산, Node Group 전략
- Cluster 별 역할 분리 (mgmt / dev / staging / prod)
- 네트워크 정책, IAM, 보안경계 설계
여기서는 로컬 kind라 단순하게:
- Docker 네트워크 하나(kind)
- 그 안에 control-plane 컨테이너 3개(mgmt/dev/prd)
- 각 클러스터에 NodePort로 포트를 열어 서비스 확인
정도의 미니멀 구조로 잡았다.
테라폼으로 EKS 프로비저닝
이 섹션은 책에서는 Terraform 코드가 나오지만, 실습에서는 아래처럼 kind로 대체한다.
1) mgmt 클러스터 생성 (+ ingress 용 포트)
kind create cluster --name mgmt --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
labels:
ingress-ready: "true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- containerPort: 30000
hostPort: 30000
protocol: TCP
EOF
2) dev / prd 클러스터 생성
# dev
kind create cluster --name dev --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 31000
hostPort: 31000
EOF
# prd
kind create cluster --name prd --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 32000
hostPort: 32000
EOF
컨텍스트 확인:
kubectl config get-contexts
kubectl config use-context kind-mgmt
3) 클러스터 IP 확인 및 kubeconfig 조정
docker network inspect kind | grep -E 'Name|IPv4Address'
# "mgmt-control-plane" -> 192.168.97.2
# "dev-control-plane" -> 192.168.97.3
# "prd-control-plane" -> 192.168.97.4
kubeconfig에서 dev/prd의 server 주소를 컨테이너 IP로 변경:
cp ~/.kube/config ./kube-config.bak
vi ~/.kube/config
예시:
# dev
- cluster:
certificate-authority-data: ...
server: https://192.168.97.3:6443
name: kind-dev
# prd
- cluster:
certificate-authority-data: ...
server: https://192.168.97.4:6443
name: kind-prd
편의 alias:
alias k8s1='kubectl --context kind-mgmt'
alias k8s2='kubectl --context kind-dev'
alias k8s3='kubectl --context kind-prd'
k8s1 get node -owide
k8s2 get node -owide
k8s3 get node -owide
※ 실제 EKS에서는 이 부분이 Terraform aws_eks_cluster, aws_eks_node_group, aws_auth_configmap 등의 코드로 대체될 수 있다.
Argo CD로 EKS 부트스트랩하기
이제 관리 클러스터(mgmt)에 Argo CD를 올리고, dev/prd 클러스터를 등록해 GitOps로 관리하는 구조를 만든다.
테라폼으로 Argo CD 준비하기
책에서는 Terraform으로 Argo CD 설치를 위한 Helm 릴리스, namespace, secret 등을 선언하지만, 여기서는 아래 순서로 수동 준비한다.
- ingress-nginx 설치
- TLS 인증서 생성
- argocd namespace / TLS secret 생성
1) ingress-nginx 설치
kubectl apply -f \
https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
--enable-ssl-passthrough 추가:
kubectl get deployment ingress-nginx-controller -n ingress-nginx -o yaml \
| sed '/- --publish-status-address=localhost/a\
- --enable-ssl-passthrough' \
| kubectl apply -f -
2) self-signed TLS 인증서 생성
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout argocd.example.com.key \
-out argocd.example.com.crt \
-subj "/CN=argocd.example.com/O=argocd"
3) namespace / secret
kubectl create ns argocd
kubectl -n argocd create secret tls argocd-server-tls \
--cert=argocd.example.com.crt \
--key=argocd.example.com.key
테라폼으로 Argo CD 적용하기
Terraform 대신 Helm values로 Argo CD를 설치한다.
1) values 작성
cat <<EOF > argocd-values.yaml
global:
domain: argocd.example.com
server:
ingress:
enabled: true
ingressClassName: nginx
annotations:
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
tls: true
EOF
2) Helm 설치 (chart 9.0.5)
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd \
--version 9.0.5 \
-f argocd-values.yaml \
--namespace argocd
3) hosts 설정 및 접속
# macOS
echo "127.0.0.1 argocd.example.com" | sudo tee -a /etc/hosts
curl -vk https://argocd.example.com/
초기 비밀번호 확인 및 로그인:
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d ; echo
ARGOPW=<위 값>
argocd login argocd.example.com --insecure \
--username admin --password "$ARGOPW"
argocd account update-password \
--current-password "$ARGOPW" \
--new-password qwe12345
Argo CD 기본 상태:
argocd cluster list
argocd proj list
argocd account list
4) dev/prd 클러스터를 Argo CD에 등록
# dev 등록
argocd cluster add kind-dev --name dev-k8s
# prd 등록
argocd cluster add kind-prd --name prd-k8s --yes
# 등록된 클러스터 확인
argocd cluster list
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=cluster
※ 여기까지가 “EKS + Argo CD 부트스트랩”의 로컬 kind 버전이라고 보면 된다.
app of apps 패턴 활용
왜 app of apps 패턴인가?
App of Apps 패턴은 “Root Application 하나가 여러 Application을 생성/관리”하는 구조다.
- Root App:
- path: apps/
- 안에는 여러 Application manifest 모음
- Child App:
- 실제 서비스/유틸리티/인프라 구성 요소
※ 장점(의견):
※ 1) 공통 유틸리티/인프라를 한 번에 묶어서 관리할 수 있다.
※ 2) Root App 하나만 argocd app sync 하면 전체 스택이 올라온다.
※ 3) “플랫폼 팀이 제공하는 기본 스택”을 한 번에 부트스트랩할 때 아주 직관적이다.
유틸리티 부트스트랩하기
실습에서는 아래 구조로 Root App을 만들었다.
- Repo: https://github.com/gasida/cicd-study
- Path: apps/
- Template: 여러 Application을 for-range로 생성
# https://github.com/gasida/cicd-study/blob/main/apps/templates/applications.yaml
{{- range .Values.applications }}
{{- $config := $.Values.config -}}
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: {{ printf "example.%s" .name | quote }}
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
destination:
namespace: {{ .namespace | default .name | quote }}
server: {{ $config.spec.destination.server | quote }}
project: default
source:
path: {{ .path | default .name | quote }}
repoURL: {{ $config.spec.source.repoURL }}
targetRevision: {{ $config.spec.source.targetRevision }}
{{- with .tool }}
{{- . | toYaml | nindent 4 }}
{{- end }}
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
---
{{ end -}}
values:
# https://github.com/gasida/cicd-study/blob/main/apps/values.yaml
config:
spec:
destination:
server: https://kubernetes.default.svc
source:
repoURL: https://github.com/gasida/cicd-study
targetRevision: main
applications:
- name: helm-guestbook
tool:
helm:
releaseName: helm-guestbook
- name: kustomize-guestbook
- name: sync-waves
Root Application 생성:
argocd app create apps \
--dest-namespace argocd \
--dest-server https://kubernetes.default.svc \
--repo https://github.com/gasida/cicd-study.git \
--path apps
argocd app sync apps
argocd app list
kubectl get pod -A
삭제:
argocd app delete argocd/apps --yes
※ 이런 형태로 관측/로깅/인그레스/공통 네임스페이스 등을 Root App에 묶으면, “새 클러스터를 만들 때 Root App 하나 sync → 공통 스택 자동 설치”라는 구조를 만들 수 있다.
부트스트랩 연습
인프라 삭제
실습 환경을 깨끗하게 지우려면:
- Application / ApplicationSet 삭제
- kind 클러스터 삭제
# Argo CD Application 삭제
kubectl delete applications -n argocd mgmt-nginx dev-nginx prd-nginx
argocd appset delete guestbook --yes
argocd app delete apps --yes
# kind 클러스터 삭제
kind delete cluster --name mgmt
kind delete cluster --name dev
kind delete cluster --name prd
※ 실제 EKS라면 Terraform destroy와 AWS 리소스 삭제 전략까지 함께 고민해야 한다.
인프라 재생성
다시 kind create cluster + Argo CD 설치 + argocd cluster add를 수행하면, Git에 남아 있는 선언적 정의대로 인프라/앱이 재구성된다.
※ 실무에서는 “클러스터를 날리고 GitOps로 재복원하는 Playbook”을 문서화해 두면 DR/재해 복구 훈련에 도움이 된다.
app of apps 패턴의 단점
실습 중에 정리한 App of Apps 한계:
※ 1) 동적 생성이 어렵다
- Application 목록이 YAML 안에 정적으로 박혀 있다.
- 테넌트 100개 → Application 100개를 직접 관리해야 한다.
※ 2) Git 폴더 구조와 강하게 결합
- Git 구조가 바뀌면 Root App YAML도 수정해야 한다.
- 모노레포 / 멀티 레포 구조에서 유연성이 떨어질 수 있다.
※ 3) 멀티 클러스터/멀티 테넌트 스케일링에 불리
- 클러스터 수가 늘어날수록 Root App YAML이 비대해진다.
- Git 충돌도 자주 나고, 리뷰하기도 부담스럽다.
※ 4) 정적 구조
- “환경/레이블/PR 상태에 따라 자동으로 Application 목록이 바뀌는” 식의 동적 요구사항을 만족시키기 어렵다.
※ 이런 문제를 해결하기 위해 등장한 것이 ApplicationSet이다.
ApplicationSet은 무엇인가?
ApplicationSet Controller는 다음 역할만 한다.
- ApplicationSet CRD를 감시한다.
- 정의된 generator 설정에 따라 여러 개의 Application 리소스를 생성/수정/삭제한다.
- Argo CD의 Application을 “선언적 상위 스펙(ApplicationSet)”에 맞게 유지하는 것만 목표로 한다.
※ 한 마디로 “Application 템플릿 + 매개변수(generator) = N개의 Application”을 자동으로 뽑아주는 컨트롤러라고 보면 된다.
제너레이터
ApplicationSet에는 다양한 generator가 있고, 이번 실습에서 집중한 것은 List / Cluster 두 가지다.
- List
- 고정된 element 목록으로 여러 App 생성
- Cluster
- Argo CD에 등록된 클러스터 secret 목록을 기반으로 여러 App 생성
- label selector를 써서 특정 클러스터만 선택 가능
1) List Generator 실습
DEVK8SIP=192.168.97.3
PRDK8SIP=192.168.97.4
cat <https://github.com/gasida/cicd-study.git
targetRevision: HEAD
path: appset/list/{{.cluster}}
destination:
server: '{{.url}}'
namespace: guestbook
syncPolicy:
syncOptions:
- CreateNamespace=true
EOF
상태 확인:
kubectl get applicationsets -n argocd
argocd appset list
kubectl get applications -n argocd --show-labels
argocd app sync -l managed-by=applicationset
k8s2 get pod -n guestbook
k8s3 get pod -n guestbook
argocd appset delete guestbook --yes
2) Cluster Generator 실습 – 전체 클러스터
cat <https://github.com/gasida/cicd-study
targetRevision: HEAD
path: guestbook
destination:
server: '{{.server}}'
namespace: guestbook
syncPolicy:
syncOptions:
- CreateNamespace=true
EOF
argocd app sync -l managed-by=applicationset
k8s1 get pod -n guestbook
k8s2 get pod -n guestbook
k8s3 get pod -n guestbook
argocd appset delete guestbook --yes
3) Cluster Generator + label selector
dev-k8s secret에 label 추가:
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=cluster
DEVK8S=cluster-192.168.97.3-3897021443
kubectl label secret $DEVK8S -n argocd env=stg
kubectl get secret -n argocd -l env=stg
ApplicationSet에서 selector 사용:
cat <https://github.com/gasida/cicd-study
targetRevision: HEAD
path: guestbook
destination:
server: '{{.server}}'
namespace: guestbook
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
EOF
이제 env=stg label이 붙은 dev-k8s에만 guestbook이 배포된다.
argocd appset list
kubectl get applications -n argocd --show-labels
k8s2 get pod -n guestbook # dev-k8s
k8s3 get pod -n guestbook # prd-k8s에는 없어야 정상
argocd appset delete guestbook --yes
※ 이런 패턴을 확장하면 “label이 stg인 클러스터에만 스테이징 앱 자동 배포” 같은 멀티 클러스터 전략을 쉽게 구현할 수 있다.
요약
- kind로 mgmt/dev/prd 멀티 클러스터를 구성하고 Argo CD를 mgmt에 설치했다.
- dev/prd 클러스터는 Argo CD에 외부 클러스터로 등록해 GitOps 대상으로 만들었다.
- 간단한 nginx Helm Chart를 작성해 클러스터별로 다른 values를 적용해봤다.
- App of Apps 패턴으로 “Root App 하나로 여러 Application을 관리”하는 구조를 맛봤다.
- ApplicationSet의 List / Cluster generator 실습으로, 동적으로 Application을 생성하는 방식을 경험했다.
※ 개인적으로는 “1) kind로 전체 플로우를 연습 → 2) Terraform+EKS로 옮겨가기” 구조가 GitOps/Argo CD를 도입할 때 가장 부담이 적은 단계라고 본다.
※ 특히 ApplicationSet의 Cluster generator + label selector 조합은 멀티 클러스터/멀티 테넌트 운영에서 상당히 강력한 패턴으로 느껴진다.
더 알아보기
- Argo CD Cluster Management
- Declarative Clusters
- App of Apps / Cluster Bootstrapping
- ApplicationSet
- 실습 레포 예시
'스터디(Study) > CI·CD Study' 카테고리의 다른 글
| HashiCorp Vault로 시작하는 현대 시크릿 관리 (0) | 2025.11.29 |
|---|---|
| Jenkins · Argo CD · Keycloak · OpenLDAP로 Kubernetes SSO + RBAC 구성하기 [부록 실습] (0) | 2025.11.22 |
| 예제로 배우는 Argo CD 4장: 접근 제어 (1) | 2025.11.15 |
| 예제로 배우는 Argo CD 3장: Argo CD 운영 (1) | 2025.11.08 |
| 예제로 배우는 Argo CD 2장: Argo CD 시작하기 (0) | 2025.11.08 |
댓글