스터디(Study)/CI·CD Study

예제로 배우는 Argo CD 5장: Argo CD로 쿠버네티스 클러스터 부트스트랩

Chann._.y 2025. 11. 22.
728x90

 

 

가시다님께서 진행하신 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 등을 선언하지만, 여기서는 아래 순서로 수동 준비한다.

  1. ingress-nginx 설치
  2. TLS 인증서 생성
  3. 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을 만들었다.

# 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 조합은 멀티 클러스터/멀티 테넌트 운영에서 상당히 강력한 패턴으로 느껴진다.


더 알아보기

728x90

댓글