13년차의 서버실

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

[태그:] Argo CD

  • [Cloud] Crossplane GitOps로 멀티 클라우드 인프라 운영 전략

    [Cloud] Crossplane GitOps로 멀티 클라우드 인프라 운영 전략

    Crossplane GitOps로 멀티 클라우드 인프라 운영 전략 잡기

    Crossplane GitOps를 붙이면 처음엔 다 비슷한 착각을 하더라고요. Git에 YAML만 넣으면 멀티 클라우드 운영이 정리될 거라고요. 그런데 실제 운영에선 YAML 문법보다 인터페이스를 어디까지 추상화할지, 어느 계정과 어느 클러스터에 누가 적용 권한을 가질지, 실패했을 때 어느 계층에서 멈췄는지 어떻게 판별할지가 훨씬 더 중요합니다.

    특히 AWS, GCP, Azure가 섞여 있고 환경마다 네트워크, 계정, 규정이 다르면 더 그렇습니다. 이때 Crossplane GitOps의 진짜 가치는 단순히 인프라를 Kubernetes 안으로 가져오는 데 있지 않습니다. 제가 현업에서 더 크게 느낀 건 개발팀이 보는 요청 인터페이스와 플랫폼 팀이 유지하는 구현 세부를 분리할 수 있다는 점이었어요. 멀티 클라우드 운영이 어려운 이유는 클라우드가 여러 개라서가 아니라, 요청 인터페이스와 실행 책임이 뒤엉키기 때문이거든요.

    그래서 이 글은 단순 설치 가이드로 가지 않겠습니다. Crossplane GitOps를 언제 쓰면 이득이 커지고, 언제는 오히려 운영 복잡도만 늘어나는지, 그리고 실제 설계에서 먼저 봐야 할 기준을 중심으로 정리해보겠습니다.

    Crossplane GitOps 기반 멀티 클라우드 관리 아키텍처 다이어그램

    Git 저장소, Argo CD, Crossplane, 여러 클라우드 또는 클러스터가 어떻게 연결되는지 한눈에 보여주는 아키텍처 이미지 위치입니다.

    Crossplane GitOps가 멀티 클라우드에서 유리한 이유, 그리고 아닌 경우

    제가 이 조합을 높게 보는 이유는 하나입니다. 요청은 공통 API로 받고, 실행은 환경별 구현으로 분기할 수 있기 때문입니다. 개발팀은 비슷한 형식의 리소스를 요청하고, 플랫폼 팀은 뒤에서 AWS용 Composition을 태울지 GCP용 Composition을 태울지 고릅니다. 이 구조가 잡히면 클라우드 차이가 사용자 경험으로 바로 새지 않아요.

    • 적합한 경우: 개발팀 셀프서비스가 필요하고, 플랫폼 팀이 공통 API를 설계할 역량이 있을 때
    • 적합한 경우: 이미 Argo CD나 Flux 같은 GitOps 흐름이 있고, 인프라도 같은 승인 체계로 묶고 싶을 때
    • 보류할 경우: 팀마다 요구사항 차이가 커서 공통 인터페이스가 거의 안 나올 때
    • 보류할 경우: 단일 클라우드 비중이 높고 Terraform 워크스페이스 수준으로도 운영이 충분히 정리될 때

    여기서 핵심 판단 기준은 반복되는 요청이 실제로 존재하느냐입니다. Namespace, Object Storage, 기본 권한 연결처럼 반복 요청이 많고 정책을 통일해야 하는 자원은 Crossplane GitOps와 잘 맞습니다. 반대로 일회성 예외 구성, 팀별 편차가 큰 네트워크 토폴로지, 사람 승인 없이는 못 건드리는 보안 자원은 억지로 추상화하면 디버깅 비용만 올라갑니다.

    Crossplane GitOps 설계 전에 먼저 못 박아야 하는 결정

    Crossplane GitOps가 꼬이는 팀은 대개 YAML을 늦게 배워서가 아니라 결정 순서가 뒤집혀서 그렇습니다. 이걸 정하지 않고 XRD부터 만들면 나중에 스키마를 몇 번씩 뜯어고치게 됩니다. 이거 생각보다 꽤 아픕니다.

    의사결정 항목 선택지 이럴 때 권장 트레이드오프
    제어면 토폴로지 중앙 Crossplane 1개 플랫폼 팀이 강하고 공통 정책을 한곳에서 관리해야 할 때 장애 반경이 커지고 ProviderConfig 권한 경계 설계가 더 중요해집니다.
    제어면 토폴로지 환경별 Crossplane 분리 계정·망 분리가 엄격하고 운영 책임이 환경별로 나뉠 때 중복 배포와 업그레이드 비용이 늘지만 장애 격리가 쉽습니다.
    API 노출 방식 Namespaced XR Crossplane v2 신규 설계를 시작할 때 최신 권장 방식이지만 기존 Claim 중심 운영 모델과는 설계 습관이 달라집니다.
    API 노출 방식 Claim v1 스타일 API를 이미 쓰고 있거나 호환 운영이 필요할 때 Crossplane v2 신규 기본 모델은 아니므로 장기적으로는 XR 중심 전환을 검토해야 합니다.
    추상화 단위 얇은 API 셀프서비스를 빨리 열고 장애 분석 시간을 줄여야 할 때 리소스 수는 늘지만 실패 지점이 선명합니다.
    클라우드 분기 방식 Composition 분리 AWS/GCP/Azure 구현 차이가 큰 경우 구현은 길어지지만 디버깅과 변경 영향도 파악이 쉽습니다.
    자격 증명 모델 ProviderConfig 환경별 분리 계정, 프로젝트, 구독이 다르고 감사 경계가 중요할 때 Secret 회전과 참조 관계를 별도 운영해야 합니다.

    제가 실제로는 이렇게 봅니다. 멀티 클라우드라고 해서 처음부터 공통 API를 넓게 잡지 마세요. 공통분모가 작으면 추상화도 작아야 합니다. Namespace, Bucket, 기본 IAM 연동처럼 공통성이 높은 것부터 묶고, 네트워크처럼 클라우드별 의미가 달라지는 영역은 늦게 가져오는 편이 훨씬 덜 아픕니다.

    실전 구현 1: 저장소 분리와 기본 설치는 이렇게 가져가면 덜 꼬입니다

    실무에서는 저장소 구조가 운영 모델을 거의 결정합니다. 저는 보통 platform, providers, claims-or-xrs를 분리합니다. 이유는 단순합니다. 변경 주기와 승인 권한이 다르기 때문입니다. 플랫폼 팀이 XRD와 Composition을 바꾸는 행위와, 서비스 팀이 요청 리소스 하나 추가하는 행위를 같은 PR 정책으로 묶으면 금방 병목이 생깁니다.

    1. platform: XRD, Composition, Function
    2. providers: Provider, ProviderConfig, Secret 참조 정책
    3. claims-or-xrs: 팀별 요청 리소스

    최신 Crossplane v2 기준으로는 Namespaced XR이 기본이고, Claim은 신규 기본 모델이 아닙니다. 다만 기존 v1 스타일 API를 유지하는 환경이라면 Claim 패턴을 계속 쓸 수는 있습니다. 그래서 신규 도입 팀이라면 XR 중심으로 시작하고, 기존 팀이라면 Claim을 호환 운영 대상으로 보는 편이 더 안전합니다.

    Crossplane 자체 설치는 현재 문서 기준으로 Helm chart가 가장 깔끔합니다. 그리고 Patch & Transform을 쓸 거라면 Composition Function도 같이 올려두는 편이 좋습니다. 나중에 예제를 확장할 때 진짜 편하더라고요.

    helm repo add crossplane-stable https://charts.crossplane.io/stable
    helm repo update
    
    helm install crossplane \
      --namespace crossplane-system \
      --create-namespace \
      crossplane-stable/crossplane
    
    kubectl get pods -n crossplane-system
    
    kubectl apply -f - <<'EOF'
    apiVersion: pkg.crossplane.io/v1
    kind: Function
    metadata:
      name: function-patch-and-transform
    spec:
      package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
    EOF
    
    kubectl get functions

    GitOps 도구는 Crossplane를 애플리케이션 하나로 보지 않는 편이 좋습니다. 제가 권하는 최소 단위는 core, platform, workloads 세 층입니다. 이유는 순서와 장애 반경이 다르기 때문입니다. XRD가 아직 준비되지 않았는데 XR이나 Claim이 먼저 들어오면, YAML은 정상인데 동기화가 실패하는 황당한 상황이 바로 나옵니다.

    Argo CD를 쓰면 sync wave 또는 앱 분리로 순서를 고정하는 편이 안전합니다. 또 Crossplane 공식 가이드처럼 annotation 기반 리소스 추적을 쓰고, ProviderConfigUsage는 UI에서 제외하는 구성이 운영 노이즈를 줄이는 데 도움이 됩니다.

    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: crossplane-platform
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://git.example.com/platform/infra.git
        targetRevision: main
        path: crossplane/platform
      destination:
        server: https://kubernetes.default.svc
        namespace: crossplane-system
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
          - ApplyOutOfSyncOnly=true
          - SkipDryRunOnMissingResource=true

    여기서 포인트는 세 가지입니다. CreateNamespace=true는 기본 편의고, SkipDryRunOnMissingResource=true는 CRD가 먼저 생기는 구조에서 불필요한 실패를 줄여줍니다. 그리고 앱이 커지기 시작하면 ApplyOutOfSyncOnly=true를 고려할 만합니다. Claim이나 XR 수가 많아질수록 매 sync마다 전체 리소스를 다시 건드리는 비용이 꽤 느껴지거든요.

    Crossplane GitOps 저장소 구조와 Argo CD 동기화 흐름 이미지

    플랫폼 정의, Provider 설정, Claim 또는 XR 배포가 어떤 순서로 Git에서 흘러가는지 보여주는 구성도 위치입니다.

    실전 구현 2: XR 하나로 클라우드별 구현 갈라타기

    최신 Crossplane v2 신규 설계라면 예시는 Claim보다 Namespaced XR로 잡는 편이 맞습니다. 여러 클러스터에 걸쳐 Namespace를 만드는 패턴은 작지만 실무적인 예제예요. 추상화가 맞는지 틀린지는 거대한 데이터베이스보다 이런 작은 자원에서 먼저 드러나는 경우가 많습니다.

    먼저 provider-kubernetes와 ProviderConfig를 준비합니다. 서로 다른 대상 클러스터에 apply하는 흐름이므로 kubeconfig Secret 경계를 분명히 두는 게 핵심입니다. 운영에서 제일 흔한 사고는 권한이 부족해서 실패하는 경우보다, 잘못된 대상 클러스터에 정상 적용되는 경우입니다. 그래서 ProviderConfig 이름에 환경과 대상 의미를 같이 넣는 걸 권합니다.

    apiVersion: pkg.crossplane.io/v1
    kind: Provider
    metadata:
      name: provider-kubernetes
    spec:
      package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v1.3.0
    ---
    apiVersion: kubernetes.m.crossplane.io/v1alpha1
    kind: ProviderConfig
    metadata:
      name: aws-dev-cluster
      namespace: crossplane-system
    spec:
      credentials:
        source: Secret
        secretRef:
          namespace: crossplane-system
          name: aws-dev-cluster-kubeconfig
          key: kubeconfig
    ---
    apiVersion: kubernetes.m.crossplane.io/v1alpha1
    kind: ProviderConfig
    metadata:
      name: gcp-prod-cluster
      namespace: crossplane-system
    spec:
      credentials:
        source: Secret
        secretRef:
          namespace: crossplane-system
          name: gcp-prod-cluster-kubeconfig
          key: kubeconfig

    그다음 XRD와 Composition입니다. 아래 예시는 사용자에게는 동일한 PlatformNamespace만 보이고, 실제 구현은 AWS용과 GCP용 Composition으로 갈라집니다. 만약 기존 운영이 Claim 중심이라면 이 패턴을 v1 스타일 API와 함께 유지할 수는 있지만, 신규 도입이라면 XR로 시작하는 편이 덜 헷갈립니다.

    apiVersion: apiextensions.crossplane.io/v2
    kind: CompositeResourceDefinition
    metadata:
      name: platformnamespaces.platform.example.org
    spec:
      scope: Namespaced
      group: platform.example.org
      names:
        kind: PlatformNamespace
        plural: platformnamespaces
      versions:
      - name: v1alpha1
        served: true
        referenceable: true
        schema:
          openAPIV3Schema:
            type: object
            properties:
              spec:
                type: object
                properties:
                  parameters:
                    type: object
                    properties:
                      name:
                        type: string
                      provider:
                        type: string
                        enum:
                        - aws
                        - gcp
                    required:
                    - name
                    - provider
                required:
                - parameters
    ---
    apiVersion: apiextensions.crossplane.io/v1
    kind: Composition
    metadata:
      name: platformnamespace-aws
      labels:
        provider: aws
    spec:
      compositeTypeRef:
        apiVersion: platform.example.org/v1alpha1
        kind: PlatformNamespace
      mode: Pipeline
      pipeline:
      - step: patch-and-transform
        functionRef:
          name: function-patch-and-transform
        input:
          apiVersion: pt.fn.crossplane.io/v1beta1
          kind: Resources
          resources:
          - name: namespace
            base:
              apiVersion: kubernetes.m.crossplane.io/v1alpha1
              kind: Object
              metadata:
                namespace: crossplane-system
              spec:
                forProvider:
                  manifest:
                    apiVersion: v1
                    kind: Namespace
                    metadata:
                      name: placeholder
                providerConfigRef:
                  name: aws-dev-cluster
            patches:
            - type: FromCompositeFieldPath
              fromFieldPath: spec.parameters.name
              toFieldPath: spec.forProvider.manifest.metadata.name
    ---
    apiVersion: apiextensions.crossplane.io/v1
    kind: Composition
    metadata:
      name: platformnamespace-gcp
      labels:
        provider: gcp
    spec:
      compositeTypeRef:
        apiVersion: platform.example.org/v1alpha1
        kind: PlatformNamespace
      mode: Pipeline
      pipeline:
      - step: patch-and-transform
        functionRef:
          name: function-patch-and-transform
        input:
          apiVersion: pt.fn.crossplane.io/v1beta1
          kind: Resources
          resources:
          - name: namespace
            base:
              apiVersion: kubernetes.m.crossplane.io/v1alpha1
              kind: Object
              metadata:
                namespace: crossplane-system
              spec:
                forProvider:
                  manifest:
                    apiVersion: v1
                    kind: Namespace
                    metadata:
                      name: placeholder
                providerConfigRef:
                  name: gcp-prod-cluster
            patches:
            - type: FromCompositeFieldPath
              fromFieldPath: spec.parameters.name
              toFieldPath: spec.forProvider.manifest.metadata.name
    ---
    apiVersion: platform.example.org/v1alpha1
    kind: PlatformNamespace
    metadata:
      namespace: platform-team
      name: observability
    spec:
      crossplane:
        compositionSelector:
          matchLabels:
            provider: aws
      parameters:
        name: observability
        provider: aws

    여기서 제가 꼭 보는 판단 기준이 있습니다. 분기 기준을 사용자에게 직접 노출할지, 운영 정책으로 숨길지입니다. 팀이 아직 멀티 클라우드 배치 정책을 이해하고 선택할 단계가 아니면 provider 같은 필드를 API에 열어주지 않는 편이 낫습니다. 반대로 플랫폼 팀이 모든 배치 결정을 대신하면서 병목이 심하면, 제한된 선택지를 노출하는 게 더 현실적일 때도 있어요.

    Crossplane GitOps의 Composition 선택과 멀티 클라우드 배포 흐름

    AWS용 Composition과 GCP용 Composition이 같은 요청 인터페이스를 받아 서로 다른 대상 클러스터로 배포하는 흐름을 설명하는 이미지 위치입니다.

    실전에서 자주 터지는 문제와 근본 원인

    운영에서 힘든 지점은 YAML 자체가 아닙니다. 대부분의 장애는 Composition 선택, ProviderConfig 인증, 외부 API 응답, GitOps 적용 순서에서 생깁니다. 저도 초기에 이 구간에서 제일 오래 헤맸습니다.

    1. CRD와 요청 리소스를 같은 배포 단위로 넣었더니 가끔만 성공

    증상: 어떤 날은 되고 어떤 날은 안 됩니다. Argo CD에서는 리소스가 다 보이는데, 실제 sync에서는 XR이나 Claim이 먼저 들어가며 the server could not find the requested resource 비슷한 오류가 납니다.

    근본 원인: 선언은 동시에 넣었지만, 컨트롤 플레인 입장에서는 XRD 등록과 이를 참조하는 리소스 생성이 완전히 다른 단계이기 때문입니다.

    실무 처방: 앱을 분리하거나 sync wave를 명시하세요. 이 문제는 설치 순서라기보다 배포 계약 문제에 가깝습니다.

    2. ProviderConfig 인증 문제인데 요청 리소스만 들여다봄

    증상: XR은 생성됐는데 Ready가 끝까지 올라오지 않습니다.

    근본 원인: kubeconfig Secret 참조 오류, 잘못된 컨텍스트, 대상 클러스터 RBAC 부족, 만료된 토큰 중 하나인 경우가 많습니다.

    제가 보는 순서는 위에서 아래가 아니라 아래에서 위입니다. 요청 리소스에서 시작해 결국 Managed Resource와 Provider 로그까지 내려가야 원인이 보입니다.

    kubectl get platformnamespaces.platform.example.org -A
    kubectl get objects.kubernetes.m.crossplane.io -A
    kubectl describe object.kubernetes.m.crossplane.io <object-name> -n crossplane-system
    kubectl describe providerconfig.kubernetes.m.crossplane.io aws-dev-cluster -n crossplane-system
    kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kubernetes --tail=200
    • XR는 존재하는데 Object가 없으면 Composition 선택이나 함수 입력을 먼저 봅니다.
    • Object는 있는데 Ready=False면 외부 클러스터 적용 실패 가능성이 큽니다.
    • cannot get credentials, secret not found류 메시지는 거의 ProviderConfig 또는 Secret 참조 문제입니다.
    • forbidden가 보이면 kubeconfig는 읽었지만 대상 클러스터 RBAC가 부족한 경우가 많습니다.

    3. 요청 API를 너무 두껍게 만들어서 장애 반경이 커짐

    이건 구조적 문제입니다. Namespace, RoleBinding, NetworkPolicy, ExternalSecret, StorageClass 참조까지 한 API에 다 넣으면 처음엔 멋있어 보여요. 그런데 하나만 실패해도 전체 요청 실패로 뭉개집니다. 멀티 클라우드에서는 실패 원인이 클라우드별로 달라서, 한 API는 한 책임 원칙을 거의 고정으로 가져가는 편이 훨씬 낫습니다.

    • 네임스페이스 생성은 네임스페이스 생성
    • 권한 바인딩은 별도 API
    • 스토리지는 스토리지
    • 데이터 서비스는 데이터 서비스

    4. Composition 변경이 기존 XR 전체에 바로 전파됨

    증상: 플랫폼 팀이 Composition을 수정했는데, 의도하지 않게 기존 요청들도 동작이 바뀝니다.

    근본 원인: Crossplane는 Composition 변경을 revision으로 추적하고, XR은 기본적으로 최신 revision을 자동 추종할 수 있기 때문입니다.

    실무 처방: 변경 위험이 큰 리소스는 compositionUpdatePolicy: Manual을 검토하세요. 데이터 서비스, 네트워크, 권한 모델처럼 기존 인스턴스가 한꺼번에 바뀌면 안 되는 자원은 자동 추종보다 수동 승격이 훨씬 안전합니다.

    apiVersion: platform.example.org/v1alpha1
    kind: PlatformNamespace
    metadata:
      namespace: platform-team
      name: observability
    spec:
      crossplane:
        compositionUpdatePolicy: Manual
        compositionRef:
          name: platformnamespace-aws
      parameters:
        name: observability
        provider: aws

    5. Git에서 지웠더니 실제 리소스 삭제까지 이어져 버림

    GitOps를 인프라에 붙일 때 가장 민감한 건 생성보다 삭제입니다. Namespace나 버킷처럼 파급효과가 큰 자원은 PR 하나의 merge가 곧바로 파괴 작업으로 이어지지 않게 설계해야 합니다.

    실무 처방: 삭제 위험이 큰 자원은 저장소를 분리하거나, PR 승인 정책을 강화하거나, Argo CD의 삭제 확인 옵션을 검토하세요. Crossplane GitOps는 생성 자동화보다 삭제 통제를 먼저 설계한 팀이 훨씬 안정적이었습니다.

    검증 포인트: 무엇을 봐야 진짜 성공인지

    배포가 끝났다고 끝이 아닙니다. 저는 항상 세 층을 분리해서 봅니다. Git 상태, Crossplane 상태, 실제 대상 상태입니다. 이 셋 중 하나라도 빠지면 동기화는 됐는데 서비스는 안 되는 애매한 상태가 생깁니다.

    1. GitOps 도구에서 애플리케이션 sync와 health가 정상인지 확인
    2. XR, Composition, Managed Resource가 모두 생성됐는지 확인
    3. 조건 값에서 Synced와 Ready를 구분해 읽기
    4. 대상 클러스터 또는 클라우드에 실제 자원이 생겼는지 직접 검증
    kubectl get platformnamespace observability -n platform-team -o yaml
    kubectl get platformnamespaces.platform.example.org -A -o custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[?(@.type=="Synced")].status,READY:.status.conditions[?(@.type=="Ready")].status
    kubectl get objects.kubernetes.m.crossplane.io -A -o custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[?(@.type=="Synced")].status,READY:.status.conditions[?(@.type=="Ready")].status
    kubectl --kubeconfig ./aws-dev-cluster.kubeconfig get ns observability

    Argo CD가 Healthy여도 외부 클러스터 리소스 생성까지 보장하지는 않습니다. 반대로 Crossplane Object가 Synced라고 해도 대상 클러스터에서 admission webhook이나 정책 엔진이 막고 있을 수 있습니다. 그래서 대시보드도 정상 개수보다 Ready=False 목록과 최근 이벤트 위주로 보는 편이 훨씬 실용적입니다.

    Crossplane GitOps 운영 검증 체크리스트와 선택 가이드 인포그래픽

    검증 순서, 상태값 판독 기준, 상황별 추천 선택을 정리한 요약 인포그래픽 위치입니다.

    운영 권장안: 이런 팀이면 이렇게 가는 편이 낫습니다

    • 클라우드가 1~2개이고 플랫폼 팀이 작다: API는 얇게 두고, ProviderConfig만 환경별로 분리하세요. 초반에는 추상화의 아름다움보다 장애 분석 속도가 더 중요합니다.
    • 플랫폼 팀과 개발팀이 분리돼 있다: XRD 스키마부터 먼저 고정하세요. 요청 저장소와 platform 저장소 권한을 분리하는 게 운영 비용을 줄입니다.
    • 클라우드마다 자원 모델 차이가 크다: 억지 공통분모를 만들지 말고 Composition을 클라우드별로 나누세요. 구현 중복보다 잘못된 추상화가 더 비쌉니다.
    • 변경 승인과 감사 추적이 중요하다: 사용자 요청 merge와 Composition 변경을 같은 승인 흐름에 두지 마세요. 위험도가 다릅니다.
    • 삭제 사고가 치명적이다: GitOps 자동화 범위에서 삭제를 별도로 취급하세요. 생성 자동화보다 삭제 통제가 먼저입니다.

    반대로 요청 패턴이 아직 안정되지 않았거나, 예외가 표준보다 많거나, 운영팀이 Kubernetes CRD 디버깅 경험이 부족하다면 조금 멈추는 게 맞습니다. Crossplane가 만능 해답은 아니거든요.

    제가 실제로는 보통 이렇게 시작합니다. Namespace → Object Storage → 기본 권한 연결 → 데이터 서비스 순서입니다. 공통성이 높은 자원부터 열고, 실패 비용이 큰 자원은 뒤로 미루는 편이 운영 충격이 적었습니다.

    자주 묻는 질문과 마지막 판단 기준

    Crossplane GitOps가 Terraform을 완전히 대체하나요?

    항상 그렇지는 않습니다. 다만 플랫폼 API를 Kubernetes 안에 두고, 서비스 팀 요청을 PR 기반으로 받으며, 멀티 클라우드 구현을 뒤로 숨겨야 한다면 Crossplane GitOps가 훨씬 자연스럽습니다. 반대로 공통 API보다 프로젝트별 세밀한 조정이 더 중요하면 Terraform이 더 단순할 때도 많습니다.

    멀티 클라우드 운영을 위해 꼭 복잡한 추상화가 필요한가요?

    아닙니다. 오히려 복잡한 추상화를 기본값으로 두지 않는 편이 낫습니다. 반복되는 요청이 명확할 때만 추상화하고, 반복되지 않으면 직결 구성이 더 낫습니다.

    처음 도입하는 팀이라면 어디까지를 1차 목표로 잡는 게 좋을까요?

    제가 추천하는 1차 목표는 간단합니다. 사용자가 같은 형식의 XR 또는 Claim을 제출하고, 플랫폼 팀이 ProviderConfig와 Composition으로 대상 환경을 통제할 수 있는 상태까지입니다. 거기서 이미 운영 가치가 나옵니다. 그다음에 Composition Revision, Secret 회전, 환경 승격, 정책 검증을 붙이는 순서가 훨씬 안정적입니다.

    관련 글도 함께 보시면 흐름이 더 잘 잡힙니다. Argo CD sync wave 설계 가이드, Kubernetes GitOps 운영 체크리스트도 이어서 확인해보세요.

    마지막 판단 기준을 한 줄로 줄이면 이겁니다. Crossplane GitOps는 인프라 자동화 도구라기보다 플랫폼 인터페이스 설계 도구에 가깝습니다. AWS와 GCP를 함께 다루는 팀이라면 API는 좁고 명확하게, Composition은 클라우드별로 분리해서 시작하세요. 반대로 단일 클라우드에 가깝거나 반복 요청이 적다면 굳이 복잡한 추상화를 도입하지 않는 편이 더 낫습니다.

  • [DevOps] Argo CD vs Spinnaker: CI/CD 파이프라인 구축 비교 분석

    [DevOps] Argo CD vs Spinnaker: CI/CD 파이프라인 구축 비교 분석

    [DevOps] Argo CD vs Spinnaker: CI/CD 파이프라인 구축 비교 분석

    제가 현업이랑 홈랩에서 이것저것 굴려보면서 느낀 게 하나 있습니다. Argo CD Spinnaker 비교는 단순히 “어느 툴이 더 좋냐”의 문제가 아니더라고요. 팀이 Kubernetes를 어디까지 표준으로 쓰고 있는지, 배포 승인을 얼마나 엄격하게 가져가는지, 그리고 운영자가 원하는 가시성(visibility)이 어느 정도인지에 따라 답이 꽤 달라집니다. CI/CD 파이프라인을 처음 설계할 때 이 부분을 대충 잡고 들어가면, 나중에 배포 흐름이 꼬여서 삽질 좀 하게 됩니다 ㅎㅎ

    특히 지속적 배포(Continuous Delivery)를 도입하려는 팀이라면 더 그렇습니다. 처음엔 저도 “둘 다 배포 툴 아닌가?” 싶었는데, 실제로 써보니까 운영 철학이 꽤 다르더라고요. 오늘은 클라우드 네이티브 환경에서 Argo CD와 Spinnaker를 어떻게 봐야 하는지, 어디서 갈리는지, 실전에서는 어떻게 접근하면 되는지를 경험 섞어서 정리해보겠습니다.

    Argo CD Spinnaker 비교 아키텍처 다이어그램

    Argo CD의 GitOps 흐름과 Spinnaker의 파이프라인 중심 배포 흐름을 한 장으로 비교하는 개요 이미지입니다.

    1. 왜 Argo CD vs Spinnaker 비교가 중요한가

    배포 자동화는 이제 선택이 아니라 기본이죠. 그런데 자동화라고 다 같은 자동화가 아닙니다. 어떤 팀은 Git 저장소만 바꾸면 자동 반영되는 구조가 편하고, 어떤 팀은 승인 단계, 카나리 배포(일부 트래픽만 먼저 보내는 배포), 멀티 클라우드 전략이 더 중요하거든요.

    여기서 중요한 포인트가 있습니다. Argo CD는 GitOps(Git을 단일 진실 원본으로 삼는 운영 방식)에 굉장히 강한 도구이고, Spinnaker는 복잡한 배포 파이프라인과 멀티 클라우드 전달 흐름에 강한 플랫폼이라는 점입니다. 이 차이를 모르고 고르면, 도입 초반엔 쉬워 보여도 운영이 무거워지거나 반대로 필요한 통제가 안 붙는 상황이 생깁니다.

    • Git 변경 기반 자동 동기화가 중요하다면 Argo CD 쪽이 잘 맞습니다.
    • 복수 환경 승인, 배포 전략, 외부 시스템 연동이 많다면 Spinnaker가 더 자연스럽습니다.
    • Kubernetes 중심인지, 여러 배포 타깃이 섞여 있는지도 판단 기준입니다.

    2. 개념부터 쉽게: Argo CD와 Spinnaker는 어떻게 다를까

    2-1. Argo CD: 선언형 배포와 GitOps 중심

    쉽게 말해 Argo CD는 “클러스터 상태는 Git에 적어둔 그대로여야 한다”는 철학으로 움직입니다. Git 저장소 안의 YAML 매니페스트(manifest, 리소스 정의 파일)나 Helm 차트(Chart, 쿠버네티스 패키지)를 보고, 실제 클러스터 상태와 비교한 뒤 차이가 있으면 맞춰주는 방식이죠.

    제가 직접 해보니 이 방식의 장점은 정말 명확했습니다. 배포 이력이 Git 커밋으로 남고, 누가 뭘 바꿨는지 추적하기가 편합니다. 드리프트(drift, 선언한 상태와 실제 상태가 어긋나는 현상)도 눈에 잘 보이고요. 대신 애플리케이션 전달 과정 전체를 설계하는 엔진이라기보다는, Kubernetes 배포 상태를 Git 기준으로 맞추는 데 최적화된 도구에 가깝습니다.

    2-2. Spinnaker: 파이프라인 오케스트레이션 중심

    Spinnaker는 접근이 조금 다릅니다. 이쪽은 “어떤 조건에서 어떤 순서로 배포를 진행할지”를 세밀하게 다루는 데 강합니다. 예를 들면 빌드 완료 후 테스트 실행, 승인, 스테이징 배포, 카나리 분석, 운영 반영 같은 흐름을 하나의 파이프라인으로 묶는 식이죠.

    처음엔 이게 뭔가 싶었는데, 실제로 써보니까 대규모 환경이나 규정이 많은 조직에서 왜 Spinnaker를 선호하는지 이해가 되더라고요. 반면 구성 요소가 많고 운영 복잡도도 꽤 있습니다. 작은 팀이 가볍게 시작하기엔 다소 무겁다고 느낄 수 있습니다.

    2-3. 핵심 차이 한눈에 보기

    항목 Argo CD Spinnaker
    핵심 철학 GitOps 기반 선언형 동기화 파이프라인 기반 지속적 배포
    주요 타깃 Kubernetes 중심 멀티 클라우드 및 다양한 배포 전략
    운영 복잡도 상대적으로 단순 상대적으로 높음
    강점 상태 일치, 변경 추적, Git 연동 승인 흐름, 카나리, 복합 파이프라인
    적합한 팀 플랫폼 표준이 Kubernetes인 팀 배포 정책과 절차가 복잡한 팀

    3. 어떤 팀에 어떤 도구가 맞는가

    이 부분은 제가 후배들한테도 자주 이야기하는데요, 도구를 고를 때 기능 목록보다 운영 모델을 먼저 봐야 합니다.

    • Argo CD가 잘 맞는 경우: Git 기반 운영을 표준화하고 싶을 때, Kubernetes 리소스가 배포의 중심일 때, 운영 단순성이 중요할 때
    • Spinnaker가 잘 맞는 경우: 승인 단계가 많을 때, 여러 클라우드나 배포 전략을 함께 다룰 때, 파이프라인 오케스트레이션이 핵심일 때
    • 둘을 함께 보는 경우: CI는 다른 툴에서 처리하고, CD만 역할 분리해서 설계할 때

    사실 Argo CD Spinnaker 비교에서 제일 많이 놓치는 지점이 여기입니다. 둘은 완전히 같은 문제를 푸는 경쟁 제품이라기보다, 겹치는 영역이 있으면서도 중심축이 다른 도구라고 보는 게 더 정확합니다.

    4. 실전 구현: Argo CD로 GitOps형 CI/CD 파이프라인 구성

    먼저 Argo CD 쪽부터 보겠습니다. 홈랩에서 테스트할 때 저는 보통 “애플리케이션 매니페스트를 Git에 넣고, Argo CD가 자동 동기화하게 만드는 흐름”으로 검증합니다. 이 구조는 이해도 쉽고, 실무 전환도 빠르거든요.

    4-1. Argo CD 설치

    1. 전용 네임스페이스(namespace, Kubernetes 논리 분리 공간)를 만듭니다.
    2. 공식 설치 매니페스트를 적용합니다.
    3. 초기 관리자 비밀번호를 확인하고 UI에 접속합니다.
    kubectl create namespace argocd
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    kubectl get pods -n argocd
    kubectl port-forward svc/argocd-server -n argocd 8080:443

    여기서 저는 처음에 포트포워딩(port-forwarding, 로컬 포트를 클러스터 서비스에 연결)만 열어놓고 왜 접속이 불안정하지 싶었는데, 로컬 브라우저 인증서 경고를 그냥 넘기지 않아서 그런 경우가 있더라고요. 사소한데 은근 많이 막힙니다.

    4-2. Git 저장소에 애플리케이션 매니페스트 준비

    Argo CD의 핵심은 Git이기 때문에, 배포 대상 매니페스트가 먼저 있어야 합니다. 가장 단순한 Deployment(디플로이먼트, 파드 복제와 롤링 업데이트 관리)와 Service(서비스, 네트워크 노출 객체) 예시는 아래처럼 둘 수 있습니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-nginx
      namespace: demo
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: demo-nginx
      template:
        metadata:
          labels:
            app: demo-nginx
        spec:
          containers:
            - name: nginx
              image: nginx:stable
              ports:
                - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: demo-nginx
      namespace: demo
    spec:
      selector:
        app: demo-nginx
      ports:
        - port: 80
          targetPort: 80

    4-3. Argo CD Application 리소스 생성

    이제 Argo CD에 “어느 Git 저장소의 어느 경로를 어느 클러스터 네임스페이스에 반영할지” 알려주면 됩니다. 이 리소스가 사실상 배포 선언문 역할을 합니다.

    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: demo-nginx
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://github.com/example-org/example-manifests.git
        targetRevision: main
        path: apps/demo-nginx
      destination:
        server: https://kubernetes.default.svc
        namespace: demo
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
    kubectl apply -f application.yaml
    kubectl get applications -n argocd
    Argo CD Spinnaker 비교 중 Argo CD 동기화 설정 화면

    Git 저장소 경로, 대상 네임스페이스, 자동 동기화 옵션이 설정된 Argo CD Application 화면을 보여주는 위치입니다.

    여기서 prune은 Git에서 제거된 리소스를 클러스터에서도 정리하는 옵션이고, selfHeal은 누군가 클러스터에서 수동 변경해도 Git 기준으로 다시 되돌리는 기능입니다. 이거 진짜 편하더라고요. 운영자가 많아질수록 체감됩니다.

    5. 실전 구현: Spinnaker로 파이프라인 중심 배포 설계

    이번엔 Spinnaker 관점입니다. Spinnaker는 단순히 매니페스트를 맞추는 느낌보다, 배포 절차를 단계적으로 묶는 쪽에 가깝습니다. 그래서 예시도 “배포 흐름 설계” 관점으로 보는 게 이해가 쉽습니다.

    5-1. 추천 파이프라인 흐름

    1. CI 도구에서 이미지 빌드 및 레지스트리 푸시
    2. Spinnaker가 새 이미지 태그 감지
    3. 스테이징 환경 배포
    4. 수동 승인(Manual Judgment)
    5. 운영 환경 반영

    Spinnaker의 장점은 바로 이 지점입니다. 예를 들어 조직 정책상 운영 배포 전에 승인 절차가 꼭 필요하다면, Argo CD만으로는 별도 설계가 필요했던 부분을 Spinnaker는 비교적 자연스럽게 파이프라인에 녹일 수 있습니다.

    5-2. 파이프라인 단계 예시

    단계 설명 운영 포인트
    Trigger 이미지 변경 또는 이벤트 감지 CI와 연결 구조를 명확히 해야 함
    Deploy to Staging 스테이징 환경 우선 배포 운영과 최대한 동일한 조건 유지
    Manual Judgment 사람 승인 후 다음 단계 진행 승인 기준을 문서화해야 혼선이 적음
    Deploy to Prod 운영 환경 반영 롤백 기준과 모니터링 연동 중요

    실제로 써보니까 Spinnaker는 “배포 파이프라인을 플랫폼 차원에서 관리하고 싶다”는 팀에 꽤 매력적입니다. 대신 구성요소가 많아서 관리 비용이 올라갑니다. 작은 팀이면 이 장점이 부담으로 바뀌기도 하더라고요.

    6. ⚠️ 주의사항과 트러블슈팅: 제가 실제로 많이 부딪힌 포인트

    6-1. Argo CD에서 자주 겪는 문제

    • 드리프트 오해: 운영자가 클러스터에서 급한 수정 후 Git 반영을 빼먹으면 Argo CD가 다시 덮어씁니다.
    • 권한 문제: 네임스페이스 생성이나 특정 리소스 적용 시 RBAC(Role-Based Access Control, 역할 기반 권한 제어) 때문에 막히는 경우가 많습니다.
    • Helm 값 충돌: values 파일과 환경별 override가 섞이면 실제 반영값 추적이 어려워집니다.

    저도 처음엔 selfHeal이 멋져 보여서 다 켜놨었는데, 운영자가 수동 조치한 내용을 바로 되돌려버려서 당황한 적이 있습니다. 그래서 지금은 긴급 변경은 Git에 먼저 반영이라는 팀 규칙을 꼭 같이 둡니다.

    6-2. Spinnaker에서 자주 겪는 문제

    • 구성 복잡도: 서비스 수와 설정 포인트가 많아 초반 진입장벽이 있습니다.
    • 파이프라인 관리 비용: 애플리케이션이 늘어나면 표준화하지 않은 파이프라인이 금방 복잡해집니다.
    • 운영 관찰성 확보: 어느 단계에서 실패했는지 추적하려면 로그와 모니터링 체계를 같이 잡아야 합니다.

    근데 여기서 중요한 포인트! Spinnaker 자체가 나쁘다는 뜻이 아니라, 요구사항보다 플랫폼이 더 커지면 운영 피로도가 급격히 올라간다는 이야기입니다. 특히 팀 규모가 작을수록요.

    Argo CD Spinnaker 비교를 위한 배포 파이프라인 승인 흐름 이미지

    스테이징 배포, 수동 승인, 운영 반영으로 이어지는 Spinnaker 스타일의 파이프라인 흐름을 설명하는 이미지입니다.

    7. 검증과 결과: 어떤 선택이 더 현실적이었나

    제가 여러 환경에서 비교해보니 결과는 꽤 분명했습니다. Kubernetes가 배포 표준이고, Git 중심 운영 문화를 만들고 싶다면 Argo CD가 훨씬 빠르게 자리 잡습니다. 반대로 배포 절차가 길고 승인, 검증, 단계별 제어가 중요하다면 Spinnaker가 더 잘 맞습니다.

    검증할 때는 보통 아래 항목을 봤습니다.

    1. Git 변경 후 실제 반영까지 흐름이 단순한가
    2. 실패했을 때 원인 추적이 쉬운가
    3. 롤백(rollback, 이전 정상 상태로 되돌리기)이 명확한가
    4. 운영자 수가 늘어도 관리 규칙이 유지되는가

    Argo CD는 상태 비교가 직관적이라 운영 중 안심이 되더라고요. 반면 Spinnaker는 파이프라인 단계가 많을수록 통제력은 좋지만, 초반 설계 퀄리티가 정말 중요했습니다. 이건 진짜 경험 차이입니다.

    kubectl get applications -n argocd
    kubectl get pods -n demo
    kubectl rollout status deployment/demo-nginx -n demo

    위 명령으로 Argo CD 애플리케이션 상태, 실제 파드 상태, 롤아웃 완료 여부를 확인할 수 있습니다. 지속적 배포 환경에서는 “배포했다”보다 “원하는 상태로 수렴했는가”를 보는 게 더 중요하거든요.

    Argo CD Spinnaker 비교 결과를 보여주는 배포 검증 대시보드

    애플리케이션이 Synced, Healthy 상태인지와 실제 배포 결과를 함께 보여주는 검증용 이미지 자리입니다.

    8. Argo CD vs Spinnaker 최종 정리

    질문 추천
    우리는 Kubernetes 중심으로 단순하고 강한 CD가 필요한가? Argo CD
    GitOps 운영 모델을 팀 표준으로 만들고 싶은가? Argo CD
    승인, 카나리, 복합 배포 절차가 핵심인가? Spinnaker
    멀티 환경과 복잡한 전달 흐름을 한 플랫폼에서 통제하고 싶은가? Spinnaker

    짧게 정리하면 이렇습니다. Argo CD는 “원하는 상태를 Git에 적고 맞춰나가는 도구”이고, Spinnaker는 “배포 절차를 정교하게 설계하고 흘려보내는 플랫폼”입니다. 둘 다 훌륭하지만, 잘 맞는 환경이 다릅니다.

    혹시 이런 경험 있으신가요? 배포 자동화를 시작했는데, CI/CD 파이프라인이 오히려 더 복잡해져서 손이 더 많이 가는 상황이요. 저도 처음엔 헷갈렸는데, 결국 답은 기능 수보다 운영 방식에 있었습니다.

    Argo CD Spinnaker 비교 선택 기준 요약 인포그래픽

    팀 규모, Kubernetes 의존도, 승인 절차 복잡도에 따라 어떤 도구가 맞는지 요약한 비교 인포그래픽입니다.

    9. 마무리: 지금 시작한다면 저는 이렇게 고릅니다

    만약 지금 새로 시작하는 팀이고, 이미 Kubernetes를 기본 플랫폼으로 쓰고 있다면 저는 Argo CD부터 검토할 것 같습니다. 이유는 분명합니다. 도입 속도가 빠르고, GitOps 문화를 만들기 좋고, 운영 단순성도 꽤 좋거든요.

    반대로 조직 규모가 크고, 배포 승인과 전달 절차가 중요한 팀이라면 Spinnaker 쪽이 더 설득력 있습니다. 다만 이 경우엔 플랫폼 운영 책임까지 함께 고려해야 합니다. 단순히 배포 기능만 보고 들어가면 나중에 힘들 수 있습니다.

    다음 글에서는 Argo CD 기반으로 CI와 CD를 분리해서 운영하는 패턴, 예를 들어 GitHub Actions 같은 CI 도구와 연결하는 방식도 다뤄볼 예정입니다. 이전 글에서 다뤘던 Kubernetes 배포 기본 흐름과 함께 보시면 더 이해가 잘 되실 겁니다.

    자주 묻는 질문

    Argo CD가 CI까지 대체하나요?

    보통은 아닙니다. Argo CD는 CD에 더 가깝고, 빌드와 테스트는 별도 CI 도구가 맡는 경우가 많습니다.

    Spinnaker는 Kubernetes 환경에서만 쓰나요?

    아닙니다. 다만 이 글에서는 클라우드 네이티브와 Kubernetes 중심 관점에서 설명했습니다.

    둘 중 하나만 꼭 골라야 하나요?

    반드시 그렇진 않습니다. 팀 구조와 기존 플랫폼에 따라 역할을 나눠 설계하는 경우도 있습니다.

  • [k8s] Rancher 2.x에서 차세대로 마이그레이션: 실제 경험과 주요 변경점

    [k8s] Rancher 2.x에서 차세대로 마이그레이션: 실제 경험과 주요 변경점

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 많은 분들이 고민하고 계실 법한 주제, 바로 Rancher 2.x 환경을 차세대 쿠버네티스 관리 아키텍처로 전환하는 경험에 대해 이야기해볼게요. 제목에는 ‘Rancher 3.x’라는 표현을 썼지만, 사실 직접적인 ‘Rancher 3.x’라는 버전이 명확하게 출시된 것은 아닙니다. 대신, Rancher를 활용한 쿠버네티스 관리 방식이 점차 진화하면서, 기존 2.x 시절의 방식과는 크게 달라진 미래 지향적인 아키텍처로의 전환 과정을 ‘3.x 마이그레이션’이라는 개념으로 풀어보려고 해요. 즉, 단순히 버전을 올리는 것을 넘어, 더 효율적이고 안정적인 쿠버네티스 운영을 위한 근본적인 변화를 고민하는 시간이라고 보시면 되겠습니다.

    저도 홈랩에서부터 시작해서 프로덕션 환경까지 다양한 규모의 쿠버네티스 클러스터를 Rancher 2.x로 관리해왔어요. 처음에는 하나의 Rancher 서버로 모든 클러스터를 중앙에서 관리하는 방식이 정말 편하더라고요. 하지만 클러스터의 수가 늘어나고, 요구사항이 복잡해지면서 중앙 집중형 관리의 한계를 느끼게 됐습니다. 특히, Rancher 서버 자체의 안정성이나 업그레이드 부담, 그리고 GitOps(깃옵스)와 같은 최신 트렌드를 통합하는 데 대한 고민이 많았거든요. 그래서 ‘이제는 좀 더 분산되고 선언적인 방식으로 가야 하지 않을까?’ 하는 생각을 하게 됐습니다. 이런 고민을 하고 계신 분들이라면 오늘 제 이야기가 작은 팁이라도 될 수 있을 겁니다. 제가 직접 삽질해가며 얻은 경험들을 솔직하게 공유해볼게요!

    개념 설명: Rancher 2.x와 차세대 아키텍처의 차이

    먼저, 우리가 이야기하는 Rancher 2.x는 보통 RKE1(Rancher Kubernetes Engine 1) 기반의 쿠버네티스 클러스터들을 Rancher Management Server(랜처 관리 서버)가 중앙에서 프로비저닝하고 관리하는 형태를 의미합니다. 웹 UI를 통해 클러스터를 생성하고, 워크로드를 배포하며, 모니터링까지 한곳에서 할 수 있었죠. 정말 편리하고 직관적입니다. 하지만 단점도 명확해요.

    • 중앙 집중형 의존성: Rancher Management Server에 장애가 발생하면 모든 하위 클러스터 관리에 문제가 생길 수 있습니다.
    • 관리 복잡성 증가: 클러스터가 많아질수록 관리 서버 자체의 부담도 커집니다.
    • GitOps 통합의 어려움: UI 기반의 작업이 많아 GitOps 파이프라인과 완벽하게 통합하기 쉽지 않은 부분이 있었습니다.

    그렇다면 여기서 말하는 ‘차세대 아키텍처’ 혹은 ‘3.x스러운’ 접근 방식은 뭘까요? 저는 주로 RKE2(Rancher Kubernetes Engine 2)나 K3s(케이쓰리s)와 같은 경량화되거나 보안이 강화된 쿠버네티스 배포판을 활용하고, GitOps 원칙을 적극적으로 도입하여 선언적(Declarative)으로 클러스터와 애플리케이션을 관리하는 방식을 의미한다고 봐요. 이제 더 이상 Rancher Management Server가 클러스터의 모든 것을 직접 제어하기보다는, 클러스터 자체의 견고함과 Git을 통한 선언적 관리에 초점을 맞추는 거죠. Rancher는 이런 클러스터들을 통합적으로 보여주고 관리하는 ‘플랫폼’으로서의 역할에 더 집중하게 됩니다.

    Rancher 2.x 중앙 집중형 관리와 차세대 분산형 GitOps 아키텍처 비교 다이어그램

    Rancher 2.x의 중앙 집중형 관리 방식과 차세대 분산형 GitOps 아키텍처의 주요 차이점을 시각적으로 보여주는 다이어그램입니다.

    실전 구현: 차세대 클러스터 환경 구축 전략

    기존 Rancher 2.x 환경을 ‘마이그레이션’하는 것은 단순히 버전을 올리는 것이 아니라, 새로운 클러스터를 구축하고 워크로드를 이전하는 과정에 가깝습니다. 제가 홈랩에서 여러 번 시도해본 결과, 크게 두 가지 접근 방식이 있더라고요.

    1. 새로운 RKE2/K3s 클러스터 프로비저닝

    먼저, 기존 Rancher 2.x 관리 서버에서 새로운 RKE2나 K3s 클러스터를 프로비저닝하는 방법을 소개할게요. Rancher 2.6 버전부터는 RKE2/K3s 클러스터 생성을 공식적으로 지원해서 훨씬 수월해졌습니다.

    1. Rancher UI 접속: 기존 Rancher Management Server의 웹 UI에 접속합니다.
    2. 클러스터 생성 메뉴 진입: ‘클러스터’ 탭에서 ‘클러스터 생성’ 버튼을 클릭합니다.
    3. RKE2/K3s 선택: ‘Rancher Kubernetes Engine 2 (RKE2)’ 또는 ‘K3s’를 선택합니다. 저는 보안과 성능을 고려해 RKE2를 선호하는 편입니다.
    4. 노드 구성 및 옵션 설정: 컨트롤 플레인(Control Plane), etcd, 워커(Worker) 노드를 구성하고 필요한 네트워크, CNI(Container Network Interface, 컨테이너 네트워크 인터페이스) (예: Canal, Calico 등) 옵션을 설정합니다. 이때, 클러스터의 고가용성(High Availability, HA)을 위해 컨트롤 플레인 노드를 최소 3개 이상으로 구성하는 것이 중요합니다.
    5. 클러스터 생성: 모든 설정을 마치고 클러스터를 생성하면, Rancher가 백그라운드에서 노드에 에이전트를 설치하고 RKE2/K3s를 배포합니다.

    다음은 RKE2 클러스터를 생성할 때 필요한 기본적인 설정 예시입니다.

    # 클러스터 생성 시 RKE2/K3s 설정 예시 (Rancher UI에서 구성)
    kubernetes_version: v1.27.x-rke2r1 # 원하는 RKE2 버전 선택
    cni: canal # CNI 플러그인 선택 (calico, cilium 등)
    cloud_provider: 
      name: aws # 클라우드 환경에 맞춰 설정 (baremetal, azure, vsphere 등)
    agent_options:
      node_taints:
        - "CriticalAddonsOnly=true:NoExecute"
      labels:
        - "node-role.kubernetes.io/control-plane=true"
        - "node-role.kubernetes.io/etcd=true"
    # 노드 추가는 Rancher UI에서 직접 진행하거나, Machine Group을 통해 자동화 가능
    

    이렇게 새 클러스터를 만들고 나면, 이제 기존 워크로드들을 새로운 클러스터로 이전할 준비가 된 겁니다. 이게 생각보다 쉽지 않아요. 애플리케이션 의존성부터 데이터 마이그레이션까지 고려할 게 많거든요. 제가 초반에 이걸 간과해서 밤샘 삽질을 좀 했습니다. 😅

    Rancher UI에서 RKE2 클러스터를 생성하는 화면 예시

    Rancher UI를 통해 새로운 RKE2 클러스터를 생성하는 화면 예시입니다. 여기서 다양한 옵션을 설정할 수 있습니다.

    2. 워크로드 및 데이터 마이그레이션

    가장 중요한 단계입니다. 워크로드 마이그레이션은 서비스 중단 시간을 최소화하는 방향으로 계획해야 해요. 몇 가지 팁을 드리자면:

    • 상태 없는(Stateless) 애플리케이션 먼저: 웹 서버, API 게이트웨이 등 상태를 저장하지 않는 애플리케이션부터 이전하여 위험을 줄입니다. Helm(헬름)이나 GitOps 툴(예: Argo CD, Flux CD)을 활용하면 배포를 선언적으로 관리하고 쉽게 이전할 수 있어요.
    • 상태 있는(Stateful) 애플리케이션: 데이터베이스와 같은 상태 있는 애플리케이션은 velero(벨레로)와 같은 백업 및 복구 툴을 사용하거나, 클라우드 제공업체의 볼륨 스냅샷 기능을 활용하여 데이터를 이전합니다. 이 과정에서 네트워크 지연(Latency)이나 데이터 정합성(Data Consistency)에 문제가 없는지 꼼꼼히 확인해야 해요.
    • 네트워크 설정: Ingress(인그레스, 외부 트래픽 진입점) 컨트롤러, Service(서비스) 타입, ExternalDNS(외부 DNS) 설정 등을 새 클러스터에 맞게 재구성해야 합니다. 특히 DNS 레코드를 변경할 때는 TTL(Time-To-Live, 캐시 유지 시간)을 짧게 설정하여 롤백에 대비하는 것이 좋습니다.
    # Argo CD를 이용한 GitOps 배포 예시
    # 새 클러스터에 Argo CD 설치 후, Git 리포지토리 연결
    
    # 1. Argo CD 설치 (새 클러스터에)
    kubectl create namespace argocd
    kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
    
    # 2. Argo CD CLI 설치 및 초기 비밀번호 확인
    # (생략)
    
    # 3. 애플리케이션 정의 (예: my-app.yaml)
    # applications/my-app.yaml 파일 생성
    # apiVersion: argoproj.io/v1alpha1
    # kind: Application
    # metadata:
    #   name: my-webapp
    #   namespace: argocd
    # spec:
    #   project: default
    #   source:
    #     repoURL: https://github.com/my-org/my-app-configs.git # 애플리케이션 설정이 있는 Git 레포지토리
    #     targetRevision: HEAD
    #     path: dev # Git 레포지토리 내 경로
    #   destination:
    #     server: https://kubernetes.default.svc
    #     namespace: my-webapp-prod # 배포할 네임스페이스
    #   syncPolicy:
    #     automated:
    #       prune: true
    #       selfHeal: true
    
    # 4. Argo CD에 애플리케이션 등록
    argocd app create my-webapp --repo https://github.com/my-org/my-app-configs.git --path dev --dest-server https://kubernetes.default.svc --dest-namespace my-webapp-prod
    

    이런 GitOps 툴을 활용하면, 기존 클러스터에서 사용하던 매니페스트(Manifest) 파일들을 Git 레포지토리에 올리고, 새 클러스터에 Argo CD를 설치하여 동기화하는 방식으로 쉽게 워크로드를 이전할 수 있어요. 코드로 인프라를 관리하는(Infrastructure as Code, IaC) 경험은 정말 혁신적입니다!

    ⚠️ 주의사항 및 트러블슈팅: 삽질 경험 공유

    제가 이 ‘차세대 전환’을 하면서 겪었던 몇 가지 삽질과 해결책들을 공유해볼게요.

    1. RKE1과 RKE2/K3s의 설정 차이: RKE1은 cluster.yml 파일을 기반으로 클러스터를 구성했지만, RKE2/K3s는 /etc/rancher/rke2/config.yaml (또는 k3s) 파일을 사용하거나 Helm 차트 등을 통해 더 세분화된 설정을 합니다. 기존 RKE1의 커스텀 설정(예: 추가 컨테이너 런타임 옵션, CNI 플러그인 설정)을 그대로 옮기려다 호환성 문제로 고생 좀 했어요. 새로운 배포판의 문서(Documentation)를 꼼꼼히 확인하는 것이 정말 중요합니다.
    2. StorageClass(스토리지 클래스) 호환성: 기존 클러스터에서 사용하던 PersistentVolume(영구 볼륨)과 PersistentVolumeClaim(영구 볼륨 클레임)은 StorageClass에 따라 다르게 동작할 수 있습니다. 특히 클라우드 환경이 변경되거나 스토리지 솔루션이 달라지면, 새로운 StorageClass를 정의하고 기존 PV/PVC를 마이그레이션해야 해요. 저는 홈랩에서 Longhorn(롱혼)을 사용하는데, 새 클러스터에 Longhorn을 재설치하고 데이터를 옮기는 과정에서 네트워크 설정 문제로 볼륨이 마운트되지 않아 애먹었습니다. 😅
    3. Cilium(실리움) CNI 도입 시 주의사항: 최근 Cilium이 성능과 보안 면에서 각광받고 있어서 저도 도입을 고려했는데, 기존 CNI(예: Canal)에서 Cilium으로 변경할 때는 네트워크 정책(Network Policy)과의 호환성, kube-proxy(쿠베 프록시) 없이 동작하는 모드(Direct Routing) 설정 등 고려할 사항이 많아요. 잘못하면 클러스터 내부 통신이 마비될 수 있으니 충분한 테스트 환경에서 검증해야 합니다.
    4. Rancher Management Server의 역할 변화: 기존에는 Rancher 서버가 모든 것을 다 해주는 느낌이었다면, 이제는 ‘관측성(Observability)’과 ‘정책 관리(Policy Management)’, 그리고 ‘클러스터 수명 주기 관리(Cluster Lifecycle Management)’에 더 집중하게 됩니다. 즉, 클러스터 내부의 복잡한 운영은 GitOps 툴과 같은 다른 도구들에게 맡기고, Rancher는 그 위에서 통합된 뷰를 제공하는 역할로 전환되는 거죠. 이 역할을 이해하지 못하면 ‘왜 Rancher가 예전처럼 클러스터 설정을 다 해주지 않지?’ 하고 헤맬 수 있습니다.

    검증 및 결과: 안정적인 차세대 환경 확인

    모든 워크로드 마이그레이션이 완료되었다면, 새로운 클러스터가 제대로 동작하는지 꼼꼼히 검증해야 합니다. 다음은 제가 주로 확인하는 항목들입니다.

    • 애플리케이션 정상 동작 확인: 모든 서비스의 엔드포인트에 접속하여 기능이 정상적으로 동작하는지 확인합니다. 특히 로깅(Logging)과 모니터링(Monitoring) 시스템이 제대로 연동되는지 확인하는 것이 중요해요.
    • 리소스 사용량 모니터링: Grafana(그라파나), Prometheus(프로메테우스) 등의 툴을 통해 CPU, 메모리, 네트워크, 디스크 I/O 등 클러스터 리소스 사용량을 면밀히 모니터링합니다. 기존 클러스터와 비교하여 비정상적인 패턴이 없는지 확인해야 합니다.
    • 클러스터 컴포넌트 상태: kubectl get pods -A 명령어로 모든 네임스페이스의 파드(Pod) 상태를 확인하고, kubectl get nodes로 노드 상태를 확인하여 문제가 없는지 점검합니다.
    • 데이터 정합성 검증: 상태 있는 애플리케이션의 경우, 이전된 데이터가 정확한지, 쓰기/읽기 작업이 문제없이 이루어지는지 반드시 확인해야 합니다.

    이 모든 검증을 마치고 나면, 드디어 새로운 차세대 Rancher 환경이 안정적으로 운영되는 것을 확인할 수 있어요. 이 뿌듯함이란! 🎉

    새롭게 마이그레이션된 Rancher 환경의 클러스터 대시보드

    새롭게 마이그레이션된 Rancher 환경에서 클러스터의 전반적인 상태를 보여주는 대시보드 화면입니다.

    마무리: 배운 점과 다음 단계

    Rancher 2.x에서 차세대 쿠버네티스 관리 환경으로의 전환은 단순히 소프트웨어 버전을 올리는 작업이 아니었습니다. 이는 클러스터 아키텍처, 운영 방식, 그리고 인프라 엔지니어의 역할에 대한 근본적인 재정의 과정이라고 생각해요. 저도 이 과정을 거치면서 많은 것을 배우고 삽질도 정말 많이 했습니다. 하지만 그 덕분에 GitOps의 중요성, RKE2/K3s의 장점, 그리고 Rancher가 나아가야 할 방향에 대해 더 깊이 이해하게 되었죠.

    Rancher 환경 전환 시 고려해야 할 핵심 사항 요약 인포그래픽

    Rancher 환경 전환 시 고려해야 할 핵심 사항들을 요약한 인포그래픽입니다.

    이번 마이그레이션을 통해 얻은 주요 교훈은 다음과 같습니다.

    • 계획의 중요성: 충분한 사전 계획 없이는 성공적인 마이그레이션은 불가능해요. 의존성 파악, 백업/복구 전략, 롤백 계획 등 모든 것을 미리 준비해야 합니다.
    • GitOps의 힘: 선언적인 코드로 인프라와 애플리케이션을 관리하는 GitOps는 복잡한 마이그레이션을 훨씬 수월하게 만들고, 향후 운영의 안정성을 높여줍니다.
    • 문서화의 중요성: 모든 변경 사항과 결정 사항을 문서화하여 팀원들과 공유하고, 향후 트러블슈팅에 활용해야 합니다.

    이제 새로운 클러스터에서 더 안정적이고 효율적인 쿠버네티스 운영을 할 수 있게 되었어요. 다음 단계로는 클러스터의 보안 강화(Security Hardening), 자동화된 재해 복구(Automated Disaster Recovery) 시스템 구축, 그리고 멀티 클러스터 환경에서의 서비스 메시(Service Mesh) 도입 등을 고민해볼 예정입니다. 여러분도 혹시 비슷한 고민을 하고 계시다면, 주저하지 말고 새로운 아키텍처로의 전환을 시도해보세요. 물론 삽질은 필수겠지만, 그만큼 얻는 것도 많을 겁니다! 궁금한 점이 있다면 언제든 댓글 남겨주세요. 다음 포스팅에서 또 재미있는 이야기로 찾아뵙겠습니다!

  • [k8s] Argo CD 멀티 클러스터 동기화 문제 해결: 실제 운영 사례와 디버깅 팁

    [k8s] Argo CD 멀티 클러스터 동기화 문제 해결: 실제 운영 사례와 디버깅 팁

    Argo CD 멀티 클러스터 동기화 문제 해결: 실제 운영 사례와 디버깅 팁

    안녕하세요, 13년차 서버실 지킴이입니다. 요즘 쿠버네티스(Kubernetes) 환경에서 GitOps(깃옵스)를 도입하는 기업들이 정말 많죠? 저도 홈랩과 회사 프로젝트에서 Argo CD(아르고 CD)를 활용해서 여러 쿠버네티스 클러스터를 관리하고 있는데요. 처음엔 “와, 이거 정말 편하겠다!” 싶었는데, 막상 실제 운영 환경에서 멀티 클러스터(Multi-Cluster) 동기화 문제를 만나면 머리가 지끈거릴 때가 많더라고요.

    특히 여러 클러스터에 동일한 애플리케이션을 배포하거나, 환경별로 미묘하게 다른 설정을 적용해야 할 때 ApplicationSet(애플리케이션셋)을 쓰면 정말 유용하거든요. 그런데 간혹 특정 클러스터만 동기화가 안 되거나, 알 수 없는 에러로 삽질을 거듭하곤 해요. 제가 직접 겪었던 Argo CD 멀티 클러스터 동기화 문제들과 그 해결 과정을 솔직하게 공유해볼까 해요. 혹시 비슷한 경험으로 고생하고 계시다면, 이 글이 작은 힌트라도 되었으면 좋겠네요! 💡

    Argo CD 멀티 클러스터 관리 아키텍처: Git을 중심으로 Argo CD가 여러 쿠버네티스 클러스터에 애플리케이션을 배포하고 동기화하는 흐름을 보여줍니다.

    Argo CD, GitOps, 그리고 멀티 클러스터, 이게 뭔가요?

    본격적인 문제 해결에 앞서, 핵심 개념들을 간단히 짚고 넘어갈게요. “아, 이건 다 아는데!” 하시는 분들도 계시겠지만, 혹시 처음 접하시는 분들을 위해 쉽게 풀어 설명해 드릴게요.

    • Argo CD (아르고 CD): 쿠버네티스 환경에서 GitOps를 구현하기 위한 대표적인 선언적(Declarative) CD(Continuous Delivery, 지속적 배포) 툴입니다. Git 저장소를 애플리케이션의 ‘원본 소스(Single Source of Truth)’로 삼아, 쿠버네티스 클러스터의 실제 상태가 Git에 정의된 상태와 일치하도록 지속적으로 동기화(Synchronization)를 시도하죠. 즉, Git에 코드를 푸시하면 Argo CD가 알아서 클러스터에 배포해주는 방식입니다.

    • GitOps (깃옵스): Git을 운영의 중심(Operational Hub)으로 삼아 인프라와 애플리케이션의 모든 상태를 관리하는 방식입니다. 모든 변경 사항이 Git에 기록되므로, 누가 언제 무엇을 변경했는지 추적하기 쉽고, 문제가 생겼을 때 쉽게 롤백(Rollback)할 수 있다는 장점이 있습니다. “Git에서 정의한 대로 클러스터 상태를 유지한다”는 철학이죠.

    • 멀티 클러스터 (Multi-Cluster): 여러 개의 쿠버네티스 클러스터를 동시에 관리하는 환경을 의미합니다. 개발, 스테이징, 프로덕션 환경을 분리하거나, 재해 복구(Disaster Recovery)를 위해 여러 리전에 클러스터를 운영할 때 등 다양한 이유로 멀티 클러스터 환경을 구축합니다. Argo CD는 하나의 중앙 집중식(Centralized) 컨트롤 플레인(Control Plane)으로 여러 원격 클러스터(Remote Cluster)를 관리할 수 있게 해줍니다.

    Argo CD 멀티 클러스터 설정과 흔한 문제 시나리오

    Argo CD를 이용해 멀티 클러스터를 관리하려면, 먼저 Argo CD 컨트롤 플레인이 설치된 클러스터(보통 ‘관리 클러스터’)에 다른 클러스터들을 등록해야 합니다. argocd cluster add 명령어가 이걸 해주죠. 이때 Argo CD가 원격 클러스터에 접근할 수 있는 ServiceAccount와 ClusterRoleBinding을 생성해줍니다.

    그리고 여러 클러스터에 애플리케이션을 배포할 때는 주로 ApplicationSet을 사용합니다. 예를 들어, 다음과 같은 ApplicationSet으로 여러 클러스터에 nginx-app을 배포한다고 가정해봅시다.

    apiVersion: argoproj.io/v1alpha1
    kind: ApplicationSet
    metadata:
      name: my-nginx-appset
    spec:
      generators:
      - clusters: {}
      template:
        metadata:
          name: '{{name}}-nginx-app'
        spec:
          project: default
          source:
            repoURL: https://github.com/my-org/my-gitops-repo.git
            targetRevision: HEAD
            path: apps/nginx
          destination:
            server: '{{server}}'
            namespace: default
          syncPolicy:
            automated:
              prune: true
              selfHeal: true
    

    이렇게 설정하면 Argo CD가 등록된 모든 클러스터에 apps/nginx 경로의 매니페스트를 배포하려고 시도합니다. 그런데 여기서 문제가 발생하곤 해요. 어떤 클러스터는 잘 동기화(SYNCED)되는데, 다른 클러스터는 계속 OutOfSync 상태이거나 Failed 상태를 벗어나지 못하는 거죠. “아니, 분명 다 똑같이 설정했는데 왜 이럴까?” 하는 생각이 절로 듭니다. 저도 처음엔 정말 막막했는데, 몇 가지 공통적인 원인이 있더라고요. 😅

    Argo CD UI의 ApplicationSet 대시보드: 문제 발생 상황

    Argo CD UI의 ApplicationSet 대시보드: 여러 클러스터에 배포된 애플리케이션들의 동기화 상태를 한눈에 볼 수 있으며, 문제가 발생한 애플리케이션을 쉽게 식별할 수 있습니다.

    ⚠️ 실제 운영 사례로 본 Argo CD 동기화 문제와 디버깅 팁

    제가 13년 동안 쌓은 삽질 경험을 바탕으로, Argo CD 멀티 클러스터 환경에서 자주 발생하는 동기화 문제와 그 해결책을 알려드릴게요. 하나씩 체크하다 보면 분명 원인을 찾을 수 있을 겁니다!

    1. RBAC (Role-Based Access Control) 권한 문제
      가장 흔하면서도 놓치기 쉬운 문제예요. Argo CD 컨트롤 플레인이 관리 대상 클러스터에 배포할 권한이 없는 경우입니다. argocd cluster add 명령어가 자동으로 필요한 권한을 생성해주지만, 간혹 수동으로 클러스터를 추가했거나, 보안 정책상 특정 권한이 누락된 경우가 있거든요.

      💡 디버깅 팁:

      • 관리 대상 클러스터에서 kubectl get serviceaccount argocd-manager -n argocd 명령어로 argocd-manager ServiceAccount가 잘 생성되었는지 확인하세요.
      • 이 ServiceAccount에 연결된 ClusterRoleBinding과 ClusterRole의 권한을 확인해보세요. 보통 cluster-admin 권한이 부여되는데, 만약 특정 리소스에 대한 권한만 있다면 해당 리소스 배포가 실패할 수 있거든요. kubectl describe clusterrole argocd-manager-role 명령으로 권한 목록을 볼 수 있습니다.
      • 특히 ServiceAccount가 아닌 다른 방식으로 인증(예: kubeconfig 파일)을 사용한다면, 해당 kubeconfig 파일에 정의된 사용자에게 충분한 권한이 있는지 확인해야 합니다.
    2. 네트워크 연결 문제
      Argo CD 서버가 관리 대상 클러스터의 쿠버네티스 API 서버에 접근할 수 없는 경우네요. 방화벽, VPN, VPC 피어링 등 네트워크 구성을 꼼꼼히 확인해야 합니다. “어제까진 잘 됐는데?” 하다가 네트워크 변경 때문에 막히는 경우가 꽤 있거든요.

      💡 디버깅 팁:

      • Argo CD UI에서 해당 클러스터의 상태가 Unavailable인지 확인하세요.
      • Argo CD 컨트롤러 파드(Pod)에서 관리 대상 클러스터의 API 서버로 curl 같은 명령어로 접속을 시도해보세요. 파드에 접속하는 방법은 kubectl exec -it <argocd-repo-server-pod> -n argocd -- bash 후 curl -k https://<cluster-api-server-ip>:<port>/healthz 와 같이 시도해볼 수 있습니다.
      • argocd cluster add 시 입력한 API 서버 주소가 올바른지 다시 한번 확인해보세요. 내부망 주소여야 할 때 외부망 주소를 입력하거나, 그 반대인 경우도 있거든요.
    3. ApplicationSet 설정 오류
      ApplicationSet의 generator나 template 설정이 잘못된 경우예요. 특히 cluster 필터링이나 Git 저장소 경로(path)를 잘못 지정했을 때 문제가 발생하곤 합니다.

      💡 디버깅 팁:

      • argocd appset get <application-set-name> 명령으로 ApplicationSet의 현재 상태와 생성된 Application 목록을 확인해보세요.
      • ApplicationSet의 template 섹션에서 source.path가 Git 저장소의 실제 경로와 일치하는지 확인하세요. 대소문자나 오타가 있을 수 있거든요.
      • generator에서 특정 클러스터만 선택하도록 설정했다면, 해당 클러스터의 라벨(labels)이 selector와 정확히 일치하는지 확인해보세요.
    4. 배포하려는 리소스 매니페스트 오류
      Git 저장소에 있는 쿠버네티스 매니페스트(YAML 파일) 자체에 문제가 있는 경우네요. 문법 오류, 잘못된 필드 이름, 존재하지 않는 StorageClass 참조 등이 있을 수 있거든요.

      💡 디버깅 팁:

      • Argo CD UI에서 Application 상태를 확인하고, Events(이벤트) 탭이나 Logs(로그) 탭을 자세히 살펴보세요. 어떤 리소스에서 어떤 에러가 발생했는지 상세하게 나옵니다. 예를 들어, "admission webhook denied the request" 같은 메시지는 Admission Controller(어드미션 컨트롤러)나 OPA Gatekeeper 같은 정책 엔진에 의해 배포가 거부되었음을 의미할 수 있어요.
      • 해당 클러스터에 직접 kubectl apply -f <problematic-manifest.yaml> --dry-run=client 로 배포를 시도하여 문법 오류 등을 미리 확인해보세요.
      • kubectl describe <resource-type> <resource-name> -n <namespace> 명령으로 대상 클러스터에 배포된(혹은 배포 실패한) 리소스의 상세 상태를 확인하세요.
    5. Argo CD 컨트롤러 파드 문제
      아주 드물지만, Argo CD 자체의 argocd-application-controller나 argocd-repo-server 파드에 문제가 생겨 동기화가 제대로 작동하지 않을 수 있어요. 리소스 부족, 네트워크 문제, 버그 등이 원인일 수 있거든요.

      💡 디버깅 팁:

      • kubectl get pods -n argocd 명령으로 Argo CD 관련 파드들이 모두 Running 상태인지 확인해보세요.
      • 문제 있어 보이는 파드의 로그를 확인해보세요: kubectl logs -f <argocd-pod-name> -n argocd. 특히 argocd-application-controller 로그에 동기화 관련 오류 메시지가 나타날 수 있거든요.
      • 파드를 재시작하거나, 리소스(CPU, Memory)를 늘려주는 것도 방법이 될 수 있습니다.

    ✅ 문제 해결 후 검증하기

    위의 팁들을 바탕으로 문제를 해결했다면, 이제 제대로 동기화가 되는지 확인해야겠죠? 드디어 SYNCED 상태를 봤을 때의 그 쾌감이란! 🎉

    1. Argo CD UI/CLI 확인
      가장 먼저 Argo CD UI에 접속해서 해당 Application 또는 ApplicationSet의 상태가 Synced 그리고 Healthy로 표시되는지 확인합니다. CLI를 선호한다면 argocd app list 나 argocd app get <application-name> 명령어를 사용해보세요.

    2. 대상 클러스터 직접 확인
      Argo CD UI/CLI에서 Synced로 표시되더라도, 혹시 모르니 해당 클러스터에 접속해서 kubectl get <resource-type> -n <namespace> 명령으로 실제로 리소스가 배포되었는지, 상태는 어떤지 직접 확인하는 게 좋습니다. 특히 Deployment나 StatefulSet의 파드들이 정상적으로 Running 상태인지 확인해 주세요.

    3. 새로운 변경사항 푸시 테스트
      Git 저장소에 간단한 변경사항(예: ConfigMap 내용 변경)을 푸시해서 Argo CD가 변경을 감지하고 자동으로 동기화하는지 확인해봅니다. 이 과정이 원활하게 진행된다면 성공적으로 문제를 해결한 거예요!

    Argo CD UI의 성공적인 동기화 대시보드

    성공적인 Argo CD 동기화 대시보드: 모든 애플리케이션이 문제없이 배포되고 동기화되어 안정적인 운영 상태를 보여줍니다.

    마무리하며: 삽질은 경험이 되고, 경험은 자산이 됩니다.

    오늘은 Argo CD 멀티 클러스터 동기화 문제 해결에 대한 저의 경험과 디버깅 팁을 공유해드렸습니다. 솔직히 저도 처음엔 정말 막막해서 밤늦게까지 삽질 좀 했거든요. “왜 안 되는 거지?” 하면서 클러스터 여기저기를 들쑤시고 다녔던 기억이 생생해요. 😂

    하지만 이런 삽질 과정이 결국 저의 노하우가 되고, 여러분 같은 동료 인프라 엔지니어분들께 도움이 되는 자산이 된다는 사실을 깨달았습니다. Argo CD는 정말 강력한 툴이지만, 그만큼 복잡한 환경에서는 예상치 못한 문제들이 발생할 수 있어요. 중요한 건 문제를 만났을 때 당황하지 않고, 차근차근 원인을 찾아 해결해나가는 자세라고 생각합니다.

    이번 글이 Argo CD 멀티 클러스터 환경에서 고군분투하는 분들께 조금이나마 도움이 되었기를 바랍니다. 다음 글에서는 Argo CD의 Progressive Delivery(점진적 배포) 전략인 Argo Rollouts에 대해 다뤄볼게요. 그때까지 GitOps 즐겁게 운영하시길 바랍니다! 궁금한 점이 있다면 언제든 댓글 남겨주세요! 😊

    Argo CD 멀티 클러스터 동기화 문제 해결 체크리스트: RBAC, 네트워크, ApplicationSet 설정, 매니페스트 오류, Argo CD 파드 상태 등 주요 점검 사항을 요약한 인포그래픽입니다.

  • [Cloud] Flux CD 마이그레이션 경험기: Argo CD와 비교하며

    [Cloud] Flux CD 마이그레이션 경험기: Argo CD와 비교하며

    13년차 인프라 엔지니어의 GitOps 전환기: Flux CD 마이그레이션과 Argo CD 비교

    안녕하세요! 13년차 인프라 엔지니어입니다. 오늘은 많은 분들이 관심을 갖고 계신 Flux CD 마이그레이션 경험에 대해 얘기해볼까 합니다. 기존에 Argo CD를 쓰다가 Flux CD로 전환을 고민 중이신 분들이나 이제 막 GitOps를 시작하려는 분들께 제 경험이 도움이 되길 바랍니다. 저도 처음에는 두 도구 사이에서 정말 많이 고민했거든요. 😅

    쿠버네티스 환경에서 애플리케이션 배포를 자동화하는 GitOps는 이제 선택이 아닌 필수가 되어가고 있습니다. Git 저장소를 단일 진실 공급원(Single Source of Truth)으로 삼아 인프라와 애플리케이션의 상태를 관리하는 방식인데요. 이 GitOps 워크플로우를 구현하는 데 핵심적인 역할을 하는 도구가 바로 Argo CD와 Flux CD입니다. 오늘은 이 두 도구를 비교하며, 제가 Flux CD v2로 마이그레이션하면서 겪었던 실제 경험과 배운 점들을 솔직하게 공유해볼게요.

    GitOps 아키텍처 개요 다이어그램: Git 저장소, GitOps 컨트롤러, 쿠버네티스 클러스터 간의 동기화 흐름

    GitOps의 기본적인 흐름과 도구의 역할을 보여주는 아키텍처 다이어그램입니다.

    1. GitOps와 CI/CD 도구, 왜 이렇게 중요할까요?

    개발자라면 누구나 ‘배포’라는 단어에 복잡한 감정을 느낄 겁니다. 빠르고 정확하게 배포하고 싶지만, 현실은 늘 예상치 못한 문제들로 가득하죠. 수동 배포는 실수를 유발하기 쉽고, 스크립트 기반 자동화는 관리 포인트가 자꾸 늘어납니다. GitOps는 이런 고민을 해결해주는 강력한 방법론입니다. Git 저장소를 통해 인프라와 애플리케이션의 상태를 선언적으로 관리하기 때문에, 누가 언제 무엇을 바꿨는지 명확하게 추적할 수 있고, 문제가 생기면 이전 상태로 롤백하기도 쉬워요. 롤백? 네, Git 커밋 하나면 끝입니다! 🎉

    이런 GitOps 환경을 구축할 때 Argo CD와 Flux CD는 대표적인 선택지입니다. 두 도구 모두 Git 저장소의 상태와 쿠버네티스 클러스터의 실제 상태를 동기화하는 역할을 하지만, 동작 방식이나 기능, 설정 방법 등에서 확실히 달라요. 제가 Flux CD로 마이그레이션하게 된 배경도 이런 차이점들과 저희 팀의 특정 요구사항 때문이었습니다.

    2. Argo CD vs Flux CD: 핵심 개념 비교

    마이그레이션 얘기를 시작하기 전에, 두 도구의 기본적인 차이를 짚고 넘어가겠습니다. 쉽게 말해, Argo CD는 Pull 방식에 집중하고, Flux CD는 GitOps Toolkit이라는 모듈식 접근 방식을 사용합니다.

    Argo CD는 클러스터 내부에 설치되어 Git 저장소를 주기적으로 폴링(polling)하며 변경 사항을 감지합니다. 사용자는 Argo CD UI나 CLI를 통해 Git 저장소와 클러스터의 동기화 상태를 쉽게 확인하고 관리할 수 있죠. 다양한 Git provider와의 통합이 잘 되어 있고, 사용자 친화적인 웹 UI가 강점입니다.

    반면 Flux CD는 GitOps Toolkit이라는 여러 컴포넌트(Source Controller, Kustomize Controller, Helm Controller, Notification Controller 등)로 구성돼 있어요. 각 컴포넌트가 특정 역할을 수행하며, 필요한 컴포넌트만 선택적으로 구성할 수 있습니다. Flux CD는 Git 저장소를 주기적으로 폴링하기보다는 Git hook이나 내부 Controller를 통해 변경 사항을 감지하는 방식을 선호하며, 좀 더 세밀한 제어가 가능하다는 특징이 있어요. 특히 Flux CD v2부터는 이런 모듈식 아키텍처가 훨씬 강화됐습니다.

    구분 Argo CD Flux CD (v2)
    아키텍처 단일 컨트롤러 기반 모듈식 GitOps Toolkit (Source, Kustomize, Helm Controller 등)
    설정 방식 Application CRD, UI, CLI Kustomization/HelmRelease CRD, CLI
    동기화 방식 주기적 폴링 (기본), Git hook 지원 Git hook (권장), 주기적 폴링
    UI 풍부하고 사용자 친화적인 웹 UI CLI 중심, 웹 UI는 제한적 (지속 개선 중)
    확장성/유연성 높음 매우 높음 (모듈식)
    학습 곡선 비교적 완만함 초기 학습 곡선 있음 (GitOps Toolkit 이해 필요)
    Flux CD v2 GitOps Toolkit 구성 요소 다이어그램: Source, Kustomize, Helm Controller 등 모듈 설명

    Flux CD v2의 핵심 컴포넌트인 GitOps Toolkit의 구조를 나타내는 다이어그램입니다.

    3. Flux CD v2 마이그레이션 여정: 삽질과 깨달음 😅

    저희는 기존에 Argo CD를 잘 사용하고 있었어요. 하지만 몇 가지 이유로 Flux CD v2로의 전환을 결정했습니다. 첫째, 클러스터의 상태를 좀 더 세밀하게 제어하고 싶다는 생각이 들었어요. Flux CD의 모듈식 아키텍처가 이런 요구를 충족해줄 수 있다고 판단했거든요. 둘째, Helm Chart 관리를 더 효율적으로 하고 싶었는데, Flux CD의 Helm Controller가 이를 잘 지원한다는 정보를 얻었습니다.

    마이그레이션 단계는 크게 다음과 같았습니다.

    1. Flux CD 설치 및 기본 설정: 먼저 클러스터에 Flux CD v2를 설치했어요. bootstrapping 과정을 통해 Git 저장소와 연결하고, 초기 동기화 설정을 완료합니다. 이때 Git 저장소의 디렉토리 구조나 접근 권한 등을 꼼꼼히 확인해야 했습니다.
    2. 기존 Argo CD 애플리케이션 전환: Argo CD에서 관리하던 Kubernetes Manifests (YAML 파일)나 Helm Charts를 Flux CD가 인식할 수 있는 형태로 변경했습니다. 저는 주로 Kustomize를 사용하고 있었기 때문에, Flux CD의 Kustomization Controller가 참조할 수 있도록 Git 저장소 구조를 조정하는 작업이 필요했어요.
    3. Helm Chart 관리 전환: Argo CD에서 Helm Release를 사용했다면, Flux CD의 Helm Controller를 사용하도록 설정 파일을 변경합니다. Helm Chart의 `values.yaml` 파일 관리 방식이나 Release 이름, 네임스페이스 등을 Flux CD의 `HelmRelease` CRD(Custom Resource Definition)에 맞게 수정해야 했어요. 이 과정에서 몇 가지 오타와 설정 누락으로 배포가 실패하는 경험도 했습니다. ⚠️
    4. 동기화 방식 검토 및 적용: Git hook을 사용할지, 주기적인 폴링을 사용할지 결정하고 설정했어요. 보안과 효율성을 고려했을 때 Git hook이 낫다고 판단하여, Git Provider (GitHub/GitLab 등)와 Flux CD 간의 Webhook 설정을 진행했습니다.
    5. 검증 및 테스트: 모든 설정이 완료된 후, 실제 애플리케이션 배포 및 업데이트를 반복적으로 테스트했습니다. 변경 사항이 제대로 Git에 반영되고, Flux CD가 이를 감지하여 클러스터에 적용하는지, 롤백은 잘 되는지 등을 꼼꼼히 확인했어요.
    Flux CD CLI를 이용한 Git 저장소 부트스트랩 및 초기 설정 화면

    Flux CD CLI를 사용하여 Git 저장소와 클러스터를 연결하고 초기 설정을 진행하는 과정의 일부입니다.

    4. ⚠️ 주의사항 및 트러블슈팅 경험

    마이그레이션 과정에서 몇 가지 예상 밖의 문제들을 겪었어요. 여러분도 이런 상황에 마주칠 수 있으니 미리 알아두시면 좋을 겁니다.

    • Git 저장소 구조 및 권한 문제: Flux CD는 Git 저장소의 특정 경로를 바라보고 동기화합니다. 처음에는 Git 저장소 구조를 Argo CD 방식 그대로 사용하려다 보니 Flux CD가 파일을 제대로 찾지 못하더라고요. Git 저장소 구조를 Flux CD가 이해하기 쉬운 형태로 재구성하는 게 중요했습니다. 또한 Git 저장소에 대한 클러스터의 접근 권한 (SSH Key 또는 Access Token) 설정이 올바르지 않으면 동기화 자체가 실패합니다.
    • HelmRelease CRD 설정 오류: Helm Chart를 관리할 때 `HelmRelease` CRD의 필드 이름을 잘못 입력하거나, 필수 값을 누락하는 경우가 있었어요. 예를 들어, `chart.spec.version` 대신 `chart.version`으로 잘못 입력한다거나, `releaseName`을 지정하지 않아 예상치 못한 이름으로 Helm Release가 생성되는 등의 문제가 발생했습니다. Flux CD의 공식 문서를 옆에 끼고 설정하는 걸 추천합니다.
    • Controller 간의 의존성 문제: Flux CD v2는 여러 Controller가 협력하여 동작합니다. Source Controller가 Git 저장소에서 소스를 가져오면, Kustomize Controller나 Helm Controller가 이를 받아 실제 리소스를 생성/업데이트하는 식이죠. Source Controller에 문제가 생기면 후속 Controller들도 제대로 동작하지 않아요. Flux CD의 각 Controller 상태를 `flux get kustomization`, `flux get helmrelease` 같은 명령어로 자주 확인하는 습관이 중요합니다.
    • 네임스페이스 (Namespace) 관리: Argo CD에서는 Application CRD에 네임스페이스를 지정하는 방식이 Flux CD의 `HelmRelease`나 `Kustomization` CRD와 조금 달라요. 기존 Argo CD에서 여러 네임스페이스에 걸쳐 리소스를 관리하고 있었다면, Flux CD에서는 각 CRD에 네임스페이스를 명확히 지정해주거나, Git 저장소 구조를 네임스페이스별로 분리하는 전략이 필요했습니다.

    가장 큰 삽질은 아마 Helm Chart의 `values.yaml`을 GitOps 방식으로 관리하면서 발생했던 버전 충돌 문제였어요. Git 저장소에서 Helm Chart를 가져오고, 이를 Kustomize로 패치하는 과정에서 버전 관리가 꼬여버렸거든요. 결국에는 Flux CD의 **`HelmRelease` CRD 내에서 `valuesFrom` 필드를 활용하여 Git 파일 참조 방식을 명확히 지정**해주고, Kustomize를 통한 패치보다는 `HelmRelease` 자체에서 값을 관리하는 방식으로 변경했어요. 이게 가장 큰 깨달음 중 하나였습니다. 💡

    Flux CD CLI를 이용한 동기화 상태 및 리소스 정보 확인 화면

    Flux CD의 CLI 명령어로 확인한 동기화 상태 및 리소스 정보를 보여주는 화면입니다.

    5. Flux CD 마이그레이션 결과: 뭐가 달라졌나? ✅

    Flux CD v2로 성공적으로 마이그레이션한 후, 저희는 몇 가지 긍정적인 변화를 경험했어요.

    • 향상된 모듈성과 유연성: GitOps Toolkit 덕분에 필요한 기능만 선택적으로 활성화하고 설정할 수 있게 됐어요. 이건 클러스터 리소스 사용량을 최적화하는 데도 도움이 되더라고요.
    • 강력해진 Helm Chart 관리: Helm Controller를 통해 Helm Chart 버전을 관리하고, `values.yaml`을 GitOps 방식으로 선언적으로 관리하는 게 훨씬 수월해졌어요.
    • 세밀한 제어 기능: Git hook을 통한 실시간 동기화, Controller별 상태 확인 등 이전보다 클러스터 상태를 더 세밀하게 제어하고 모니터링할 수 있게 됐습니다.
    • 개발팀의 GitOps 경험 개선: Git 저장소에 대한 변경 사항이 자동으로 클러스터에 반영되는 경험은 개발팀의 만족도를 높였어요. Pull Request를 통해 변경 사항을 리뷰하고 머지하는 과정이 자연스럽게 CI/CD 파이프라인으로 통합됐거든요.

    물론, Argo CD의 풍부한 웹 UI가 제공하는 편함은 Flux CD에서는 조금 줄어들었어요. 하지만 CLI의 발전과 커뮤니티의 노력으로 많은 부분이 개선되고 있고, 저희 팀은 CLI 중심의 워크플로우에 금방 익숙해졌습니다. 오히려 **CLI의 강력한 자동화 가능성** 덕분에 더 많은 부분을 스크립트로 관리할 수 있게 된 점을 긍정적으로 보고 있어요.

    Flux CD 마이그레이션 이후 CI/CD 파이프라인의 성공률 및 배포 속도 개선 지표를 시각화한 그래프입니다.

    6. 마무리하며: Flux CD와 함께하는 GitOps 여정

    Flux CD로의 마이그레이션은 분명 쉽지 않은 과정이었어요. Argo CD와는 다른 철학과 구조를 가지고 있었기에, 새로운 학습이 필요했고 예상치 못한 문제들과도 많이 씨름했거든요. 하지만 결과적으로 저희 팀은 GitOps 환경을 더욱 견고하고 효율적으로 구축할 수 있었습니다.

    핵심은 각 도구의 철학을 이해하고, 우리 환경에 맞는 방식을 선택하는 것입니다.

    • 단순하고 직관적인 GitOps 환경을 원하고, 풍부한 웹 UI가 중요하다면 Argo CD가 좋은 선택이 될 거예요.
    • 세밀한 제어, 모듈성, 그리고 강력한 Helm/Kustomize 통합을 원한다면 Flux CD v2가 매력적인 대안이 될 겁니다.

    아직 GitOps를 도입하지 않으셨거나, CI/CD 도구 전환을 고민하고 계신다면, 오늘 제가 나눈 경험이 조금이라도 도움이 되었으면 좋겠어요. 다음 글에서는 Flux CD의 특정 기능 (예: Policy as Code, Observability)에 대해 더 깊이 파고드는 내용을 다룰 예정이니 기대해주세요!

    궁금한 점이 있다면 언제든지 댓글로 남겨주세요. 함께 성장하는 엔지니어들이 되겠습니다! 감사합니다. 😊

  • [k8s] Flux CD GitOps 파이프라인 구축: 안정적인 쿠버네티스 배포 자동화

    [k8s] Flux CD GitOps 파이프라인 구축: 안정적인 쿠버네티스 배포 자동화

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 많은 인프라 엔지니어분들이 한 번쯤은 고민해봤을 주제, 바로 쿠버네티스(Kubernetes) 배포 자동화에 대해 이야기해보려고 합니다. 특히 GitOps(깃옵스) 방법론과 그 구현체 중 하나인 Flux CD(플럭스 CD)를 활용한 파이프라인 구축 경험을 공유해 드릴게요.

    혹시 이런 경험 있으신가요? 개발팀에서 “배포해주세요!” 요청이 들어오면, kubectl apply -f 명령어를 조심스럽게 입력하면서, “혹시나 다른 설정이 덮어씌워지는 건 아닐까?”, “이전 버전으로 되돌리려면 어떻게 해야 하지?” 같은 걱정을 했던 순간 말이죠. 저는 셀 수 없이 많습니다. 수동 배포는 휴먼 에러의 온상이었고, 배포하다 새벽을 맞이하는 일도 비일비재했거든요. 이런 삽질을 거듭하다가 GitOps를 만나고 나서야 비로소 안정적인 배포의 빛을 보게 되었습니다. 제 홈랩에서도 여러 서비스를 GitOps로 관리하고 있는데, 정말 편하더라고요!

    GitOps 파이프라인은 Git 저장소를 ‘진리의 단일 출처(Single Source of Truth)’로 삼아, 쿠버네티스 클러스터의 상태를 Git에 선언된 대로 유지하는 방식입니다. 간단히 말해, Git에 저장된 설정 파일이 곧 클러스터의 현재 상태여야 한다는 거죠.

    GitOps와 Flux CD, 정말 좋은 이유

    처음 GitOps라는 개념을 들었을 때는 “어차피 Git에 YAML 파일 올리는 건 똑같은데, 뭐가 다르지?” 싶었어요. 근데 실제로 써보니 이 방식이 가진 장점이 정말 많더라고요. 제가 느낀 가장 큰 장점들을 꼽아보면 이렇습니다.

    • 일관성(Consistency): 모든 설정이 Git에 있으니, 개발, 스테이징, 운영 환경 간의 차이를 최소화할 수 있어요. “제 환경에서는 잘 되는데요?” 하는 말이 확 줄어듭니다.
    • 감사 및 추적(Auditability & Traceability): Git 커밋(Commit) 기록 자체가 변경 이력이 되니까요. 누가, 언제, 무엇을 변경했는지 명확하게 알 수 있죠. 문제 발생 시 롤백(Rollback)도 Git으로 너무나 쉽습니다.
    • 안정성(Reliability): Git에 선언된 상태와 클러스터의 실제 상태가 다르면, GitOps 에이전트가 자동으로 클러스터를 Git의 상태로 되돌립니다. 마치 자가 치유(Self-healing) 능력 같달까요?
    • 생산성(Productivity): 수동 작업이 줄어들고 자동화되면서, 엔지니어는 더 중요한 일에 집중할 수 있게 됩니다.

    이런 GitOps의 철학을 구현하는 대표적인 도구가 바로 Flux CD입니다. Flux CD는 쿠버네티스 클러스터 내에서 동작하는 컨트롤러(Controller)들의 집합인데요, 주기적으로 Git 저장소를 감시하다가 변경 사항이 감지되면 자동으로 클러스터에 적용해주는 역할을 합니다. 주요 컴포넌트로는 Source Controller(소스 컨트롤러), Kustomize Controller(커스터마이즈 컨트롤러), Helm Controller(헬름 컨트롤러) 등이 있습니다.

    Flux CD GitOps 파이프라인 구축 실전 가이드

    자, 그럼 이제 제 홈랩에서 Flux CD를 어떻게 구축했는지, 단계별로 자세히 알려드릴게요. 저도 처음엔 공식 문서를 보면서 여러 번 헤맸는데, 이 가이드가 여러분의 삽질 시간을 줄여주길 바랍니다!

    1단계: 사전 준비 및 Flux CLI 설치

    먼저, 쿠버네티스 클러스터에 접근 가능한 환경과 kubectl, git이 설치되어 있어야 합니다. 저는 로컬 PC에 flux CLI(Command Line Interface)를 설치하는 것부터 시작했어요.

    # Flux CLI 설치 (macOS 기준)
    brew install fluxcd/flux/flux
    
    # 설치 확인
    flux --version
    # flux version 2.x.x (최신 버전으로 설치됩니다)
    
    # 클러스터에 Flux CD가 설치될 네임스페이스 생성 (선택 사항)
    kubectl create namespace flux-system
    

    flux-system 네임스페이스는 Flux CD 컨트롤러들이 배포될 기본 공간입니다. 따로 지정하지 않으면 기본으로 이 네임스페이스에 설치되죠.

    2단계: Git 저장소 준비

    Flux CD는 Git 저장소를 바라보며 작동하기 때문에, 쿠버네티스 설정 파일(YAML)들을 저장할 Git 저장소가 필요합니다. 저는 GitHub에 gitops-k8s-homelab이라는 프라이빗(Private) 저장소를 만들었어요. 저장소 안에는 다음과 같은 구조로 파일을 구성할 예정입니다.

    gitops-k8s-homelab/
    ├── clusters/
    │   └── my-cluster/
    │       ├── flux-system/
    │       │   └── gotk-components.yaml
    │       │   └── gotk-sync.yaml
    │       └── apps/
    │           └── my-app/
    │               └── kustomization.yaml
    ├── apps/
    │   └── my-app/
    │       ├── deployment.yaml
    │       ├── service.yaml
    │       └── ingress.yaml
    └── README.md
    

    clusters/my-cluster/flux-system 경로에는 Flux CD 자체의 설정 파일들이, clusters/my-cluster/apps 경로에는 클러스터에 배포할 애플리케이션들을 정의하는 Kustomization 파일들이 들어갈 겁니다. 실제 애플리케이션 YAML 파일들은 apps/my-app 경로에 두고요.

    3단계: Flux CD 부트스트랩 (Bootstrap)

    이제 가장 중요한 단계입니다. flux bootstrap github 명령어를 사용해서 Flux CD를 쿠버네티스 클러스터에 설치하고, Git 저장소와 연결하는 작업을 수행합니다. 이 명령 한 방으로 Flux CD가 클러스터에 필요한 모든 컴포넌트를 배포하고, 지정된 Git 저장소를 바라보도록 설정해줍니다. 저는 GitHub를 사용했으니 github 옵션을 사용했어요.

    # 환경 변수 설정 (개인 GitHub 토큰과 사용자명으로 대체하세요!)
    export GITHUB_TOKEN="YOUR_GITHUB_TOKEN" # Personal Access Token
    export GITHUB_USER="YOUR_GITHUB_USERNAME"
    
    # Flux CD 부트스트랩 실행
    flux bootstrap github \
      --owner=${GITHUB_USER} \
      --repository=gitops-k8s-homelab \
      --branch=main \
      --path=clusters/my-cluster \
      --personal
    

    이 명령을 실행하면 Flux CD가 클러스터에 설치되고, clusters/my-cluster 경로를 기준으로 Git 저장소의 내용을 동기화하기 시작합니다. --personal 옵션은 개인 저장소에 주로 사용하고, 조직(Organization) 저장소의 경우 --owner를 조직명으로 지정하고 SSH 키 방식으로 인증하는 것이 일반적입니다. 저는 홈랩이라 개인 토큰으로 편하게 작업했어요.

    부트스트랩이 완료되면, Git 저장소의 clusters/my-cluster/flux-system 경로에 Flux CD 자체의 Kustomization 파일들이 자동으로 생성되어 커밋되는 것을 볼 수 있습니다. 이게 바로 Flux CD가 스스로를 GitOps 방식으로 관리하는 모습이죠. 신기하더라고요!

    4단계: 애플리케이션 배포를 위한 Kustomization 정의

    이제 클러스터에 배포할 애플리케이션을 정의하고 Flux CD가 이를 인식하도록 설정할 차례입니다. 먼저, apps/my-app 경로에 간단한 NGINX 애플리케이션을 정의하는 YAML 파일들을 만듭니다.

    # apps/my-app/deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-nginx-app
      labels:
        app: my-nginx-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: my-nginx-app
      template:
        metadata:
          labels:
            app: my-nginx-app
        spec:
          containers:
          - name: nginx
            image: nginx:latest
            ports:
            - containerPort: 80
    ---
    # apps/my-app/service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx-app-service
    spec:
      selector:
        app: my-nginx-app
      ports:
      - protocol: TCP
        port: 80
        targetPort: 80
      type: ClusterIP
    

    그리고 이 애플리케이션을 클러스터에 배포하도록 지시하는 Kustomization 파일을 clusters/my-cluster/apps/my-app/kustomization.yaml 경로에 생성합니다.

    # clusters/my-cluster/apps/my-app/kustomization.yaml
    apiVersion: kustomize.toolkit.fluxcd.io/v1
    kind: Kustomization
    metadata:
      name: my-nginx-app
      namespace: flux-system
    spec:
      interval: 1m0s
      sourceRef:
        kind: GitRepository
        name: flux-system
      path: ./apps/my-app
      prune: true
      validation: client
    

    이 파일들을 Git 저장소에 커밋(Commit)하고 푸시(Push)합니다.

    git add .
    git commit -m "Add my-nginx-app and kustomization"
    git push origin main
    

    Flux CD는 clusters/my-cluster/flux-system/gotk-sync.yaml 파일에 정의된 Kustomization에 의해 clusters/my-cluster 경로를 1분마다 동기화합니다. 그러면 방금 추가한 clusters/my-cluster/apps/my-app/kustomization.yaml 파일이 클러스터에 적용되고, 이 Kustomization이 다시 apps/my-app 경로의 NGINX 애플리케이션을 클러스터에 배포하게 됩니다. 이 모든 과정이 자동으로 이뤄지는 거죠!

    ⚠️ 삽질 경험 & 트러블슈팅 팁

    제가 Flux CD를 사용하면서 겪었던 몇 가지 삽질과 해결 팁을 공유해 드릴게요. 여러분은 저처럼 헤매지 마시라고요! ㅎㅎ

    • 동기화 지연 문제: Git에 변경사항을 푸시했는데, 클러스터에 바로 적용이 안 되는 경우가 있습니다. Kustomization 리소스의 interval 값이 기본 10분으로 설정되어 있거나, 네트워크 문제 등으로 Git 저장소에 접근이 안 되는 경우가 많았어요. 급할 때는 다음 명령어로 강제 동기화를 시도합니다.
      flux reconcile kustomization my-nginx-app --with-source
      

      이 명령은 해당 Kustomization과 그 소스 GitRepository를 즉시 동기화하도록 Flux CD에 지시합니다.

    • Git Repository 구조 설계: 처음에는 모든 YAML 파일을 한 폴더에 때려 넣었는데, 나중에 애플리케이션이 많아지고 환경이 복잡해지면서 관리가 힘들어지더라고요. clusters/[클러스터이름]/[환경]/apps/[앱이름] 같은 계층적인 구조나, clusters와 apps를 분리하는 구조로 가져가는 것이 훨씬 효율적입니다. 저는 지금 clusters와 apps를 분리해서 관리하고 있어요.
    • 시크릿(Secret) 관리: 데이터베이스 비밀번호 같은 민감한 정보는 Git에 평문으로 올릴 수 없죠. 저는 SOPS(Secrets OPerationS)를 사용해서 Git 저장소에 암호화된 시크릿을 저장하고, Flux CD가 배포 시 자동으로 복호화하도록 설정했습니다. EKS(Elastic Kubernetes Service) 같은 클라우드 환경에서는 KMS(Key Management Service)를 연동해서 SOPS를 사용하는 것이 일반적입니다.
    • CRD(Custom Resource Definition) 적용 순서: 때로는 특정 컨트롤러나 미들웨어의 CRD가 먼저 클러스터에 적용되어야만 해당 리소스를 배포할 수 있습니다. 예를 들어, Istio(이스티오)나 Cert-Manager(서트 매니저) 같은 도구들이 그렇죠. 이런 경우, Kustomization의 dependsOn 필드를 사용하거나, healthChecks를 설정하여 의존성을 명확히 해주는 것이 중요합니다.

    ✅ 배포 결과 확인 및 검증

    이제 모든 설정이 잘 적용되었는지 확인해볼 시간입니다. flux get kustomizations 명령어로 Flux CD가 관리하는 Kustomization 리소스들의 상태를 확인할 수 있습니다.

    flux get kustomizations
    NAME            REVISION        SUSPENDED   READY   MESSAGE                                         LAST ACTIVITY
    flux-system     main/a1b2c3d4   False       True    Kustomization reconciled successfully           2026-05-15T10:00:00Z
    my-nginx-app    main/e5f6g7h8   False       True    Kustomization reconciled successfully           2026-05-15T10:01:00Z
    

    READY 상태가 True이고 MESSAGE에 성공 메시지가 보인다면, Flux CD가 정상적으로 작동하고 있다는 뜻입니다. 이제 쿠버네티스 클러스터에 NGINX 애플리케이션이 잘 배포되었는지 확인해볼까요?

    kubectl get deployment my-nginx-app
    NAME           READY   UP-TO-DATE   AVAILABLE   AGE
    my-nginx-app   2/2     2            2           5m
    
    kubectl get service my-nginx-app-service
    NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    my-nginx-app-service     ClusterIP   10.96.100.123   <none>        80/TCP    5m
    

    🎉 드디어 성공입니다! my-nginx-app 배포가 정상적으로 2개의 레플리카(Replica)로 동작하고 있는 것을 확인할 수 있네요. 이제 Git 저장소의 apps/my-app/deployment.yaml 파일에서 replicas 수를 3으로 변경하고 푸시해보세요. 잠시 후 Flux CD가 이를 감지하고 자동으로 클러스터의 레플리카 수를 3으로 업데이트할 겁니다. 이 경험을 하고 나면 GitOps의 매력에서 헤어 나올 수 없을 거예요!

    GitOps는 이렇게 Git에 대한 변경만으로 클러스터의 상태를 관리할 수 있게 해줍니다. 저는 이 편리함 덕분에 홈랩에서 새로운 서비스를 배포할 때마다 kubectl 명령어를 직접 입력할 일이 거의 없어졌어요. 모든 변경사항은 Git에 기록되니, 누가 어떤 변경을 했는지도 명확하게 알 수 있고요. 정말 안정적이고 편리한 배포 파이프라인이 구축된 거죠.

    마무리하며: GitOps와 Flux CD, 현대 인프라의 필수 도구

    오늘은 13년차 인프라 엔지니어의 경험을 바탕으로 Flux CD GitOps 파이프라인 구축에 대해 상세히 이야기해봤습니다. 쿠버네티스 배포 자동화는 현대 인프라에서 선택이 아닌 필수가 되어가고 있습니다. 특히 GitOps는 그 중심에서 안정성, 일관성, 생산성이라는 세 마리 토끼를 동시에 잡을 수 있게 해주는 강력한 방법론이라고 생각합니다.

    물론 처음에는 GitOps 개념이나 Flux CD 사용법이 낯설고 어렵게 느껴질 수 있습니다. 저도 그랬거든요. 하지만 한 번 제대로 구축하고 나면, 인프라 관리의 패러다임이 확 바뀌는 것을 경험하실 수 있을 겁니다. 마치 수동으로 서버를 한 대 한 대 설치하다가 프로비저닝(Provisioning) 자동화를 접했을 때의 충격과 비슷하다고 할까요?

    다음 글에서는 오늘 다루지 못한 Helm Chart(헬름 차트)를 Flux CD로 관리하는 방법이나, 멀티 클러스터(Multi-cluster) 환경에서의 GitOps 전략, 그리고 SOPS를 이용한 시크릿 관리 등 좀 더 심화된 주제들을 다뤄볼 예정입니다. 이 글이 여러분의 쿠버네티스 여정에 작은 도움이 되었기를 바랍니다!

    궁금한 점이나 저의 삽질 경험에 대한 질문이 있으시면 언제든지 댓글 남겨주세요! 저도 계속 배우고 성장하는 중이거든요. 😊