13년차의 서버실

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

[태그:] OpenTelemetry

  • [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] 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를 시각적으로 요약한 인포그래픽입니다.