13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 멀티클러스터

  • [k8s] Argo CD 멀티 클러스터 GitOps 도입 사례: 운영 노하우와 도전 과제

    [k8s] Argo CD 멀티 클러스터 GitOps 도입 사례: 운영 노하우와 도전 과제

    [인프라] Argo CD 멀티 클러스터 GitOps 도입 사례: 운영 노하우와 도전 과제

    안녕하세요, 13년차의 서버실 운영자입니다. 오늘은 제가 직접 겪었던 경험을 바탕으로 Argo CD 멀티 클러스터 GitOps 도입 사례에 대해 이야기해볼까 합니다. 쿠버네티스(Kubernetes)를 운영하다 보면 자연스럽게 여러 클러스터를 관리해야 하는 상황에 부딪히게 되죠. 개발, 스테이징, 프로덕션 환경을 분리하거나, 재해 복구(Disaster Recovery)를 위해 지리적으로 분산된 클러스터를 운영하는 경우도 많고요. 처음엔 각각의 클러스터에 배포하는 것도 정말 힘들었는데, 이 모든 것을 효율적으로 관리하려니 골머리가 지끔했었습니다. 혹시 이런 경험 있으신가요?

    저도 처음엔 수동 배포와 스크립트의 늪에서 헤맸습니다. 그러다 GitOps(깃옵스)라는 개념을 접하고, 특히 Argo CD(아르고 CD)가 이 문제를 해결해 줄 수 있다는 걸 알게 되었죠. 특히 여러 클러스터를 한 곳에서 관리할 수 있는 Argo CD 멀티 클러스터 기능은 정말 반가웠습니다. 오늘은 이 기능을 도입하면서 제가 겪었던 삽질과 노하우, 그리고 얻었던 운영 효율성에 대해 솔직하게 풀어보려 합니다.

    Argo CD 멀티 클러스터 GitOps 아키텍처 다이어그램

    Argo CD 멀티 클러스터 GitOps의 전체 아키텍처 다이어그램입니다. 중앙의 Argo CD 컨트롤 플레인이 여러 쿠버네티스 클러스터에 애플리케이션을 배포하고 관리하는 모습을 보여줍니다.

    GitOps와 Argo CD, 그리고 멀티 클러스터 관리

    먼저, GitOps(깃옵스)가 뭔지 간단히 짚고 넘어가 보겠습니다. 쉽게 말하면, Git(깃) 저장소를 진리의 원천(Source of Truth)으로 삼아 인프라와 애플리케이션 배포를 관리하는 방식이에요. 모든 변경 사항은 Git에 기록되고, Git의 상태와 실제 클러스터의 상태를 일치시키는 것이 핵심입니다. 이렇게 하면 누가 언제 무엇을 변경했는지 추적하기 쉽고, 문제가 생겼을 때 이전 상태로 되돌리기도(Rollback) 훨씬 수월하죠.

    그리고 Argo CD(아르고 CD)는 이런 GitOps 원칙을 쿠버네티스 환경에서 구현하도록 도와주는 선언적(Declarative) GitOps 지속적 배포(Continuous Delivery) 도구입니다. Git 저장소에 정의된 상태를 주기적으로 감지해서 쿠버네티스 클러스터의 실제 상태와 비교하고, 다르면 Git의 상태에 맞춰 동기화(Sync)해줍니다. 정말 편리하더라고요.

    그렇다면 멀티 클러스터 관리(Multi-Cluster Management)는 뭘까요? Argo CD는 한 개의 인스턴스로 여러 개의 쿠버네티스 클러스터에 배포를 관리할 수 있는 기능을 제공합니다. 메인 Argo CD가 설치된 클러스터(저는 이걸 컨트롤 플레인 클러스터(Control Plane Cluster)라고 부릅니다)에서 여러 타겟 클러스터(Target Cluster)들을 등록하고, 각각의 클러스터에 어떤 애플리케이션을 어떤 버전으로 배포할지 Git을 통해 지시하는 방식이에요. 덕분에 여러 환경에 동일한 애플리케이션을 일관성 있게 배포하고 관리하는 게 훨씬 쉬워졌습니다. 💡

    실전 구현: Argo CD 멀티 클러스터 환경 구축하기

    자, 이제 실제로 어떻게 구성했는지 살펴보겠습니다. 저는 세 개의 클러스터를 준비했는데, 하나는 Argo CD가 설치될 컨트롤 플레인 클러스터, 그리고 나머지 두 개는 애플리케이션이 배포될 타겟 클러스터입니다. 모든 클러스터는 kubeconfig(쿠브콘피그)를 통해 접근할 수 있도록 설정되어 있다고 가정하겠습니다.

    1. Argo CD 설치 및 타겟 클러스터 등록

    먼저, 컨트롤 플레인 클러스터에 Argo CD를 설치합니다. 이건 공식 문서에 잘 나와 있으니 간단하게 넘어갈 테고, 핵심은 타겟 클러스터를 Argo CD에 등록하는 것입니다. Argo CD CLI를 사용하면 정말 간단하게 등록할 수 있어요.

    
    # Argo CD CLI 설치 (공식 문서 참고)
    # brew install argocd
    
    # Argo CD 로그인 (admin 계정 패스워드는 초기 설치 시 자동으로 생성됩니다)
    argocd login <ARGOCD_SERVER_IP_OR_HOSTNAME>
    
    # 타겟 클러스터 등록
    # kubeconfig에 등록된 클러스터 이름을 사용합니다.
    # 제 경우엔 dev-cluster와 prod-cluster라는 이름으로 등록되어 있었죠.
    argocd cluster add dev-cluster --name dev-cluster-region-a
    argocd cluster add prod-cluster --name prod-cluster-region-b
    
    # 등록된 클러스터 목록 확인
    argocd cluster list
    

    이렇게 하면 Argo CD가 해당 클러스터에 접근해서 애플리케이션을 배포할 수 있는 권한을 가지게 됩니다. 중요한 부분은 RBAC(Role-Based Access Control, 역할 기반 접근 제어) 설정인데, Argo CD가 타겟 클러스터에서 필요한 리소스(Deployment, Service 등)를 생성/수정/삭제할 수 있도록 적절한 권한을 부여해야 합니다. 저는 처음엔 이걸 놓쳐서 “왜 배포가 안 될까?” 한참 삽질했었습니다. ⚠️

    Argo CD UI 클러스터 목록 스크린샷

    Argo CD 웹 UI에 등록된 dev-cluster-region-a와 prod-cluster-region-b 클러스터의 목록과 상태를 보여주는 화면입니다. 모든 클러스터가 Healthy 상태로 나타나 있네요.

    2. Git 저장소 구조화: App of Apps 패턴

    멀티 클러스터 환경에서 Git 저장소를 효율적으로 관리하는 방법 중 하나는 App of Apps 패턴(App of Apps Pattern)입니다. 하나의 최상위 Argo CD Application이 여러 하위 Application들을 관리하는 방식이에요. 저는 다음과 같이 Git 저장소를 구성했습니다.

    
    .
    ├── applications/
    │   ├── base/ # 공통 애플리케이션 정의 (예: Ingress Controller, Cert Manager)
    │   └── microservices/ # 마이크로서비스별 애플리케이션 정의
    ├── clusters/
    │   ├── dev-cluster-region-a/ # 개발 클러스터별 설정
    │   │   └── root-app.yaml
    │   └── prod-cluster-region-b/ # 프로덕션 클러스터별 설정
    │       └── root-app.yaml
    └── environments/
        ├── dev/ # 개발 환경별 값 (values.yaml for Helm)
        └── prod/ # 프로덕션 환경별 값 (values.yaml for Helm)
    

    각 클러스터 폴더 안의 `root-app.yaml`은 해당 클러스터에 배포될 모든 하위 애플리케이션을 정의하는 최상위 Argo CD Application입니다. 예를 들어 `dev-cluster-region-a/root-app.yaml`은 다음과 같이 작성할 수 있죠.

    
    # clusters/dev-cluster-region-a/root-app.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: dev-cluster-root-app
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://github.com/my-org/my-gitops-repo.git # 본인의 Git 저장소 URL
        targetRevision: HEAD
        path: applications # 하위 애플리케이션 정의들이 있는 경로
      destination:
        server: https://kubernetes.default.svc # 이 Application은 Argo CD가 설치된 클러스터에 적용
        namespace: argocd # Argo CD가 Application 리소스를 관리하는 네임스페이스
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
    

    그리고 `applications/` 경로 안에는 실제 배포될 애플리케이션들의 정의가 들어갑니다. 예를 들어, `applications/microservices/my-service.yaml`은 다음과 같을 수 있어요. 여기서 ApplicationSet(애플리케이션셋)을 사용하면 여러 클러스터에 동일한 애플리케이션을 배포하는 것이 훨씬 유연해집니다.

    
    # applications/microservices/my-service.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: ApplicationSet
    metadata:
      name: my-service-all-clusters
      namespace: argocd
    spec:
      generators:
      - list:
          elements:
          - cluster: dev-cluster-region-a # Argo CD에 등록된 클러스터 이름
            url: https://kubernetes.default.svc # 타겟 클러스터의 API 서버 URL
            name: dev
          - cluster: prod-cluster-region-b
            url: https://kubernetes.default.svc
            name: prod
      template:
        metadata:
          name: my-service-{{name}} # 클러스터 이름에 따라 Application 이름이 생성됩니다 (예: my-service-dev, my-service-prod)
          namespace: default
        spec:
          project: default
          source:
            repoURL: https://github.com/my-org/my-app-helm-charts.git # 애플리케이션 Helm 차트 저장소
            targetRevision: 1.0.0
            path: my-service
            helm:
              values: |
                replicaCount: 2
                image:
                  tag: "1.0.0-{{name}}" # 환경별 이미지 태그 (예: 1.0.0-dev, 1.0.0-prod)
                ingress:
                  host: my-service.{{name}}.example.com
          destination:
            server: "{{url}}" # 위에서 정의된 타겟 클러스터의 URL
            namespace: default
          syncPolicy:
            automated:
              prune: true
              selfHeal: true
            syncOptions:
              - CreateNamespace=true
    

    위 `ApplicationSet` 예시는 `list` 제너레이터를 사용해서 `dev`와 `prod` 두 환경에 `my-service`를 배포하도록 정의하고 있습니다. 각 환경에 맞는 `values`를 `helm` 섹션에서 오버라이드(Override)할 수 있어서 정말 유용해요. 저는 Helm(헬름) 차트를 주로 사용하는데, Kustomize(커스터마이즈)나 일반 YAML도 물론 지원합니다.

    ⚠️ 삽질 경험과 운영 노하우

    Argo CD 멀티 클러스터를 운영하면서 몇 가지 정말 잊지 못할 경험들이 있습니다. 독자분들은 저와 같은 실수를 반복하지 않으시길 바라며 몇 가지 중요한 팁을 공유합니다.

    1. RBAC 권한 문제

    가장 흔하고 고통스러운 문제예요. Argo CD가 타겟 클러스터에 배포할 때, 필요한 권한이 없어서 배포가 실패하는 경우가 정말 많습니다. Argo CD를 등록할 때 부여하는 권한은 ServiceAccount(서비스 어카운트)를 통해 부여되는데, 이 서비스 어카운트가 타겟 클러스터에서 ClusterRole(클러스터 롤)과 ClusterRoleBinding(클러스터 롤 바인딩)을 통해 적절한 권한을 가지고 있는지 반드시 확인해야 합니다. 저는 처음엔 `admin` 권한을 무심코 줬다가 보안 팀에게 한소리 듣고, 나중에는 필요한 최소한의 권한만 부여하도록 변경했습니다. 💡

    
    # 예시: Argo CD ServiceAccount에 필요한 ClusterRoleBinding 부여 (타겟 클러스터에서 실행)
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: argocd-manager-role-binding
    subjects:
    - kind: ServiceAccount
      name: argocd-manager # Argo CD가 사용하는 ServiceAccount 이름
      namespace: argocd # Argo CD가 설치된 네임스페이스
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: cluster-admin # 임시로 전체 권한 부여. 실제 운영에서는 더 세분화된 Role을 사용하세요.
    

    실제 운영에서는 `cluster-admin` 대신 `edit`이나 `view` 같은 기본 Role을 조합하거나, 특정 리소스에 대한 `get`, `list`, `watch`, `create`, `update`, `patch`, `delete` 권한을 명시적으로 부여하는 커스텀 ClusterRole을 만들어서 사용하는 게 훨씬 안전합니다.

    2. 네트워크 연결 및 방화벽

    Argo CD 컨트롤 플레인 클러스터에서 타겟 클러스터의 API 서버에 접근할 수 있어야 합니다. 만약 클러스터들이 서로 다른 네트워크에 있거나, 방화벽(Firewall) 정책이 엄격하다면 연결 문제가 발생할 수 있죠. 저는 회사 보안 정책 때문에 VPC 피어링(VPC Peering)이나 VPN(Virtual Private Network) 설정을 통해 연결을 확보해야 했어요. 핑(Ping) 테스트나 `kubectl`로 직접 연결해보면서 연결성을 확인하는 것이 정말 중요합니다.

    3. 시크릿 관리(Secret Management)

    여러 클러스터에 배포되는 애플리케이션들은 데이터베이스 비밀번호나 API 키 같은 민감한 정보(Secret)들을 필요로 합니다. 이걸 Git에 그대로 올릴 수는 없겠죠. 저는 External Secrets Operator(외부 시크릿 오퍼레이터)와 Vault(볼트)를 연동해서 사용하거나, Sealed Secrets(실드 시크릿)를 활용해서 시크릿을 안전하게 관리했습니다. Git에 암호화된 형태로 저장하고, 각 클러스터에서 복호화해서 사용하는 방식이에요. 이 부분은 보안상 정말 중요하니 깊이 있게 고민해야 합니다.

    4. 설정 드리프트(Configuration Drift)

    GitOps의 핵심은 Git 저장소의 상태와 실제 클러스터의 상태가 일치해야 한다는 거예요. 그런데 운영 중에 누군가 클러스터에서 직접 리소스를 수정해서 Git과 클러스터의 상태가 달라지는 설정 드리프트(Configuration Drift)가 발생할 수 있습니다. Argo CD는 이런 드리프트를 감지하고 자동으로 동기화(Self-Heal)하거나, 수동으로 동기화할 수 있는 기능을 제공합니다. 하지만 근본적으로는 클러스터에 직접 접근해서 변경하는 것을 정책적으로 금지하고, 모든 변경은 Git을 통해서만 이루어지도록 강제하는 게 중요합니다.

    🎉 Argo CD 멀티 클러스터 도입 후기 및 성과

    숱한 삽질 끝에 Argo CD 멀티 클러스터 GitOps 환경을 성공적으로 구축하고 운영하게 되었습니다. 그 결과는 정말 놀라웠어요.

    • 배포 일관성 및 안정성 향상: 개발, 스테이징, 프로덕션 클러스터에 동일한 버전의 애플리케이션을 일관된 방식으로 배포할 수 있게 되었습니다. 수동 작업으로 인한 휴먼 에러(Human Error)가 정말 줄었죠.
    • 배포 속도 단축: Git에 코드를 푸시(Push)만 하면 Argo CD가 자동으로 감지하고 배포를 시작합니다. CI/CD 파이프라인(Pipeline)과 연동하여 개발자들이 훨씬 빠르게 변경 사항을 적용할 수 있게 되었어요.
    • 쉬운 롤백(Rollback): 문제가 발생했을 때, Git의 이전 커밋(Commit)으로 되돌리는 것만으로 애플리케이션을 이전 상태로 쉽게 롤백할 수 있습니다. 비상 상황에서 정말 큰 도움이 되더군요.
    • 운영 가시성 확보: Argo CD UI를 통해 모든 클러스터의 애플리케이션 상태를 한눈에 확인할 수 있게 되었습니다. 어떤 애플리케이션이 어느 클러스터에 어떤 버전으로 배포되어 있는지 파악하기가 정말 쉬워졌어요.
    Argo CD UI 멀티 클러스터 애플리케이션 상태 대시보드

    Argo CD 웹 UI에서 dev-cluster와 prod-cluster에 배포된 여러 애플리케이션들의 동기화 상태와 Health 상태를 한눈에 보여주는 대시보드 화면입니다. 모든 애플리케이션이 Sync’d and Healthy 상태를 나타내고 있네요.

    Argo CD 멀티 클러스터 도입 전후 비교

    구분 도입 전 (수동/스크립트 기반) 도입 후 (Argo CD 멀티 클러스터 GitOps)
    배포 방식 클러스터별 수동 `kubectl` 적용 또는 쉘 스크립트 실행 Git 커밋 기반 자동 동기화 (선언적)
    배포 일관성 환경별 설정 차이 발생 가능성 높음, 휴먼 에러 빈번 Git을 통한 일관된 설정 적용, 휴먼 에러 최소화
    롤백 용이성 이전 상태 복구 어려움, 시간 소요 Git 커밋 되돌리기로 빠르고 안정적인 롤백
    운영 가시성 각 클러스터에 개별 접근하여 상태 확인 단일 Argo CD UI에서 모든 클러스터 및 애플리케이션 상태 확인
    보안 및 감사 수동 변경 이력 추적 어려움 Git 변경 이력을 통한 완벽한 감사 추적

    위 표에서 보시는 것처럼, Argo CD 멀티 클러스터 GitOps는 인프라 운영 방식에 혁신적인 변화를 가져다주었습니다. 특히 여러 클러스터를 관리해야 하는 복잡한 환경에서는 그 진가가 더욱 빛을 발하더군요.

    마무리하며: 배운 점과 다음 도전 과제

    오늘은 제가 Argo CD 멀티 클러스터 GitOps를 도입하면서 겪었던 과정과 노하우, 그리고 얻었던 성과에 대해 이야기해봤습니다. 처음엔 낯선 개념과 복잡한 설정 때문에 막막하기도 했지만, 한번 구축하고 나니 인프라 운영의 질이 한 단계 높아지는 걸 체감할 수 있었습니다. 특히 쿠버네티스 멀티 클러스터 관리에 대한 고민이 많으셨던 분들께 저의 Argo CD GitOps 사례가 작은 도움이 되었으면 좋겠어요.

    물론 아직 도전 과제는 남아있습니다. 예를 들어, 프로그레시브 딜리버리(Progressive Delivery)를 위해 Argo Rollouts(아르고 롤아웃)과 연동하거나, 더 복잡한 클러스터 간 의존성을 관리하는 방법, 그리고 비용 최적화와 같은 부분은 앞으로도 계속 고민하고 실험해봐야 할 영역입니다. 💡

    저처럼 GitOps 도입 후기를 찾고 계셨던 분들이라면, Argo CD를 적극적으로 검토해보시길 추천합니다. 직접 해보면 생각보다 어렵지 않고, 얻는 이점은 훨씬 크다는 걸 느끼실 겁니다. 다음번에는 더 깊이 있는 주제로 찾아오겠습니다. 긴 글 읽어주셔서 감사합니다! 궁금한 점이 있다면 언제든지 댓글로 남겨주세요.

  • [k8s] ArgoCD 멀티 클러스터 GitOps 배포 및 관리 전략

    [k8s] ArgoCD 멀티 클러스터 GitOps 배포 및 관리 전략

    클러스터가 하나일 때는 괜찮았는데…

    처음 쿠버네티스를 도입했을 때 저도 클러스터 하나로 시작했어요. 개발(dev), 스테이징(staging), 프로덕션(production) 환경을 네임스페이스(Namespace)로 분리해서 쓰는 방식이었는데, 솔직히 그때는 그게 맞다고 생각했거든요. 근데 조직이 커지고 팀이 늘어나면서 문제가 하나씩 터지기 시작했습니다.

    “개발팀이 실수로 프로덕션 네임스페이스에 배포했어요.” 이 한 마디에 심장이 철렁 내려앉은 적 있으신가요? 저는 있습니다 ㅎㅎ. 결국 클러스터를 분리하기로 결정했고, 그때부터 ArgoCD 멀티 클러스터 전략을 본격적으로 파고들었습니다.

    이 글에서는 ArgoCD를 허브(Hub) 클러스터에 설치하고, 여러 개의 쿠버네티스 클러스터를 GitOps 방식으로 배포 및 관리하는 전략을 단계별로 정리해 드리겠습니다. 저처럼 삽질하지 않도록요.

    ArgoCD 멀티 클러스터 Hub-and-Spoke 아키텍처 다이어그램 - 허브 클러스터에서 dev, staging, prod 클러스터로 GitOps 배포 흐름

    ▲ ArgoCD 허브 클러스터가 여러 대상 클러스터(dev, staging, prod)를 관리하는 전체 아키텍처 구조. Hub-and-Spoke 패턴의 핵심입니다.

    ArgoCD 멀티 클러스터란? 개념부터 잡고 가요

    GitOps가 뭔지 먼저 짚고 가면

    GitOps는 쉽게 말해 “Git 저장소가 인프라와 애플리케이션의 단일 진실 공급원(Single Source of Truth)이 되는 운영 방식”이에요. 배포하고 싶으면 kubectl로 직접 명령을 날리는 게 아니라, Git에 커밋하면 자동으로 클러스터에 반영되는 방식이죠.

    ArgoCD는 이 GitOps를 쿠버네티스 위에서 구현해주는 대표적인 오픈소스 도구입니다. Git 저장소를 지속적으로 감시하다가 변경이 감지되면 클러스터에 자동으로 동기화(Sync)해줘요.

    멀티 클러스터 관리, 왜 필요한가요?

    네임스페이스 분리 방식의 가장 큰 문제는 폭발 반경(Blast Radius)이 너무 넓다는 거예요. 클러스터 레벨의 장애가 모든 환경에 영향을 주고, 보안 격리도 완벽하지 않습니다. 반면 클러스터를 분리하면:

    • 환경 간 완전한 격리 — 개발 배포 실수가 프로덕션에 영향 없음
    • 클러스터별 독립적인 리소스 할당 및 스케일링
    • 규정 준수(Compliance) 요구사항 충족 용이
    • 팀별 독립적인 업그레이드 주기 관리

    그런데 클러스터가 여러 개가 되면 관리 포인트도 여러 개가 되잖아요. 각 클러스터에 ArgoCD를 따로 설치하는 방법도 있지만, 저는 Hub-and-Spoke 패턴을 선호합니다. 허브 클러스터 하나에 ArgoCD를 설치하고, 나머지 클러스터들을 원격으로 관리하는 방식이에요.

    방식 장점 단점 추천 상황
    클러스터별 ArgoCD 설치 독립성 높음, 장애 격리 관리 포인트 분산, 일관성 유지 어려움 팀/조직이 완전히 분리된 경우
    Hub-and-Spoke (중앙화) 단일 관리 포인트, 일관된 정책 적용 허브 클러스터 장애 시 배포 불가 중앙 플랫폼팀이 관리하는 경우

    ArgoCD 설치 및 멀티 클러스터 등록 — 실전으로 들어갑니다

    1단계: 허브 클러스터에 ArgoCD 설치

    저는 허브 클러스터로 별도의 관리 전용 클러스터를 사용합니다. 프로덕션 워크로드가 없는 클러스터에 ArgoCD를 올리는 게 안전하더라고요.

    # ArgoCD 네임스페이스 생성
    kubectl create namespace argocd
    
    # ArgoCD 공식 매니페스트로 설치
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    
    # 설치 확인
    kubectl get pods -n argocd
    

    설치가 완료되면 argocd-server 파드가 Running 상태가 되는데, 처음엔 이미지 풀(Image Pull) 때문에 좀 기다려야 할 수도 있어요. 저도 처음에 “왜 안 되지?” 하고 5분 기다렸더니 그냥 올라왔습니다 ㅎㅎ.

    # 초기 admin 비밀번호 확인
    kubectl -n argocd get secret argocd-initial-admin-secret \
      -o jsonpath="{.data.password}" | base64 -d
    
    # ArgoCD CLI 설치 (macOS 기준)
    brew install argocd
    
    # ArgoCD 서버 포트포워딩 (로컬 접근용)
    kubectl port-forward svc/argocd-server -n argocd 8080:443
    
    # CLI 로그인
    argocd login localhost:8080 --username admin --password <위에서_확인한_비밀번호> --insecure
    

    2단계: 대상 클러스터(Spoke) 등록

    이 부분이 멀티 클러스터 설정의 핵심이에요. ArgoCD CLI로 대상 클러스터를 등록하면, ArgoCD가 해당 클러스터에 argocd-manager라는 서비스 어카운트(Service Account)를 만들고 필요한 RBAC 권한을 자동으로 설정해줍니다.

    # 현재 kubeconfig에 등록된 컨텍스트 확인
    kubectl config get-contexts
    
    # 예시 출력:
    # CURRENT   NAME            CLUSTER         AUTHINFO
    # *         hub-cluster     hub-cluster     hub-admin
    #           dev-cluster     dev-cluster     dev-admin
    #           staging-cluster staging-cluster staging-admin
    #           prod-cluster    prod-cluster    prod-admin
    
    # 개발 클러스터 등록
    argocd cluster add dev-cluster --name dev
    
    # 스테이징 클러스터 등록
    argocd cluster add staging-cluster --name staging
    
    # 프로덕션 클러스터 등록
    argocd cluster add prod-cluster --name prod
    
    # 등록된 클러스터 목록 확인
    argocd cluster list
    

    💡 팁: 클러스터 등록 시 --name 옵션으로 별칭을 지정하면 나중에 Application 설정에서 훨씬 읽기 편합니다. URL 대신 이름으로 참조할 수 있거든요.

    ArgoCD Settings Clusters 화면에서 dev, staging, prod 멀티 클러스터가 등록된 관리 UI 화면

    ▲ ArgoCD 웹 UI의 Settings > Clusters 화면. dev, staging, prod 클러스터가 각각 등록되어 연결 상태를 실시간으로 확인할 수 있습니다.

    Git 저장소 구조 설계 — 이게 진짜 중요합니다

    멀티 클러스터 GitOps에서 Git 저장소 구조를 어떻게 잡느냐가 나중에 관리 편의성을 크게 좌우해요. 제가 여러 방식을 시도해보고 정착한 구조를 공유합니다.

    모노레포(Monorepo) 방식 — 제가 선호하는 방식

    gitops-repo/
    ├── apps/                          # 애플리케이션 정의
    │   ├── base/                      # 공통 기본 설정 (Kustomize base)
    │   │   ├── my-app/
    │   │   │   ├── deployment.yaml
    │   │   │   ├── service.yaml
    │   │   │   └── kustomization.yaml
    │   └── overlays/                  # 환경별 오버레이
    │       ├── dev/
    │       │   └── my-app/
    │       │       ├── kustomization.yaml
    │       │       └── patch-replicas.yaml
    │       ├── staging/
    │       │   └── my-app/
    │       └── prod/
    │           └── my-app/
    ├── argocd/                        # ArgoCD 설정 자체도 Git으로 관리
    │   ├── projects/                  # ArgoCD Project 정의
    │   │   ├── dev-project.yaml
    │   │   ├── staging-project.yaml
    │   │   └── prod-project.yaml
    │   └── applications/              # ArgoCD Application 정의
    │       ├── dev/
    │       ├── staging/
    │       └── prod/
    └── clusters/                      # 클러스터 레벨 설정
        ├── dev/
        ├── staging/
        └── prod/
    

    이 구조의 핵심은 Kustomize(커스터마이즈)를 활용해서 base 설정을 공유하면서 환경별로 다른 값(레플리카 수, 리소스 제한, 이미지 태그 등)만 오버레이로 덮어쓰는 방식이에요.

    Kustomize 오버레이 예시

    # apps/base/my-app/deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-app
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          containers:
          - name: my-app
            image: my-registry/my-app:latest
            resources:
              requests:
                cpu: 100m
                memory: 128Mi
    
    # apps/overlays/prod/my-app/kustomization.yaml
    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    resources:
      - ../../../base/my-app
    patches:
      - path: patch-replicas.yaml
    images:
      - name: my-registry/my-app
        newTag: v1.2.3  # 프로덕션은 명시적 태그 사용
    
    # apps/overlays/prod/my-app/patch-replicas.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-app
    spec:
      replicas: 3  # 프로덕션은 3개로
    

    ArgoCD Application 및 AppProject 설정

    AppProject(앱 프로젝트)로 클러스터 접근 제어

    ArgoCD의 AppProject는 애플리케이션들을 논리적으로 그룹화하고, 어떤 Git 저장소에서 어떤 클러스터로 배포할 수 있는지 제한하는 역할을 합니다. 저는 이걸 환경별로 나눠서 씁니다.

    # argocd/projects/prod-project.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: AppProject
    metadata:
      name: production
      namespace: argocd
    spec:
      description: Production environment project
      # 허용된 소스 저장소
      sourceRepos:
        - 'https://github.com/myorg/gitops-repo.git'
      # 배포 가능한 대상 클러스터와 네임스페이스
      destinations:
        - server: https://prod-cluster-api.example.com
          namespace: '*'
      # 클러스터 범위 리소스 생성 제한 (선택적)
      clusterResourceWhitelist:
        - group: ''
          kind: Namespace
      # RBAC 역할 정의
      roles:
        - name: prod-deployer
          description: Production deployment role
          policies:
            - p, proj:production:prod-deployer, applications, sync, production/*, allow
          groups:
            - myorg:platform-team
    

    Application 정의 — 멀티 클러스터 배포의 핵심

    # argocd/applications/prod/my-app.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: my-app-prod
      namespace: argocd
      # 앱 삭제 시 리소스도 함께 삭제되지 않도록 (중요!)
      finalizers:
        - resources-finalizer.argocd.argoproj.io
    spec:
      project: production
      source:
        repoURL: https://github.com/myorg/gitops-repo.git
        targetRevision: main
        path: apps/overlays/prod/my-app  # 프로덕션 오버레이 경로
      destination:
        # 등록한 클러스터 이름 또는 API 서버 URL
        server: https://prod-cluster-api.example.com
        namespace: my-app
      syncPolicy:
        automated:
          prune: true       # Git에서 삭제된 리소스는 클러스터에서도 삭제
          selfHeal: true    # 클러스터 상태가 Git과 다르면 자동 복구
        syncOptions:
          - CreateNamespace=true  # 네임스페이스 없으면 자동 생성
          - PrunePropagationPolicy=foreground
        retry:
          limit: 5
          backoff:
            duration: 5s
            factor: 2
            maxDuration: 3m
    

    ApplicationSet으로 여러 클러스터에 한 번에 배포

    근데 여기서 더 편한 방법이 있어요. ApplicationSet(애플리케이션셋)을 쓰면 여러 클러스터에 대한 Application을 템플릿 하나로 자동 생성할 수 있거든요. 처음 이걸 알았을 때 “이게 왜 이렇게 편하지?” 싶었습니다.

    # argocd/applicationsets/my-app-all-clusters.yaml
    apiVersion: argoproj.io/v1alpha1
    kind: ApplicationSet
    metadata:
      name: my-app-all-clusters
      namespace: argocd
    spec:
      generators:
        - list:
            elements:
              - cluster: dev
                url: https://dev-cluster-api.example.com
                env: dev
                revision: HEAD
              - cluster: staging
                url: https://staging-cluster-api.example.com
                env: staging
                revision: main
              - cluster: prod
                url: https://prod-cluster-api.example.com
                env: prod
                revision: main
      template:
        metadata:
          name: 'my-app-{{env}}'
        spec:
          project: '{{env}}'
          source:
            repoURL: https://github.com/myorg/gitops-repo.git
            targetRevision: '{{revision}}'
            path: 'apps/overlays/{{env}}/my-app'
          destination:
            server: '{{url}}'
            namespace: my-app
          syncPolicy:
            automated:
              prune: true
              selfHeal: true
            syncOptions:
              - CreateNamespace=true
    

    💡 팁: ApplicationSet의 generators에는 List 방식 외에도 클러스터 레이블 기반으로 자동 감지하는 Cluster Generator도 있어요. 클러스터가 많아질수록 이쪽이 훨씬 강력합니다.

    ⚠️ 실제로 겪은 트러블슈팅 — 이거 꼭 읽으세요

    문제 1: 클러스터 등록 후 연결 끊김 (Connection Refused)

    클러스터를 등록했는데 ArgoCD UI에서 계속 “Unknown” 상태가 뜨는 경우가 있었어요. 원인을 파고들어보니 허브 클러스터에서 대상 클러스터의 API 서버로 직접 네트워크 연결이 안 되는 거였습니다. VPC 피어링이나 방화벽 규칙을 확인해야 해요.

    # 허브 클러스터에서 대상 클러스터 API 서버 연결 테스트
    kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- \
      curl -k https://prod-cluster-api.example.com/healthz
    

    문제 2: prune 옵션으로 인한 의도치 않은 리소스 삭제

    이건 진짜 아찔했던 경험인데요. automated.prune: true를 켜놨는데, 실수로 Git에서 파일을 지웠다가 프로덕션 Deployment가 통째로 삭제된 적이 있었어요. 다행히 빠르게 복구했지만…

    이후로 저는 프로덕션 환경에는 Sync Windows(동기화 창)를 설정해서 업무 시간 외에는 자동 동기화가 안 되도록 했습니다.

    # AppProject에 Sync Window 추가
    spec:
      syncWindows:
        - kind: allow
          schedule: '0 9 * * 1-5'  # 평일 오전 9시에만
          duration: 8h
          applications:
            - '*'
          manualSync: true  # 수동 동기화는 항상 허용
    

    문제 3: Helm 차트 버전 충돌

    여러 클러스터에 같은 Helm 차트를 다른 버전으로 배포하다 보면 values 파일 구조가 버전마다 달라서 오류가 나는 경우가 있어요. 저는 이걸 해결하기 위해 클러스터별 values 파일을 명확하게 분리해서 관리하고 있습니다.

    # Helm 소스 예시 - 클러스터별 values 파일 분리
    source:
      repoURL: https://charts.example.com
      chart: my-chart
      targetRevision: 1.2.3
      helm:
        valueFiles:
          - values.yaml
          - values-prod.yaml  # 환경별 values 파일
    
    ArgoCD Applications 대시보드에서 멀티 클러스터 배포된 애플리케이션들이 모두 Synced Healthy 상태로 표시된 결과 화면

    ▲ ArgoCD 웹 UI의 Applications 화면. dev, staging, prod 클러스터에 배포된 my-app들이 모두 Synced/Healthy 상태로 표시되는 모습입니다.

    ✅ 배포 검증 및 운영 팁

    헬스 체크와 동기화 상태 모니터링

    # 전체 Application 상태 확인
    argocd app list
    
    # 특정 앱 상세 상태 확인
    argocd app get my-app-prod
    
    # 수동으로 동기화 실행
    argocd app sync my-app-prod
    
    # 동기화 상태 및 히스토리 확인
    argocd app history my-app-prod
    
    # 롤백 (이전 버전으로)
    argocd app rollback my-app-prod 
    

    Notification(알림) 설정으로 배포 상태 파악

    ArgoCD에는 argocd-notifications라는 컴포넌트가 있어서 Slack, 이메일, PagerDuty 등으로 배포 상태를 알림 받을 수 있어요. 저는 Slack 채널에 연동해서 배포 성공/실패를 실시간으로 받고 있는데, 이거 없으면 이제 못 살 것 같습니다 ㅎㅎ.

    RBAC으로 팀별 접근 제어

    # argocd-rbac-cm ConfigMap 예시
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: argocd-rbac-cm
      namespace: argocd
    data:
      policy.csv: |
        # 개발팀: dev 프로젝트만 접근 가능
        p, role:dev-team, applications, *, dev/*, allow
        p, role:dev-team, applications, sync, dev/*, allow
        
        # 플랫폼팀: 전체 접근 가능
        p, role:platform-team, applications, *, */*, allow
        p, role:platform-team, clusters, *, *, allow
        
        g, myorg:dev-team, role:dev-team
        g, myorg:platform-team, role:platform-team
      policy.default: role:readonly
    

    전략 정리 — 이것만 기억하세요

    ArgoCD 멀티 클러스터 GitOps 전략 핵심 구성요소 요약 인포그래픽 - AppProject, ApplicationSet, Kustomize, RBAC 관계도

    ▲ ArgoCD 멀티 클러스터 GitOps 전략의 핵심 요소를 한눈에 정리한 요약 다이어그램. Git 저장소 구조, ArgoCD 컴포넌트, 클러스터 관계를 보여줍니다.

    구성 요소 역할 핵심 포인트
    AppProject 배포 범위 및 권한 제한 환경별로 분리, Sync Window 설정
    Application 개별 앱 배포 정의 destination.server로 대상 클러스터 지정
    ApplicationSet 다중 클러스터 배포 자동화 템플릿 하나로 여러 클러스터에 배포
    Kustomize Overlay 환경별 설정 분리 base 공유, 환경별 차이만 patch
    RBAC 팀별 접근 제어 AppProject role + argocd-rbac-cm

    마무리하며 — 그래서 뭐가 달라졌냐면

    ArgoCD 멀티 클러스터 구성을 제대로 잡고 나서 가장 크게 달라진 건, 배포에 대한 불안감이 사라졌다는 거예요. 예전엔 “내가 지금 어떤 클러스터에 붙어있지?”를 항상 확인해야 했는데, 이제는 Git에 PR 올리고 머지하면 끝이거든요.

    물론 처음 설계할 때 Git 저장소 구조를 잘 잡는 게 제일 중요합니다. 나중에 구조를 바꾸려면 진짜 손이 많이 가거든요. 저도 한 번 갈아엎었습니다… ㅎㅎ 이 글을 보시는 분들은 처음부터 overlays 구조로 시작하시길 강력 추천합니다.

    다음 글에서는 ArgoCD Image Updater를 활용해서 컨테이너 이미지 태그 업데이트까지 완전 자동화하는 방법을 다뤄볼 예정이에요. CI 파이프라인에서 이미지 빌드되면 ArgoCD가 자동으로 Git 커밋하고 배포까지 이어지는 풀 사이클인데, 이거 진짜 편합니다.

    혹시 멀티 클러스터 구성하면서 막히는 부분 있으시면 댓글로 남겨주세요. 제가 겪어본 삽질이라면 같이 해결해 드릴 수 있을 것 같습니다 🎉

    자주 묻는 질문 (FAQ)

    Q. ArgoCD 자체가 설치된 허브 클러스터가 다운되면 어떻게 되나요?

    A. 허브 클러스터가 다운되면 새로운 배포는 불가능하지만, 이미 배포된 애플리케이션들은 각 클러스터에서 계속 정상 동작합니다. 이 때문에 허브 클러스터는 고가용성(HA) 구성으로 운영하는 것을 권장합니다.

    Q. ApplicationSet과 Application을 언제 선택해야 하나요?

    A. 동일한 앱을 여러 클러스터에 배포해야 한다면 ApplicationSet이 훨씬 효율적입니다. 클러스터마다 배포 설정이 크게 다르거나 배포 시점을 완전히 독립적으로 관리해야 한다면 개별 Application을 사용하는 게 맞습니다.

    Q. Helm과 Kustomize 중 어떤 걸 쓰는 게 좋나요?

    A. 서드파티 차트를 그대로 쓸 때는 Helm, 자체 매니페스트를 환경별로 관리할 때는 Kustomize가 더 직관적입니다. 둘을 혼합해서 쓰는 것도 가능하고, 저는 실제로 두 방식을 같이 씁니다.