13년차의 서버실

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

[태그:] 클라우드 네이티브

  • [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은 클라우드별로 분리해서 시작하세요. 반대로 단일 클라우드에 가깝거나 반복 요청이 적다면 굳이 복잡한 추상화를 도입하지 않는 편이 더 낫습니다.

  • [OpenStack] OpenStack Magnum Kubernetes 운영: 6개월 회고와 교훈

    [OpenStack] OpenStack Magnum Kubernetes 운영: 6개월 회고와 교훈

    OpenStack Magnum Kubernetes 운영: 6개월 회고와 교훈

    OpenStack Magnum Kubernetes 조합으로 클러스터를 6개월 정도 운영해보면, 처음 기대했던 포인트와 실제 운영 포인트가 꽤 다르다는 걸 느끼게 됩니다. 저도 처음엔 'OpenStack 위에서 Kubernetes를 좀 더 쉽게 만들 수 있겠네?' 정도로 접근했는데, 막상 돌려보니 편한 부분은 분명했고 반대로 운영자가 꼭 알아야 하는 제약도 꽤 있더라고요. 특히 사내 프라이빗 클라우드나 홈랩처럼 OpenStack 자원이 이미 깔린 환경에서는 Magnum 사용 후기를 많이 찾게 되는데, 실제로는 설치보다 운영이 더 중요했습니다. 이번 글은 OpenStack Magnum Kubernetes 환경을 6개월 운용하면서 느낀 점, 부딪힌 문제, 그리고 어떤 팀에 잘 맞는지 차분하게 정리한 회고입니다.

    혹시 이런 경험 있으신가요? 클러스터는 금방 만들었는데 업그레이드, 노드 교체, 네트워크 연결, 외부 로드밸런서 연동에서 갑자기 난도가 확 올라가는 순간 말이죠. 저도 딱 그랬습니다. 생성이 끝났다고 안심했는데, 운영은 또 다른 문제더라고요.

    Magnum, OpenStack, Kubernetes 워커 노드와 로드밸런서 관계를 보여주는 전체 구성도입니다.

    OpenStack Magnum이 여전히 의미 있는 이유

    쉽게 말해 Magnum은 OpenStack에서 COE(Container Orchestration Engine) 클러스터를 프로비저닝하는 서비스입니다. 과거 문서에는 Mesos나 Swarm도 함께 언급됐지만, 최근 공식 문서 기준으로는 사실상 Kubernetes 중심으로 보는 편이 맞습니다. OpenStack을 이미 쓰고 있다면 가상머신 인프라를 따로 만들고 그 위에 수동으로 kubeadm을 올리는 대신, OpenStack API와 템플릿 기반으로 클러스터 생성 흐름을 어느 정도 표준화할 수 있다는 점이 장점이거든요.

    제가 직접 써보니 이 지점이 꽤 중요했습니다. 인프라 팀 입장에서는 Nova, Neutron, Cinder, Octavia 같은 OpenStack 리소스 운영 경험이 이미 있잖아요. Magnum은 그 위에서 Kubernetes 클러스터를 조금 더 OpenStack스럽게 관리하게 해줍니다. 다만 완전한 Managed Kubernetes처럼 기대하면 실망할 수 있습니다. 제 경험상 Magnum은 '운영을 없애주는 도구'라기보다 'OpenStack 친화적인 클러스터 생성 자동화 계층'으로 이해할 때 만족도가 높았습니다.

    OpenStack Magnum 핵심 개념: Magnum, Cluster Template, Heat

    처음엔 이 구조가 꽤 헷갈렸는데, 큰 그림을 이해하고 나면 훨씬 수월해집니다. 여기서 중요한 포인트는 Magnum이 혼자 모든 걸 하는 게 아니라는 점입니다.

    • Magnum: Kubernetes 같은 COE 클러스터를 생성하고 관리하는 OpenStack 서비스입니다.
    • Cluster Template: 어떤 이미지, 네트워크, 플러그인, 노드 사양으로 클러스터를 만들지 정의하는 청사진입니다.
    • Heat: 전통적인 Magnum 배포에서 실제 인프라 스택을 구성하는 오케스트레이션 계층입니다. 장애를 파고들다 보면 결국 Heat 이벤트를 보게 되더라고요.
    • BayModel: 과거 명칭입니다. 최신 문서와 운영 맥락에서는 보통 Cluster Template 기준으로 보면 됩니다.

    한 줄로 요약하면 이렇습니다. Magnum이 요청을 받고, Cluster Template을 참고해 OpenStack 자원을 만들고, 전통적인 드라이버 환경에서는 Heat가 실제 스택 생성을 수행하는 구조입니다. 참고로 최근 공식 문서에서는 Heat 기반 드라이버가 deprecated로 안내되고 있어서, 운영 중인 환경이 어떤 드라이버를 쓰는지도 꼭 확인해두는 게 좋습니다.

    구성 요소 역할 운영할 때 봐야 할 포인트
    Magnum 클러스터 생성/삭제 요청 처리 API 상태, 템플릿 파라미터, 사용 중인 드라이버
    Heat 스택 생성과 자원 오케스트레이션 이벤트 로그, 실패 리소스 추적
    Neutron/Octavia 네트워크와 로드밸런서 외부 연결, 서비스 노출, 포트/보안그룹
    Kubernetes 워크로드 스케줄링과 운영 노드 상태, CNI, Ingress, 스토리지

    시작 전에 체크했던 항목들

    Magnum 사용 후기에서 잘 안 보이는데, 실제로는 사전 점검이 절반입니다. 저도 처음엔 클러스터 생성 명령부터 쳤다가 네트워크랑 이미지 때문에 한참 돌아갔거든요.

    1. OpenStack 서비스 상태 확인
      Magnum만 살아 있어도 되는 게 아니라 Keystone, Nova, Neutron, Glance, Heat가 기본적으로 안정적이어야 합니다.
    2. Kubernetes용 이미지 준비
      이미지 이름보다 더 중요한 건 클러스터 드라이버 요구사항입니다. 최근 공식 문서 기준으로는 Kubernetes용 이미지의 os_distro 메타데이터가 드라이버와 맞아야 하고, 기본 예시는 Fedora CoreOS 계열을 전제로 설명합니다.
    3. 외부 네트워크와 DNS 설계
      API 접근, 노드 egress, 이미지 pull 경로를 미리 봐야 합니다.
    4. Load Balancer 정책 확인
      마스터 API 엔드포인트와 Service 노출 방식이 Octavia와 어떻게 연결되는지 미리 확인해야 합니다.
    5. 스토리지 전략 결정
      Cinder 연동 방식을 어떻게 가져갈지, 동적 볼륨 전략을 초반부터 쓸지 미리 정해두는 게 좋습니다.

    OpenStack Magnum으로 Kubernetes 클러스터 만들기

    이제 실전입니다. 아래 예시는 홈랩과 테스트 환경에서 자주 확인하던 흐름을 기준으로 정리했습니다. 다만 배포판, 드라이버, OpenStack 릴리스에 따라 옵션 이름이나 권장 이미지가 달라질 수 있습니다. 그래서 핵심은 명령어를 외우는 것보다 어떤 리소스를 먼저 확인해야 하는지를 잡는 데 있습니다.

    1. 기본 리소스 확인

    openstack coe service list
    openstack network list
    openstack subnet list
    openstack image list
    openstack flavor list
    openstack keypair list

    여기서 네트워크, 서브넷, 이미지, flavor, keypair가 실제로 준비되어 있는지 먼저 봅니다. 이거 안 보고 바로 템플릿 만들면 높은 확률로 다시 돌아오게 됩니다.

    2. Cluster Template 생성

    openstack coe cluster template create k8s-template \
      --coe kubernetes \
      --image fedora-coreos-k8s \
      --external-network public \
      --fixed-network private-net \
      --fixed-subnet private-subnet \
      --flavor m1.medium \
      --master-flavor m1.medium \
      --docker-storage-driver overlay2 \
      --network-driver flannel \
      --volume-driver cinder \
      --floating-ip-enabled \
      --master-lb-enabled

    여기서 실수가 가장 많이 나왔습니다. 특히 external-network, fixed-network, image 조합이 안 맞으면 생성이 중간에 터지더라고요. 처음엔 Magnum 문제인 줄 알았는데, 파고 들어가면 Neutron 설정이나 이미지 메타데이터 문제인 경우가 많았습니다. 이미지 이름은 예시일 뿐이고, 실제 환경에서는 해당 드라이버가 요구하는 이미지 속성을 꼭 확인하셔야 합니다.

    OpenStack Magnum Cluster Template과 Kubernetes 리소스 매핑 이미지

    Cluster Template이 네트워크, 이미지, 플레버, 로드밸런서와 연결되는 흐름을 설명하는 다이어그램입니다.

    3. 클러스터 생성

    openstack coe cluster create k8s-prod-like \
      --cluster-template k8s-template \
      --master-count 1 \
      --node-count 3 \
      --keypair mykey

    테스트 환경에서는 master-count와 node-count를 보수적으로 시작하는 게 좋습니다. 처음부터 크게 만들면 실패 원인도 커지고, 디버깅 비용도 같이 올라갑니다. 저는 1 control plane, 2~3 worker부터 시작해서 네트워크와 스토리지가 안정적인지 먼저 봤습니다.

    4. cluster config 가져와서 접속

    mkdir -p k8s-prod-like-config
    openstack coe cluster config k8s-prod-like --dir k8s-prod-like-config
    export KUBECONFIG=$(pwd)/k8s-prod-like-config/config
    kubectl get nodes
    kubectl get pods -A

    이 단계는 환경에 따라 조금 다릅니다. 어떤 환경에서는 eval $(openstack coe cluster config <cluster-name>) 형태로 바로 접속하기도 하고, 어떤 환경에서는 생성된 config 파일을 KUBECONFIG로 잡아 쓰는 식이 더 명확했습니다. 생성 직후에는 CNI가 아직 완전히 올라오지 않은 경우도 있으니 몇 분 정도 여유를 두고 확인하는 게 좋습니다.

    5. 간단한 워크로드 배포

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-nginx
    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
    spec:
      selector:
        app: demo-nginx
      ports:
      - port: 80
        targetPort: 80
      type: LoadBalancer
    kubectl apply -f demo-nginx.yaml
    kubectl get deploy,svc,pods

    이 단계에서 OpenStack Magnum Kubernetes 환경이 정말 내 환경에서 굴러가는지 감이 옵니다. Service가 외부 IP를 잘 받는지, Pod가 정상 스케줄링되는지, 노드 간 통신이 되는지까지 같이 볼 수 있거든요.

    6개월 운영하면서 좋았던 점

    장점은 분명합니다. 특히 인프라 팀이 OpenStack에 익숙할수록 체감이 큽니다.

    • 클러스터 생성 표준화: 템플릿 기반이라 환경 복제가 수월했습니다.
    • OpenStack 권한 체계와 잘 맞음: 프로젝트 단위 자원 분리가 익숙해서 운영 흐름이 덜 낯설었습니다.
    • 홈랩 실험에 좋음: kubeadm을 매번 손으로 치는 것보다 반복 실험이 편하더라고요.
    • 자원 가시성 확보: VM, 볼륨, 포트, 로드밸런서를 OpenStack 관점에서 같이 추적할 수 있었습니다.

    특히 여러 팀이 비슷한 Kubernetes 클러스터 운영 패턴을 가져가야 한다면 Cluster Template은 생각보다 강력합니다. 누가 만들어도 기본 뼈대가 크게 흔들리지 않거든요. 이건 실제로 꽤 편했습니다.

    운영하면서 부딪힌 문제와 트러블슈팅

    여기가 진짜 중요했습니다. OpenStack Magnum Kubernetes 글을 보면 설치 데모는 많아도, 운영 중 생기는 문제는 상대적으로 덜 보이더라고요. 그런데 결국 운영은 장애와 변경 관리 싸움이었습니다.

    1. 문제를 Kubernetes에서만 찾으면 늦습니다

    Pod가 안 뜨거나 Service 외부 노출이 안 되면 처음엔 kubectl만 보게 됩니다. 저도 그랬습니다. 그런데 실제 원인은 Heat 스택 실패, 보안그룹 규칙, Neutron 포트 상태, Octavia 리스너 문제인 경우가 적지 않았습니다.

    openstack stack list
    openstack stack resource list <stack-name>
    openstack stack event list <stack-name>

    여기서 중요한 포인트는 Magnum 장애 분석에는 Kubernetes와 OpenStack을 같이 보는 시야가 필요하다는 점입니다. 한쪽만 보면 원인을 놓치기 쉽습니다.

    2. 업그레이드 전략은 미리 정해야 합니다

    Kubernetes 클러스터 운영에서 가장 민감한 건 업그레이드입니다. Magnum이 있다고 해서 업그레이드가 마법처럼 쉬워지는 건 아니었습니다. 오히려 템플릿, 이미지, 애드온, CNI 버전 호환성을 같이 보게 되더라고요. 그래서 운영 중반부터는 '인플레이스 업그레이드에 집착하지 말고, 새 템플릿 기반 병행 클러스터를 검증한 뒤 전환하자' 쪽으로 사고를 바꿨습니다.

    조금 번거로워 보여도 결과적으로는 더 안전했습니다. 특히 사내 서비스나 장기 운영 워크로드가 얹힌 상태라면 더 그렇습니다.

    3. 네트워크 플러그인과 외부 노출은 가장 먼저 검증해야 합니다

    Ingress나 LoadBalancer 서비스가 기대대로 동작하지 않으면, 사용자 입장에서는 클러스터가 죽은 것처럼 보입니다. 실제로 써보니까 네트워크 드라이버와 OpenStack 네트워크 설계가 맞물리는 구간이 생각보다 까다로웠습니다.

    • Node 간 Pod 통신 확인
    • API 서버 접근 경로 확인
    • 외부 IP 할당 정책 확인
    • 보안그룹 인바운드/아웃바운드 확인

    처음엔 애플리케이션 문제인 줄 알았는데, 보안그룹 한 줄 빠진 거여서 허탈했던 적도 있습니다. 이런 삽질이 은근 많더라고요.

    4. 노드 장애 복구는 '재현 가능성'이 핵심입니다

    노드 하나가 망가졌을 때 수동 조치만으로 복구가 되면 당장은 편합니다. 그런데 그게 반복 가능하지 않으면 다음 장애 때 더 힘들어집니다. 저는 6개월 운영하면서 로그를 많이 남겼고, 결국에는 '노드 문제는 수동 응급조치보다 템플릿과 생성 절차 정비가 우선'이라는 결론을 내렸습니다.

    검증과 결과: 운영 기준으로 무엇을 확인했나

    클러스터가 떴다는 것과 운영 가능한 상태라는 것은 다릅니다. 저는 아래 순서로 검증했습니다.

    1. 노드 Ready 상태 확인
    2. CoreDNS, CNI, kube-proxy 같은 기본 시스템 Pod 확인
    3. LoadBalancer 서비스 외부 노출 확인
    4. Persistent Volume 동작 확인
    5. 재부팅 또는 노드 교체 후 상태 재확인
    kubectl get nodes
    kubectl get pods -A
    kubectl get svc -A
    kubectl get storageclass
    kubectl describe node <node-name>

    간단하지만 이 체크리스트가 꽤 유효했습니다. 6개월 동안 느낀 건, OpenStack Magnum Kubernetes 환경에서는 처음 생성 성공보다 반복 검증 성공이 훨씬 중요하다는 점입니다.

    OpenStack Magnum Kubernetes 클러스터 검증 결과 대시보드 이미지

    노드 Ready 상태, 시스템 Pod, 서비스 외부 IP 상태를 함께 보여주는 결과 확인 이미지입니다.

    운영 결과를 한 줄로 정리하면 이렇습니다. 소규모 프라이빗 클라우드나 내부 플랫폼 팀 환경에서는 충분히 의미가 있지만, 모든 운영 복잡도를 감춰주는 도구는 아니다. 이 기대치만 정확하면 꽤 만족스럽게 사용할 수 있습니다.

    어떤 환경에 잘 맞고, 어떤 경우엔 아쉬운가

    상황 Magnum 적합도 이유
    OpenStack 중심의 내부 인프라 팀 높음 기존 운영 지식과 연결되기 좋습니다.
    홈랩/검증 환경 높음 반복 생성과 실험에 유리합니다.
    완전관리형 서비스 기대 낮음 운영자가 알아야 할 영역이 여전히 넓습니다.
    빠른 버전 전환이 잦은 환경 중간 이미지, 템플릿, 네트워크 호환성 검증이 필요합니다.

    저도 처음엔 Magnum이 조금 더 많은 걸 알아서 해주길 기대했었습니다. 그런데 6개월 써보니 관점을 바꾸게 되더라고요. Magnum은 운영을 없애주는 도구가 아니라, OpenStack 안에서 Kubernetes 클러스터 운영의 출발점을 정리해주는 도구에 가깝습니다. 이 차이를 이해하면 만족도가 확 올라갑니다.

    OpenStack Magnum 도입 전후 Kubernetes 운영 방식 비교 이미지

    수동 kubeadm 방식과 Magnum 기반 운영 방식의 차이를 요약한 비교 인포그래픽입니다.

    정리와 다음 단계

    정리해보면, OpenStack Magnum으로 Kubernetes 클러스터 운영을 해보면서 얻은 교훈은 꽤 명확했습니다.

    • Magnum은 생성 자동화에 강점이 있습니다.
    • 운영 문제는 결국 OpenStack과 Kubernetes를 함께 봐야 풀립니다.
    • 업그레이드와 네트워크 검증은 초반 설계 단계에서 방향을 잡아야 덜 힘듭니다.
    • 작게 시작해서 반복 검증하는 방식이 가장 현실적이었습니다.

    지금 OpenStack 기반으로 클라우드 네이티브 환경을 고민하고 계시다면 Magnum은 충분히 검토할 만한 선택지입니다. 다만 Managed Kubernetes처럼 생각하면 실망할 수 있고, OpenStack 친화적인 클러스터 프로비저닝 계층으로 이해하면 훨씬 잘 맞습니다. 관련해서 이전에 정리한 OpenStack 네트워크 기본기 글이나 스토리지 구성 글도 함께 보면 이해가 훨씬 빨라집니다.

    다음 글에서는 이번 회고에서 살짝 언급했던 Ingress 구성, Cinder 기반 스토리지 연결, 그리고 운영 중 모니터링 포인트를 따로 묶어서 정리해보려 합니다. 이어서 보면 실제 운영 흐름이 더 선명하게 보이실 거예요.

    자주 묻는 질문 FAQ

    Magnum이 있으면 Kubernetes 운영이 쉬워지나요?

    일부는 맞고, 일부는 아닙니다. 클러스터 생성과 표준화는 확실히 편해집니다. 하지만 장애 분석, 네트워크, 업그레이드 전략은 여전히 운영자의 몫입니다.

    Magnum과 kubeadm 중 무엇이 더 낫나요?

    OpenStack 환경 표준화가 중요하면 Magnum이 편합니다. 반대로 세밀한 수동 제어가 더 중요하고 OpenStack 통합이 핵심이 아니라면 kubeadm 쪽이 더 단순하게 느껴질 수도 있습니다.

    6개월 운영 기준으로 가장 먼저 챙길 것은 무엇인가요?

    네트워크와 업그레이드 전략입니다. 이 두 가지를 초반에 대충 잡으면 나중에 운영 피로도가 크게 올라갑니다.

  • [Kubernetes] VPA vs HPA: 리소스 오토스케일링 최적화 전략 비교

    [Kubernetes] VPA vs HPA: 리소스 오토스케일링 최적화 전략 비교

    [Kubernetes] VPA vs HPA: 리소스 오토스케일링 최적화 전략 비교

    Kubernetes VPA HPA를 처음 비교할 때 가장 헷갈리는 지점이 바로 “뭘 늘리는 거지?”였습니다. Replica(레플리카, Pod 개수)를 늘리는 건지, 아니면 Pod 하나가 먹는 CPU/Memory(메모리) 요청값을 바꾸는 건지요. 저도 처음엔 HPA(Horizontal Pod Autoscaler, 수평 Pod 오토스케일링)만 걸어두고 끝난 줄 알았는데, 실제로 운영해보니 Pod 수는 늘어나도 개별 Pod 요청값이 너무 작아서 계속 Throttling(스로틀링, CPU 제한으로 인한 성능 저하)이 나는 경우가 있더라고요. 반대로 VPA(Vertical Pod Autoscaler, 수직 Pod 오토스케일링)를 무턱대고 적용했다가 재시작 타이밍 때문에 서비스가 흔들리는 경험도 있었습니다.

    그래서 이번 글에서는 Kubernetes VPA HPA를 비교 중심으로 정리해보겠습니다. 단순 개념 비교가 아니라, 오토스케일링 비교 관점에서 어떤 워크로드에 무엇이 맞는지, 그리고 리소스 최적화와 Pod 스케일링을 어떻게 나눠서 생각해야 하는지 실제 운영자 시선으로 풀어보겠습니다. 혹시 지금 클러스터에서 CPU는 남는데 응답 속도는 들쭉날쭉하고, 어떤 서비스는 OOMKilled(메모리 부족 종료)까지 난다면 이 주제가 꽤 중요하실 거예요.

    HPA는 Pod 개수를, VPA는 Pod 자원 요청값을 조정한다는 차이를 한눈에 보여주는 개요 이미지입니다.

    1. 왜 Kubernetes VPA HPA 비교가 중요한가

    쉽게 말해 HPA는 옆으로 늘리는 방식이고, VPA는 위로 키우는 방식이에요. 둘 다 오토스케일링이긴 한데 해결하는 문제가 달라요. 여기서 중요한 포인트가 있어요. 트래픽이 몰릴 때 모든 문제가 Pod 개수 부족 때문에 생기는 건 아니거든요. 어떤 앱은 싱글 Pod당 메모리를 더 줘야 안정적이고, 어떤 앱은 그냥 복제본을 여러 개 띄우는 게 훨씬 낫다니까요.

    • HPA: 평균 CPU 사용률이나 메모리, 혹은 Custom Metric(커스텀 메트릭), External Metric(외부 메트릭)을 기준으로 Replica 수를 조절해요.
    • VPA: Pod의 requests/limits 같은 리소스 설정을 추천하거나 자동 조정합니다.
    • 핵심 차이: HPA는 분산 처리에 강하고, VPA는 개별 Pod의 자원 부족 문제를 다루는 데 유리해요.

    제가 직접 해보니 웹 애플리케이션처럼 상태가 거의 없고 수평 확장이 쉬운 경우에는 HPA가 훨씬 직관적이었어요. 반면 배치 작업이나 메모리 사용량이 시간이 지나면서 조금씩 커지는 워크로드는 VPA의 추천값이 꽤 도움이 됐습니다. 이거 진짜 편하더라고요. 다만 자동 적용은 신중해야 했어요.

    2. 개념 정리: VPA vs HPA를 쉽게 말해보면

    2-1. HPA는 언제 쓰나

    HPA는 요청이 갑자기 몰리는 서비스에 잘 맞아요. 예를 들어 API 서버, 프론트엔드 백엔드, 이벤트 소비자를 여러 개 띄워 병렬 처리할 수 있는 구조라면 HPA가 기본 선택지가 되거든요. Metrics Server(메트릭 서버)나 Prometheus Adapter(프로메테우스 어댑터)와 함께 쓰는 경우가 많습니다.

    • 장점: 빠르게 Replica를 늘려 트래픽을 분산할 수 있어요.
    • 장점: 무상태(Stateless, 상태 비저장) 서비스에 특히 잘 맞습니다.
    • 주의: 시작 시간이 긴 앱은 스케일 아웃이 늦게 체감될 수 있어요.

    2-2. VPA는 언제 쓰나

    VPA는 “이 Pod에 CPU 100m만 준 게 애초에 잘못된 거 아닌가?” 같은 상황에서 정말 빛을 봐요. 실제 사용량을 보고 requests를 추천해주기 때문에, 운영자가 감으로 자원값을 넣던 습관에서 벗어나는 데 정말 도움이 돼요. 처음엔 이게 뭔가 싶었는데 추천 모드부터 보기 시작하면 생각보다 실용적이더라고요.

    • 장점: 과소 설정된 requests/limits를 바로잡는 데 좋아요.
    • 장점: 장기적으로 노드 자원 낭비를 줄이는 리소스 최적화에 유리해요.
    • 주의: 자원값 변경을 적용하는 과정에서 Pod 재시작이 개입될 수 있습니다.

    2-3. 한눈에 보는 오토스케일링 비교

    항목 HPA VPA
    무엇을 조절하나 Replica 수 Pod requests/limits
    잘 맞는 대상 웹/API, 무상태 서비스 배치, 메모리 민감 워크로드
    주요 지표 CPU, 메모리, 커스텀/외부 메트릭 실사용 리소스 기반 추천
    반응 방식 수평 확장/축소 수직 조정
    운영 포인트 급격한 부하 대응 초기 자원 설정 보정
    주의점 잘못된 메트릭 설계 시 오동작 적용 시 재시작 영향 가능

    정리하면 HPA는 트래픽 대응, VPA는 자원 설정 보정에 가깝습니다. 둘 중 하나만 정답이라기보다 워크로드 성격에 따라 고르는 문제예요.

    3. 실전 구현: HPA부터 적용해보겠습니다

    실제로 써보니까 대부분 팀은 HPA부터 시작하는 편이 안정적이었어요. 이유가 간단해요. 애플리케이션을 재시작하지 않고도 Replica 수 조절로 대응 가능한 경우가 많기 때문입니다.

    3-1. 전제 조건 확인

    1. 클러스터에 Metrics Server가 있어야 해요.
    2. Deployment에 requests 값이 어느 정도 합리적으로 들어가 있어야 합니다.
    3. readinessProbe(레디니스 프로브)와 livenessProbe(라이브니스 프로브)가 정리되어 있으면 더 좋아요.

    여기서 requests가 엉망이면 HPA 기준도 같이 흔들려버려요. 이 부분을 무시하고 들어가면 나중에 “왜 평균 CPU가 이상하지?” 하면서 삽질 좀 했습니다 ㅎㅎ

    3-2. 샘플 Deployment

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: demo-api
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: demo-api
      template:
        metadata:
          labels:
            app: demo-api
        spec:
          containers:
            - name: demo-api
              image: nginx:stable
              resources:
                requests:
                  cpu: "100m"
                  memory: "128Mi"
                limits:
                  cpu: "500m"
                  memory: "512Mi"
              ports:
                - containerPort: 80

    3-3. HPA 리소스 생성

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: demo-api-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: demo-api
      minReplicas: 2
      maxReplicas: 10
      metrics:
        - type: Resource
          resource:
            name: cpu
            target:
              type: Utilization
              averageUtilization: 70
    kubectl apply -f deployment.yaml
    kubectl apply -f hpa.yaml
    kubectl get hpa
    kubectl describe hpa demo-api-hpa

    이 상태에서 부하 테스트를 걸면 평균 CPU 사용률을 기준으로 Replica가 늘어나요. 물론 바로 늘지 않는다고 당황하실 필요는 없습니다. 메트릭 수집 주기와 안정화 구간 때문에 약간의 시간차가 생기거든요.

    Kubernetes HPA가 Metrics Server 기반으로 Pod 스케일링하는 구성도

    Metrics Server에서 수집한 지표를 바탕으로 HPA가 Deployment Replica 수를 조정하는 구성 예시입니다.

    4. 실전 구현: VPA는 추천 모드부터 시작하는 게 안전합니다

    VPA는 개인적으로 처음부터 Auto 모드로 넣기보다 Recommendation(추천) 확인부터 시작하는 걸 권장해요. 저도 초반에는 자동 조정이 멋져 보여서 바로 적용하고 싶었는데, 운영 중 Pod 교체 타이밍을 무시하면 생각보다 거칠게 느껴질 수 있더라고요.

    4-1. VPA 샘플 리소스

    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: demo-api-vpa
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: demo-api
      updatePolicy:
        updateMode: "Off"
      resourcePolicy:
        containerPolicies:
          - containerName: demo-api
            controlledResources: ["cpu", "memory"]
    kubectl apply -f vpa.yaml
    kubectl describe vpa demo-api-vpa

    updateMode: "Off"로 두면 자동 반영은 하지 않고 추천값 위주로 볼 수 있어요. 운영 초반에는 이 방식이 훨씬 덜 위험합니다.

    4-2. 추천값을 어떻게 읽나

    보통 운영자가 보는 포인트는 이렇습니다.

    • 현재 requests가 실제 사용량보다 너무 작은가
    • 메모리 사용 패턴이 일정한가, 피크가 큰가
    • 권장값을 적용했을 때 노드 밀도(Node Density, 노드당 수용량)가 나빠지지 않는가

    제가 직접 해보니 CPU는 HPA가 어느 정도 흡수해주는데, 메모리는 오히려 VPA 추천이 더 실무적으로 도움이 되는 경우가 많았어요. 특히 Java 계열이나 캐시가 붙은 워크로드는 순간 피크보다 장기 패턴을 보는 게 중요하더라고요.

    5. 같이 쓰면 안 되는 건가요? 조합 전략이 핵심입니다

    이 질문 정말 많이 나와요. 결론부터 말하면 무조건 같이 쓰면 안 된다는 아니에요. 다만 같은 CPU/메모리 지표를 기준으로 HPA와 VPA가 동시에 서로를 흔드는 구조는 피하는 게 좋습니다. 이건 운영해보면 왜 위험한지 금방 느껴져요.

    조합 방식 추천 여부 이유
    HPA on CPU + VPA on CPU/Memory 자동 주의 requests 변경이 HPA 계산에 영향 주어 피드백 루프 가능
    HPA on Custom Metric + VPA on requests 추천 권장 역할 분리가 비교적 명확해요
    HPA만 사용 권장 무상태 웹 서비스 기본 선택지로 단순해요
    VPA 추천만 사용 권장 초기 자원 튜닝 단계에 안정적이에요

    즉, Pod 스케일링은 HPA가 맡고, VPA는 추천과 보정 역할로 두는 식이 현실적입니다. 특히 트래픽 기반 서비스라면 HPA를 메인으로 두고, VPA는 운영 관찰 도구처럼 활용하는 접근이 꽤 괜찮았어요.

    Kubernetes VPA HPA 함께 사용할 때의 권장 조합과 충돌 위험 다이어그램

    HPA와 VPA를 함께 사용할 때 역할을 분리하는 권장 패턴과 충돌 위험 구간을 시각화한 이미지입니다.

    6. ⚠️ 실제 운영에서 자주 만난 문제와 해결법

    6-1. HPA가 안 늘어나는 문제

    • 원인: Metrics Server 미설치 또는 메트릭 수집 실패
    • 원인: requests 값이 비현실적이라 CPU 사용률 계산이 왜곡돼요
    • 해결: kubectl top pod, kubectl describe hpa로 먼저 메트릭 상태 확인
    kubectl top pod
    kubectl describe hpa demo-api-hpa
    kubectl get apiservices

    저도 처음엔 애플리케이션 성능 문제인 줄 알고 로그만 뒤졌는데, 알고 보니 메트릭 자체가 비어 있던 적이 있었어요. 이런 건 진짜 허무합니다.

    6-2. VPA 적용 후 Pod 재시작 때문에 놀라는 문제

    • 원인: 자원값 변경을 적용하려면 기존 Pod 교체가 필요할 수 있거든요
    • 해결: 먼저 추천 모드로 충분히 관찰하고, PDB(PodDisruptionBudget, Pod 중단 예산)와 롤링 업데이트 전략 점검
    • 해결: 단일 Replica 서비스는 특히 조심해야 해요

    여기서 중요한 포인트! 단일 Pod 서비스에 VPA 자동 적용은 생각보다 부담이 커요. 실제로 써보니까 “자원은 맞아졌는데 순간 끊김이 생겼네?” 같은 상황이 나올 수 있더라고요.

    6-3. HPA와 VPA를 동시에 걸었더니 지표가 이상한 문제

    • 원인: VPA가 requests를 바꾸면 HPA의 utilization 계산 기준도 달라질 수 있거든요
    • 해결: HPA는 CPU 대신 QPS(Requests Per Second, 초당 요청 수)나 큐 길이 같은 Custom/External Metric 기반으로 분리 검토해요

    이 부분은 문서만 읽으면 감이 잘 안 오는데, 운영 그래프를 보면 이해가 돼요. 기준점이 움직이는 상태에서 자동화 둘이 같이 판단하니 안정적이지 않더라고요.

    7. 검증과 결과 확인: 무엇을 봐야 제대로 적용한 걸까

    오토스케일링 비교는 설정 파일만 보고 끝내면 안 돼요. 검증 항목을 꼭 정해두셔야 합니다.

    1. 부하 테스트 전후 평균 응답 시간 변화
    2. Replica 증가 시 에러율 변화
    3. OOMKilled 발생 여부
    4. 노드 자원 사용률과 Bin Packing(빈 패킹, 노드 자원 배치 효율)
    5. 스케일 아웃/인 빈도와 안정성
    kubectl get hpa -w
    kubectl get vpa
    kubectl top pod
    kubectl top node

    제가 홈랩에서 테스트했을 때도, HPA만 걸어둔 서비스는 트래픽 대응은 빨랐지만 requests가 너무 작게 잡힌 컨테이너는 CPU 제한에 자주 걸렸어요. 반대로 VPA 추천을 반영하고 나니 자원 낭비는 줄고 불안정성도 꽤 줄었습니다. 드디어 됐다! 싶은 순간이 오긴 하더라고요. 물론 그 전에 requests 값과 메트릭 파이프라인 때문에 몇 번 헤맸습니다.

    Kubernetes VPA HPA 적용 결과를 검증하는 모니터링 대시보드

    HPA와 VPA 적용 이후 Replica 변화, CPU/메모리 추이, 권장 자원값을 검증하는 모니터링 예시입니다.

    8. 어떤 워크로드에 무엇이 맞나: 실무 선택 기준

    • 웹/API 서버: HPA 우선. 빠른 Pod 스케일링이 중요해요.
    • 배치 작업: VPA 추천이 유용할 수 있어요. 작업 특성상 개별 Pod 자원량이 중요하거든요.
    • 메모리 민감 애플리케이션: VPA 추천으로 기준값 정리 후 신중히 반영
    • 큐 소비자/워커: HPA를 큐 길이나 지연 시간 기반으로 설계하면 좋습니다.
    • 단일 인스턴스성 서비스: VPA 자동 적용은 매우 조심해야 해요. 먼저 수동 조정 검토

    결국 Kubernetes VPA HPA는 경쟁 관계라기보다 역할이 다른 도구예요. 리소스 최적화가 우선인지, 트래픽 분산이 우선인지부터 정해야 선택이 쉬워집니다.

    9. 정리와 FAQ

    정리하면, HPA는 서비스 확장성에, VPA는 자원 적정화에 강합니다. 저는 운영 초기에 HPA로 안정적인 Pod 스케일링 구조를 먼저 만들고, 그다음 VPA 추천으로 requests/limits를 보정하는 순서를 권장해요. 이 흐름이 가장 덜 아프고, 실수했을 때 되돌리기도 쉽습니다.

    다음 글에서는 Cluster Autoscaler(클러스터 오토스케일러, 노드 수 자동 조절)까지 포함해서 노드 레벨 확장 전략도 다뤄볼 예정입니다. 이전 글에서 다룬 Ingress와 Observability(옵저버빌리티, 관측성) 구성이 되어 있으면 검증이 훨씬 수월해요.

    워크로드 유형에 따라 VPA와 HPA를 어떻게 선택할지 빠르게 판단할 수 있는 요약 인포그래픽입니다.

    자주 묻는 질문

    • Q. 둘 중 하나만 써야 하나요?
      A. 아니에요. 다만 같은 CPU/메모리 기준으로 동시에 자동 제어하는 구성은 주의가 필요합니다.
    • Q. 초보자는 무엇부터 시작하면 좋을까요?
      A. HPA부터 시작하고, VPA는 추천 모드로 관찰하는 접근이 가장 무난했어요.
    • Q. 리소스 최적화 목적이면 바로 VPA Auto로 가도 되나요?
      A. 운영 중 서비스라면 추천값 검증 후 단계적으로 가는 쪽이 안전합니다.
  • [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 중심 관점에서 설명했습니다.

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

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

  • [Cloud] Jenkins on Kubernetes 마이그레이션: 클라우드 네이티브 CI/CD 전환 사례

    [Cloud] Jenkins on Kubernetes 마이그레이션: 클라우드 네이티브 CI/CD 전환 사례

    안녕하세요, 13년차의 서버실 운영자입니다. 오늘은 많은 분들이 고민하고 계실 법한 주제, 바로 Jenkins on Kubernetes 마이그레이션 경험담을 풀어보려고 합니다. 사실 저도 기존 Jenkins 환경을 운영하면서 여러 가지 페인 포인트(Pain Point)를 겪었거든요. 빌드가 몰리면 서버 리소스가 부족하고, 특정 에이전트(Agent)에 문제가 생기면 전체 파이프라인이 멈추고… 이런 경험, 혹시 여러분도 있으신가요?

    클라우드 네이티브(Cloud-Native) 환경으로의 전환은 이제 선택이 아닌 필수가 되어가고 있습니다. CI/CD(Continuous Integration/Continuous Deployment, 지속적 통합/지속적 배포) 파이프라인의 핵심인 Jenkins 역시 예외는 아니죠. 기존 VM 기반의 Jenkins를 Kubernetes(쿠버네티스) 위로 옮기면서 얻게 된 이점과, 그 과정에서 겪었던 삽질 경험을 솔직하게 공유해 드릴게요. 이 글이 여러분의 Jenkins Kubernetes 마이그레이션 여정에 멘토 같은 역할을 해주었으면 좋겠습니다.

    Jenkins on Kubernetes 클라우드 네이티브 CI/CD 아키텍처 다이어그램

    Jenkins on Kubernetes 아키텍처는 효율적인 CI/CD 파이프라인을 위한 핵심입니다. 동적 에이전트가 필요할 때마다 생성되어 리소스를 최적화합니다.

    Jenkins on Kubernetes, 왜 필요할까요?

    기존 Jenkins는 보통 고정된 마스터(Master)와 여러 개의 에이전트(Agent) 서버로 구성됩니다. 작업이 많아지면 에이전트 서버를 증설해야 하고, 사용하지 않을 때도 리소스를 계속 점유하죠. 특정 에이전트에 문제가 생기면 해당 에이전트에서 동작하는 모든 작업이 실패할 수 있으니 정말 골치 아픈 거거든요.

    하지만 Jenkins on Kubernetes 환경은 다릅니다. 쉽게 말해, 필요한 순간에만 에이전트(Agent)를 띄워서 작업을 처리하고, 끝나면 바로 없애는 방식입니다. Jenkins 마스터는 Kubernetes 클러스터 내의 컨트롤러(Controller)로 동작하고, CI/CD 파이프라인 작업이 필요할 때마다 Kubernetes Pod(파드) 형태로 에이전트를 동적으로 생성합니다. 작업이 완료되면 해당 Pod는 자동으로 소멸됩니다. 이게 바로 클라우드 네이티브 CI/CD의 핵심 장점 중 하나예요.

    ✅ 주요 장점

    • 탄력적인 확장성 (Elastic Scalability): 빌드 요청이 폭증해도 Kubernetes가 알아서 에이전트 Pod를 늘려줍니다.
    • 리소스 효율성 (Resource Efficiency): 작업이 없을 때는 에이전트 Pod가 존재하지 않으므로 불필요한 리소스 낭비가 없습니다.
    • 고가용성 (High Availability): Kubernetes의 자체 복구(Self-healing) 기능 덕분에 에이전트 Pod에 문제가 생겨도 다른 Pod로 대체되어 안정성이 높아집니다.
    • 선언적 구성 (Declarative Configuration): Jenkinsfile(젠킨스파일)과 JCasC(Jenkins Configuration as Code)를 통해 CI/CD 파이프라인과 Jenkins 자체 구성을 코드로 관리할 수 있습니다.

    Jenkins Kubernetes 마이그레이션, 실전 구현 단계

    자, 그럼 이제 제가 실제로 어떻게 Jenkins 현대화를 진행했는지 단계별로 알려드릴게요. 처음엔 어디서부터 손대야 할지 막막했는데, 하나씩 해나가다 보니 길이 보이더라고요.

    1. Kubernetes 클러스터 준비

    가장 먼저 할 일은 Jenkins를 올릴 Kubernetes 클러스터를 준비하는 겁니다. 클라우드 환경에서는 AWS EKS(Elastic Kubernetes Service), Azure AKS(Azure Kubernetes Service), Google GKE(Google Kubernetes Engine) 같은 관리형 서비스를 이용하는 게 편합니다. 저는 홈랩에서 K3s 클러스터를 운영하고 있어서 그걸 활용했어요. 온프레미스(On-premise) 환경이라면 kubeadm이나 OpenShift 등을 고려할 수 있겠죠.

    2. Helm을 이용한 Jenkins 설치

    Kubernetes에 애플리케이션을 배포하는 가장 일반적인 방법 중 하나가 Helm(헬름) 차트입니다. Jenkins도 공식 Helm 차트를 제공하고 있어서 쉽게 설치할 수 있어요. 물론, 설치 전에 PersistentVolume(영구 볼륨)과 PersistentVolumeClaim(영구 볼륨 클레임)을 설정해서 Jenkins 설정과 데이터를 보존할 수 있도록 해야 합니다. 이 부분이 제일 중요해요. 데이터 날리면 대형사고잖아요?

    # Helm 리포지토리 추가
    helm repo add jenkins https://charts.jenkins.io
    helm repo update
    
    # values.yaml 파일 커스터마이징 (예시)
    # persistentVolume 셋팅, resource limit, ingress 설정 등
    # 자세한 내용은 공식 Helm 차트 문서를 참고하세요!
    
    # Jenkins 설치
    helm install jenkins -f my-jenkins-values.yaml jenkins/jenkins -n jenkins --create-namespace
    

    위 명령어는 기본적인 설치 예시입니다. 실제 운영 환경에서는 `my-jenkins-values.yaml` 파일에 Ingress(인그레스, 외부 트래픽 진입점), 리소스 제한(Resource Limits), 플러그인 설정 등을 상세하게 정의해야 해요. 이 파일 하나로 Jenkins의 거의 모든 설정을 제어할 수 있거든요.

    Jenkins Helm values.yaml 설정 파일 예시

    Helm `values.yaml` 파일은 Jenkins 배포의 핵심입니다. 여기서 영구 스토리지, 리소스, 에이전트 설정을 세밀하게 제어할 수 있습니다.

    3. Kubernetes Cloud Plugin 설정

    Jenkins 마스터가 Kubernetes 클러스터와 통신하며 동적 에이전트를 생성하려면 Kubernetes Cloud Plugin(쿠버네티스 클라우드 플러그인)을 설치하고 설정해야 합니다. Jenkins UI에서 ‘Jenkins 관리’ → ‘시스템 설정’ → ‘클라우드’ 섹션으로 이동해서 Kubernetes 클러스터 정보를 입력합니다. 이때 Service Account(서비스 어카운트)와 RBAC(Role-Based Access Control) 설정을 통해 Jenkins가 Kubernetes API에 접근할 수 있도록 권한을 부여하는 것이 매우 중요합니다.

    여기서 Pod Template(파드 템플릿)을 정의하게 되는데, 이 템플릿이 바로 동적 에이전트 Pod의 설계도입니다. 어떤 도커(Docker) 이미지를 사용할지, 얼마나 많은 CPU와 메모리를 할당할지, 필요한 도구들은 무엇인지 등을 정의하죠. 예를 들어, Node.js 빌드가 필요하면 Node.js 런타임이 포함된 이미지를 지정하는 식이에요.

    4. Jenkinsfile 업데이트

    기존 Jenkinsfile도 약간의 수정이 필요할 수 있어요. 특히 `agent any`나 `agent { label ‘my-fixed-agent’ }` 같은 부분을 `agent { kubernetes { yaml ”’…”’ } }` 형태로 바꿔서 동적 Pod를 사용하도록 유도해야 합니다.

    // 기존 Jenkinsfile (예시)
    // pipeline {
    //     agent any
    //     stages {
    //         stage('Build') {
    //             steps {
    //                 sh 'npm install'
    //                 sh 'npm build'
    //             }
    //         }
    //     }
    // }
    
    // Kubernetes 동적 에이전트를 사용하는 Jenkinsfile (예시)
    pipeline {
        agent {
            kubernetes {
                // Kubernetes Pod 템플릿을 직접 정의
                yaml """
    apiVersion: v1
    kind: Pod
    spec:
      containers:
      - name: jnlp
        image: jenkins/inbound-agent:4.11.2-1
        resources:
          limits:
            memory: "512Mi"
            cpu: "500m"
      - name: nodejs
        image: node:16-alpine
        command: ["cat"]
        tty: true
        resources:
          limits:
            memory: "1Gi"
            cpu: "1000m"
    """
                defaultContainer 'nodejs' // 이 컨테이너에서 스크립트 실행
            }
        }
        stages {
            stage('Build Frontend') {
                steps {
                    container('nodejs') { // 'nodejs' 컨테이너에서 실행
                        sh 'npm install'
                        sh 'npm run build'
                    }
                }
            }
            stage('Package Docker Image') {
                // 다른 컨테이너 또는 스크립트 실행
            }
        }
    }
    

    위 Jenkinsfile 예시처럼, 하나의 Pod 안에 여러 컨테이너를 띄워서 각 컨테이너의 역할을 분리할 수 있어요. 예를 들어, `nodejs` 컨테이너에서 프론트엔드 빌드를 하고, `maven` 컨테이너에서 백엔드를 빌드하는 식이죠. 이 덕분에 빌드 환경을 더욱 유연하게 구성할 수 있거든요.

    5. Configuration as Code (JCasC) 적용

    Jenkins 설정을 UI에서 하나하나 하는 건 사실 귀찮고 실수를 유발하기 쉽습니다. JCasC(Jenkins Configuration as Code)는 Jenkins의 모든 설정을 YAML(야믈) 파일로 관리할 수 있게 해줘요. 이 파일을 버전 관리 시스템(VCS)에 커밋(Commit)해두면, Jenkins 인스턴스를 새로 띄우거나 설정을 변경할 때 코드로 관리할 수 있어서 매우 편리합니다. 저도 이걸 적용하고 나서 “이거 진짜 편하더라고요!”라고 감탄했었어요. Jenkins 현대화의 필수 요소라고 생각합니다.

    ⚠️ 주의사항 및 트러블슈팅

    마이그레이션 과정에서 저도 꽤 삽질 좀 했습니다. 여러분은 저 같은 시행착오를 겪지 않으시길 바라며 몇 가지 팁을 공유합니다.

    • Persistent Volume(PV) 설정: Jenkins 마스터의 홈 디렉토리(`JENKINS_HOME`)는 반드시 영구 볼륨으로 설정해야 합니다. 안 그러면 Jenkins Pod가 재시작될 때마다 모든 설정과 플러그인이 날아가는 대참사가 발생합니다. 😱
    • 리소스 제한 (Resource Limits): 에이전트 Pod 템플릿에 CPU와 메모리 리소스 제한을 명확하게 설정해야 합니다. 너무 적으면 빌드가 실패하고, 너무 많으면 클러스터 리소스가 고갈될 수 있어요.
    • 이미지 크기 및 빌드 시간: 동적 에이전트에 사용할 도커 이미지는 필요한 도구만 포함하여 최대한 가볍게 만드는 것이 좋습니다. 이미지가 너무 크면 Pod가 스케줄링(Scheduling)되고 컨테이너(Container)가 시작되는 시간이 길어져 빌드 성능에 악영향을 줄 수 있거든요.
    • 네트워크 문제: Jenkins 마스터에서 GitHub, Nexus, SonarQube 등 외부 서비스에 접근할 수 있는지, 또는 그 반대로 외부에서 Ingress를 통해 Jenkins UI에 접근할 수 있는지 네트워크 설정을 꼼꼼히 확인해야 합니다.
    • RBAC 권한: Jenkins 컨트롤러가 Kubernetes API에 접근할 수 있도록 적절한 Role(역할)과 RoleBinding(역할 바인딩)을 가진 Service Account를 생성하고 연결해야 합니다. 권한 부족으로 Pod 생성이 안 되는 경우가 많아요.

    ✅ 검증 및 마이그레이션 결과

    모든 설정을 마치고 파이프라인을 실행하면, Jenkins 대시보드에서 동적으로 생성되는 에이전트 Pod들을 확인할 수 있습니다. Kubernetes 클러스터에서도 `kubectl get pods -n jenkins` 명령어로 새로운 Pod들이 생성되었다가 작업 완료 후 사라지는 것을 볼 수 있을 거예요. 드디어 됐다! 하고 외쳤던 순간이 기억나네요. 🎉

    Jenkins 대시보드에서 Kubernetes 동적 에이전트 빌드 성공 확인

    Jenkins 대시보드에서 동적 에이전트의 효율적인 동작을 확인하는 것은 마이그레이션 성공의 중요한 지표입니다.

    Jenkins Kubernetes 마이그레이션을 통해 저희 팀은 다음과 같은 이점들을 누릴 수 있었습니다.

    • 빌드 시간 단축: 필요한 리소스를 즉시 할당받아 빌드 시간이 단축되었습니다.
    • 운영 비용 절감: 유휴 리소스가 없어 불필요한 클라우드 비용을 줄일 수 있었어요.
    • 안정성 향상: 특정 에이전트 문제로 인한 빌드 실패가 줄어들고, Kubernetes의 복원력 덕분에 전체 시스템의 안정성이 높아졌습니다.
    • CI/CD 파이프라인 관리의 용이성: JCasC와 Jenkinsfile을 통해 파이프라인과 Jenkins 자체를 코드로 관리하게 되면서, 변경 사항 추적 및 재현성이 크게 향상되었습니다.
    Jenkins on Kubernetes 마이그레이션 핵심 이점 인포그래픽

    Jenkins on Kubernetes 마이그레이션은 탄력적 확장성, 비용 효율성, 그리고 안정성을 동시에 달성하는 효과적인 방법입니다.

    마무리하며: 클라우드 네이티브 CI/CD의 미래

    오늘은 제가 직접 경험했던 Jenkins on Kubernetes 마이그레이션 사례를 공유해 드렸습니다. 처음엔 VM 기반 Jenkins 환경에 익숙해서 변화가 어렵게 느껴졌지만, 클라우드 네이티브 환경으로의 전환은 분명 더 나은 CI/CD 파이프라인을 위한 투자라고 생각해요.

    물론 Jenkins 외에도 Argo CD(아르고 CD)나 Tekton(텍톤) 같은 훌륭한 클라우드 네이티브 CI/CD 도구들이 많이 있습니다. 하지만 기존 Jenkins 환경에 익숙하고, 점진적인 Jenkins 현대화를 원한다면 Kubernetes 위로 Jenkins를 옮기는 것이 좋은 시작점이 될 수 있다고 확신합니다.

    이 글이 여러분의 인프라 여정에 작은 도움이 되었기를 바랍니다. 다음번에는 또 다른 삽질 경험과 해결책으로 찾아오겠습니다. 궁금한 점이나 공유하고 싶은 경험이 있다면 언제든지 댓글 남겨주세요! 😉

  • [K8s] Pod Security Admission 1년 사용 후기 및 실수담

    [K8s] Pod Security Admission 1년 사용 후기 및 실수담

    K8s Pod Security Admission, 1년 사용 후기 및 실수담

    안녕하세요, 13년차 서버실 주인장입니다. 오늘은 Kubernetes(이하 K8s) 환경에서 보안을 한층 강화해주는 Pod Security Admission(PSA)에 대해 이야기해보려 합니다. 제가 운영하는 홈랩 환경에서 PSA를 도입한 지 어느덧 1년이 되었는데요, 처음에는 조금 헷갈렸지만 지금은 없어서는 안 될 기능이 되었답니다. 오늘은 1년 동안 PSA를 사용하면서 겪었던 시행착오와 함께, 어떻게 하면 좀 더 효율적으로 PSA를 활용할 수 있을지에 대한 경험을 솔직하게 공유해 드릴게요. 혹시 K8s 보안에 대해 고민하고 계셨다면, 오늘 제 이야기가 작게나마 도움이 되기를 바랍니다. 😊

    Kubernetes 클러스터의 전반적인 아키텍처와 Pod Security Admission이 어떤 위치에 있는지 보여주는 다이어그램

    K8s Pod Security Admission, 왜 필요할까요?

    K8s는 유연하고 강력한 컨테이너 오케스트레이션 도구이지만, 그만큼 보안 설정에 신경 써야 할 부분이 많습니다. 특히 Pod(파드)는 K8s의 가장 기본적인 배포 단위인데요, 이 Pod가 실행될 때 어떤 보안 정책을 따라야 하는지 명확하게 제어하지 않으면 예상치 못한 보안 취약점이 발생할 수 있습니다. 예를 들어, 특권(privileged) 컨테이너가 실수로 실행되거나, 호스트 파일 시스템에 접근하는 등의 위험한 상황이 생길 수 있죠.

    이런 문제를 해결하기 위해 K8s에서는 Pod Security Admission(PSA)이라는 기능을 제공합니다. 쉽게 말해, Pod를 생성하거나 업데이트할 때 미리 정의된 보안 기준에 부합하는지 자동으로 검사하고, 기준에 맞지 않으면 해당 Pod의 생성을 막아주는(Admission Controller) 기능이라고 생각하시면 됩니다. 💡

    Pod Security Admission, 어떻게 작동하나요?

    PSA는 K8s API 서버의 Admission Controller 중 하나로 작동합니다. Pod 생성 요청이 API 서버에 전달되면, PSA는 해당 Pod가 특정 Pod Security Standards(PSS)를 준수하는지 확인합니다. PSS에는 크게 세 가지 레벨이 있습니다:

    • Restricted (제한): 가장 엄격한 보안. Pod는 격리되고 제한된 권한만 가지며, 최신 보안 기능(예: Seccomp, AppArmor, SELinux)을 사용해야 합니다.
    • Baseline (기준): 최소한의 보안을 보장. 알려진 취약점을 악용하기 어려운 수준으로, 대부분의 워크로드에 권장됩니다.
    • Privileged (특권): 가장 낮은 수준의 보안. 컨테이너가 호스트 시스템에 대한 거의 모든 권한을 갖게 되므로, 매우 신중하게 사용해야 합니다.

    이 세 가지 레벨 외에도, 각 네임스페이스(Namespace)별로 Enforce, Audit, Warn 모드를 설정할 수 있습니다.

    • 1. Enforce (강제): 보안 정책을 위반하는 Pod의 생성을 차단합니다. 가장 강력한 모드죠.
    • 2. Audit (감사): 정책을 위반하더라도 Pod 생성을 차단하지는 않지만, 해당 이벤트를 로그로 기록합니다. 보안 감사에 유용합니다.
    • 3. Warn (경고): 정책을 위반하는 Pod에 대해 사용자에게 경고 메시지를 보냅니다. Pod 생성은 허용됩니다.

    실전! Pod Security Admission 설정하기 (제 경험담)

    제가 처음 PSA를 도입할 때 가장 헷갈렸던 부분이 바로 이 네임스페이스별 정책 설정이었습니다. 저희 홈랩에는 개발용, 테스트용, 운영용 등 다양한 환경의 네임스페이스가 있었기 때문에, 각기 다른 보안 정책을 적용하고 싶었거든요.

    K8s 클러스터 전체에 PSA를 활성화하는 것은 API 서버 설정에서 간단히 할 수 있습니다. 하지만 실제로는 각 네임스페이스별로 정책을 세밀하게 조정하는 것이 중요합니다. 저는 다음과 같은 방식으로 설정을 진행했습니다.

    1단계: 네임스페이스별 레이블 설정

    먼저, 각 네임스페이스에 어떤 보안 정책을 적용할지 명시하는 레이블을 붙여줍니다. 예를 들어, 보안이 중요한 운영 네임스페이스에는 pod-security.kubernetes.io/enforce=baseline과 같이 설정할 수 있습니다.

    kubectl label --overwrite ns production pod-security.kubernetes.io/enforce=baseline
    kubectl label --overwrite ns production pod-security.kubernetes.io/audit=baseline
    kubectl label --overwrite ns production pod-security.kubernetes.io/warn=baseline
    
    kubectl label --overwrite ns development pod-security.kubernetes.io/enforce=privileged
    kubectl label --overwrite ns development pod-security.kubernetes.io/audit=baseline
    kubectl label --overwrite ns development pod-security.kubernetes.io/warn=baseline
    
    네임스페이스별 Pod Security Admission 정책 설정 예시

    각 네임스페이스에 할당된 Pod Security Standards 레벨과 모드를 보여주는 예시 화면

    2단계: PSA 동작 확인 (Audit 모드 활용)

    처음부터 enforce 모드로 설정하면, 예상치 못한 Pod 배포 실패로 인해 서비스 장애가 발생할 수 있습니다. 그래서 저는 먼저 audit 모드를 활용하여 어떤 Pod들이 보안 정책을 위반하는지 파악하는 단계를 거쳤습니다. K8s API 서버 감시 로그를 주기적으로 확인하면서, 어떤 Pod가 어떤 이유로 위반되는지 분석했죠.

    💡 팁: kube-apiserver의 감시 로그(audit log)를 통해 PSA 관련 감사 정보를 확인할 수 있습니다. 로컬 클러스터에서는 kubectl describe nodes 또는 클러스터 설정에 따라 로그 파일에서 감시 로그를 조회할 수 있습니다.

    3단계: Enforce 모드로 전환 및 검증

    Audit 모드를 통해 문제를 파악하고 수정했다면, 이제 enforce 모드로 전환할 차례입니다. enforce 모드로 전환한 후, 중요한 워크로드부터 순차적으로 배포해보면서 문제가 없는지 꼼꼼하게 검증했습니다.

    kubectl label --overwrite ns production pod-security.kubernetes.io/enforce=baseline
    

    Pod Security Admission 정책을 위반했을 때 API 서버 로그에 기록되는 내용

    1년간 사용해보니… 삽질과 깨달음

    PSA를 1년 가까이 사용하면서 정말 많은 것을 배웠습니다. 처음에는 단순히 PSS 레벨을 설정하면 되는 줄 알았는데, 실제로는 Pod의 설정 하나하나가 PSS와 충돌할 수 있다는 것을 깨달았죠.

    가장 흔하게 겪었던 실수담은 바로 privileged: true 설정이었습니다. 저희 팀에서 사용하는 특정 사이드카(Sidecar) 컨테이너가 네트워킹 관련 기능을 위해 이 설정을 필요로 했는데, restricted 또는 baseline PSS에서는 당연히 막히더라고요. 처음에는 왜 Pod 생성이 안 되는지 한참을 헤맸습니다. 😂

    이런 문제를 해결하기 위해 다음과 같은 방법을 사용했습니다:

    • Pod Security Admission 이해하기: Pod Security Policies(PSP)는 K8s 1.25에서 deprecated 되었고 1.28에서 완전히 제거되었습니다. 대신 PSA로 전환하는 과정에서, PSA는 훨씬 간결하고 명확하게 정책을 적용할 수 있다는 걸 체감했습니다.
    • Pod Spec 검토 및 수정: 사이드카 컨테이너의 Pod Spec을 면밀히 검토하여, privileged: true 설정 없이도 동일한 기능을 구현할 수 있는지 확인했습니다. 결국, 필요한 권한만 최소한으로 부여하도록 수정하여 baseline PSS를 만족시킬 수 있었습니다.
    • Audit 모드를 적극 활용: 새로운 애플리케이션을 배포하거나 기존 설정을 변경할 때, 바로 enforce 모드로 전환하기보다는 audit 모드로 먼저 테스트하면서 잠재적인 충돌을 미리 파악하는 습관을 들였습니다.

    PSA는 K8s 클러스터의 보안 수준을 크게 향상시켜주는 강력한 기능입니다. 하지만 모든 Pod가 완벽하게 PSS를 준수하는 것은 아니므로, Service Account, RBAC(Role-Based Access Control)와 함께 종합적으로 고려하여 보안 전략을 수립하는 것이 중요합니다.

    Pod Security Admission과 Pod Security Policy의 주요 차이점 비교

    Pod Security Admission과 이전의 Pod Security Policy의 주요 차이점을 비교한 표

    결론: K8s 보안, PSA로 한 단계 업그레이드하세요!

    Pod Security Admission은 K8s 환경의 보안을 강화하는 데 필수적인 요소입니다. 1년간의 경험을 통해 PSA가 단순히 복잡한 설정이 아니라, 실질적인 보안 위협을 줄여주는 강력한 방어선이라는 것을 체감했습니다.

    물론 초기 설정 과정에서 시행착오를 겪을 수 있습니다. 하지만 audit 모드를 활용하고, Pod Spec을 꼼꼼히 검토하는 습관을 들인다면, 충분히 안정적으로 PSA를 운영할 수 있더라고요.

    혹시 아직 PSA를 도입하지 않으셨다면, 지금 바로 시작해보시는 것을 강력히 추천합니다! 먼저 개발 네임스페이스부터 baseline PSS로 설정하고 audit 모드로 운영해보면서 점차 확대해나가는 것을 권장합니다.

    다음 글에서는 K8s 네트워크 보안의 또 다른 핵심 요소인 Network Policy에 대해 다뤄볼 예정이니, 많은 기대 부탁드립니다! 😉

  • [k8s] 쿠버네티스 Ingress Controller 비교: Nginx, Traefik, HAProxy 선택 가이드

    [k8s] 쿠버네티스 Ingress Controller 비교: Nginx, Traefik, HAProxy 선택 가이드

    쿠버네티스 Ingress Controller, 뭘 써야 할까요?

    쿠버네티스를 처음 공부할 때 저도 Ingress Controller 선택 앞에서 한참 멈췄던 기억이 나요. Nginx는 익숙하고, Traefik은 이름이 예쁘고, HAProxy는 오래됐으니 안정적일 것 같고… 근데 막상 뭘 골라야 할지 기준이 없으니까 그냥 남들 쓰는 거 따라갔었거든요.

    13년 동안 인프라 엔지니어로 일하면서 홈랩부터 실제 프로덕션 환경까지 세 가지 컨트롤러를 다 써봤습니다. 오늘은 그 경험을 바탕으로 Nginx Ingress, Traefik Ingress, HAProxy Ingress를 솔직하게 비교해 드릴게요. 어떤 상황에서 어떤 걸 골라야 하는지, 실제 겪었던 삽질 포인트까지 전부 공유해 드리겠습니다.

    팀에서 쿠버네티스 Ingress Controller를 선택해야 하는데 레퍼런스가 너무 많고 각자 장점만 얘기해서 오히려 더 헷갈리는 상황, 있으시죠? 이 글이 그 혼란을 정리해 드릴 거예요.

    쿠버네티스 Ingress Controller 전체 아키텍처 - Nginx, Traefik, HAProxy 트래픽 흐름 비교

    쿠버네티스 클러스터에서 외부 트래픽이 Ingress Controller를 통해 각 서비스로 라우팅되는 전체 흐름. Nginx, Traefik, HAProxy 세 가지 컨트롤러의 위치를 비교하여 보여줍니다.

    Ingress Controller가 뭔지 먼저 짚고 가요

    쉽게 말해서, Ingress(인그레스)는 클러스터 외부에서 내부 서비스로 들어오는 트래픽을 관리하는 쿠버네티스 오브젝트예요. 근데 Ingress 오브젝트 자체는 그냥 규칙 명세서일 뿐이고, 이걸 실제로 실행하는 게 바로 Ingress Controller(인그레스 컨트롤러)입니다.

    비유하자면 Ingress는 “3번 테이블 손님은 A 주방으로, 4번 테이블은 B 주방으로” 같은 안내판이고, Ingress Controller는 그 안내판을 읽고 실제로 손님을 안내하는 직원인 거죠. 이 직원 역할을 Nginx가 할 수도 있고, Traefik이나 HAProxy가 할 수도 있는 겁니다.

    • Ingress Resource: 라우팅 규칙을 정의하는 쿠버네티스 오브젝트
    • Ingress Controller: Ingress 규칙을 실제로 처리하는 프록시/로드밸런서 파드
    • 쿠버네티스는 기본으로 Ingress Controller를 제공하지 않아서 직접 설치해야 해요

    세 가지 Ingress Controller 핵심 특징 비교

    본격 비교 전에 각 컨트롤러의 DNA를 먼저 이해하면 선택이 훨씬 쉬워져요.

    Nginx Ingress Controller

    가장 널리 쓰이는 컨트롤러입니다. 사실 Nginx Ingress Controller에는 두 가지 버전이 있어요. 쿠버네티스 커뮤니티가 관리하는 ingress-nginx와 Nginx 사가 직접 관리하는 nginx-ingress. 보통 오픈소스 쪽에서 말하는 건 ingress-nginx 쪽이에요. 처음에 저도 이게 헷갈려서 잘못된 걸 설치했던 기억이 있습니다.

    • 레퍼런스가 가장 많고 커뮤니티가 활발해요
    • Nginx.conf 기반이라 기존 Nginx 경험자에게 친숙함
    • Annotation(어노테이션) 기반 세밀한 설정 가능
    • 설정 변경 시 nginx reload가 필요한 구조 (무중단 배포 시 주의)

    Traefik Ingress Controller

    클라우드 네이티브 시대에 맞게 설계된 현대적인 리버스 프록시예요. 처음 Traefik을 접했을 때 “이게 진짜 쿠버네티스랑 찰떡이구나” 싶었거든요. 서비스 디스커버리(Service Discovery)를 자동으로 처리해서 설정 파일을 거의 안 만져도 되는 게 매력이에요.

    • 자동 서비스 디스커버리로 설정 최소화
    • 내장 대시보드(Dashboard)로 트래픽 시각화 가능
    • Let’s Encrypt TLS 인증서 자동 발급/갱신 지원
    • 미들웨어(Middleware) 체인으로 요청 처리 파이프라인 구성
    • CRD(Custom Resource Definition) 기반 설정으로 쿠버네티스 네이티브한 느낌

    HAProxy Ingress Controller

    HAProxy는 원래 고성능 로드밸런서로 유명하죠. 금융권이나 대용량 트래픽 환경에서 오랫동안 검증된 친구입니다. 쿠버네티스 Ingress Controller로서의 HAProxy는 이 안정성과 성능을 그대로 가져왔어요.

    • 로드밸런싱 알고리즘 선택지가 풍부함
    • TCP/UDP 레이어 처리에 강점
    • 설정 변경 시 무중단 리로드(hitless reload) 지원
    • Nginx, Traefik 대비 국내 레퍼런스가 적은 편

    Ingress Controller 핵심 비교표

    항목 Nginx Ingress Traefik Ingress HAProxy Ingress
    설정 방식 Annotation + ConfigMap CRD + Annotation ConfigMap + Annotation
    자동 서비스 디스커버리 제한적 ✅ 강력 지원 제한적
    TLS 자동화 cert-manager 연동 필요 ✅ 내장 지원 cert-manager 연동 필요
    내장 모니터링 UI ❌ 없음 ✅ 대시보드 내장 HAProxy Stats 페이지
    무중단 설정 리로드 부분 지원 ✅ 지원 ✅ 지원
    학습 난이도 낮음 (친숙함) 중간 중간~높음
    커뮤니티/레퍼런스 매우 풍부 풍부 보통
    적합한 환경 범용, 소~대규모 클라우드 네이티브, 동적 환경 고성능, 금융/엔터프라이즈

    실전 설치 및 기본 설정 방법

    자, 이제 실제로 설치해 봅시다. 각 컨트롤러별로 가장 빠른 설치 방법을 보여드릴게요. 제가 홈랩에서 테스트할 때 쓰는 방식이에요.

    Helm을 이용한 Nginx, Traefik, HAProxy Ingress Controller 설치 및 배포 구성

    Helm을 이용한 Nginx, Traefik, HAProxy Ingress Controller 설치 과정과 각 컨트롤러의 파드 구성을 보여주는 다이어그램.

    1. Nginx Ingress Controller 설치

    Helm을 이용하는 게 제일 편합니다. ingress-nginx 공식 차트를 사용해요.

    # Helm 레포지토리 추가
    helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
    helm repo update
    
    # 설치
    helm install ingress-nginx ingress-nginx/ingress-nginx \
      --namespace ingress-nginx \
      --create-namespace

    설치 후 기본 Ingress 리소스 예시입니다.

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: my-app-ingress
      annotations:
        kubernetes.io/ingress.class: "nginx"
        nginx.ingress.kubernetes.io/rewrite-target: /
    spec:
      rules:
      - host: myapp.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

    2. Traefik Ingress Controller 설치

    Traefik도 Helm으로 설치하는 게 가장 깔끔해요. 대시보드 활성화 옵션도 같이 넣어줄게요.

    # Helm 레포지토리 추가
    helm repo add traefik https://helm.traefik.io/traefik
    helm repo update
    
    # values 파일 생성 (대시보드 활성화)
    cat < traefik-values.yaml
    dashboard:
      enabled: true
    api:
      dashboard: true
    EOF
    
    # 설치
    helm install traefik traefik/traefik \
      --namespace traefik \
      --create-namespace \
      -f traefik-values.yaml

    Traefik에서는 IngressRoute라는 CRD를 사용하는 방식이 더 권장돼요.

    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: my-app-route
      namespace: default
    spec:
      entryPoints:
        - web
      routes:
        - match: Host(`myapp.example.com`)
          kind: Rule
          services:
            - name: my-app-service
              port: 80

    3. HAProxy Ingress Controller 설치

    # Helm 레포지토리 추가
    helm repo add haproxytech https://haproxytech.github.io/helm-charts
    helm repo update
    
    # 설치
    helm install haproxy-ingress haproxytech/kubernetes-ingress \
      --namespace haproxy-controller \
      --create-namespace
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: my-app-ingress
      annotations:
        kubernetes.io/ingress.class: "haproxy"
    spec:
      rules:
      - host: myapp.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

    ⚠️ 실제로 겪었던 주의사항과 트러블슈팅

    이론은 여기까지 하고, 제가 실제로 삽질했던 포인트들 정리해 드릴게요. 이게 진짜 중요한 부분이에요.

    Nginx Ingress에서 자주 만나는 문제들

    ⚠️ 문제 1: Annotation 오타로 설정이 안 먹히는 경우

    Nginx Ingress는 Annotation에 오타가 있어도 에러를 안 뿜고 그냥 무시해버려요. 처음에 이걸 몰라서 한참 헤맸습니다. 설정이 안 먹힌다 싶으면 아래 명령어로 컨트롤러 로그를 꼭 확인하세요.

    kubectl logs -n ingress-nginx \
      $(kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -o jsonpath='{.items[0].metadata.name}')

    ⚠️ 문제 2: 413 Request Entity Too Large 에러

    파일 업로드 기능이 있는 서비스를 Nginx Ingress 뒤에 놓으면 자주 만나는 에러예요. Annotation으로 해결 가능합니다.

    annotations:
      nginx.ingress.kubernetes.io/proxy-body-size: "50m"

    Traefik에서 자주 만나는 문제들

    ⚠️ 문제 1: IngressRoute와 기본 Ingress 혼용 시 충돌

    Traefik은 기본 쿠버네티스 Ingress 오브젝트도 처리하고 자체 IngressRoute CRD도 처리해요. 근데 팀 내에서 두 가지 방식을 섞어 쓰면 나중에 관리가 진짜 복잡해집니다. 처음부터 방식을 통일하는 걸 강력 추천해요.

    ⚠️ 문제 2: 대시보드 외부 노출 시 인증 필수

    Traefik 대시보드를 아무 인증 없이 외부에 노출하면 큰일 납니다. BasicAuth 미들웨어라도 꼭 붙여주세요.

    apiVersion: traefik.containo.us/v1alpha1
    kind: Middleware
    metadata:
      name: dashboard-auth
      namespace: traefik
    spec:
      basicAuth:
        secret: traefik-dashboard-auth-secret

    HAProxy Ingress에서 자주 만나는 문제들

    ⚠️ 문제 1: ConfigMap 설정 반영 지연

    HAProxy Ingress는 ConfigMap을 통한 글로벌 설정 변경이 즉시 반영되지 않는 경우가 있어요. 설정 변경 후 컨트롤러 파드를 재시작해줘야 하는 상황이 생길 수 있습니다.

    kubectl rollout restart deployment haproxy-ingress -n haproxy-controller

    어떤 상황에서 뭘 골라야 할까요?

    제가 내린 결론을 솔직하게 말씀드릴게요. 완벽한 정답은 없지만, 상황별 추천은 꽤 명확해요.

    Nginx Traefik HAProxy Ingress Controller 성능 및 기능 비교 대시보드

    Nginx, Traefik, HAProxy Ingress Controller의 주요 지표(성능, 설정 편의성, 생태계, 모니터링)를 레이더 차트로 비교한 시각화.

    ✅ Nginx Ingress를 선택하세요, 만약…

    • 팀원들이 Nginx에 익숙한 경우
    • 레퍼런스와 커뮤니티 지원이 중요한 경우
    • 처음 쿠버네티스를 도입하는 팀
    • 범용적인 웹 트래픽 처리가 주 목적인 경우

    ✅ Traefik Ingress를 선택하세요, 만약…

    • 마이크로서비스가 자주 추가/삭제되는 동적인 환경
    • TLS 인증서 관리를 자동화하고 싶은 경우
    • 별도 모니터링 설정 없이 트래픽을 시각화하고 싶은 경우
    • 클라우드 네이티브 환경에서 GitOps 방식으로 운영하는 경우

    ✅ HAProxy Ingress를 선택하세요, 만약…

    • 기존에 HAProxy를 잘 알고 있는 팀
    • L4(TCP/UDP) 레벨의 세밀한 로드밸런싱이 필요한 경우
    • 금융, 통신 등 고성능/고가용성이 핵심인 환경
    • 엄격한 엔터프라이즈 지원이 필요한 경우

    자주 묻는 질문 (FAQ)

    Q. 클러스터에 Ingress Controller를 여러 개 동시에 설치할 수 있나요?

    네, 가능합니다. IngressClass 리소스를 이용해서 각 Ingress 오브젝트가 어떤 컨트롤러를 사용할지 지정할 수 있어요. 다만 운영 복잡도가 올라가니까 특별한 이유가 없다면 하나만 쓰는 걸 추천합니다.

    Q. 쿠버네티스 Ingress Controller와 서비스 메시는 다른 건가요?

    다릅니다. Ingress Controller는 클러스터 외부에서 내부로 들어오는 북-남(North-South) 트래픽을 처리하고, Istio 같은 서비스 메시는 클러스터 내부 서비스 간의 동-서(East-West) 트래픽을 관리해요. 보통 두 가지를 같이 쓰기도 합니다.

    Q. Traefik v2와 v3는 많이 다른가요?

    Traefik v3에서 일부 API와 설정 방식이 변경됐어요. 특히 일부 CRD API 버전이 바뀌었으니 마이그레이션 시 공식 문서를 꼭 확인하세요. 신규 설치라면 최신 버전을 쓰는 게 좋습니다.

    마무리 — 결국 팀의 상황이 답이에요

    쿠버네티스 Ingress Controller 선택 가이드 - Nginx Traefik HAProxy 상황별 추천 요약

    Nginx, Traefik, HAProxy Ingress Controller의 특징과 적합한 사용 환경을 한눈에 정리한 요약 인포그래픽.

    오늘 세 가지 쿠버네티스 Ingress Controller를 비교해 봤는데요. 제 개인적인 결론을 한 줄로 정리하면 이래요.

    “처음이라면 Nginx, 클라우드 네이티브 환경이라면 Traefik, 고성능/엔터프라이즈라면 HAProxy.”

    저는 홈랩에서는 Traefik을 주로 써요. 대시보드가 있어서 트래픽 흐름 보기 편하고, TLS 자동화가 정말 편리하거든요. 근데 실무에서 처음 쿠버네티스를 도입하는 팀이라면 역시 Nginx가 제일 무난합니다. 레퍼런스가 많아서 문제 생겼을 때 해결하기 수월하니까요.

    💡 팁 하나 더! 어떤 Ingress Controller를 선택하든 cert-manager와 함께 쓰면 TLS 인증서 관리가 훨씬 수월해집니다. cert-manager 설정 방법은 다음 글에서 자세히 다룰 예정이에요.

    궁금한 점이나 다른 삽질 경험이 있으시면 댓글로 공유해 주세요. 같이 고민해 봅시다! 🎉

  • [K8s] 쿠버네티스 Ingress Controller 비교: Nginx, Traefik, Gateway API 선택 가이드

    [K8s] 쿠버네티스 Ingress Controller 비교: Nginx, Traefik, Gateway API 선택 가이드

    쿠버네티스 Ingress Controller, 뭘 써야 할지 모르겠다면?

    쿠버네티스(Kubernetes)를 처음 도입할 때 가장 많이 막히는 부분 중 하나가 바로 Ingress Controller(인그레스 컨트롤러) 선택이에요. 저도 처음엔 “Nginx 쓰면 되는 거 아닌가?” 하고 무심코 설치했다가, 나중에 Traefik이 더 편하다는 걸 알고 갈아엎은 경험이 있거든요. 그리고 최근엔 Gateway API라는 새로운 표준까지 등장해서 선택지가 더 복잡해졌습니다.

    이 글에서는 쿠버네티스 클러스터 운영에서 가장 중요한 선택 중 하나인 Ingress Controller의 세 가지 주요 선택지를 실제 운영 경험을 바탕으로 비교해 드릴게요. 홈랩부터 프로덕션까지 다양한 환경에서 직접 써본 입장에서 솔직하게 이야기해 보겠습니다.

    쿠버네티스 Ingress Controller 전체 아키텍처 — 외부 트래픽이 인그레스 컨트롤러를 통해 내부 서비스로 라우팅되는 구조

    ▲ 쿠버네티스 클러스터에서 외부 트래픽이 Ingress Controller를 거쳐 내부 서비스로 전달되는 전체 흐름 다이어그램

    Ingress Controller가 뭔지부터 짚고 가자

    쉽게 말해서, Ingress(인그레스)는 외부에서 쿠버네티스 클러스터 안으로 들어오는 HTTP/HTTPS 트래픽을 어떻게 라우팅할지 정의하는 규칙이에요. 근데 이 규칙만 있다고 실제로 동작하는 게 아니에요. 규칙을 실제로 실행해 주는 주체가 바로 Ingress Controller(인그레스 컨트롤러)입니다.

    비유하자면, Ingress 리소스는 “1번 도메인은 A 서비스로, 2번 도메인은 B 서비스로 보내라”는 지시서고, Ingress Controller는 그 지시서를 읽고 실제로 트래픽을 처리하는 리버스 프록시(Reverse Proxy) 서버라고 보시면 됩니다.

    쿠버네티스 자체에는 Ingress Controller가 기본 내장되어 있지 않아요. 그래서 우리가 직접 선택해서 설치해야 합니다. 그리고 이 선택이 생각보다 중요한 결정이거든요.

    왜 선택이 중요한가요?

    • 운영 중에 교체하면 서비스 중단이 발생할 수 있어요
    • 각 컨트롤러마다 지원하는 기능과 설정 방식이 달라요
    • 팀의 기술 스택과 러닝 커브도 고려해야 합니다
    • 트래픽 규모와 패턴에 따라 성능 특성이 다르게 나타나요

    세 가지 선택지 한눈에 비교

    본격적인 비교 전에 전체 그림을 먼저 보시죠.

    항목 Nginx Ingress Controller Traefik Gateway API
    기반 기술 Nginx (리버스 프록시) Go 기반 자체 구현 표준 API (구현체 별도)
    설정 방식 Ingress + Annotation Ingress + CRD Gateway/HTTPRoute CRD
    자동 설정 갱신 지원 (일부 재시작 필요) ✅ 완전 동적 구현체에 따라 다름
    대시보드 별도 설치 필요 ✅ 내장 없음 (구현체 의존)
    학습 난이도 낮음 (Nginx 경험자) 중간 높음
    성숙도 매우 높음 높음 성장 중 (GA 달성)
    멀티 팀 지원 제한적 중간 ✅ 설계 목표
    커뮤니티/레퍼런스 매우 풍부 풍부 성장 중

    Nginx Ingress Controller — 검증된 베테랑

    솔직히 말씀드리면, 저도 처음 쿠버네티스 클러스터 구성할 때 고민 없이 Nginx Ingress를 선택했어요. 이유는 단순했습니다. Nginx를 이미 10년 넘게 써왔으니까요.

    Nginx Ingress Controller는 CNCF(Cloud Native Computing Foundation) 산하 프로젝트인 kubernetes/ingress-nginx와, Nginx Inc.에서 만든 nginxinc/kubernetes-ingress 두 가지가 있어요. 헷갈리시는 분들이 많은데, 커뮤니티에서 주로 쓰는 건 전자인 ingress-nginx입니다.

    Helm으로 설치하기

    # Nginx Ingress Controller Helm 설치
    helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
    helm repo update
    
    helm install ingress-nginx ingress-nginx/ingress-nginx \
      --namespace ingress-nginx \
      --create-namespace \
      --set controller.replicaCount=2

    기본 Ingress 리소스 예시

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: my-app-ingress
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
        nginx.ingress.kubernetes.io/ssl-redirect: "true"
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - myapp.example.com
        secretName: myapp-tls
      rules:
      - host: myapp.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

    Nginx Ingress의 장단점

    ✅ 장점

    • 레퍼런스가 압도적으로 많아요. 구글에서 뭐든 찾을 수 있습니다
    • Nginx에 익숙한 분들은 Annotation으로 세밀한 튜닝이 가능해요
    • 안정성이 검증된 오랜 역사를 가지고 있습니다
    • 대규모 트래픽 처리에 강점이 있어요

    ⚠️ 단점

    • 설정 변경 시 nginx.conf를 reload해야 해서, 대규모 클러스터에선 부담이 될 수 있어요
    • Annotation이 너무 많아서 처음엔 뭘 써야 할지 헷갈립니다
    • 내장 대시보드가 없어서 별도로 Prometheus + Grafana를 구성해야 해요

    Traefik — 쿠버네티스 네이티브의 매력

    Traefik을 처음 접한 건 홈랩에서 Docker Compose로 운영하다가였어요. 당시에 “이거 레이블(Label) 하나 달면 자동으로 라우팅이 된다고?” 하면서 신기해했던 기억이 납니다. 쿠버네티스에서도 비슷한 경험을 할 수 있어요.

    Traefik의 핵심 철학은 동적 설정(Dynamic Configuration)이에요. 새로운 서비스가 배포되면 자동으로 감지해서 라우팅 규칙을 업데이트합니다. Nginx처럼 reload가 없어요.

    Traefik Helm 설치

    # Traefik Helm 설치
    helm repo add traefik https://helm.traefik.io/traefik
    helm repo update
    
    helm install traefik traefik/traefik \
      --namespace traefik \
      --create-namespace \
      --set dashboard.enabled=true \
      --set ingressRoute.dashboard.enabled=true

    Traefik IngressRoute CRD 예시

    Traefik은 표준 Ingress 리소스도 지원하지만, 자체 CRD인 IngressRoute를 쓰면 훨씬 풍부한 기능을 쓸 수 있어요.

    apiVersion: traefik.io/v1alpha1
    kind: IngressRoute
    metadata:
      name: my-app-ingressroute
      namespace: default
    spec:
      entryPoints:
        - websecure
      routes:
        - match: Host(`myapp.example.com`)
          kind: Rule
          services:
            - name: my-app-service
              port: 80
          middlewares:
            - name: my-ratelimit
      tls:
        certResolver: letsencrypt

    Middleware로 Rate Limiting 적용

    apiVersion: traefik.io/v1alpha1
    kind: Middleware
    metadata:
      name: my-ratelimit
    spec:
      rateLimit:
        average: 100
        burst: 50

    💡 Traefik의 진짜 강점은 Let’s Encrypt 인증서 자동 발급이 내장되어 있다는 거예요. cert-manager를 별도로 설치하지 않아도 TLS 인증서를 자동으로 관리해 줍니다. 홈랩에서 이거 하나 때문에 Traefik으로 갈아탔을 정도로 편하더라고요.

    Traefik 내장 대시보드 화면 — 라우터, 서비스, 미들웨어 현황을 한눈에 확인하는 모니터링 인터페이스

    ▲ Traefik 내장 대시보드에서 라우터, 서비스, 미들웨어 현황을 한눈에 확인할 수 있는 화면

    Traefik의 장단점

    ✅ 장점

    • 동적 설정 갱신 — 서비스 변경 시 reload 없이 즉시 반영돼요
    • 내장 대시보드로 라우팅 현황을 바로 확인할 수 있어요
    • Let’s Encrypt 자동 인증서 발급/갱신 내장
    • Middleware로 인증, Rate Limiting, 헤더 조작 등을 깔끔하게 구성 가능

    ⚠️ 단점

    • CRD 방식이 Nginx Annotation과 달라서 러닝 커브가 있어요
    • Nginx에 비해 레퍼런스가 적습니다 (그래도 요즘은 많이 늘었어요)
    • 초대형 클러스터에서의 성능 검증 사례가 Nginx보다 적은 편이에요

    Gateway API — 쿠버네티스 네트워크의 미래

    처음 Gateway API 문서를 읽었을 때 솔직히 “이게 뭐가 다른 건데?” 싶었어요. 근데 알고 보니 기존 Ingress 리소스의 한계를 근본적으로 해결하려는 시도더라고요.

    Gateway API는 SIG Network(쿠버네티스 네트워킹 특별관심그룹)에서 만든 공식 표준이에요. 2023년에 v1.0으로 GA(Generally Available, 정식 출시)를 달성했습니다. Ingress 리소스를 대체하려는 게 아니라, 더 표현력 있고 역할 기반으로 분리된 네트워크 설정을 가능하게 하는 게 목표예요.

    Gateway API의 핵심 개념

    기존 Ingress는 개발자와 인프라 관리자가 같은 리소스를 수정해야 했어요. Gateway API는 역할을 명확히 분리합니다.

    • GatewayClass: 인프라 제공자가 정의 (“이 구현체를 쓸게”)
    • Gateway: 클러스터 운영자가 정의 (“이 포트, 이 프로토콜로 받을게”)
    • HTTPRoute: 개발자가 정의 (“이 경로는 내 서비스로 보내줘”)

    Gateway API 설치 (CRD 먼저)

    # Gateway API CRD 설치
    kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
    
    # 구현체 예시: Nginx Gateway Fabric
    helm install ngf oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric \
      --namespace nginx-gateway \
      --create-namespace

    Gateway와 HTTPRoute 설정 예시

    # Gateway 리소스 (운영자가 관리)
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: main-gateway
      namespace: infra
    spec:
      gatewayClassName: nginx
      listeners:
      - name: https
        port: 443
        protocol: HTTPS
        tls:
          mode: Terminate
          certificateRefs:
          - name: main-tls-cert
        allowedRoutes:
          namespaces:
            from: Selector
            selector:
              matchLabels:
                gateway-access: allowed
    # HTTPRoute 리소스 (개발자가 관리)
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: my-app-route
      namespace: my-app-ns
    spec:
      parentRefs:
      - name: main-gateway
        namespace: infra
      hostnames:
      - myapp.example.com
      rules:
      - matches:
        - path:
            type: PathPrefix
            value: /api
        backendRefs:
        - name: api-service
          port: 8080
      - matches:
        - path:
            type: PathPrefix
            value: /
        backendRefs:
        - name: frontend-service
          port: 3000

    보이시나요? Gateway는 인프라팀이 관리하고, HTTPRoute는 각 개발팀이 자기 네임스페이스에서 독립적으로 관리할 수 있어요. 멀티 팀 환경에서 진짜 강력한 구조입니다.

    Gateway API의 장단점

    ✅ 장점

    • 역할 기반 분리로 멀티 팀 환경에 최적화되어 있어요
    • 표현력이 풍부해서 복잡한 라우팅 규칙도 명확하게 표현 가능
    • 쿠버네티스 공식 표준이라 장기적으로 생태계 지원이 확대될 거예요
    • 구현체를 교체해도 HTTPRoute 설정은 그대로 재사용 가능 (이식성)

    ⚠️ 단점

    • 개념이 많아서 처음 배우기가 쉽지 않아요. 저도 꽤 헷갈렸습니다
    • 구현체마다 지원하는 기능이 달라서 사전 확인이 필요해요
    • 소규모 팀이나 단순한 환경에선 오버엔지니어링이 될 수 있어요
    • 레퍼런스와 사례가 아직 Nginx/Traefik에 비해 적습니다

    ⚠️ 실제 트러블슈팅 경험담

    각 컨트롤러를 쓰면서 제가 직접 겪은 문제들을 공유할게요.

    Nginx Ingress — 413 Request Entity Too Large

    파일 업로드 기능이 있는 서비스에서 자꾸 413 에러가 났었어요. Nginx 기본 설정의 client_max_body_size 제한 때문이었는데, Annotation으로 해결했습니다.

    metadata:
      annotations:
        nginx.ingress.kubernetes.io/proxy-body-size: "50m"

    Traefik — 인증서가 갱신 안 되는 문제

    Let’s Encrypt 인증서를 Traefik이 자동으로 갱신해 주는 게 편한데, 어느 날 갑자기 인증서 만료 알림이 왔어요. 알고 보니 acme.json 파일이 저장된 PersistentVolume이 Pod 재시작 시 초기화되는 문제였습니다. acme.json은 반드시 영구 스토리지에 마운트해야 해요.

    # values.yaml에서 persistence 설정
    persistence:
      enabled: true
      storageClass: "longhorn"
      size: 128Mi

    Gateway API — 구현체 호환성 확인 필수

    HTTPRoute의 고급 기능(예: RequestRedirect, URLRewrite)을 사용했는데, 특정 구현체에서 지원하지 않아서 라우팅이 묵묵히 실패하는 경우가 있었어요. 구현체의 Conformance Report(적합성 보고서)를 미리 확인하는 게 중요합니다. Gateway API 공식 문서에서 구현체별 지원 기능표를 제공하고 있어요.

    쿠버네티스 Ingress Controller 모니터링 대시보드 — Prometheus와 Grafana를 활용한 요청 수, 에러율, 레이턴시 시각화

    ▲ Prometheus와 Grafana를 활용한 Ingress Controller 모니터링 대시보드 — 요청 수, 에러율, 레이턴시를 실시간으로 확인

    상황별 선택 가이드

    “그래서 뭘 써야 하나요?” 라고 물어보신다면, 상황에 따라 다르다고 답할 수밖에 없어요. 하지만 경험상 이런 기준으로 선택하시면 크게 후회는 없더라고요.

    Nginx Ingress Controller를 선택하세요, 만약…

    • 팀에 Nginx 경험자가 있고, 레퍼런스가 많은 안정적인 선택을 원할 때
    • 대규모 트래픽을 처리해야 하고 검증된 성능이 필요할 때
    • 기존 Nginx 설정을 쿠버네티스로 마이그레이션할 때
    • 복잡한 Nginx 설정(custom snippet 등)이 필요할 때

    Traefik을 선택하세요, 만약…

    • 홈랩이나 소규모 환경에서 편리하게 운영하고 싶을 때
    • Let’s Encrypt 자동 인증서 관리를 간단하게 하고 싶을 때
    • 내장 대시보드로 라우팅 현황을 빠르게 파악하고 싶을 때
    • 동적 환경에서 서비스 변경이 잦을 때

    Gateway API를 선택하세요, 만약…

    • 여러 팀이 각자의 라우팅 설정을 독립적으로 관리해야 할 때
    • 장기적으로 쿠버네티스 표준에 맞춰 가고 싶을 때
    • 복잡한 트래픽 분산(카나리 배포, A/B 테스트 등)이 필요할 때
    • 구현체 이식성을 확보하고 싶을 때
    쿠버네티스 Ingress Controller 비교 인포그래픽 — Nginx Ingress, Traefik, Gateway API 특징과 선택 기준 요약

    ▲ 환경과 요구사항에 따른 Ingress Controller 선택 가이드 요약 인포그래픽

    마무리 — 정답은 없지만, 기준은 있다

    13년 동안 인프라를 운영하면서 느낀 건, 기술 선택에 절대적인 정답은 없다는 거예요. 중요한 건 내 환경과 팀의 요구사항에 맞는 선택을 하는 겁니다.

    개인적으로는 이렇게 정리하고 싶어요.

    • 처음 시작하는 분들: Nginx Ingress로 시작하세요. 레퍼런스가 많아서 막힐 때 찾기 쉬워요
    • 홈랩이나 소규모 팀: Traefik을 강력 추천합니다. 편의성이 정말 좋아요
    • 엔터프라이즈/멀티 팀 환경: Gateway API를 진지하게 검토해 보세요. 미래 방향성이 여기 있거든요

    그리고 하나 더 — 저는 지금 홈랩에서 Traefik을 쓰고, 회사 프로덕션에서는 Nginx Ingress를 씁니다. 두 개 다 쓸 줄 알면 더 좋아요. 😄

    다음 글에서는 Traefik의 Middleware를 활용한 인증/인가(Authentication/Authorization) 설정을 더 자세히 다룰 예정이에요. cert-manager를 이용한 TLS 자동화 내용도 이전 글에서 다룬 적 있으니 참고해 보세요.

    자주 묻는 질문 (FAQ)

    Q. Nginx Ingress Controller와 Nginx Gateway Fabric은 다른 건가요?

    네, 다릅니다. ingress-nginx는 기존 Ingress API 기반이고, Nginx Gateway Fabric은 Gateway API 표준의 구현체예요. 용도와 설정 방식이 다릅니다.

    Q. 하나의 클러스터에 여러 Ingress Controller를 동시에 쓸 수 있나요?

    가능합니다. ingressClassName을 통해 어떤 컨트롤러를 사용할지 지정할 수 있어요. 다만 운영 복잡도가 올라가니 꼭 필요한 경우에만 권장합니다.

    Q. Gateway API는 Ingress를 완전히 대체하나요?

    장기적으로는 그 방향이지만, 현재는 공존하는 상태예요. 기존 Ingress 리소스가 deprecated되지는 않았고, Gateway API가 더 복잡한 요구사항을 위한 차세대 표준으로 자리잡고 있는 중입니다.