13년차의 서버실

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

[태그:] Kubernetes

  • [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] OpenTelemetry K8s 모니터링, 비용 효율적인 구축 전략

    [k8s] OpenTelemetry K8s 모니터링, 비용 효율적인 구축 전략

    OpenTelemetry K8s 모니터링, 비용 효율적인 구축 전략

    안녕하세요, 13년차의 서버실 주인장입니다. 😎

    요즘 Kubernetes(쿠버네티스) 환경에서 애플리케이션을 운영하는 건 거의 기본이 되었죠. 근데 이 복잡한 환경을 제대로 모니터링하는 게 정말 쉽지 않더라고요. 특히 OpenTelemetry K8s 모니터링은 분산 시스템의 가시성(Observability)을 확보하는 데 필수적인데, 이걸 어떻게 구축해야 할지 막막한 분들이 많으실 거예요.

    비용 문제도 무시할 수 없죠. 상용 모니터링 솔루션은 비싸고, 직접 구축하려니 삽질이 이만저만이 아니거든요. 😅

    오늘은 제가 직접 Kubernetes 모니터링을 OpenTelemetry로 구축하면서 겪었던 삽질과, 어떻게 하면 비용 효율적인 구축 전략을 가져갈 수 있는지 제 경험을 바탕으로 이야기해보려 합니다. 초기엔 저도 뭐가 뭔지 헷갈렸는데, 결국 해내고 나니 뿌듯하더라고요. 함께 가시죠! 💪

    OpenTelemetry를 활용한 Kubernetes 모니터링 아키텍처는 위 그림처럼 구성할 수 있습니다. 데이터를 수집하고 백엔드로 보내는 과정이 깔끔하죠.

    개념 설명: OpenTelemetry, K8s 모니터링의 핵심

    자, 그럼 핵심 개념부터 간단히 짚고 넘어갈까요?

    • Observability(옵저버빌리티): 시스템 내부 상태를 외부에서 추론할 수 있는 능력입니다. 주로 Metrics(메트릭), Logs(로그), Traces(트레이스) 세 가지 기둥으로 구성되거든요. 이 세 가지를 제대로 봐야 시스템에서 무슨 일이 일어나는지 파악할 수 있죠.
    • OpenTelemetry(오픈텔레메트리): 이 옵저버빌리티 데이터를 수집하고, 처리하고, 내보내는 표준화된 프레임워크입니다. 벤더 종속성을 줄여주는 엄청난 장점이 있어요. 예전에는 각 모니터링 솔루션마다 에이전트를 다르게 심고 그랬었는데, OpenTelemetry 덕분에 한 번만 계측(Instrumentation)하면 다양한 백엔드로 데이터를 보낼 수 있게 된 거죠. 이게 진짜 편하더라고요!
    • Kubernetes 모니터링: K8s 클러스터 내의 Pod(파드), Node(노드), Deployment(디플로이먼트) 등의 상태와 성능을 지속적으로 관찰하는 활동입니다. CPU 사용량, 메모리, 네트워크 트래픽부터 애플리케이션 로그, 에러 트레이스까지 다 포함되거든요.
    • 비용 분석: 모니터링 솔루션은 데이터 수집량에 따라 비용이 천차만별입니다. 특히 클라우드 환경에서는 데이터 전송량, 저장량, 쿼리량 등에 따라 과금되기 때문에, 불필요한 데이터를 줄이고 효율적으로 관리하는 전략이 정말 중요하더라고요. 놓치면 배보다 배꼽이 더 커질 수 있거든요.

    실전 구현: OpenTelemetry Collector와 백엔드 구축

    이제 본격적으로 OpenTelemetry Collector를 K8s에 배포하고, 데이터를 수집하는 방법을 알아볼게요. 저는 비용 효율성을 위해 Prometheus(프로메테우스) + Grafana(그라파나) 조합으로 메트릭을, Loki(로키)로 로그를, Jaeger(예거)로 트레이스를 수집하는 방법을 선호합니다. 다 오픈소스라서 초기 비용 부담이 적거든요. 제 홈랩에서도 이 조합으로 잘 쓰고 있습니다. 👍

    먼저 OpenTelemetry Collector를 DaemonSet(데몬셋)으로 배포해서 각 노드에서 데이터를 수집하도록 설정합니다. 이게 가장 일반적이고 안정적인 방법이에요.

    # opentelemetry-collector-daemonset.yaml
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: otel-collector
      namespace: monitoring
      labels:
        app: otel-collector
    spec:
      selector:
        matchLabels:
          app: otel-collector
      template:
        metadata:
          labels:
            app: otel-collector
        spec:
          serviceAccountName: otel-collector
          containers:
          - name: otel-collector
            image: otel/opentelemetry-collector:0.87.0 # 최신 안정 버전 확인 필요합니다. 공식 도커 허브를 참고하세요!
            command: ["--config=/conf/otel-collector-config.yaml"]
            volumeMounts:
            - name: otel-collector-config-vol
              mountPath: /conf
            - name: host-proc
              mountPath: /proc
              readOnly: true
            - name: host-sys
              mountPath: /sys
              readOnly: true
            securityContext:
              privileged: true # 노드 메트릭 수집을 위해 필요할 수 있습니다. 보안에 유의하세요.
          volumes:
          - name: otel-collector-config-vol
            configMap:
              name: otel-collector-config
          - name: host-proc
            hostPath:
              path: /proc
          - name: host-sys
            hostPath:
              path: /sys
    

    다음은 ConfigMap(컨피그맵)으로 OpenTelemetry Collector의 설정을 정의합니다. 여기서 중요한 건 어떤 데이터를 수집해서 어디로 보낼지 정의하는 부분이에요. Receivers, Processors, Exporters가 핵심이죠.

    # opentelemetry-collector-config.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: otel-collector-config
      namespace: monitoring
    data:
      otel-collector-config.yaml: |
        receivers:
          otlp:
            protocols:
              grpc:
              http:
          prometheus:
            config:
              scrape_configs:
                - job_name: 'kubernetes-nodes'
                  scrape_interval: 15s
                  kubernetes_sd_configs:
                    - role: node
                  relabel_configs:
                    - action: labelmap
                      regex: __meta_kubernetes_node_label_(.+)
                    - target_label: __address__
                      replacement: kubernetes.default.svc:443
                    - source_labels: [__meta_kubernetes_node_name]
                      regex: (.+)
                      target_label: __metrics_path__
                      replacement: /api/v1/nodes/${1}/proxy/metrics
                - job_name: 'kubernetes-pods'
                  kubernetes_sd_configs:
                    - role: pod
                  relabel_configs:
                    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
                      action: keep
                      regex: true
                    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
                      action: replace
                      target_label: __metrics_path__
                      regex: (.+)
                    - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
                      action: replace
                      regex: ([^:]+)(?::\d+)?;(\d+)
                      target_label: __address__
                      replacement: $1:$2
                    - action: labelmap
                      regex: __meta_kubernetes_pod_label_(.+)
                    - source_labels: [__meta_kubernetes_namespace]
                      action: replace
                      target_label: kubernetes_namespace
                    - source_labels: [__meta_kubernetes_pod_name]
                      action: replace
                      target_label: kubernetes_pod_name
        
        processors:
          batch:
            send_batch_size: 1000
            timeout: 5s
          memory_limiter:
            limit_mib: 200
            spike_limit_mib: 50
          resourcedetection:
            detectors: [env, system, kubernetes]
            timeout: 2s
            kubernetes:
              pod_association:
                - from: cgroup
                - from: resource_attribute
                  key: k8s.pod.uid
          attributes:
            actions:
              - key: host.name
                action: delete
              - key: host.id
                action: delete
              - key: os.type
                action: delete
        
        exporters:
          prometheus:
            endpoint: "0.0.0.0:8889" # Prometheus가 이 엔드포인트를 스크래핑하여 메트릭 수집
          prometheusremotewrite:
            endpoint: "http://prometheus-server.monitoring.svc.cluster.local:9090/api/v1/write" # Prometheus Remote Write API
          otlp/logs:
            endpoint: "loki.monitoring.svc.cluster.local:3100" # Loki OTLP/HTTP Endpoint
            tls:
              insecure: true # 개발 환경에서만 사용하세요. 프로덕션에서는 TLS 구성이 필요합니다.
          otlp/traces:
            endpoint: "jaeger-collector.monitoring.svc.cluster.local:14250" # Jaeger gRPC Endpoint
            tls:
              insecure: true # 개발 환경에서만 사용하세요. 프로덕션에서는 TLS 구성이 필요합니다.
    
        service:
          pipelines:
            metrics:
              receivers: [otlp, prometheus]
              processors: [resourcedetection, attributes, batch, memory_limiter]
              exporters: [prometheus, prometheusremotewrite] # prometheus는 메트릭 노출, prometheusremotewrite는 원격 Prometheus로 전송
            logs:
              receivers: [otlp]
              processors: [resourcedetection, attributes, batch, memory_limiter]
              exporters: [otlp/logs]
            traces:
              receivers: [otlp]
              processors: [resourcedetection, attributes, batch, memory_limiter]
              exporters: [otlp/traces]
    

    여기서 exporters 부분을 보면 Prometheus, Loki, Jaeger로 데이터를 보내도록 설정한 걸 볼 수 있죠. 각 백엔드에 맞게 엔드포인트를 지정해주면 됩니다. 특히 processors 섹션에서 attributes를 사용해서 불필요한 메트릭 속성들을 제거하는 게 비용 효율적인 모니터링에 큰 도움이 되더라고요. 클라우드에서 데이터 전송량이나 저장량 줄이는 데 효과 만점입니다. 💡

    OpenTelemetry Collector와 모니터링 백엔드 상세 구성도

    OpenTelemetry Collector와 모니터링 백엔드 간의 상세 데이터 흐름 구성도입니다. 이 그림을 보면서 각 컴포넌트가 어떻게 연결되는지 쉽게 이해할 수 있을 거예요.

    주의사항/트러블슈팅: 삽질 피하기! ⚠️

    제가 이 부분에서 삽질을 좀 많이 했었습니다. 여러분은 이런 실수 안 하시길 바라면서 몇 가지 중요한 주의사항과 해결법을 공유해드릴게요. 멘토의 조언이라고 생각해주세요! 😉

    • ⚠️ 권한 문제: OpenTelemetry Collector가 노드 메트릭을 제대로 수집하려면 host-proc, host-sys 마운트와 privileged: true 설정이 필요할 수 있거든요. 보안상 민감한 부분이니 필요한 최소한의 권한만 부여하는 게 중요해요. 처음엔 권한 부족으로 메트릭이 안 들어와서 한참 헤맸습니다.
    • ⚠️ 설정 파일 오타: YAML 파일은 스페이스 하나에도 민감하죠. 특히 ConfigMap에 data 아래에 otel-collector-config.yaml: | 부분의 들여쓰기를 조심하세요. 오타 하나 때문에 Collector가 시작도 안 되는 경우가 허다하거든요. kubectl logs로 Collector Pod의 로그를 꼭 확인하세요.
    • ⚠️ 네트워크 문제: 백엔드(Prometheus, Loki, Jaeger)의 서비스 이름과 포트가 정확한지 확인해야 합니다. 특히 K8s 클러스터 내에서 service.namespace.svc.cluster.local 형태로 접근하는 게 일반적이에요. 방화벽이나 NetworkPolicy(네트워크 정책) 때문에 통신이 안 되는 경우도 많으니 kubectl exec로 Collector Pod에 들어가서 curl 등으로 연결 테스트를 해보는 게 좋습니다.
    • 💡 데이터 과부하: OpenTelemetry Collector의 memory_limiter와 batch 프로세서를 적절히 설정해서 Collector가 과도한 리소스를 사용하거나, 데이터 전송에 병목이 생기는 걸 방지해야 합니다. 저도 한 번 Collector가 메모리 폭주해서 노드가 비정상적으로 동작하는 바람에 새벽에 호출된 적이 있습니다… 😅

    검증/결과: 모니터링 대시보드 확인

    이제 모든 설정이 끝났으니, 제대로 동작하는지 확인해볼 시간입니다! 🎉 드디어 됐다! 하고 외칠 준비 되셨죠?

    1. 먼저 Grafana(그라파나)에 접속해서 Prometheus 데이터 소스를 추가하고, Kubernetes 노드/파드 메트릭 대시보드를 임포트해보세요. 저는 보통 Node Exporter Full이나 Kubernetes / Kubelet 대시보드를 사용합니다.
    2. 로그는 Loki에 쌓인 데이터를 Grafana의 Explore(탐색) 기능으로 확인하거나, Loki 전용 대시보드를 만들어 볼 수 있습니다. logcli 같은 도구로도 확인 가능하고요.
    3. 트레이스는 Jaeger UI에 접속해서 애플리케이션에서 보낸 트레이스가 잘 수집되는지 확인합니다. 서비스 이름과 오퍼레이션 이름으로 검색해보면 되겠죠.

    OpenTelemetry K8s 모니터링 Grafana 대시보드 예시

    OpenTelemetry로 수집한 Kubernetes 메트릭을 Grafana 대시보드에서 시각화한 모습입니다. 이렇게 한눈에 시스템 상태를 파악할 수 있으면 얼마나 든든한지 몰라요!

    마무리: 비용 효율과 미래를 위한 전략

    오늘은 OpenTelemetry K8s 모니터링을 비용 효율적으로 구축하는 전략에 대해 제 경험을 공유해드렸습니다.

    가장 중요한 포인트는 OpenTelemetry를 통해 특정 벤더에 종속되지 않고, 오픈소스 백엔드(Prometheus, Grafana, Loki, Jaeger)를 활용하여 초기 구축 비용을 최소화하는 것이었습니다. 이 조합은 특히 홈랩이나 소규모 프로젝트에서 빛을 발하더라고요.

    특히 OpenTelemetry Collector의 processors를 활용하여 불필요한 데이터를 필터링하고, 샘플링(Sampling) 전략을 적용하는 것이 클라우드 환경에서 비용 분석 및 절감에 큰 영향을 미친다는 점을 꼭 기억해주세요. 제가 이 부분에서 시행착오를 많이 겪었거든요. 데이터 양을 줄이는 게 진짜 중요합니다!

    Kubernetes 모니터링은 한 번 구축했다고 끝이 아닙니다. 지속적으로 데이터를 분석하고, 대시보드를 개선하며, 새로운 요구사항에 맞춰 시스템을 확장해나가야 하거든요. OpenTelemetry는 앞으로도 옵저버빌리티 분야에서 핵심적인 역할을 할 것이 분명합니다. 여러분의 환경에 맞춰 최적의 비용 효율적인 구축 전략을 찾아보시길 바랍니다. 다음 기회에 심화 내용을 다뤄볼게요! 😊

    OpenTelemetry를 활용한 비용 효율적인 Kubernetes 모니터링 이점

    OpenTelemetry를 통한 모니터링 구축은 단순히 기술적인 선택을 넘어, 장기적인 관점에서 비용 효율성과 유연성을 확보하는 전략적인 결정이 될 수 있습니다.

  • [Kubernetes] External Secrets Operator 보안 강화 체크리스트 10가지

    [Kubernetes] External Secrets Operator 보안 강화 체크리스트 10가지

    [Kubernetes] External Secrets Operator 보안 강화 체크리스트 10가지

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 Kubernetes(쿠버네티스) 환경에서 민감한 정보를 안전하게 관리하는 데 필수적인 External Secrets Operator(외부 시크릿 연동 오퍼레이터, 이하 ESO)의 보안을 어떻게 더 튼튼하게 만들 수 있는지, 제가 직접 겪고 배운 경험들을 바탕으로 10가지 체크리스트를 공유하려고 합니다. 사실 저도 처음엔 Kubernetes Secret(쿠버네티스 시크릿)에 직접 값을 넣다가, GitOps(깃옵스) 환경에서 시크릿 노출 문제 때문에 엄청 고민했었거든요. 그때 External Secrets Operator를 만나고 나서 ‘이거다!’ 싶었죠. 하지만 편리함 뒤에는 항상 보안이라는 그림자가 따라붙는 법입니다. ESO를 잘 쓰는 것만큼, 안전하게 쓰는 것이 중요하더라고요.

    Kubernetes 클러스터에서 데이터베이스 비밀번호, API 키 같은 민감한 정보들을 안전하게 보관하고 관리하는 건 인프라 엔지니어의 숙명과도 같습니다. 특히 GitOps를 도입하면 모든 설정이 Git(깃) 저장소에 코드 형태로 관리되는데, 여기에 시크릿이 그대로 노출되면 정말 큰일 나잖아요? ESO는 이런 문제의 훌륭한 해답이 되어주지만, 그 자체로 또 다른 보안 고려사항을 만들어냅니다. 제가 홈랩에서 이것저것 실험하면서, 그리고 실제 서비스에 적용하면서 얻은 노하우들을 지금부터 하나씩 풀어볼게요. 혹시 이런 고민 해보신 적 있으신가요? 그렇다면 이 글이 큰 도움이 될 겁니다. 💡

    Kubernetes External Secrets Operator와 외부 시크릿 저장소 연동 아키텍처 다이어그램: Kubernetes 클러스터 내의 ESO가 AWS Secrets Manager, HashiCorp Vault 등 외부 시크릿 저장소와 안전하게 통신하며 시크릿을 동기화하는 구조를 보여줍니다.

    External Secrets Operator, 왜 중요할까요?

    External Secrets Operator는 쉽게 말해 Kubernetes 클러스터와 외부 시크릿 저장소(Secret Store)를 연결해주는 다리 역할을 합니다. AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Google Secret Manager 같은 전문 시크릿 관리 서비스에 저장된 민감 정보를 Kubernetes Secret 리소스(Resource)로 동기화(Synchronize)해주는 거죠. 이렇게 하면:

    • ✅ 시크릿 중앙 관리: 모든 시크릿을 한 곳에서 관리할 수 있어서 일관성을 유지하고 감사(Audit)하기가 편해집니다.
    • ✅ Git 노출 방지: Git 저장소에는 ExternalSecret 리소스의 정의만 있고, 실제 민감한 값은 들어가지 않으니 보안 위험이 줄어듭니다.
    • ✅ 보안 기능 활용: 외부 시크릿 저장소가 제공하는 강력한 암호화, 접근 제어, 감사 로깅 등의 기능을 그대로 활용할 수 있어요.

    이런 장점들 때문에 많은 분들이 ESO를 사용하시는데요, 중요한 건 이 편리함을 누리면서도 보안을 놓치지 않는 겁니다. 제가 13년 동안 Kubernetes와 인프라를 다루면서 깨달은 External Secrets Operator 보안 강화 체크리스트 10가지를 지금부터 공유합니다!

    External Secrets Operator 보안 강화 체크리스트 10가지

    자, 이제 본론입니다. 제가 직접 써보고 효과를 본 핵심 보안 설정들을 하나씩 짚어볼게요.

    1. SecretStore(시크릿 저장소) 권한 최소화 (Principle of Least Privilege)

    가장 기본 중의 기본이죠. ESO가 외부 시크릿 저장소에 접근할 때 사용하는 계정(예: AWS IAM Role, Vault AppRole)에는 필요한 최소한의 권한만 부여해야 합니다. 예를 들어, 특정 Path(경로)나 Prefix(접두사)에 해당하는 시크릿만 읽을 수 있도록 제한하는 거죠. 제가 예전에 실수로 모든 시크릿을 읽을 수 있는 권한을 줬다가 등골이 서늘했던 기억이 있습니다. ⚠️

    apiVersion: external-secrets.io/v1beta1
    kind: SecretStore
    metadata:
      name: aws-secrets-manager
    spec:
      provider:
        aws:
          service: SecretsManager
          region: ap-northeast-2
          auth:
            jwt:
              serviceAccountRef:
                name: external-secrets-sa
          # 특정 시크릿만 접근하도록 IAM 정책에서 제한
          # 예시: arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:my-app/*
    

    2. SecretStore 백엔드 인증 방식 강화

    외부 시크릿 저장소와의 인증은 강력한 방식을 사용해야 합니다. 클라우드 환경에서는 IRSA(IAM Roles for Service Accounts)나 Workload Identity(워크로드 아이덴티티) 같은 방식을 활용해서 Kubernetes Service Account(서비스 어카운트)에 IAM Role(아이덴티티 및 접근 관리 역할)을 연결하는 게 가장 안전합니다. 자격 증명(Credentials)을 파일로 관리하거나 환경 변수에 직접 넣는 방식은 피해야 해요. Vault(볼트)를 쓴다면 AppRole(앱 역할)이나 Kubernetes Auth Method(쿠버네티스 인증 방식)를 추천합니다.

    3. ExternalSecret Scope(범위) 제한 및 네임스페이스 분리

    ExternalSecret 리소스는 특정 Kubernetes Namespace(네임스페이스)에 배포되어야 합니다. 또한, ClusterSecretStore를 사용하더라도 ExternalSecret 자체는 해당 네임스페이스에만 영향을 미치도록 설계해야 합니다. 각 애플리케이션이나 팀별로 별도의 네임스페이스를 사용하고, 그 안에 필요한 ExternalSecret만 정의하는 것이 모범 사례입니다. 이렇게 하면 한 곳에서 문제가 생겨도 다른 곳으로 전파되는 걸 막을 수 있어요.

    4. 데이터 전송 및 저장 중 암호화 (Encryption in Transit/At Rest)

    이건 ESO 자체보다는 외부 시크릿 저장소의 역할이 더 큽니다. 외부 시크릿 저장소가 제공하는 암호화 기능을 반드시 활성화해야 합니다. 대부분의 클라우드 서비스는 기본적으로 저장 중 암호화(Encryption at Rest)를 제공하지만, 전송 중 암호화(Encryption in Transit)도 TLS(전송 계층 보안) 등을 통해 보장되는지 확인해야 합니다. ESO와 외부 시크릿 저장소 간의 통신은 HTTPS(보안 하이퍼텍스트 전송 프로토콜) 기반으로 이루어지므로, 이 부분은 크게 걱정할 필요는 없지만, 클라우드 설정 단계에서 한 번 더 확인해두면 좋습니다.

    5. RBAC(Role-Based Access Control) 설정 강화

    Kubernetes RBAC을 이용해서 누가 ExternalSecret 리소스를 생성, 수정, 삭제할 수 있는지 명확하게 정의해야 합니다. 개발팀은 자신의 네임스페이스 내에서 ExternalSecret을 관리할 수 있지만, 다른 네임스페이스의 리소스에는 접근할 수 없도록 제한하는 거죠. ExternalSecret 리소스를 직접 수정할 수 있다는 건, 결국 어떤 시크릿을 Kubernetes Secret으로 가져올지 결정할 수 있다는 의미이기 때문에 강력한 접근 제어가 필요합니다.

    # 예시: 특정 네임스페이스에서 ExternalSecret을 관리할 수 있는 Role
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: external-secrets-manager
      namespace: my-app-namespace
    rules:
    - apiGroups: ["external-secrets.io"]
      resources: ["externalsecrets"]
      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
    
    ExternalSecret 리소스의 보안 설정 예시 YAML

    ExternalSecret 리소스의 보안 설정 예시 YAML: ExternalSecret 정의에서 SecretStore 참조, 시크릿 키 매핑 등 보안과 관련된 주요 설정 요소들을 강조하여 보여줍니다.

    6. Secret Rotation(시크릿 주기적 교체) 자동화

    아무리 강력한 시크릿이라도 오래 사용하면 위험이 커집니다. 외부 시크릿 저장소가 제공하는 시크릿 교체(Rotation) 기능을 적극 활용해서 데이터베이스 비밀번호나 API 키를 주기적으로 자동으로 바꿔주세요. ESO는 동기화된 시크릿이 외부 저장소에서 변경되면 자동으로 Kubernetes Secret도 업데이트해주기 때문에, 이 부분이 정말 편하더라고요. 수동으로 하다가 깜빡해서 서비스 장애를 냈던 경험이 있다면, 이 자동화가 얼마나 소중한지 아실 겁니다. 🎉

    7. Audit Logging(감사 로깅) 활성화 및 모니터링

    누가, 언제, 어떤 시크릿에 접근했는지, 그리고 어떤 변경이 있었는지 꼼꼼하게 기록하고 모니터링해야 합니다. 외부 시크릿 저장소의 감사 로그 기능(예: AWS CloudTrail, Vault Audit Devices)을 활성화하고, 이를 중앙 로깅 시스템(예: ELK Stack, Splunk)으로 수집하여 비정상적인 접근 시도를 탐지할 수 있도록 설정해야 합니다. ESO 자체 로그도 중요한 단서가 될 수 있으니 항상 주의 깊게 살펴보세요.

    8. Deprecation(폐기) 정책 수립 및 불필요한 시크릿 정리

    사용하지 않는 시크릿은 더 이상 존재할 필요가 없습니다. 불필요한 시크릿은 잠재적인 공격 벡터(Attack Vector)가 될 수 있으므로, 주기적으로 검토하고 삭제하는 정책을 수립해야 합니다. 애플리케이션 배포 주기나 서비스 생명 주기(Life Cycle)에 맞춰 시크릿도 함께 관리하는 것이 중요합니다.

    9. 최신 버전 유지 및 보안 패치 적용

    ESO와 외부 시크릿 저장소 모두 항상 최신 버전으로 유지하고, 보안 패치가 나오면 빠르게 적용해야 합니다. 오픈소스 프로젝트인 ESO는 꾸준히 개선되고 보안 취약점이 발견되면 패치되거든요. 제가 예전에 마이너 버전 업데이트를 미루다가 특정 기능이 제대로 작동하지 않아 삽질했던 기억이 있네요. 😅 물론 업데이트 전에는 테스트 환경에서 충분히 검증해야 합니다!

    10. GitOps 워크플로우 통합 및 검증

    ExternalSecret 리소스 정의를 Git에 저장하고 CI/CD(지속적 통합/지속적 배포) 파이프라인을 통해 배포하는 GitOps 워크플로우를 완벽하게 통합하고 검증해야 합니다. Git 저장소에 민감 정보가 포함되지 않도록 하는 것은 물론, Pull Request(풀 리퀘스트) 검토 프로세스를 강화하여 ExternalSecret 변경 사항에 대한 보안 검토를 필수화해야 합니다. 이렇게 하면 휴먼 에러(Human Error)로 인한 보안 사고를 줄일 수 있습니다.

    ⚠️ 삽질 경험 공유: 외부 시크릿 연동 오류와 권한 문제

    제가 ESO를 처음 도입했을 때 가장 많이 겪었던 삽질은 바로 권한 문제였습니다. AWS Secrets Manager와 연동하는데, 분명히 IAM Role을 잘 설정했다고 생각했는데도 계속 Permission Denied(권한 거부) 에러가 뜨더라고요. 한참을 헤매다가 결국 발견한 건, Service Account에 연결된 IAM Role의 정책(Policy)이 특정 시크릿에 대한 secretsmanager:GetSecretValue 액션을 허용하지 않고 있었다는 겁니다. 🤯

    이걸 해결하려면 AWS IAM 콘솔에서 해당 Role의 권한을 꼼꼼히 확인하고, 필요한 시크릿 ARN(Amazon Resource Name)과 액션을 명시적으로 추가해야 했어요. 특히, 시크릿 이름에 와일드카드(*)를 사용할 때는 정말 신중해야 합니다. 최소 권한 원칙을 지키는 게 생각보다 어렵지만, 이 과정을 통해 저는 IAM 정책 디버깅 능력을 키울 수 있었습니다. 여러분도 꼭 로그를 꼼꼼히 확인하고, 에러 메시지를 구글링하기 전에 권한부터 다시 한번 살펴보세요!

    ✅ 검증 및 결과 확인

    위 체크리스트를 적용한 후에는 반드시 제대로 작동하는지 확인해야겠죠? 저는 주로 다음 명령어로 시크릿 동기화 상태를 확인합니다.

    # ExternalSecret 리소스 상태 확인
    kubectl get externalsecrets -n my-app-namespace
    
    # 동기화된 Kubernetes Secret 확인
    kubectl get secrets my-app-secret -n my-app-namespace -o yaml
    
    # External Secrets Operator 컨트롤러 로그 확인
    kubectl logs -f -l app.kubernetes.io/name=external-secrets -n external-secrets
    

    externalsecrets 리소스의 Status 필드를 보면 동기화 성공 여부와 마지막 동기화 시간을 알 수 있어요. 만약 에러가 있다면 컨트롤러 로그에서 자세한 내용을 확인할 수 있습니다. 모든 시크릿이 안전하게 동기화되고, 불필요한 권한이 없는지 주기적으로 검토하는 것이 중요합니다. 드디어 제가 원하는 대로 시크릿이 안전하게 동기화되는 걸 봤을 때, 그 성취감이란! 🎉

    External Secrets Operator를 통한 시크릿 동기화 확인 결과

    External Secrets Operator를 통한 시크릿 동기화 확인 결과: kubectl get externalsecrets와 kubectl get secrets 명령의 터미널 출력으로 ESO가 외부 시크릿을 Kubernetes 시크릿으로 성공적으로 동기화했음을 보여줍니다.

    마무리하며: 안전한 Kubernetes 시크릿 관리, 이제 시작입니다!

    오늘은 Kubernetes External Secrets Operator의 보안을 강화하기 위한 10가지 체크리스트를 제가 경험하면서 깨달은 내용들을 함께 나누어봤습니다. 시크릿 관리는 인프라 보안의 핵심 중 하나이며, ESO는 이를 효율적으로 도와주는 강력한 도구입니다. 하지만 어떤 도구든 올바르게, 그리고 안전하게 사용하는 것이 중요하죠. 제가 오늘 공유한 내용들이 여러분의 Kubernetes 환경을 더 안전하게 만드는 데 도움이 되었으면 좋겠습니다.

    핵심은 최소 권한 원칙, 강력한 인증, 주기적인 관리, 그리고 철저한 모니터링입니다. 이 네 가지를 항상 염두에 두시고, 오늘 알려드린 체크리스트를 여러분의 환경에 맞춰 적용해보세요. 처음엔 좀 번거롭게 느껴질 수도 있지만, 한 번 세팅해두면 나중에 훨씬 더 안정적이고 안전한 운영이 가능할 겁니다. 혹시 궁금한 점이 있다면 댓글로 남겨주시고요! 다음 글에서는 ESO와 Admission Controller(어드미션 컨트롤러)를 연동해서 시크릿 접근을 더 세밀하게 제어하는 방법에 대해 다뤄볼 예정입니다. 기대해주세요!

    External Secrets Operator 보안 강화 10가지 체크리스트 요약 인포그래픽

    External Secrets Operator 보안 강화 10가지 체크리스트 요약 인포그래픽: 최소 권한, 강력한 인증, 암호화, RBAC 등 10가지 주요 보안 강화 요소를 아이콘과 함께 시각적으로 요약하여 보여줍니다.

  • [k8s] OpenShift 비용 최적화: 실제 청구서와 예상 비용 불일치 분석

    [k8s] OpenShift 비용 최적화: 실제 청구서와 예상 비용 불일치 분석

    OpenShift 비용 최적화: 실제 청구서와 예상 비용 불일치 분석

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 OpenShift 비용 최적화라는, 생각해보면 모든 인프라 엔지니어들의 영원한 숙제 같은 이야기를 해볼까 합니다. 특히 클라우드 환경에서 OpenShift(오픈시프트)를 운영하시는 분들이라면, 매달 날아오는 청구서를 보고 한숨 쉬어본 경험, 분명 있으실 거예요. 저도 그랬거든요.

    처음 OpenShift를 도입하고 예상 비용을 산정했을 때와, 실제로 청구서에 찍힌 금액을 받아봤을 때의 그 괴리감이란… 😅 이건 마치 홈랩에 새 장비를 들여놓고 전력 소비량을 예상했는데, 막상 한 달 전기요금 고지서를 보고 깜짝 놀라는 것과 비슷하죠. 오늘은 제가 직접 겪었던 OpenShift 비용 불일치 삽질 경험을 솔직하게 공유하고, 어떻게 이 문제를 해결해나갔는지 그 과정을 보여드리려고 합니다. 클라우드 비용 관리, 특히 Kubernetes 비용과 OpenShift 청구서 분석에 관심 있으신 분들이라면 끝까지 읽어주세요!

    OpenShift 클라우드 아키텍처 다이어그램 및 비용 흐름

    OpenShift 클라우드 환경 아키텍처 다이어그램: 복잡하게 얽힌 구성 요소와 각 영역에서 발생하는 비용 흐름을 한눈에.

    OpenShift 비용, 왜 그렇게 복잡할까요?

    사실 OpenShift는 Kubernetes(쿠버네티스)를 기반으로 하는 엔터프라이즈 컨테이너 플랫폼이에요. 단순히 VM(가상 머신) 몇 대 돌리는 것과는 차원이 다르게 복잡한 비용 구조를 가지고 있더라고요. 크게 보면 몇 가지 축으로 나눌 수 있습니다.

    • 라이선스 (Subscription): Red Hat OpenShift 자체에 대한 라이선스 비용이에요. 보통 코어(Core) 수나 노드(Node) 수에 따라 책정되죠.
    • 인프라 (Infrastructure): OpenShift 클러스터가 올라가는 클라우드 자원 비용입니다. 이게 사실 가장 예측하기 어려운 부분 중 하나더라고요.
      • 컴퓨트 (Compute): Control Plane(컨트롤 플레인) 노드와 Worker Node(워커 노드)의 CPU, Memory 사용량에 따른 비용이에요.
      • 스토리지 (Storage): Persistent Volume(영구 볼륨)과 같은 데이터 저장 공간 비용. IOPS(초당 입출력 작업 수)나 용량에 따라 가격이 천차만별이더라고요.
      • 네트워크 (Network): 클러스터 내부 및 외부와의 통신에 사용되는 데이터 전송 비용인데, 특히 Egress(외부 송신 트래픽)가 주범입니다.
    • 부가 서비스 (Add-on Services): 클라우드 제공사의 로드밸런서(Load Balancer), 관리형 데이터베이스(Managed Database), 모니터링 도구 등 OpenShift와 연동되는 다양한 서비스 비용이에요.

    이 모든 요소들이 유기적으로 연결되어 있어서, 한두 가지만 보고 비용을 예측하기가 정말 어렵더라고요. 특히 클라우드 환경에서는 사용량에 따라 실시간으로 요금이 변동되니, Kubernetes 비용 예측이 더욱 까다로울 수밖에 없어요.

    실제 청구서와 예상 비용, 어디서 어긋났나? 삽질 경험담

    제가 운영하는 OpenShift 클러스터에서 OpenShift 청구서를 받아보고 ‘어라?’ 했던 몇 가지 사례를 공유해볼게요.

    사례 1: 스토리지 비용 예상치 못한 증가

    처음엔 Persistent Volume Claim (PVC, 영구 볼륨 요청)을 만들 때 ‘넉넉하게’ 요청해두는 게 좋다고 생각했어요. 나중에 부족해서 확장하는 것보다야 낫다고 봤거든요. 예를 들어, 10GB만 필요한데 100GB짜리 PVC를 요청하는 식이었어요.

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: my-app-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 100Gi # 넉넉하게 100GB 요청
      storageClassName: standard-ssd
    

    근데 이걸 모든 애플리케이션에 적용하다 보니, 실제 사용량은 10%도 안 되는데 청구서에는 100% 용량에 대한 비용이 꼬박꼬박 붙고 있었어요. 당황하지 않을 수 없었습니다. 😱 게다가 자동으로 생성되는 스냅샷(Snapshot) 정책을 제대로 관리하지 못해서, 불필요한 스냅샷들이 쌓여 용량을 잡아먹고 있었더라고요.

    사례 2: 네트워크 비용, 무시무시한 Egress!

    클라우드에서 네트워크 비용의 주범은 항상 Egress(외부 송신 트래픽)예요. 내부에서 아무리 데이터를 주고받아도 비용이 거의 발생하지 않지만, 클러스터 외부로 나가는 데이터는 칼같이 요금이 매겨지죠. 저희 시스템에서는 특정 서비스가 외부 API와 통신하는 양이 많았는데, 이걸 간과했어요.

    또 하나는 불필요한 Ingress(인그레스, 외부 트래픽 진입점) 라우팅이었어요. 특정 PoC(개념 증명) 환경에서 불필요하게 외부로 노출된 서비스가 있었고, 여기에 공격성 트래픽이나 스캐닝 트래픽이 유입되면서 Egress가 발생했던 경우도 있었어요. 아, 진짜 머리 아프더라고요. 😫

    사례 3: 컴퓨트 자원, 워커 노드 스케일링의 오해

    처음에는 ‘오토스케일링(Autoscaling)이 알아서 잘 해주겠지!’ 하는 막연한 기대를 했었어요. 하지만 OpenShift의 Cluster Autoscaler(클러스터 오토스케일러)나 Horizontal Pod Autoscaler(수평 Pod 오토스케일러)를 제대로 설정하지 않으면, 불필요하게 많은 워커 노드(Worker Node)가 유지되거나 Pod(파드)가 과하게 스케일 아웃(Scale Out)될 수 있어요. 특히 새벽 시간처럼 트래픽이 거의 없는 시간에도 워커 노드 수가 줄지 않고 계속 유지되면서 비용이 낭비되고 있었더라고요.

    apiVersion: autoscaling.k8s.io/v1
    kind: HorizontalPodAutoscaler
    metadata:
      name: my-app-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: my-app
      minReplicas: 1
      maxReplicas: 5
      targetCPUUtilizationPercentage: 50 # CPU 사용률이 50%를 넘으면 Pod 증가
    

    targetCPUUtilizationPercentage를 너무 낮게 잡거나, minReplicas를 실제 필요한 것보다 높게 설정해두는 실수를 범했어요. 이런 미세한 설정 오류가 장기적으로 큰 비용 낭비로 이어지더라고요.

    OpenShift 리소스 사용량 및 비용 모니터링 대시보드

    OpenShift 콘솔의 리소스 모니터링 대시보드: Pod, PVC, 네트워크 사용량을 통해 비용 관련 지표를 확인하는 화면.

    OpenShift 비용 최적화를 위한 실전 전략

    이런 삽질들을 겪으면서 얻은 교훈과 최적화 방법을 공유해봅니다. 💡

    1. 스토리지 비용 최적화

    • StorageClass (스토리지 클래스) 정책 검토: 클라우드 제공사마다 다양한 스토리지 타입을 제공해요. 애플리케이션의 요구사항(성능, 내구성)에 맞춰 가장 저렴하면서도 적합한 StorageClass를 선택하는 게 중요해요. 예를 들어, 고성능 SSD가 필요 없는 워크로드에는 HDD 기반 스토리지를 쓰는 거죠.
    • PVC (Persistent Volume Claim) 사용량 모니터링: Prometheus(프로메테우스)나 Grafana(그라파나) 같은 모니터링 툴을 활용해서 실제 PVC 사용량을 주기적으로 확인하고, 필요 이상으로 프로비저닝된 볼륨은 줄여야 해요.
    • Snapshot (스냅샷) 정책 자동화 및 관리: 불필요한 스냅샷이 쌓이지 않도록 명확한 보존 정책을 세우고, 자동화된 스크립트로 관리하는 게 필수적이에요.

    2. 네트워크 비용 최적화

    • Egress (외부 송신) 트래픽 분석: 어떤 서비스가 얼마나 많은 외부 트래픽을 발생시키는지 정확히 파악하는 게 중요해요. 클라우드 제공사의 네트워크 모니터링 도구나 클러스터 내부의 네트워크 플로우(Flow) 모니터링 툴(예: OpenShift Network Observability Operator)을 활용하면 됩니다.
    • 내부 트래픽 최적화: 가능한 한 클러스터 내부에서 통신하도록 설계하고, 서비스 메쉬(Service Mesh, 예: Istio)를 활용해 내부 트래픽 라우팅을 최적화할 수 있어요.
    • CDN (콘텐츠 전송 네트워크) 활용 검토: 정적 콘텐츠 전송에 많은 Egress 비용이 발생한다면, CDN을 도입하여 비용을 절감할 수 있어요.

    3. 컴퓨트 자원 비용 최적화

    • Cluster Autoscaler (클러스터 오토스케일러) 및 HPA (수평 Pod 오토스케일러) 정교화: 워크로드 패턴에 맞춰 minReplicas, maxReplicas, targetCPUUtilizationPercentage 등을 섬세하게 조정해야 해요. 특히 야간이나 주말처럼 유휴 시간이 긴 워크로드의 경우, minReplicas를 0으로 설정하여 완전히 스케일 다운(Scale Down)되도록 하는 것도 효과적이에요.
    • Resource Request/Limit (자원 요청/제한) 정교화: Pod에 필요한 CPU, Memory를 정확하게 예측하여 requests와 limits를 설정하는 게 중요해요. 너무 과하게 잡으면 자원 낭비, 너무 적게 잡으면 OOMKilled(메모리 부족으로 인한 종료) 같은 문제가 생기거든요.
    • 클라우드 비용 관리 도구 활용: Red Hat Advanced Cluster Management (RHACM) for Kubernetes의 비용 관리 기능이나 클라우드 제공사의 비용 관리 대시보드를 적극적으로 활용하여 클러스터 전체의 비용 가시성을 확보하는 게 중요해요.

    비용 가시성 확보: OpenShift 비용 관리 도구 활용

    OpenShift 환경에서 비용을 효과적으로 관리하려면 무엇보다 ‘가시성(Visibility)’이 중요해요. 어디서 돈이 새고 있는지 알아야 막을 수 있겠죠? 제가 추천하는 방법은 다음과 같습니다.

    1. Kube-state-metrics (쿠베 스테이트 메트릭스): Kubernetes API 오브젝트의 상태를 메트릭으로 노출해줘요. Pod의 requests, limits 설정 같은 정보를 알 수 있죠.
    2. Prometheus (프로메테우스) + Grafana (그라파나): kube-state-metrics나 cAdvisor(컨테이너 어드바이저) 등을 통해 수집된 데이터를 Prometheus로 저장하고, Grafana로 시각화하면 클러스터 전체의 리소스 사용량과 추이를 한눈에 파악할 수 있어요.
    3. Red Hat Advanced Cluster Management (ACM) for Kubernetes의 비용 관리 기능: RHACM은 멀티 클러스터 환경을 관리하는 데 특화되어 있는데, 여기에 비용 관리(Cost Management) 기능이 포함되어 있어 클러스터별, 네임스페이스(Namespace)별, 심지어 Pod별 비용을 추정할 수 있도록 도와줘요. 이건 정말 유용하더라고요.

    ⚠️ 주의사항 & 트러블슈팅: 최적화하다가 성능 저하?!

    클라우드 비용 관리를 한다고 너무 무리하게 자원을 줄이다 보면, 오히려 서비스 안정성이나 성능에 문제가 생길 수 있어요. 제가 경험했던 대표적인 사례는 다음과 같습니다.

    • 너무 빡빡한 Resource Limit 설정: Pod의 Memory Limit(메모리 제한)을 너무 낮게 설정했다가, 순간적인 트래픽 증가로 메모리 사용량이 폭증하면서 OOMKilled(메모리 부족으로 인한 종료)가 발생해 Pod가 재시작되는 문제가 있었어요. 결국 애플리케이션 장애로 이어졌죠.
    • Cluster Autoscaler의 aggressive한 설정: 유휴 노드를 너무 빨리 줄이도록 설정했더니, 갑자기 트래픽이 몰려 Pod가 스케줄링(Scheduling)될 때 필요한 워커 노드가 부족해서 서비스 지연이 발생했어요.

    비용 최적화는 단순히 절감만이 목표가 아니라, 최소한의 비용으로 최대한의 가치를 얻는 것이에요. 따라서 비용과 성능 사이의 적절한 균형점을 찾는 게 정말 중요해요. 트래픽 패턴, 애플리케이션의 특성, SLA(서비스 수준 협약) 등을 종합적으로 고려하여 신중하게 접근해야 해요. ✅

    Grafana 대시보드의 OpenShift 리소스 사용량 및 비용 비교 그래프

    Grafana 대시보드: OpenShift 클러스터의 리소스 사용량 및 최적화 전후 예상 비용 변화를 보여주는 시각화 자료.

    마무리: 지속적인 모니터링과 정책 업데이트가 핵심

    OpenShift 비용 최적화는 한 번 설정하고 끝나는 작업이 아니에요. 서비스가 진화하고 트래픽 패턴이 변하듯, 비용 구조도 계속 변하기 마련이죠. 따라서 지속적인 모니터링과 주기적인 정책 업데이트가 필수적이에요.

    저도 여전히 홈랩에서 다양한 OpenShift 환경을 구축하고 비용 효율적인 운영 방안을 실험하고 있어요. 이 글을 통해 클라우드 비용 관리의 어려움을 겪고 계신 분들에게 조금이나마 도움이 되었기를 바랍니다. 다음 글에서는 OpenShift 환경에서 비용 관리 도구를 더 심층적으로 활용하는 방법에 대해 다뤄볼까 합니다. 기대해주세요! 🎉

    OpenShift 비용 최적화 전략 요약 인포그래픽

    OpenShift 비용 최적화 전략 인포그래픽: 스토리지, 네트워크, 컴퓨트 자원별 핵심 최적화 팁을 아이콘과 함께 요약.

  • [Kubernetes] OpenTelemetry K8s 분산 추적: 13년차 삽질 & 활용 사례

    [Kubernetes] OpenTelemetry K8s 분산 추적: 13년차 삽질 & 활용 사례

    [Kubernetes] OpenTelemetry K8s 분산 추적: 13년차 삽질 & 활용 사례

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 마이크로서비스 아키텍처(MSA)를 운영하시는 분들이라면 한 번쯤은 머리 싸매고 고민했을 주제, 바로 분산 추적(Distributed Tracing)에 대해 이야기해보려고 해요. 특히 Kubernetes(쿠버네티스) 환경에서 OpenTelemetry(오픈텔레메트리)를 활용해서 어떻게 이 복잡한 문제를 풀어갈 수 있었는지, 제가 직접 삽질했던 경험들을 솔직하게 공유해볼게요.

    요즘 애플리케이션들은 웬만하면 다 마이크로서비스 형태로 쪼개져 있잖아요? 서비스 간 호출이 꼬리에 꼬리를 물고 이어지다 보니, ‘도대체 이 에러가 어디서부터 시작된 거야?’ 하고 원인을 찾기 시작하면 정말 막막할 때가 많더라고요. 저도 처음엔 로그만 가지고 씨름했는데, 시간이 갈수록 이건 아니다 싶었어요. 그래서 자연스럽게 분산 추적에 관심을 가지게 되었고, 홈랩에서 이것저것 실험하다가 OpenTelemetry라는 보석 같은 녀석을 만나게 되었죠. 🎉

    이 글을 통해 여러분도 저처럼 서비스 트러블슈팅 시간을 확 줄이고, 시스템 전체를 한눈에 파악하는 데 큰 도움을 받으실 수 있을 거예요. 자, 그럼 함께 시작해볼까요?

    OpenTelemetry와 Kubernetes 환경에서 분산 추적이 어떻게 작동하는지 보여주는 전체 아키텍처 다이어그램입니다.

    OpenTelemetry, 분산 추적, 그리고 Observability: 개념 정리

    본격적인 설정에 들어가기 전에, 몇 가지 핵심 개념을 짚고 넘어가야겠죠? 제가 처음 이 분야를 접했을 때 가장 헷갈렸던 부분들이거든요.

    • Observability (옵저버빌리티, 관측 가능성): 시스템 내부 상태를 외부에서 추론할 수 있는 능력이에요. 단순히 ‘이상이 있다/없다’를 넘어 ‘왜 이상이 발생했는지’까지 파악할 수 있는 거죠. 보통 Logs(로그), Metrics(메트릭), Traces(추적) 세 가지 기둥으로 구성돼요.
    • Distributed Tracing (분산 추적): 마이크로서비스 아키텍처에서 사용자 요청이 여러 서비스를 거쳐 처리될 때, 그 전체 흐름을 따라가며 추적하는 기술입니다. 각 서비스 간 호출이 하나의 Trace(트레이스, 추적)로 묶이고, 그 안에서 각 작업 단위는 Span(스팬, 구간)으로 표현돼요. 쉽게 말해, 요청의 여권을 만들어서 각 서비스마다 도장을 찍고 다니게 하는 거라고 보면 돼요.
    • OpenTelemetry (오픈텔레메트리): CNCF(Cloud Native Computing Foundation)에서 주도하는 오픈소스 프로젝트예요. 벤더에 종속되지 않고, 모든 Observability 데이터를 수집하고 내보내는 표준화된 방법을 제공합니다. 애플리케이션 코드에 SDK(Software Development Kit)를 넣어 계측(Instrumentation)하고, OpenTelemetry Collector(컬렉터)를 통해 데이터를 수집하고 원하는 백엔드(Jaeger, Prometheus, Zipkin 등)로 전송할 수 있어요.

    처음엔 이 용어들이 다 비슷비슷하게 느껴졌는데, 결국 OpenTelemetry는 Observability를 구현하기 위한 도구고, 그중 분산 추적은 특히 마이크로서비스 환경에서 빛을 발하는 핵심 기능이라고 이해하면 편해요.

    실전 구현: K8s 환경에서 OpenTelemetry Collector 설정하기

    자, 이제 직접 Kubernetes 클러스터에 OpenTelemetry를 설정해볼 시간입니다. 저는 주로 Helm(헬름)을 사용해서 배포하는 편인데, 이게 관리하기도 편하고 설정도 직관적이더라고요.

    1. OpenTelemetry Collector 배포 전략 선택

    OpenTelemetry Collector는 데이터를 수집하고 처리해서 백엔드로 보내는 역할을 합니다. K8s 환경에서는 보통 두 가지 방식으로 배포할 수 있어요.

    1. Agent (DaemonSet) 모드: 각 노드에 하나씩 Collector를 배포해서, 해당 노드에서 실행되는 애플리케이션들의 트레이스/메트릭을 수집합니다. 노드별로 리소스를 분리할 수 있고 안정적이거든요. 저는 주로 이 방식을 선호해요.
    2. Gateway (Deployment) 모드: 클러스터 내부에 중앙 Collector를 배포해서 모든 트래픽을 한곳으로 모읍니다. 관리는 편하지만, 부하가 한곳에 집중될 수 있고 네트워크 경로가 복잡해질 수 있죠.

    이 글에서는 DaemonSet 모드로 Agent를 배포하는 방법을 기준으로 설명드릴게요. 각 노드에서 효율적으로 데이터를 수집하는 데 유리하거든요.

    2. Helm으로 OpenTelemetry Collector 배포

    먼저 OpenTelemetry Helm Chart를 추가하고 업데이트합니다.

    
    helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
    helm repo update
    

    다음으로, values.yaml 파일을 만들어서 Collector 설정을 커스터마이징해요. 여기서는 Jaeger(예거) 백엔드로 트레이스를 내보내는 설정을 해볼 거고, Jaeger는 분산 추적 데이터를 시각화하는 데 정말 유용한 도구예요.

    
    # otel-collector-values.yaml
    agent:
      mode: "daemonset"
      config:
        receivers:
          otlp:
            protocols:
              grpc:
              http:
        processors:
          batch:
        exporters:
          jaeger:
            endpoint: "jaeger-collector.monitoring.svc.cluster.local:14250" # Jaeger Collector 서비스 주소
            tls:
              insecure: true # 실제 프로덕션 환경에서는 TLS 설정 필요
        service:
          pipelines:
            traces:
              receivers: [otlp]
              processors: [batch]
              exporters: [jaeger]
    
    # Jaeger도 함께 배포한다고 가정합니다. (별도 Helm Chart 사용)
    # jaeger:
    #   agent:
    #     strategy: "daemonset"
    #   collector:
    #     ingress:
    #       enabled: true
    #       hosts:
    #         - jaeger.mydomain.com
    

    위 values.yaml 파일로 Helm을 이용해 Collector를 배포해요.

    
    helm install otel-collector open-telemetry/opentelemetry-collector -f otel-collector-values.yaml -n monitoring --create-namespace
    

    -n monitoring은 monitoring 네임스페이스에 배포하겠다는 의미예요. --create-namespace로 없으면 새로 만들어줍니다. 이 과정이 끝나면 각 노드에 OpenTelemetry Collector Agent가 실행될 거고요.

    Kubernetes 클러스터 내 OpenTelemetry Collector DaemonSet 배포 구성도

    Kubernetes 클러스터 내에서 OpenTelemetry Collector Agent가 DaemonSet으로 배포되어 각 노드의 애플리케이션 트레이스를 수집하는 구성도입니다.

    3. 애플리케이션 계측 (Instrumentation)

    Collector가 준비되었으니, 이제 애플리케이션에서 트레이스를 생성해서 Collector로 보내야 해요. OpenTelemetry는 다양한 언어별 SDK를 제공하거든요. 예를 들어 Python 애플리케이션이라면 다음과 같이 계측할 수 있어요.

    
    from opentelemetry import trace
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import SimpleSpanProcessor
    
    # 리소스 설정 (서비스 이름 등)
    resource = Resource.create({"service.name": "my-python-service"})
    
    # TracerProvider 생성
    tracer_provider = TracerProvider(resource=resource)
    trace.set_tracer_provider(tracer_provider)
    
    # OTLP 익스포터 설정 (Collector Agent의 주소)
    # Kubernetes 내부에서 서비스 이름으로 접근 가능합니다.
    # otel-collector는 'otel-collector-agent' 서비스로 노출됩니다.
    exporter = OTLPSpanExporter(endpoint="otel-collector-agent.monitoring.svc.cluster.local:4317")
    
    # SpanProcessor 등록
    tracer_provider.add_span_processor(SimpleSpanProcessor(exporter))
    
    # Tracer 가져오기
    tracer = trace.get_tracer(__name__)
    
    # 트레이스 생성 예시
    with tracer.start_as_current_span("my-operation") as span:
        span.set_attribute("http.method", "GET")
        span.set_attribute("http.route", "/data")
        # ... 여기에 실제 비즈니스 로직
        print("Doing some work...")
        with tracer.start_as_current_span("sub-operation"):
            print("Doing sub-work...")
    
    print("Trace sent!")
    

    otel-collector-agent.monitoring.svc.cluster.local:4317이 여기서 핵심인데요, 이는 Kubernetes 서비스 디스커버리를 통해 monitoring 네임스페이스의 otel-collector-agent 서비스(OpenTelemetry Collector Agent가 노출하는 gRPC 포트)로 트레이스를 보낸다는 의미예요.

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

    제가 이 과정을 진행하면서 겪었던 몇 가지 삽질과 해결책을 공유해 드릴게요. 여러분은 저처럼 헤매지 마시길!

    1. Collector Agent 포드 상태 불량: kubectl get pods -n monitoring으로 확인했을 때 Collector Agent 포드가 CrashLoopBackOff 상태인 경우가 있었어요. kubectl logs <pod-name> -n monitoring으로 로그를 확인해보니, 설정 파일에 오타가 있거나 리소스 부족으로 인해 시작되지 못하는 경우가 많더라고요. 특히 config: 아래 들여쓰기(indentation) 오류가 잦았습니다.

    2. 네트워크 연결 문제: 애플리케이션에서 Collector로, Collector에서 Jaeger로 트레이스가 전송되지 않는 문제가 있었어요. 이럴 땐 다음을 확인해보세요.

      • Kubernetes Service Name: 애플리케이션에서 Collector로 트레이스를 보낼 때, otel-collector-agent.monitoring.svc.cluster.local:4317 주소가 올바른지 확인해야 합니다. 서비스 이름이 잘못되었거나 네임스페이스가 다르면 연결이 안 되죠.
      • Port: OTLP gRPC 기본 포트인 4317이 맞는지, 또는 OTLP HTTP 기본 포트인 4318이 맞는지 확인하세요.
      • Network Policy: 혹시 Kubernetes Network Policy(네트워크 정책)가 설정되어 있다면, 애플리케이션 파드에서 Collector 파드로의 4317/4318 포트 통신이 허용되어 있는지 확인해야 해요. 저도 이 정책 때문에 한참을 헤맸거든요.
      • Firewall: 클러스터 외부와 통신하는 경우, 방화벽 규칙도 확인해야 합니다.
    3. Jaeger UI에서 트레이스 안 보임: Collector는 잘 동작하는데 Jaeger UI에서 아무것도 안 보이는 경우가 있었어요. 이건 주로 Collector의 exporters 설정 문제이거나 Jaeger Collector 서비스 주소가 잘못된 경우였거든요. Jaeger Collector의 포트(gRPC는 14250, HTTP는 14268)가 맞는지, 서비스 이름이 정확한지 꼼꼼히 확인해야 해요. 💡

    이런 문제들을 해결할 때 가장 중요한 건 로그(Logs)를 꼼꼼히 확인하고, kubectl describe pod 명령어로 파드의 이벤트와 환경 변수를 살펴보는 거예요. 그리고 curl이나 telnet 같은 간단한 도구로 포트 연결 테스트를 해보는 것도 큰 도움이 됩니다.

    OpenTelemetry Collector 트러블슈팅 과정에서 로그를 분석하는 엔지니어의 모습

    OpenTelemetry Collector 설정 중 문제가 발생하여 로그와 Kubernetes 이벤트를 분석하며 트러블슈팅하는 인프라 엔지니어의 모습입니다.

    결과 확인 및 활용 사례

    모든 설정이 완료되고 애플리케이션에서 트레이스를 보내기 시작하면, 이제 Jaeger UI 같은 백엔드에서 멋진 시각화된 트레이스를 확인할 수 있어요. 저도 처음 성공했을 때 ‘드디어 됐다!’ 하고 외쳤던 기억이 나네요. 🎉

    Jaeger UI에 접속해서 서비스 이름을 선택하고 검색하면, 아래와 같이 요청의 전체 흐름을 시각적으로 볼 수 있습니다. 각 스팬이 어떤 서비스에서 얼마나 시간을 소모했는지, 어떤 에러가 발생했는지 한눈에 파악할 수 있어요.

    • 성능 병목 지점 파악: 특정 서비스나 함수 호출에서 유독 시간이 오래 걸린다면, 그 부분이 성능 병목 지점일 가능성이 높아요. 트레이스를 통해 정확히 어디서 시간이 지연되는지 찾아낼 수 있죠.
    • 에러 원인 분석: 에러가 발생한 스팬을 클릭하면 관련 로그나 태그(tags) 정보를 확인하여 문제의 근본 원인을 빠르게 파악할 수 있어요. 수많은 로그를 뒤지는 것보다 훨씬 효율적이죠.
    • 서비스 의존성 파악: 복잡한 마이크로서비스 간의 호출 관계를 시각적으로 이해하는 데 큰 도움이 돼요. 새로운 팀원이 합류했을 때 시스템 구조를 설명하는 자료로도 활용할 수 있거든요.
    Jaeger UI에서 분산 추적 데이터가 성공적으로 시각화된 대시보드 스크린샷

    Jaeger UI에서 애플리케이션의 분산 추적 데이터가 성공적으로 수집되어 시각화된 대시보드 스크린샷입니다. 서비스 간 호출 흐름과 각 스팬의 지연 시간을 보여줍니다.

    마무리하며: OpenTelemetry, 선택이 아닌 필수

    OpenTelemetry를 Kubernetes 환경에서 설정하고 활용하는 과정이 처음엔 다소 복잡하게 느껴질 수 있어요. 하지만 한 번 구축하고 나면 얻을 수 있는 이점은 정말 상상 이상이라고 생각합니다. 특히 복잡한 마이크로서비스 아키텍처에서는 분산 추적이 선택이 아닌 필수가 되고 있거든요.

    저도 13년 동안 수많은 인프라 환경을 운영하면서, 이렇게 시스템의 속을 들여다볼 수 있는 도구의 중요성을 절실히 느꼈어요. OpenTelemetry는 단순히 에러를 찾는 것을 넘어, 시스템의 건강 상태를 지속적으로 모니터링하고 최적화하는 데 큰 인사이트를 제공해줍니다.

    다음 글에서는 OpenTelemetry를 활용해서 메트릭(Metrics)과 로그(Logs)를 수집하고 시각화하는 방법에 대해 좀 더 깊이 있게 다뤄볼 예정이에요. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요!

    OpenTelemetry가 제공하는 Observability의 세 가지 핵심 기둥인 Logs, Metrics, Traces를 시각적으로 요약한 인포그래픽입니다.

  • [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    [Kubernetes] Helm 차트 비용 최적화 전략: 클라우드 리소스 낭비 줄이기

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 클라우드 비용 때문에 밤잠 설치셨던 분들을 위한 이야기를 해볼까 합니다. <code>Kubernetes 환경에서 Helm을 쓰다 보면 편리함 뒤에 숨겨진 클라우드 리소스 낭비라는 복병을 만나게 될 때가 많거든요. 저도 처음엔 이게 뭔가 싶었는데, 월말 청구서를 받아보고 깜짝 놀라 아찔했던 경험이 한두 번이 아닙니다. 😮

    특히 Helm 차트는 재사용성과 배포 편의성을 높여주지만, 기본값이 너무 후하거나, 최적화 없이 무심코 배포하다 보면 불필요하게 많은 리소스를 할당하게 됩니다. 결국 이 모든 게 불어나는 클라우드 비용으로 이어지죠. 그래서 오늘은 제가 직접 삽질하며 터득한 Helm 비용 최적화 전략에 대해 이야기해볼까 합니다. 쿠버네티스 비용 절감, 함께 고민하고 해결해나가 봐요!

    클라우드 환경에서 Helm을 사용하여 Kubernetes 리소스를 배포하고, 이 과정에서 리소스 낭비가 발생하는 상황을 개념적으로 보여주는 다이어그램입니다.

    Helm과 클라우드 비용 낭비 이해하기

    Helm(헬름)은 Kubernetes(쿠버네티스) 애플리케이션을 관리하는 패키지 매니저입니다. 복잡한 Kubernetes 배포를 Chart(차트)라는 형태로 묶어서 쉽게 설치, 업데이트, 삭제할 수 있게 해주죠. 마치 apt나 yum처럼요. 정말 편리한 도구인데, 이 편리함이 때로는 방심을 부르기도 합니다.

    많은 Helm 차트들은 ‘일단 잘 돌아가게’ 만드는 데 초점이 맞춰져 있습니다. 그래서 기본 values.yaml 파일에 명시된 리소스 요청(requests)이나 제한(limits)이 실제 필요한 것보다 훨씬 크게 잡혀있는 경우가 많아요. 예를 들어, 작은 개발 환경인데도 CPU 1코어, 메모리 2GB를 기본으로 할당한다거나 하는 식이죠. 이게 여러 개의 Pod(파드)로 복제되고, 여러 서비스가 배포되면 눈덩이처럼 불어나는 건 순식간입니다. 😱

    • 리소스 요청 (requests): Kubernetes 스케줄러가 Pod를 노드에 할당할 때 필요한 최소 리소스입니다. 이만큼은 보장해달라는 의미죠.
    • 리소스 제한 (limits): Pod가 사용할 수 있는 최대 리소스입니다. 이 이상은 사용하지 못하게 막는 거죠.

    이 두 가지 설정이 너무 높게 잡히면, 사용하지도 않는 리소스를 미리 예약하고 돈을 내는 꼴이 됩니다. 클라우드 비용 분석을 해보면 예상치 못한 곳에서 돈이 새고 있다는 걸 알 수 있어요. K8s 배포 최적화의 첫걸음은 바로 여기서 시작됩니다.

    실전 Helm 차트 리소스 최적화 단계

    1. 정확한 리소스 요청/Limit 설정

    가장 기본적이면서도 중요한 단계입니다. values.yaml 파일에서 각 컨테이너의 resources 섹션을 꼼꼼히 검토해야 합니다. 제가 직접 운영하던 서비스에서 컨테이너들이 실제 얼마나 리소스를 쓰는지 모니터링 툴로 쭉 살펴봤거든요. 처음엔 그냥 대충 넣어놨었는데, 실제로 써보니 CPU는 0.1코어, 메모리는 256MB면 충분하더라고요. 이걸 1코어 1GB로 해놓고 있었다니… 🤦‍♂️

    예시 values.yaml:

    # myapp/values.yaml
    replicaCount: 2
    
    image:
      repository: myapp/web
      pullPolicy: IfNotPresent
      tag: "1.0.0"
    
    resources:
      requests:
        cpu: 100m  # 0.1 core
        memory: 256Mi
      limits:
        cpu: 200m  # 0.2 core
        memory: 512Mi
    
    service:
      type: ClusterIP
      port: 80
    

    여기서 cpu: 100m은 0.1 코어를 의미하고, memory: 256Mi는 256 메비바이트를 의미합니다. 처음에는 조금 타이트하게 설정하고, 서비스 운영 중 모니터링하면서 점진적으로 늘려나가는 것을 추천해요. ⚠️ 너무 타이트하게 잡으면 OOMKilled(메모리 부족으로 인한 강제 종료)나 CPU Throttling(CPU 사용 제한으로 인한 성능 저하)이 발생할 수 있으니 주의해야 합니다.

    2. HPA, VPA를 활용한 자동 스케일링

    수동으로 리소스를 조정하는 건 한계가 있습니다. 사용량에 따라 자동으로 리소스를 조절해주는 Horizontal Pod Autoscaler (HPA, 수평 Pod 자동 확장)와 Vertical Pod Autoscaler (VPA, 수직 Pod 자동 확장)를 적극 활용해야 합니다. 제가 홈랩에서 여러 서비스에 적용해봤는데, 확실히 피크 타임에는 리소스를 늘려주고, 한가할 때는 줄여주니 Helm 리소스 관리에 엄청난 도움이 되더라고요!

    • HPA: Pod의 개수를 자동으로 늘리거나 줄입니다. CPU 사용률, 메모리 사용률, 또는 커스텀 메트릭을 기준으로 합니다.
    • VPA: Pod의 requests와 limits를 자동으로 조절합니다. Pod를 재시작해야 적용되는 경우가 많으니 주의해야 합니다.

    values.yaml에 HPA를 추가하는 예시:

    # myapp/values.yaml
    ...
    hpa:
      enabled: true
      minReplicas: 1
      maxReplicas: 5
      targetCPUUtilizationPercentage: 70 # CPU 사용률이 70%를 넘으면 Pod 개수 증가
      targetMemoryUtilizationPercentage: 80 # 메모리 사용률이 80%를 넘으면 Pod 개수 증가
    ...
    

    VPA는 조금 더 복잡한 설정이 필요하지만, Kubernetes 클러스터에 VPA 컨트롤러가 설치되어 있다면 values.yaml에서 간단히 활성화할 수 있습니다. K8s 배포 최적화를 위한 필수 요소라고 생각합니다.

    Helm 차트 values.yaml 리소스, HPA, VPA 설정 구성 다이어그램

    Helm 차트의 values.yaml 파일에서 리소스 요청(requests), 제한(limits), HPA, VPA 설정을 보여주는 YAML 코드 스니펫과 설명이 포함된 구성 다이어그램입니다.

    3. 불필요한 리소스 제거 및 효율적인 차트 설계

    가끔 Helm 차트를 보면 기본적으로 따라오는 사이드카 컨테이너나 불필요한 리소스들이 있어요. 예를 들어, 로깅 에이전트나 모니터링 에이전트가 모든 Pod에 기본적으로 포함되어 있는데, 이미 클러스터 레벨에서 DaemonSet(데몬셋)으로 관리하고 있다면 중복일 수 있습니다. 이런 것들은 과감히 values.yaml에서 enabled: false로 꺼버려야 합니다.

    • 불필요한 컨테이너/Sidecar(사이드카) 비활성화: values.yaml을 확인하여 사용하지 않는 기능을 끄세요.
    • 효율적인 _helpers.tpl 활용: 공통적으로 사용되는 라벨, 어노테이션 등을 _helpers.tpl 파일에 정의하여 중복을 줄이고 일관성을 유지합니다.
    • Node Affinity(노드 어피니티) / Tolerations(톨러레이션): 특정 워크로드를 특정 노드에 배치하여 리소스 활용도를 높일 수 있습니다. 예를 들어, GPU 노드에만 특정 머신러닝 워크로드를 배치하는 거죠.

    ⚠️ 주의사항과 삽질 경험

    Helm 비용 최적화는 한 번에 끝나지 않는 여정입니다. 저도 여러 번 뼈아픈 경험을 했는데요.

    • 과도한 리소스 절감은 독이 됩니다: 너무 아끼려다가 서비스가 느려지거나 뻗어버리는 경험, 저도 해봤습니다. 😭 결국 비싼 야간 작업비와 고객 불만으로 이어지더라고요. 항상 모니터링과 테스트가 병행되어야 합니다.
    • Helm Release(헬름 릴리즈) 정리: helm uninstall만으로는 완전히 사라지지 않는 경우가 있습니다. --purge 옵션을 사용하거나, Kubernetes 리소스를 직접 확인해서 삭제하는 습관을 들이세요. Secret(시크릿)이나 PersistentVolumeClaim(영구 볼륨 클레임) 같은 것들이 남아있어 불필요한 비용을 발생시키기도 합니다.
    • VPA와 HPA의 충돌: VPA와 HPA를 동시에 사용할 때 주의해야 합니다. 서로 다른 방식으로 스케일링을 시도하기 때문에 충돌이 발생할 수 있습니다. 일반적으로 VPA는 requests/limits를 조절하고, HPA는 Pod 개수를 조절하므로, VPA가 requests/limits를 관리하고 HPA는 replicaCount만 조절하도록 설정하는 것이 좋습니다.

    검증 및 지속적인 모니터링

    최적화 작업을 마쳤다면, 반드시 그 효과를 검증해야 합니다. Prometheus(프로메테우스)와 Grafana(그라파나) 같은 모니터링 툴을 활용해서 Pod별 CPU, 메모리 사용량을 꾸준히 지켜봐야 합니다. 클라우드 제공업체의 대시보드에서 실제 청구되는 비용도 확인해야겠죠. 저는 최적화 작업 후 한 달 정도 지켜보니, 이전보다 CPU 사용률은 높아졌지만, 전체 Pod 개수는 줄고 클라우드 비용도 눈에 띄게 줄어드는 걸 보면서 얼마나 뿌듯했는지 모릅니다. 🎉

    Helm 비용 최적화 전후 클라우드 리소스 사용량 및 비용 변화 대시보드 그래프

    최적화 전후의 클라우드 리소스 사용량 및 비용 변화를 보여주는 대시보드 그래프입니다.

    마무리하며: Helm 비용 최적화는 끝없는 여정

    Helm 차트 비용 최적화는 한 번 설정하고 끝나는 작업이 아닙니다. 서비스의 트래픽 패턴이 바뀌거나, 새로운 기능을 추가할 때마다 리소스 요구사항도 달라지거든요. 그래서 주기적인 검토와 튜닝이 필수입니다. 마치 자동차 정비하듯이요.

    오늘 제가 공유한 Helm 리소스 관리 팁들이 여러분의 클라우드 비용 절감에 조금이나마 도움이 되었으면 좋겠습니다. 처음엔 복잡하고 어렵게 느껴질 수 있지만, 한번 손에 익으면 클라우드 자원을 훨씬 효율적으로 사용할 수 있게 될 거예요. FinOps(파인옵스)의 관점에서 봐도, 엔지니어가 직접 비용 최적화에 참여하는 것은 매우 중요합니다.

    혹시 이런 경험 있으신가요? 아니면 더 좋은 팁이 있다면 댓글로 공유해주세요! 저도 계속 배우고 있습니다. 다음 글에서는 더 재미있는 Kubernetes 이야기로 찾아오겠습니다. 그때까지 모두 즐거운 삽질(?) 되시길 바랍니다! 😊

    Helm 비용 최적화 핵심 전략 요약 및 비용 절감 효과 비교 인포그래픽

    Helm 비용 최적화의 핵심 전략들을 요약하고, 최적화 전후의 비용 절감 효과를 인포그래픽 형태로 비교하는 이미지입니다.

  • [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)에 대해 더 깊이 파고드는 내용을 다룰 예정이니 기대해주세요!

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

  • [Kubernetes] Cilium CNI 성능 벤치마크: Calico, Flannel과 직접 비교

    [Kubernetes] Cilium CNI 성능 벤치마크: Calico, Flannel과 직접 비교

    [Kubernetes] Cilium CNI 성능 벤치마크: Calico, Flannel과 직접 비교

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 Kubernetes(쿠버네티스) 환경에서 가장 중요한 요소 중 하나인 CNI(Container Network Interface, 컨테이너 네트워크 인터페이스) 솔루션들의 성능을 제가 직접 벤치마크해본 경험을 공유하려고 해요. 특히 요즘 핫한 Cilium(실리움)이 기존의 Calico(칼리코), Flannel(플라넬)과 비교해서 얼마나 뛰어난지 궁금했거든요.

    인프라 엔지니어로 일하다 보면 ‘네트워크 성능이 왜 이렇게 느리지?’ 하는 답답함을 겪을 때가 많습니다. 특히 마이크로서비스 아키텍처에서는 Pod(파드) 간의 통신이 엄청나게 빈번하게 일어나기 때문에, CNI의 선택이 전체 애플리케이션 성능에 결정적인 영향을 미치죠. 저도 홈랩에서 이것저것 실험해보면서 이 문제로 삽질을 좀 했거든요. 그래서 이번 기회에 주요 CNI 솔루션들을 직접 비교해보면서 그 차이를 피부로 느껴보고 싶었습니다.

    CNI란 뭐고 어떤 종류가 있을까?

    먼저 CNI가 뭔지 간단하게 짚고 넘어갈게요. CNI는 컨테이너 런타임과 네트워크 플러그인 간의 표준 인터페이스를 정의한 규약입니다. 쉽게 말해, Kubernetes Pod들이 어떻게 서로 통신하고 외부와 연결될지 결정하는 ‘네트워크 길잡이’ 역할을 하는 거죠.

    • Flannel (플라넬): 가장 단순하고 설치가 쉬운 CNI 중 하나입니다. 주로 Overlay Network(오버레이 네트워크) 방식인 VXLAN(Virtual Extensible LAN)을 사용해요. 설정이 간단해서 초보자들이나 소규모 클러스터에서 많이 선택하지만, 성능 오버헤드가 좀 있는 편입니다.
    • Calico (칼리코): 네트워크 정책(Network Policy) 기능이 강력하고 성능도 준수해서 많은 프로덕션 환경에서 사용됩니다. IP-in-IP 터널링이나 BGP(Border Gateway Protocol) 라우팅 방식을 주로 쓰는데, 특히 BGP 모드에서는 오버헤드가 적어서 좋은 성능을 보여주죠. 보안 기능도 뛰어나고요.
    • Cilium (실리움): 오늘 주인공이죠! eBPF(extended Berkeley Packet Filter, 확장된 버클리 패킷 필터)라는 기술을 기반으로 합니다. eBPF는 리눅스 커널 내부에서 프로그램을 실행할 수 있게 해주는 기술인데, 이걸 활용해서 Cilium은 컨테이너 네트워크 트래픽을 커널 레벨에서 직접 처리합니다. 이 덕분에 기존 CNI들이 가졌던 성능 오버헤드를 크게 줄이고, 더 세밀한 네트워크 정책과 가시성(Observability)을 제공할 수 있게 되는 거예요. 처음엔 이게 뭔가 싶었는데, 써보니 진짜 매력적이더라고요.
    Flannel, Calico, Cilium CNI 솔루션의 네트워크 아키텍처 비교 다이어그램

    이 이미지는 Flannel, Calico, Cilium 각 CNI 솔루션의 네트워크 아키텍처를 시각적으로 비교하여, 데이터 플레인 처리 방식의 차이를 보여줍니다.

    실전 구현: 벤치마크 환경 준비와 테스트 방법

    자, 그럼 이제 제가 어떻게 벤치마크를 진행했는지 공유해볼게요. 홈랩에 Kubernetes 클러스터를 kubeadm으로 구성했고, 각 CNI를 순서대로 설치해가며 성능을 측정했습니다.

    1. Cilium CNI 벤치마크를 위한 환경 준비

    저는 3노드(마스터 1, 워커 2) Kubernetes 클러스터를 사용했습니다. 테스트의 공정성을 위해 각 CNI를 설치하기 전에는 항상 클러스터를 초기화하고 깨끗한 상태에서 시작했어요. 벤치마크 도구로는 네트워크 성능 측정에 널리 사용되는 netperf를 선택했습니다. TCP_STREAM(대역폭), TCP_RR(요청/응답 지연 시간) 두 가지 모드로 측정했어요.

    
    # netperf 설치 (Ubuntu 기준)
    sudo apt update
    sudo apt install netperf -y
    
    # 벤치마크용 Pod 배포 예시
    kubectl apply -f - <

    각 CNI를 설치한 후, netperf-server Pod를 배포하고 다른 Pod에서 netperf 클라이언트를 실행하여 서버 Pod로 트래픽을 보냈습니다. Pod-to-Pod 통신과 Pod-to-Service 통신 두 가지 시나리오를 모두 측정했어요.

    2. Cilium 포함 각 CNI 설치 및 성능 측정

    1. Flannel 설치 및 측정:
      
      kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
      # Pod Ready 확인 후 netperf 측정
      
    2. Calico 설치 및 측정:
      
      kubectl apply -f https://docs.tigera.io/calico/latest/manifests/calico.yaml
      # Pod Ready 확인 후 netperf 측정
      
    3. Cilium 설치 및 성능 측정:
      
      helm repo add cilium https://helm.cilium.io/
      helm install cilium cilium/cilium --version 1.15.5 \
        --namespace kube-system \
        --set ipam.mode=kubernetes \
        --set tunnel=vxlan \
        --set egressGateway.enabled=true # 예시 설정
      # Pod Ready 확인 후 netperf 측정
      

    ⚠️ 주의사항: 각 CNI를 설치하기 전에 기존 CNI를 완전히 삭제하고 클러스터를 초기화하거나, CNI 관련 리소스를 제거해야 합니다. 그렇지 않으면 네트워크 충돌로 Pod들이 정상적으로 시작되지 않을 수 있거든요. 제가 처음엔 이걸 놓쳐서 Pod들이 Pending(대기) 상태에서 벗어나지 못하는 삽질을 좀 했습니다 ㅎㅎ.

    이 이미지는 Kubernetes 클러스터에 Cilium CNI를 helm을 이용하여 설치하는 과정을 보여주는 터미널 스크린샷입니다.

    ⚠️ 삽질 경험과 Cilium 트러블슈팅

    솔직히 말씀드리면, Cilium을 처음 설치할 때 좀 애먹었습니다. kubeadm으로 구성한 클러스터에서 Cilium Pod들이 CrashLoopBackOff(크래시 루프백 오프) 상태에 빠지더라고요. 확인해보니 커널 버전 문제와 eBPF 관련 의존성 설정이 제대로 안 된 경우였습니다. Cilium은 eBPF 기반이라 리눅스 커널 버전이 어느 정도 이상이어야 하고, 특정 커널 모듈이나 설정이 활성화되어 있어야 하거든요. 예를 들어, CONFIG_BPF_JIT 같은 커널 옵션이 활성화되어 있는지 확인해야 합니다.

    이런 문제 때문에 Cilium 설치 전에 공식 문서를 꼼꼼히 읽어보고, cilium preflight check 같은 명령어로 환경을 미리 점검하는 게 정말 중요합니다. 덕분에 커널 컴파일 옵션까지 찾아보는 등 깊은 공부를 하게 됐네요. 역시 삽질은 최고의 공부 방법입니다!

    검증 결과: Cilium CNI의 성능은 정말 압도적일까?

    수많은 테스트와 삽질 끝에 얻은 결과는 예상대로였습니다. Cilium이 Calico, Flannel 대비 전반적으로 더 우수한 네트워크 성능을 보여줬거든요.

    • Throughput (대역폭): 특히 대량의 데이터를 전송하는 TCP_STREAM 테스트에서 Cilium은 Calico나 Flannel보다 더 높은 처리량을 기록했습니다. eBPF 덕분에 커널 레벨에서 패킷을 직접 처리하면서 컨텍스트 스위칭(Context Switching) 오버헤드가 크게 줄어든 거 같아요.
    • Latency (지연 시간): 요청-응답(TCP_RR) 테스트에서도 Cilium이 가장 낮은 지연 시간을 보여줬습니다. 이는 마이크로서비스 간의 빈번한 API 호출에 있어 정말 중요한 이점이거든요. 애플리케이션의 반응성이 훨씬 좋아지는 거죠.

    물론, 테스트 환경이나 네트워크 구성에 따라 결과는 달라질 수 있습니다. 하지만 제 홈랩 환경에서는 Cilium의 성능 향상이 눈에 띄게 확인되었어요. 특히 Pod 간의 통신이 잦은 환경이라면 Cilium CNI의 도입을 진지하게 고려해볼 만하다고 생각합니다.

    Cilium, Calico, Flannel CNI 성능 벤치마크 결과 비교 그래프

    이 이미지는 Cilium, Calico, Flannel 각 CNI 솔루션의 네트워크 Throughput(대역폭)과 Latency(지연 시간) 벤치마크 결과를 보여주는 비교 그래프입니다.

    결론: Cilium은 미래의 CNI 표준이 될까?

    이번 벤치마크를 통해 Cilium의 강력한 성능을 직접 확인할 수 있었습니다. eBPF라는 혁신적인 기술을 기반으로 기존 CNI의 한계를 뛰어넘는 모습을 보여줬네요. 특히 고성능이 요구되는 환경이나 복잡한 네트워크 정책, 그리고 뛰어난 가시성이 필요한 곳이라면 Cilium은 정말 훌륭한 선택지가 될 겁니다.

    하지만 Cilium이 만능은 아닙니다. eBPF에 대한 이해가 필요하고, 상대적으로 커널 의존성이 높아서 환경 구성에 더 신경 써야 할 부분이 있어요. 설치와 설정도 Calico나 Flannel보다는 복잡할 수 있다는 점도 고려해야 합니다.

    결론적으로, 간단하고 빠른 배포가 우선이라면 Flannel, 안정적인 성능과 강력한 네트워크 정책이 필요하다면 Calico, 그리고 최고의 성능과 보안, 고급 가시성을 원한다면 Cilium을 고려해보시길 추천합니다. 저도 이제 프로덕션 환경에서 Cilium을 더 적극적으로 도입해볼까 고민 중입니다.

    다음번엔 Cilium의 네트워크 정책(Network Policy) 기능과 서비스 메시(Service Mesh) 연동에 대해 더 자세히 다뤄볼 예정이니 기대해주세요! 긴 글 읽어주셔서 감사합니다.

    Flannel, Calico, Cilium CNI 솔루션 장단점 및 추천 시나리오 요약 인포그래픽

    이 이미지는 Flannel, Calico, Cilium 각 CNI 솔루션의 주요 특징, 장점, 단점 및 추천 사용 시나리오를 요약 비교한 인포그래픽입니다.

  • [k8s] K3s와 AWX 통합 사례 연구: 경량 Kubernetes 자동화 워크플로우 구축

    [k8s] K3s와 AWX 통합 사례 연구: 경량 Kubernetes 자동화 워크플로우 구축

    안녕하세요, 13년차의 서버실입니다. 오늘은 제가 홈랩에서 직접 경험하고 삽질하면서 얻은 지식 하나를 여러분과 공유해볼게요. 바로 K3s(케이쓰리쓰)와 AWX(에이더블유엑스)를 통합하여 경량 Kubernetes(쿠버네티스) 환경에서 자동화 워크플로우를 구축하는 사례입니다.

    작은 규모의 인프라나 홈랩에서 Kubernetes를 운영하다 보면, 리소스 제약 때문에 고민이 많아지죠. 게다가 반복적인 배포나 설정 변경 작업을 수동으로 하다 보면 시간 낭비는 물론이고 휴먼 에러 발생 확률도 높아지고요. 저도 “이걸 어떻게 하면 좀 더 효율적으로 관리할 수 있을까?” 하는 고민에 빠져있었거든요. 그러다가 경량 Kubernetes인 K3s와 Ansible(앤서블) 기반의 자동화 플랫폼인 AWX의 조합을 떠올리게 됐습니다.

    이 둘을 잘 엮으면 리소스 효율적인 환경에서 강력한 자동화 시스템을 만들 수 있겠다는 확신이 들었죠. 그래서 직접 부딪혀가며 구축해봤고, 그 과정과 삽질 경험을 솔직하게 풀어보려 합니다. 혹시 여러분도 비슷한 고민을 하고 계셨다면, 이 글이 좋은 가이드가 될 수 있을 거예요. 💡

    K3s와 AWX를 통합한 자동화 워크플로우의 전체 아키텍처 다이어그램

    K3s 클러스터 위에 AWX가 배포되어 외부 Git 리포지토리의 Ansible 플레이북을 가져와 다른 K3s/Kubernetes 클러스터에 배포하는 통합 아키텍처 다이어그램입니다.

    K3s와 AWX, 왜 이 조합일까요?

    먼저, 이 두 가지 기술이 무엇인지, 그리고 왜 함께 사용하면 시너지가 나는지 간단하게 짚고 넘어가겠습니다.

    K3s: 가볍지만 강력한 경량 Kubernetes

    K3s(케이쓰리쓰)는 Rancher Labs(랜처 랩스)에서 만든 경량 Kubernetes(쿠버네티스) 배포판입니다. 이름에서 알 수 있듯이 ‘K8s(케이츠) – 5 = K3s’라는 유머러스한 의미를 가지고 있어요. 그만큼 바이너리 크기가 작고, 메모리 사용량도 적으니까요. 일반적인 Kubernetes는 컨트롤 플레인(Control Plane) 구성 요소들이 많은 리소스를 요구하는데, K3s는 SQLite를 기본 데이터베이스로 사용하고 불필요한 기능을 제거해서 엣지 컴퓨팅(Edge Computing), IoT(사물 인터넷), 또는 저사양 환경에 정말 딱 맞습니다. 제가 홈랩에서 운영하는 라즈베리 파이(Raspberry Pi) 클러스터 같은 곳에 정말 찰떡이더라고요. ✅

    AWX: Ansible 자동화 플랫폼의 중앙 허브

    AWX(에이더블유엑스)는 Red Hat(레드햇)의 Ansible Tower(앤서블 타워)의 오픈소스 업스트림 프로젝트입니다. Ansible(앤서블)은 강력한 IT 자동화 도구인데, AWX는 이 Ansible 플레이북(Playbook)들을 웹 UI를 통해 중앙에서 관리하고 실행할 수 있게 해줍니다. 그냥 복잡한 커맨드 라인(Command Line) 없이도 프로젝트, 인벤토리(Inventory), 자격 증명(Credential) 등을 관리하고, 잡 템플릿(Job Template)을 만들어 반복적인 작업을 예약하거나 실행 결과를 쉽게 확인할 수 있죠. 특히 팀 단위로 자동화 작업을 공유하고 접근 제어를 해야 할 때 진짜 빛을 발합니다. 🎉

    통합의 시너지: 경량 K3s 위의 자동화 플랫폼

    저는 K3s의 가벼움 위에 AWX를 올려서 경량 Kubernetes 환경을 위한 자동화 허브를 구축하고 싶었습니다. K3s에서 돌아가는 애플리케이션의 배포나 설정을 AWX를 통해 관리하려는 거였거든요. 예를 들어, 새로운 컨테이너 이미지를 빌드하면 AWX가 자동으로 K3s 클러스터에 배포하도록 하거나, 특정 서비스의 스케일링(Scaling) 작업을 AWX 잡 템플릿으로 만들어서 필요할 때마다 클릭 한 번으로 실행하는 식입니다. 이렇게 하면 인프라 관리가 정말 수월해지더라고요. 💡

    실전 구현: K3s 위에 AWX 배포하기

    이제 제가 직접 K3s 클러스터에 AWX를 배포했던 과정을 단계별로 설명해 드릴게요. 저는 이미 K3s 클러스터가 구성되어 있다는 가정하에 진행했습니다. 아직 K3s 설치가 안 되어 있다면, 다음 명령어로 간단하게 설치할 수 있습니다.

    curl -sfL https://get.k3s.io | sh -

    1. AWX Operator 설치

    AWX는 Kubernetes 환경에 AWX Operator(오퍼레이터)를 통해 배포하는 것이 가장 일반적입니다. Operator는 특정 애플리케이션(여기서는 AWX)을 Kubernetes의 컨트롤러처럼 관리해주는 도구라고 생각하시면 돼요. 배포부터 업데이트, 스케일링까지 알아서 처리해주니까요.

    AWX Operator를 설치하려면 공식 AWX Operator 저장소에서 최신 설치 가이드를 확인하시고, 다음 명령어로 설치합니다.

    kubectl apply -f https://raw.githubusercontent.com/ansible/awx-operator/devel/deploy/operator.yaml

    이 명령어가 AWX Operator와 필요한 CRD(Custom Resource Definition, 사용자 정의 리소스 정의)를 모두 설치해줍니다. CRD는 Kubernetes가 AWX라는 새로운 리소스 타입을 이해할 수 있도록 해주는 설정 파일이거든요. 이 과정을 거치면 awx-operator 파드(Pod)가 실행되면서 AWX 인스턴스를 배포할 준비가 완료됩니다.

    2. AWX 인스턴스 배포

    Operator가 준비되었다면, 이제 우리가 원하는 AWX 인스턴스를 생성할 차례입니다. AWX라는 Custom Resource(CR) 객체를 생성하면, Operator가 이 CR을 감지하고 실제 AWX 애플리케이션을 배포해줍니다. 저는 awx.yaml이라는 파일을 만들어서 다음과 같이 설정했습니다.

    apiVersion: awx.ansible.com/v1beta1
    kind: AWX
    metadata:
      name: my-awx
    spec:
      # AWX Operator가 배포할 AWX 인스턴스의 이름
      # 여기서는 my-awx로 지정했습니다.
    
      # ingressType: Ingress를 사용하여 Kubernetes Ingress를 통해 AWX에 접근하도록 설정합니다.
      # K3s는 기본적으로 Traefik Ingress Controller를 포함하고 있어 편리합니다.
      ingressType: Ingress
      # ingress_host: AWX에 접근할 호스트 이름을 지정합니다.
      # 실제 환경에서는 DNS에 이 호스트를 등록해야 합니다.
      ingress_host: awx.myhomelab.local
      
      # 기타 설정 (필요에 따라 주석 해제 및 수정)
      # postgres_storage_class: AWX 데이터베이스를 위한 Persistent Volume Claim(PVC)의 StorageClass를 지정합니다.
      # 홈랩 환경에서는 hostPath나 local-storage 등을 사용할 수 있습니다.
      # postgres_storage_class: standard
      
      # web_extra_volume_mounts: AWX 웹 파드에 추가 볼륨을 마운트할 때 사용합니다.
      # e.g., 인증서 마운트 등
      # tasks_extra_volume_mounts: AWX 태스크 파드에 추가 볼륨을 마운트할 때 사용합니다.
      
      # image_version: 사용할 AWX 이미지 버전 (특정 버전을 고정할 경우)
      # image_version: "23.3.0"
      
      # resource_requirements: AWX 파드들의 리소스 요청/제한을 설정합니다.
      # 작은 환경에서는 이 부분을 적절히 조절해야 합니다.
      # web_resource_requirements:
      #   requests:
      #     cpu: 500m
      #     memory: 1Gi
      #   limits:
      #     cpu: 1000m
      #     memory: 2Gi
    

    이 파일을 저장하고 kubectl apply -f awx.yaml 명령어를 실행하면 AWX Operator가 AWX 인스턴스에 필요한 Deployment(디플로이먼트), Service(서비스), Ingress(인그레스), Persistent Volume Claim(PVC) 등을 자동으로 생성하기 시작합니다.

    K3s 클러스터에 AWX 파드들이 정상적으로 실행 중임을 나타내는 터미널 화면

    AWX 웹 UI의 로그인 화면 또는 kubectl get pods -n awx 명령어를 실행했을 때 AWX 관련 파드들이 모두 Running 상태인 것을 보여주는 스크린샷입니다.

    ⚠️ 삽질 경험과 트러블슈팅

    제가 이 과정을 진행하면서 겪었던 몇 가지 삽질 경험과 해결 방법을 공유합니다. 여러분은 저처럼 헤매지 않으시길 바라요! 😂

    1. 리소스 부족 문제

    K3s는 가볍지만, AWX는 생각보다 많은 리소스를 필요로 합니다. 특히 데이터베이스(PostgreSQL)와 웹, 태스크 파드들이 동시에 돌아가기 때문에, 2GB RAM 이하의 시스템에서는 버벅이거나 파드가 계속 재시작되는 문제가 발생할 수 있어요. 최소 4GB RAM, 2코어 CPU 이상을 권장합니다. 저도 처음엔 라즈베리 파이 4(4GB 모델)에서 돌렸는데, 다른 서비스들과 함께 돌리니 정말 아슬아슬하더라고요. 결국 8GB 모델로 업그레이드하거나, AWX 전용 노드를 따로 두는 것을 고려하게 됐습니다.

    해결책: awx.yaml 파일의 resource_requirements 섹션을 통해 각 파드의 리소스 요청(requests)과 제한(limits)을 조절할 수 있습니다. 하지만 너무 낮추면 성능 문제가 발생하니, 하드웨어 사양을 충분히 확보하는 것이 최고의 방법입니다.

    2. Persistent Volume(영구 볼륨) 설정

    AWX는 데이터베이스(PostgreSQL)와 작업 실행 로그 등을 저장하기 위해 Persistent Volume(PV, 영구 볼륨)이 필요합니다. K3s는 기본적으로 local-path-provisioner(로컬 패스 프로비저너)를 내장하고 있어서 별도의 StorageClass(스토리지 클래스) 설정 없이도 PVC(Persistent Volume Claim)를 생성하면 자동으로 PV가 프로비저닝(Provisioning)됩니다. 하지만 이 방식은 특정 노드에 데이터가 종속되기 때문에 노드 장애 시 데이터 손실 위험이 있습니다.

    해결책: 홈랩 환경에서는 간단하게 hostPath 타입을 사용하여 특정 경로에 데이터를 저장할 수 있지만, 좀 더 안정적인 환경을 원한다면 NFS(네트워크 파일 시스템) 서버를 구축하고 NFS CSI Driver(드라이버)를 설치하여 공유 스토리지를 사용하는 것이 좋습니다. 저도 NFS를 사용해서 여러 노드에서 AWX 파드들이 유연하게 움직일 수 있도록 구성했거든요.

    3. Ingress(인그레스) 접근 문제

    ingress_host를 설정했지만, 웹 브라우저에서 접속이 안 되는 경우가 있었습니다. K3s는 기본적으로 Traefik(트래픽) Ingress Controller(인그레스 컨트롤러)를 내장하고 있어 편리합니다. 하지만 제 경우에는 다음과 같은 문제가 있었습니다.

    • DNS 설정: awx.myhomelab.local과 같은 호스트 이름을 사용하려면, 홈랩 내 DNS 서버에 해당 호스트를 등록하거나, 접속하려는 PC의 /etc/hosts (Linux/macOS) 또는 C:\Windows\System32\drivers\etc\hosts (Windows) 파일에 IP 주소와 호스트 이름을 직접 매핑해주어야 합니다.
      # /etc/hosts 예시
      192.168.1.100 awx.myhomelab.local # K3s 마스터 노드의 IP 주소
    • Traefik 설정: 가끔 Traefik이 외부 트래픽을 제대로 라우팅(Routing)하지 못하는 경우가 있는데, K3s 설치 시 Traefik 관련 옵션을 확인하거나, Traefik 대시보드(보통 http://<k3s_master_ip>:8080/dashboard/)에서 Ingress Route(인그레스 라우트)가 제대로 생성되었는지 확인해야 합니다.

    해결책: 저는 /etc/hosts 파일을 수정하고, kubectl get ingress -n awx 명령어로 AWX Ingress 리소스가 정상적으로 생성되었는지 확인하면서 문제를 해결했습니다.

    검증 및 결과: AWX로 K3s 자동화하기

    여러 삽질 끝에 드디어 AWX가 K3s 클러스터 위에 성공적으로 배포되었습니다! 🎉 이제 웹 브라우저를 열고 설정했던 ingress_host로 접속하면 AWX 로그인 화면이 보일 겁니다. 초기 관리자 비밀번호는 AWX Operator가 Secret(시크릿)으로 생성해두는데, 다음 명령어로 확인할 수 있습니다.

    kubectl get secret my-awx-admin-password -o jsonpath='{.data.password}' | base64 --decode

    로그인 후, 저는 간단한 Ansible 플레이북을 만들어서 K3s 클러스터의 노드 목록을 조회하는 작업을 자동화해봤습니다. AWX에서 Project(프로젝트), Inventory(인벤토리), Credential(자격 증명)을 설정하고 Job Template(잡 템플릿)을 생성한 뒤 실행하니, 깔끔하게 K3s 노드 정보가 출력되는 것을 확인할 수 있었습니다. 이 화면을 보는 순간, 그동안의 고생이 눈 녹듯 사라지더라고요! 정말 뿌듯했어요. 😄

    AWX 웹 UI에서 K3s 노드 목록을 조회하는 Ansible 잡이 성공적으로 실행된 결과 화면

    AWX 웹 UI에서 Ansible 플레이북이 성공적으로 실행되어 K3s 클러스터의 노드 목록을 출력하는 잡 실행 결과 화면 스크린샷입니다. 초록색 성공 메시지와 함께 결과가 표시됩니다.

    마무리: 경량 Kubernetes 자동화, 여러분도 해보세요!

    K3s와 AWX를 통합하여 경량 Kubernetes 환경에서 자동화 워크플로우를 구축한 경험은 저에게 정말 값진 시간이었습니다. 비록 중간중간 삽질도 많이 했지만, 그 과정에서 얻은 지식과 해결 능력은 어떤 책에서도 배울 수 없는 것이었거든요.

    주요 배운 점:

    • K3s는 가볍지만, AWX처럼 리소스를 많이 쓰는 애플리케이션을 올릴 때는 충분한 하드웨어 리소스 계획이 필요하다는 것.
    • Persistent Volume 설정은 안정적인 운영을 위한 핵심이라는 것. (홈랩이라도 NFS는 정말 중요합니다.)
    • Kubernetes Ingress와 DNS 설정은 항상 꼼꼼하게 확인해야 한다는 것.
    • AWX Operator를 사용하면 복잡한 AWX 배포를 Kubernetes 스타일로 정말 간단하게 할 수 있다는 것.

    이 조합은 특히 저처럼 홈랩을 운영하거나, 소규모 팀에서 효율적인 인프라 자동화를 목표로 할 때 정말 강력한 솔루션이 될 수 있다고 확신합니다. 여러분도 직접 K3s와 AWX를 설치하고 이것저것 만져보면서 자신만의 자동화 워크플로우를 구축해보시길 강력히 추천합니다. 직접 해보면서 얻는 경험만큼 값진 건 없으니까요! 💪

    다음 글에서는 AWX를 이용한 좀 더 복잡한 GitOps(깃옵스) 파이프라인 구축에 대해 더 깊이 다뤄볼 예정입니다. 기대해주세요! 😄

    K3s와 AWX 통합으로 얻을 수 있는 주요 장점을 시각적으로 요약한 인포그래픽

    K3s와 AWX 통합의 주요 장점 (예: 리소스 효율성, 중앙 집중식 자동화, 빠른 배포, 간편한 관리)을 시각적으로 요약한 인포그래픽입니다.

  • [k8s] 쿠버네티스 스토리지: CSI 드라이버를 활용한 영구 스토리지 관리 및 최적화

    [k8s] 쿠버네티스 스토리지: CSI 드라이버를 활용한 영구 스토리지 관리 및 최적화

    안녕하세요, 13년차의 서버실입니다!

    저는 인프라 엔지니어로 일하면서 수많은 서버실을 드나들었고, 홈랩에서도 다양한 기술을 직접 실험해보고 있거든요. 특히 쿠버네티스(Kubernetes)를 운영하면서 가장 머리 아팠던 부분 중 하나가 바로 스토리지(Storage) 문제였습니다.

    컨테이너는 Stateless(무상태)여야 한다지만, 실제 애플리케이션은 데이터가 필요하잖아요? DB나 파일 서버 같은 것들 말이죠. 컨테이너가 죽거나 재시작하면 데이터가 홀라당 날아가 버리는 경험… 혹시 해보셨나요? 제가 그랬거든요, 처음엔. 😭

    그래서 오늘은 쿠버네티스 환경에서 데이터를 안전하게 보관하고 관리하는 핵심 기술인 CSI (Container Storage Interface) 드라이버를 활용한 영구 스토리지(Persistent Storage) 관리 및 최적화 방법에 대해 제 경험을 바탕으로 솔직하게 이야기해보려고 합니다. 💡

    쿠버네티스에서 스토리지가 어떻게 연결되는지 전반적인 아키텍처를 보여주는 다이어그램입니다. CSI 드라이버가 핵심 역할을 하는 것을 볼 수 있습니다.

    1. 쿠버네티스 영구 스토리지, 왜 필요하고 어떻게 작동하나요?

    쿠버네티스에서 애플리케이션이 데이터를 영구적으로 저장하려면 몇 가지 핵심 개념을 알아야 합니다. 쉽게 말해, 컨테이너는 휘발성(Ephemeral)이라서 데이터를 저장해도 컨테이너가 사라지면 데이터도 같이 사라져요. 그래서 컨테이너 외부에 데이터를 안전하게 보관할 수 있는 공간이 필요한데, 이걸 영구 스토리지(Persistent Storage)라고 부릅니다.

    PersistentVolume (PV, 영구 볼륨): 물리적인 스토리지 자원

    PV는 실제 물리적인 스토리지 공간, 예를 들면 네트워크 파일 시스템(NFS)의 특정 디렉터리나 클라우드 제공자의 디스크(AWS EBS, Google Persistent Disk 등)를 추상화한 겁니다. 클러스터 관리자가 미리 정의해두죠. 마치 서버실의 빈 하드디스크 같은 개념이랄까요? PV는 특정 스토리지 솔루션과 연결되어 실제 데이터를 저장하는 역할을 합니다.

    PersistentVolumeClaim (PVC, 영구 볼륨 요청): 애플리케이션의 스토리지 요구

    PVC는 Pod(파드)가 사용할 스토리지의 “요구 사항”을 선언하는 겁니다. “나는 10GiB(기가바이트) 용량의 ReadWriteOnce(한 번에 한 Pod만 쓰기 가능) 모드의 스토리지가 필요해!” 하고 요청하는 거죠. 사용자가 빈 하드디스크를 “내 거”라고 찜하는 것과 비슷합니다. Pod는 PV에 직접 접근하는 대신 PVC를 통해 스토리지를 요청하고 사용합니다.

    StorageClass (스토리지 클래스): 스토리지 프로비저닝 자동화

    여기서부터 좀 더 편리해집니다. StorageClass는 스토리지의 “종류”를 정의하는 템플릿이라고 보시면 돼요. 예를 들어, “빠른 SSD 스토리지”, “저렴한 HDD 스토리지”, “백업용 스토리지” 같은 거죠. StorageClass를 사용하면 PVC가 요청할 때 PV를 자동으로 생성(Dynamic Provisioning)해줍니다. 제가 처음엔 PV를 일일이 만들었었는데, StorageClass 덕분에 삽질을 훨씬 줄일 수 있었어요. 이거 진짜 편하더라고요! ✅

    StorageClass는 어떤 CSI 드라이버를 사용할지, 어떤 볼륨 타입(SSD/HDD), 회수 정책(reclaim policy) 등을 정의합니다.

    CSI (Container Storage Interface) 드라이버: 쿠버네티스와 스토리지 연결 고리

    자, 오늘의 주인공입니다! CSI 드라이버는 쿠버네티스가 다양한 외부 스토리지 시스템(NFS, Ceph, AWS EBS, OpenEBS 등)과 통신할 수 있도록 표준화된 인터페이스를 제공합니다. 스토리지 벤더들은 이 CSI 표준에 맞춰 드라이버를 개발하고, 쿠버네티스는 이 드라이버를 통해 어떤 스토리지든 일관된 방식으로 사용할 수 있게 되는 거죠. 마치 USB 포트에 어떤 장치를 꽂아도 표준 드라이버 덕분에 작동하는 것과 비슷하다고 보시면 됩니다. 덕분에 쿠버네티스가 특정 스토리지 솔루션에 종속되지 않고 유연하게 확장될 수 있더라고요. 💡

    2. CSI 드라이버를 활용한 영구 스토리지 설정하기 (NFS CSI 드라이버 예시)

    제가 홈랩에서 가장 많이 쓰는 방식 중 하나가 바로 NFS (Network File System)를 이용한 영구 스토리지입니다. 간편하고 설정하기도 쉬워서 처음 시작하는 분들께도 추천드려요. 여기서는 NFS CSI 드라이버를 예시로 들어볼게요.

    (⚠️ 사전에 NFS 서버가 구성되어 있고, 쿠버네티스 클러스터에서 접근 가능해야 합니다.)

    2.1. NFS CSI 드라이버 배포

    먼저 NFS CSI 드라이버를 쿠버네티스 클러스터에 배포해야 합니다. 보통 Helm 차트나 manifests 파일을 통해 배포하죠. 저는 안정적인 버전의 Helm 차트를 선호하는 편입니다.

    저는 보통 프로젝트 공식 저장소에서 제공하는 Helm 차트 가이드를 따라 설치합니다. Kubernetes-CSI 프로젝트의 NFS CSI 드라이버를 설치하는 방법을 보여드릴게요.

    
    helm repo add csi-driver-nfs https://kubernetes-csi.github.io/csi-driver-nfs
    helm repo update
    helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs --namespace kube-system
    

    배포가 완료되면, 다음과 같이 CSI 드라이버 관련 Pod들이 잘 올라왔는지 확인해볼 수 있습니다.

    
    kubectl get pods -n kube-system -l app.kubernetes.io/name=csi-driver-nfs
    

    2.2. StorageClass 생성

    이제 이 CSI 드라이버를 사용할 StorageClass를 정의합니다. NFS 서버의 주소와 공유할 경로를 지정해줘야 해요.

    
    # nfs-storageclass.yaml
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: nfs-csi-storage
    provisioner: nfs.csi.k8s.io # CSI 드라이버의 이름
    parameters:
      server: 192.168.1.100 # 여러분의 NFS 서버 IP 주소로 변경하세요!
      share: /mnt/nfs_share # NFS 서버의 공유 경로로 변경하세요!
    reclaimPolicy: Delete # Pod 삭제 시 PV도 함께 삭제 (Retain으로 하면 수동 삭제 필요)
    volumeBindingMode: Immediate
    mountOptions:
      - hard
      - nfsvers=4.1
    

    이 파일을 적용하면 <code>nfs-csi-storage라는 이름의 StorageClass가 생성됩니다.

    
    kubectl apply -f nfs-storageclass.yaml
    

    StorageClass를 정의하는 YAML 파일의 예시입니다. NFS CSI 드라이버를 이용해 스토리지 클래스를 생성하는 과정을 보여줍니다.

    2.3. PersistentVolumeClaim (PVC) 생성

    이제 애플리케이션이 스토리지 1GiB를 요청할 PVC를 만들어봅시다.

    
    # my-pvc.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: my-nfs-pvc
    spec:
      accessModes:
        - ReadWriteOnce # 한 Pod만 읽기/쓰기 가능
      storageClassName: nfs-csi-storage # 위에서 생성한 StorageClass 이름 지정
      resources:
        requests:
          storage: 1Gi # 1 기가바이트 스토리지 요청
    

    적용하면 자동으로 PV가 프로비저닝됩니다. 정말 편해졌죠?

    
    kubectl apply -f my-pvc.yaml
    kubectl get pvc my-nfs-pvc
    kubectl get pv
    

    PVC가 Bound 상태가 되고, 그에 맞는 PV가 생성된 것을 확인할 수 있을 겁니다. 🎉

    2.4. Pod에서 PVC 사용

    마지막으로, 이 PVC를 사용할 Pod를 생성합니다. 간단한 Nginx Pod를 예시로 들어볼게요.

    
    # nginx-pod-with-pvc.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx-with-nfs
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nfs-storage
              mountPath: /usr/share/nginx/html # Nginx의 웹 루트에 마운트
      volumes:
        - name: nfs-storage
          persistentVolumeClaim:
            claimName: my-nfs-pvc # 위에서 생성한 PVC 이름 지정
    

    이제 이 Pod를 배포하고, /usr/share/nginx/html 경로에 파일을 생성해보세요. Pod가 재시작되어도 데이터가 유지되는 것을 확인할 수 있을 겁니다!

    
    kubectl apply -f nginx-pod-with-pvc.yaml
    kubectl exec -it nginx-with-nfs -- bash
    echo "Hello from NFS CSI!" > /usr/share/nginx/html/index.html
    exit
    # Pod를 삭제했다가 다시 만들어도 데이터가 유지되는지 확인해보세요.
    kubectl delete pod nginx-with-nfs
    kubectl apply -f nginx-pod-with-pvc.yaml
    kubectl exec -it nginx-with-nfs -- cat /usr/share/nginx/html/index.html
    

    정상적으로 “Hello from NFS CSI!”라는 문구가 보인다면 성공입니다! 드디어 됐다! 🎉

    3. 삽질 경험담: CSI 드라이버 사용 시 주의사항과 트러블슈팅

    제가 이 과정을 거치면서 몇 번이고 머리를 쥐어뜯었던 경험이 있습니다. 여러분은 저 같은 삽질을 하지 마시라고 몇 가지 팁을 드릴게요.

    3.1. ⚠️ Access Modes (접근 모드) 이해하기

    PVC를 만들 때 accessModes를 지정하는데, 이게 꽤 중요합니다.

    • ReadWriteOnce (RWO): 단일 Pod만 읽기/쓰기 가능. (가장 흔함)
    • ReadOnlyMany (ROX): 여러 Pod가 읽기만 가능.
    • ReadWriteMany (RWX): 여러 Pod가 읽기/쓰기 가능. (NFS 같은 공유 파일 시스템에서 주로 사용)

    만약 RWX가 필요한데 RWO로 설정하면, 다른 Pod에서 접근이 안 돼서 문제가 생길 수 있습니다. 특히 클라우드 볼륨(EBS 같은)은 대부분 RWO만 지원하므로, RWX가 필요하면 NFS나 CephFS 같은 공유 파일 시스템 기반 CSI 드라이버를 고려해야 해요. 제가 이 부분에서 많이 헷갈렸었죠.

    3.2. ⚠️ Reclaim Policy (회수 정책) 신중하게 설정하기

    StorageClass에 reclaimPolicy를 Delete로 설정하면, PVC가 삭제될 때 연결된 PV와 실제 스토리지 볼륨도 함께 삭제됩니다. 개발 환경에서는 편하지만, 실제 운영 환경에서는 데이터가 날아갈 수 있으니 주의해야 합니다!

    저는 처음에 이걸 모르고 테스트용 DB를 날려먹을 뻔했어요… 다행히 백업이 있었지만요. 😅

    운영 환경에서는 Retain으로 설정해서 PVC 삭제 시 PV만 삭제하고, 실제 볼륨은 수동으로 관리하는 것을 고려해봐야 합니다. 아니면 백업 정책을 철저히 해야겠죠.

    3.3. 네트워크 연결 문제 (NFS 예시)

    NFS CSI 드라이버를 사용할 경우, 쿠버네티스 노드들이 NFS 서버에 접근할 수 있는지 확인해야 합니다. 방화벽 문제나 네트워크 경로 설정 문제로 연결이 안 되는 경우가 많거든요. showmount -e [NFS 서버 IP]나 mount 명령어로 직접 마운트 테스트를 해보는 것이 가장 확실합니다.

    3.4. CSI 드라이버 설치 버전 호환성

    가끔 CSI 드라이버 버전과 쿠버네티스 클러스터 버전 간의 호환성 문제로 말썽을 일으킬 때가 있습니다. CSI 드라이버의 공식 문서를 참조하여 호환되는 버전을 사용하는 것이 중요해요. 최신 버전이 무조건 좋은 건 아니더라고요.

    4. CSI 드라이버를 통한 영구 스토리지 관리의 성과

    이렇게 CSI 드라이버를 통해 영구 스토리지를 구성하고 나면, 다음과 같은 이점을 얻을 수 있습니다.

    • 데이터 영속성 (Data Persistence) 확보: Pod가 죽거나 재시작되어도 데이터는 안전하게 유지됩니다. DB나 상태를 가지는 애플리케이션 운영에 필수적이죠.
    • 스토리지 관리의 유연성 (Flexibility): 특정 스토리지 벤더에 종속되지 않고, 다양한 스토리지 솔루션을 쿠버네티스에서 일관된 방식으로 사용할 수 있습니다.
    • 운영 효율성 (Operational Efficiency) 증대: StorageClass를 통한 동적 프로비저닝(Dynamic Provisioning) 덕분에 스토리지 할당 및 관리가 자동화되어 운영 부담이 크게 줄어듭니다. 제가 직접 PV를 일일이 만들던 시절을 생각하면 정말 격세지감이죠. 😮
    • 확장성 (Scalability): 필요한 만큼 스토리지를 손쉽게 확장하거나 축소할 수 있게 됩니다.

    실제로 제가 운영하는 서비스에서도 CSI 드라이버 덕분에 안정적으로 데이터를 관리하고, 필요에 따라 스토리지를 유연하게 변경하거나 확장할 수 있게 되었어요. K8s 스토리지 최적화의 첫걸음이라고 할 수 있습니다.

    쿠버네티스 클러스터에서 PV, PVC, StorageClass 등의 스토리지 리소스 상태를 모니터링하는 대시보드의 예시입니다.

    5. 마무리하며: 쿠버네티스 스토리지, CSI 드라이버로 마스터하기

    오늘은 쿠버네티스 환경에서 영구 스토리지를 관리하고 최적화하는 데 필수적인 CSI 드라이버에 대해 제 경험을 바탕으로 이야기해보았습니다. PersistentVolume(PV), PersistentVolumeClaim(PVC), StorageClass, 그리고 CSI 드라이버라는 핵심 개념들이 처음에는 복잡하게 느껴질 수 있지만, 몇 번 직접 구성해보면 금방 익숙해지실 거예요.

    특히 StorageClass와 CSI 드라이버를 활용한 동적 프로비저닝은 쿠버네티스 운영의 편의성을 극대화시켜주는 정말 강력한 기능이라고 생각합니다. 저의 삽질 경험담이 여러분의 K8s 스토리지 여정에 작은 도움이 되었으면 좋겠네요. 🤝

    다음번에는 CephFS나 Rook-Ceph 같은 분산 스토리지 솔루션을 CSI 드라이버와 함께 사용하는 방법에 대해서도 다뤄볼 기회가 있었으면 좋겠습니다. 궁금한 점이나 공유하고 싶은 경험이 있다면 댓글로 남겨주세요! 저는 13년차의 서버실이었습니다. 감사합니다!

    다양한 쿠버네티스 영구 스토리지 솔루션(NFS, Ceph, 클라우드 볼륨 등)의 특징과 장단점을 비교하는 인포그래픽입니다.