Jaeger, OpenTelemetry 활용 Kubernetes 트래픽 흐름 추적

이 포스트에서는 Kubernetes 클러스터에 OpenTelemetry Operator를 통해 Jaeger 를 배포하고, OpenTelemetry Collector와 통합하여 Pod 간의 트래픽 흐름을 추적하는 방법을 다룹니다. 클러스터 내부의 마이크로서비스 애플리케이션의 트래픽 흐름을 추적하여 병목 구간을 식별하고, 성능 개선을 위한 방향을 도출하는 데 도움을 줄 수 있습니다.

먼저 Kubernetes 클러스터에 OpenTelemetry Operator를 통해 Jaeger를 배포하는 방법을 알아보고, 데모 마이크로서비스 애플리케이션인 Online Boutique를 클러스터에 배포하도록 하겠습니다.
이후 OpenTelemetry Operator의 Auto-instrumentation을 통해 애플리케이션의 코드 변경 없이 원격 측정 데이터를 OpenTelemetry Collector로 전송하도록 설정하고, Collector가 수집한 데이터를 Jaeger로 전송하여 확인하도록 하겠습니다.

이 포스트는 Kuberenetes 클러스터에 OpenTelemetry Operator가 사전에 배포된 환경에서 진행됩니다. OpenTelemetry Operator를 클러스터에 배포하는 방법은 OpenTelemetry Operator, Collector Kubernetes 배포 가이드 포스트를 참고하세요.

또한 데모 애플리케이션, Jaeger UI 연결을 위해 사전에 배포된 NGINX Ingress Controller를 사용했습니다.

목차

1. Jaeger란?
2. Auto-instrumentation이란?

3. 버전 정보
4. Jaeger 배포
5. Kubernetes 클러스터 구성
 5-1. Online Boutique 배포
 5-2. OpenTelemetry Collector 구성 – 데이터 수집 및 Jaeger 전송
 5-3. Auto-instrumentation 구성
6. Jaeger UI로 트래픽 흐름 추적
7. 결론

1. Jaeger 란?

Jaeger

Jaeger는 분산 시스템에서 서비스 간 요청 흐름을 추적하고 분석하는 오픈소스 분산 추적 시스템입니다. Cloud Native Computing Foundation(CNCF)의 졸업 프로젝트로, 마이크로서비스 아키텍처에서 서비스 간 요청이 어떻게 처리되는지를 시각화하는 데 특화되어 있습니다.

Jaeger의 주요 특징

오류 추적 및 문제 해결
분산 시스템에서 발생하는 오류를 추적하고 근본 원인을 분석하는 데 유용합니다. 어떤 서비스에서 문제가 시작되었는지, 그리고 그 영향이 시스템 전체에 어떻게 퍼지는지를 확인할 수 있습니다.

분산 서비스 호출 추적
Jaeger는 여러 서비스에 걸쳐 발생하는 요청의 흐름을 추적하여 전체 서비스 호출 체인을 시각화합니다. 이를 통해 복잡한 마이크로서비스 환경에서 요청이 어떤 경로를 따라 전달되는지 쉽게 파악할 수 있습니다.

성능 및 지연 시간 분석
서비스 간 호출의 지연 시간을 측정하고 시각화하여 성능 병목 현상을 식별하고 해결하는 데 도움을 줍니다. 각 요청 단계에서 소요된 시간을 정확히 파악할 수 있어 최적화가 필요한 부분을 빠르게 찾아낼 수 있습니다.

2. Auto-instrumentation이란?

OpenTelemetry Operator의 Auto Instrumentation은 기존 애플리케이션의 코드를 수정하지 않고 원격 측정(telemetry) 데이터를 수집할 수 있게 해주는 기능입니다. 일반적으로 애플리케이션에서 원격 측정 데이터 수집 설정을 적용하려면 OpenTelemetry SDK를 직접 코드에 삽입해야 하지만, Auto Instrumentation을 사용하면 이러한 작업 없이도 자동으로 트레이스를 수집할 수 있습니다.

OpenTelemetry Operator의 커스텀 리소스인 Instrumentation을 구성하고, Pod의 annotation을 통해 Auto-instrumentation을 적용하면 Sidecar 또는 Init Container를 통해 OpenTelemetry 에이전트가 자동으로 계측 코드(Instrumentation)를 삽입하여 원격 측정 데이터를 수집할 수 있습니다.

OpenTelemetry Operator 0.117.0 버전 기준으로 Apache HTTPD, .NET, Deno(unstable), Go, Java, Node.js, Python에 대한 Auto Instrumentation을 지원합니다.

3. 버전 정보

  • Kubernetes : v1.30.3
  • OpenTelemetry Operator : 0.117.0
  • Jaeger : 2.3.0

4. Jaeger 배포

Kubnernetes 클러스터에 Jaeger를 배포하는 방법은 Kubernetes Operator로 배포하는 방식과, Helm Chart를 통해 배포하는 방식이 있습니다. 이 포스트에서는 Kubernetes Operator로 배포하는 방식을 사용하며, Helm Chart를 통한 배포는 Jaeger Helm Chart GitHub를 참고하세요.

Jaeger V2 버전부터, Kubernetes 클러스터에서의 배포는 Opentelemetry Operator을 통해서 배포됩니다. 따라서 Opentelemetry Operator 및 Opentelemetry Collector 배포에 필요한 cert-manager 배포가 필수적입니다.
OpenTelemetry Operator를 클러스터에 배포하는 방법은 OpenTelemetry Operator, Collector Kubernetes 배포 가이드 포스트를 참고하세요.

메모리 스토리지로 배포

Jaeger는 OpenTelemetryCollector 리소스로 클러스터에 배포됩니다.
다음 yaml 파일을 사용해 클러스터에 배포할 수 있습니다.

apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: jaeger-inmemory-instance
spec:
  image: jaegertracing/jaeger:latest
  ports:
  - name: jaeger
    port: 16686
  config:
    service:
      extensions: [jaeger_storage, jaeger_query]
      pipelines:
        traces:
          receivers: [otlp]    
          exporters: [jaeger_storage_exporter]
    extensions:
      jaeger_query:
        storage:
          traces: memstore
      jaeger_storage:
        backends:
          memstore:
            memory:
              max_traces: 100000
    receivers:
      otlp:
        protocols:
          grpc:
          http:
    exporters:
      jaeger_storage_exporter:
        trace_storage: memstore

monitoring 네임스페이스에 배포를 진행했습니다.

$ kubectl apply -f jaeger.yaml -n monitoring

opentelemetrycollector.opentelemetry.io/jaeger-inmemory-instance created


$ kubectl get opentelemetrycollectors.opentelemetry.io -n monitoring 

NAME                       MODE         VERSION   READY   AGE   IMAGE                                                                                 MANAGEMENT
jaeger-inmemory-instance   deployment   0.117.0   1/1     46s   jaegertracing/jaeger:latest                                                           managed

OpenTelemetryCollector 리소스로 배포되어 Deployment 및 Service가 자동으로 배포됩니다.

$ kubectl get all -n monitoring

NAME                                                      READY   STATUS    RESTARTS     AGE

pod/jaeger-inmemory-instance-collector-5686846544-gfllg   1/1     Running   0            67s

NAME                                                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                       AGE

service/jaeger-inmemory-instance-collector              ClusterIP   10.108.138.23    <none>        16686/TCP,4317/TCP,4318/TCP   67s
service/jaeger-inmemory-instance-collector-extension    ClusterIP   10.107.57.16     <none>        16686/TCP                     67s
service/jaeger-inmemory-instance-collector-headless     ClusterIP   None             <none>        16686/TCP,4317/TCP,4318/TCP   67s
service/jaeger-inmemory-instance-collector-monitoring   ClusterIP   10.102.24.28     <none>        8888/TCP                      67s

NAME                                                 READY   UP-TO-DATE   AVAILABLE   AGE

deployment.apps/jaeger-inmemory-instance-collector   1/1     1            1           67s

NAME                                                            DESIRED   CURRENT   READY   AGE

replicaset.apps/jaeger-inmemory-instance-collector-5686846544   1         1         1       67s
DB 스토리지로 배포

Jaeger는 클러스터에 배포된 DB에 트레이스 데이터를 저장할 수 있습니다. DB와의 연동을 위해선 DB Pod가 서비스로 노출된 상태여야 합니다.

jaeger_storage:
  backends:
    some_storage:
      cassandra:
        connection:
          servers: [<service 이름>]

...

    exporters:
      jaeger_storage_exporter:
        trace_storage: some_storage
jaeger_storage:
  backends:
    some_storage:
      elasticseacrh:
        servers: [<service 이름>]

...

    exporters:
      jaeger_storage_exporter:
        trace_storage: some_storage
Jaeger UI

Jaeger UI는 배포된 Service의 16686 포트를 통해 연결할 수 있습니다. 이 포스트에서는 NGINX Ingress Controller와 VirtualServer 리소스를 통해 연결하도록 구성했습니다.

jaeger-vs.yaml
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: jaeger-vs
  namespace: monitoring
spec:
  host: jaeger.devopssong.com
  routes:
  - action:
      pass: jaeger
    path: /
  upstreams:
  - name: jaeger
    port: 16686
    service: jaeger-inmemory-instance-collector
Jaeger UI

구성한 VirtualServer를 통해 브라우저에서 접근할 수 있습니다.

5. Kubernetes 클러스터 구성

트래픽 흐름 추적을 위한 Kubernetes 클러스터를 이미지와 같이 구성하도록 하겠습니다.

otel 네임스페이스에 마이크로서비스 애플리케이션을 배포하고, 동일한 네임스페이스에 배포된 OpenTelemetry Collector로 트레이스 데이터를 전송하도록 구성합니다. OpenTelemetry Collector로 수집한 데이터는 monitoring 네임스페이스에 배포한 Jaeger로 전송하도록 구성합니다.

5-1. Online Boutique 배포

Online Boutique는 웹 기반 전자 상거래 애플리케이션으로, 다양한 개발 언어로 구성되어 gRPC로 통신하는 데모 마이크로서비스 애플리케이션입니다.
애플리케이션을 구성하는 Pod 사이의 요청 흐름을 Jaeger UI를 통해 확인할 수 있습니다.

https://github.com/GoogleCloudPlatform/microservices-demo/tree/main

1. 명령어를 사용하여 파일을 다운로드합니다.

$ git clone https://github.com/GoogleCloudPlatform/microservices-demo.git

2. 애플리케이션을 배포할 네임스페이스를 생성합니다.

$ kubectl create ns otel

namespace/otel created

3. 애플리케이션을 배포합니다.

$ kubectl apply -f microservices-demo/release/kubernetes-manifests.yaml -n otel

deployment.apps/emailservice created
service/emailservice created
serviceaccount/emailservice created
deployment.apps/checkoutservice created
service/checkoutservice created
serviceaccount/checkoutservice created
deployment.apps/recommendationservice created
service/recommendationservice created
serviceaccount/recommendationservice created
deployment.apps/frontend created
service/frontend created
service/frontend-external created
serviceaccount/frontend created
deployment.apps/paymentservice created
service/paymentservice created
serviceaccount/paymentservice created
deployment.apps/productcatalogservice created
service/productcatalogservice created
serviceaccount/productcatalogservice created
deployment.apps/cartservice created
service/cartservice created
serviceaccount/cartservice created
deployment.apps/redis-cart created
service/redis-cart created
deployment.apps/loadgenerator created
serviceaccount/loadgenerator created
deployment.apps/currencyservice created
service/currencyservice created
serviceaccount/currencyservice created
deployment.apps/shippingservice created
service/shippingservice created
serviceaccount/shippingservice created
deployment.apps/adservice created
service/adservice created
serviceaccount/adservice created
$ kubectl get po,svc -n otel

NAME                                         READY   STATUS    RESTARTS      AGE
pod/adservice-5b575d9444-jkk8c               1/1     Running   0             2m6s
pod/cartservice-7f7b9fc469-822px             1/1     Running   1 (85s ago)   2m7s
pod/checkoutservice-6bbccb4788-x266r         1/1     Running   0             2m9s
pod/currencyservice-795445fcb8-tzjg6         1/1     Running   0             2m6s
pod/emailservice-c498b5f8b-rcqrk             1/1     Running   0             2m9s
pod/frontend-548c468bb9-t2tx2                1/1     Running   0             2m8s
pod/loadgenerator-85757f9958-22tld           1/1     Running   0             2m7s
pod/paymentservice-6578f9dcfd-sz7dr          1/1     Running   0             2m8s
pod/productcatalogservice-5865bf7d98-2g44r   1/1     Running   0             2m8s
pod/recommendationservice-758d9b68c4-x7jch   1/1     Running   0             2m9s
pod/redis-cart-7ff8f4d6ff-hplv2              1/1     Running   0             2m7s
pod/shippingservice-65cc774694-nsnpz         1/1     Running   0             2m6s

NAME                            TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
service/adservice               ClusterIP      10.111.101.86    <none>        9555/TCP       2m6s
service/cartservice             ClusterIP      10.106.56.107    <none>        7070/TCP       2m8s
service/checkoutservice         ClusterIP      10.100.59.169    <none>        5050/TCP       2m9s
service/currencyservice         ClusterIP      10.100.252.184   <none>        7000/TCP       2m7s
service/emailservice            ClusterIP      10.100.26.137    <none>        5000/TCP       2m10s
service/frontend                ClusterIP      10.99.80.226     <none>        80/TCP         2m9s
service/frontend-external       LoadBalancer   10.97.199.91     <pending>     80:30700/TCP   2m9s
service/paymentservice          ClusterIP      10.108.88.203    <none>        50051/TCP      2m8s
service/productcatalogservice   ClusterIP      10.98.243.223    <none>        3550/TCP       2m8s
service/recommendationservice   ClusterIP      10.99.131.215    <none>        8080/TCP       2m9s
service/redis-cart              ClusterIP      10.105.67.122    <none>        6379/TCP       2m7s
service/shippingservice         ClusterIP      10.102.151.122   <none>        50051/TCP      2m6s

4. NGINX Ingress Controller을 통해 연결하기 위해 frontend-external Service의 타입을 ClusterIP로 변경하고, 해당 Service로 연결되도록 VirtualServer 리소스를 생성합니다.

$ kubectl patch svc frontend-external -n otel --type='merge' -p '{"spec":{"type":"ClusterIP","ports":[{"port":80,"targetPort":80}]}}'

service/frontend-external patched
demo-vs.yaml
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
  name: demo-vs
  namespace: otel
spec:
  host: demo.devopssong.com
  upstreams:
    - name: frontend
      service: frontend-external
      port: 80
  routes:
    - path: /
      action:
        pass: frontend

브라우저에서 접속 시 위와 같은 화면을 확인할 수 있습니다.

5-2. OpenTelemetry Collector 구성 – 데이터 수집 및 Jaeger 전송

otel 네임스페이스에 배포된 애플리케이션 Pod들의 트레이스 데이터를 수신하고, monitoring 네임스페이스에 배포된 Jaeger로 수집한 데이터를 전송하는 OpenTelemetry Collector를 배포합니다.
OpenTelemetry Collector의 구성 요소에 대한 설명은 OpenTelemetry Operator, Collector Kubernetes 배포 가이드 포스트를 참고하세요.

otelcol.yaml
apiVersion: opentelemetry.io/v1beta1
kind: OpenTelemetryCollector
metadata:
  name: otel-collector-trace
  namespace: otel
spec:
  mode: deployment # deployment로 collector 배포

  image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector

  config:
    # 데이터 수신 구성 (grpc, http)
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 0.0.0.0:4317
          http:
            endpoint: 0.0.0.0:4318

    processors:
      memory_limiter:
        check_interval: 1s     
        limit_percentage: 75     
        spike_limit_percentage: 15 
      batch:
        send_batch_size: 10000  
        timeout: 10s             

    # monitoring 네임스페이스의 Jaeger Service로 데이터 전송 설정
    exporters:
      debug: {}
      otlp/jaeger:
        endpoint: jaeger-inmemory-instance-collector.monitoring.svc.cluster.local:4317
        tls:
          insecure: true

    service:
      pipelines:
        # trace 데이터를 Jaeger를 타겟으로 설정한 exporter를 통해 전송
        traces:
          receivers: [otlp]
          processors: [memory_limiter, batch]
          exporters: [otlp/jaeger, debug]
$ kubectl get opentelemetrycollectors.opentelemetry.io -n otel

NAME                   MODE         VERSION   READY   AGE     IMAGE                                                                             MANAGEMENT
otel-collector-trace   deployment   0.117.0   1/1     18s     ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector   managed

$ kubectl get po,svc -n otel -l app.kubernetes.io/component=opentelemetry-collector

NAME                                                  READY   STATUS    RESTARTS        AGE
pod/otel-collector-trace-collector-588c4db9bc-zqndc   1/1     Running   0               20s

NAME                                                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE

service/otel-collector-trace-collector              ClusterIP   10.96.36.114     <none>        4317/TCP,4318/TCP   20s
service/otel-collector-trace-collector-headless     ClusterIP   None             <none>        4317/TCP,4318/TCP   20s
service/otel-collector-trace-collector-monitoring   ClusterIP   10.101.110.235   <none>        8888/TCP            20s

5-3. Auto-instrumentation 구성

Go, Node.js, Python, Java로 구성된 Online Boutique 애플리케이션 Pod로부터 트레이스 데이터를 수집할 수 있도록 auto-instrumentation(자동 계측)을 구성하는 방법을 알아보겠습니다.
loadgenerator, cartservice Pod에는 적용하지 않습니다.

기본적으로 OpenTelemetry Operator의 Go에 대한 Auto-instrumentation은 비활성화되어 있습니다. 해당 설정을 적용하기 위해 opentelemetry-operator-controller-manager Deployment에 flag를 추가합니다.

$ kubectl edit deployments.apps -n opentelemetry-operator-system opentelemetry-operator-controller-manager
...

  template:
    metadata:
      creationTimestamp: null
      labels:
        app.kubernetes.io/name: opentelemetry-operator
        control-plane: controller-manager
    spec:
      containers:
      - args:
        - --metrics-addr=127.0.0.1:8080
        - --enable-leader-election
        - --zap-log-level=info
        - --zap-time-encoding=rfc3339nano
        - --enable-go-instrumentation=true

...

이후 Instrumentation 리소스를 애플리케이션 Pod가 배포된 otel 네임스페이스에 배포합니다.

trace-instrumentation.yaml
apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: trace-instrumentation
spec:
  exporter:
    endpoint: http://otel-collector-trace-collector:4317  # 기본적으로 gRPC 프로토콜을 사용하여 OpenTelemetry Collector로 데이터 전송

  # 트레이스 데이터를 다른 서비스로 전파할 때 사용하는 propagators 설정
  propagators:
    - tracecontext   # W3C Trace Context: 트레이스 컨텍스트 정보를 전파하는 표준 프로토콜
    - baggage        # W3C Baggage: 트레이스 관련 추가 정보를 전파하는 표준 프로토콜
    - jaeger         # Jaeger: Jaeger 프로토콜을 사용하여 트레이스를 전파

  # 샘플러는 트레이스를 얼마나 자주 수집할지를 설정
  # 'parentbased_traceidratio'는 부모 트랜잭션에 기반하여 샘플링 비율을 결정
  # 'argument'는 샘플링 비율을 나타내며, '1'은 100% 샘플링을 의미
  sampler:
    type: parentbased_traceidratio  # 부모 트랜잭션을 기준으로 샘플링 비율을 설정하는 샘플러 타입
    argument: "1"  # 샘플링 비율: 1 = 100% 샘플링

  # Python과 Go 언어의 OpenTelemetry Collector로 데이터를 전송하는 엔드포인트를 HTTP 프로토콜 엔드포인트로 설정
  python:
    env:
      - name: OTEL_EXPORTER_OTLP_ENDPOINT
        value: http://otel-collector-trace-collector:4318  # Python의 OpenTelemetry Collector 엔드포인트

  go:
    env:
      - name: OTEL_EXPORTER_OTLP_ENDPOINT
        value: http://otel-collector-trace-collector:4318  # Go의 OpenTelemetry Collector 엔드포인트

Instrumentation의 propagators, sampler 설정에서 사용할 수 있는 값은 OpenTelemetry SDK 설정 문서를 참고하세요.

$ kubectl get instrumentations.opentelemetry.io -n otel

NAME                    AGE    ENDPOINT                                     SAMPLER                    SAMPLER ARG
trace-instrumentation   169m   http://otel-collector-trace-collector:4317   parentbased_traceidratio   1

기존에 OnlineBoutique 애플리케이션 배포에 사용한 kubernetes-manifests.yaml 파일을 수정합니다.
포스트에서 사용한 전체 yaml 파일은 NGINX STORE GitHub에서 확인하세요.

1. 각 Pod의 annotation을 추가하여 auto-instrumentation을 활성화합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: emailservice
  labels:
    app: emailservice
  # 이 위치는 Deployment의 annotation 위치
spec:
  selector:
    matchLabels:
      app: emailservice
  template:
    metadata:
      labels:
        app: emailservice
      annotations:
        instrumentation.opentelemetry.io/inject-python: "trace-instrumentation"
  • Deployment로 배포 시 annotation을 Pod의 metadata가 아닌, Deployment의 meatdata에 추가하지 않도록 주의합니다.
  • inject-python : 각 애플리케이션 환경에 맞춰 구성합니다
    • .NET: instrumentation.opentelemetry.io/inject-dotnet: "true"
    • Deno: instrumentation.opentelemetry.io/inject-sdk: "true"
    • Go: instrumentation.opentelemetry.io/inject-go: "true"
    • Java: instrumentation.opentelemetry.io/inject-java: "true"
    • Node.js: instrumentation.opentelemetry.io/inject-nodejs: "true"
    • Python: instrumentation.opentelemetry.io/inject-python: "true"
  • “trace-instrumentation” : 네임스페이스에 배포된 Instrumentation 리소스의 이름을 정의합니다.

Go 애플리케이션의 경우 실행 파일의 경로를 지정해야 합니다.

  template:
    metadata:
      labels:
        app: checkoutservice
      annotations:
        instrumentation.opentelemetry.io/inject-go: "trace-instrumentation"
        instrumentation.opentelemetry.io/otel-go-auto-target-exe: "/src/checkoutservice"

각 애플리케이션 환경에 맞춰 구성합니다. OnlineBoutique의 Go 애플리케이션은 다음과 같이 구성되어 있습니다.

  • checkoutservice : /src/checkoutservice
  • shippingservice : /src/shippingservice
  • productcatalogservice, frontend : /src/server

2. Go 애플리케이션의 경우 auto-instrumentation이 Sidecar 형태로 구현되어, Pod의 권한 설정이 필요합니다.

    spec:
      serviceAccountName: frontend

      # 기존 Pod 적용 설정 비활성화
      #securityContext:
      #  fsGroup: 1000
      #  runAsGroup: 1000
      #  runAsNonRoot: true
      #  runAsUser: 1000
      containers:
        - name: server
          securityContext:
            runAsGroup: 1000       #
            runAsNonRoot: true     # Pod의 설정을 컨테이너로 이동
            runAsUser: 1000        #
         
            # 기존 컨테이너 설정
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
            privileged: false
            readOnlyRootFilesystem: true

위와 같이 기존에 Pod 단계에 적용된 설정을 비활성화하지 않을 경우 권한 문제로 인해 Sidecar 컨테이너가 생성되지 않습니다. 기존 Pod에 적용된 설정을 비활성화하고, 컨테이너로 옮깁니다.

3. Go를 제외한 애플리케이션 컨테이너의 리소스 제한을 해제합니다. 제한이 유지될 경우 Init container의 auto-instrumentation으로 인한 성능 오버헤드로 인해 Pod가 동작하지 않을 수 있습니다.

        #resources:
        #  requests:
        #    cpu: 100m
        #    memory: 64Mi
        #  limits:
        #    cpu: 200m
        #    memory: 128Mi

전체 yaml 파일은 NGINX STORE GitHub에서 확인하세요.

변경한 yaml 파일을 통해 애플리케이션을 다시 배포하면 Go 애플리케이션의 경우 Sidecar 컨테이너가 추가됩니다.

$ kubectl get po -n otel

NAME                                              READY   STATUS    RESTARTS      AGE
adservice-556fc48b6d-jzmf7                        1/1     Running   0             117m
cartservice-7f75c74cfc-xjlcp                      1/1     Running   0             121m
checkoutservice-ccdb4f964-qlp72                   2/2     Running   0             117m
currencyservice-7fc4547d5f-7skn8                  1/1     Running   0             121m
emailservice-6cf45dfc9b-j7674                     1/1     Running   0             121m
frontend-7447695d7-nclwt                          2/2     Running   1 (71m ago)   117m
loadgenerator-67fc657cc5-2r7m8                    1/1     Running   0             121m
otel-collector-trace-collector-588c4db9bc-zqndc   1/1     Running   0             2d
paymentservice-5667bcccb5-j8k92                   1/1     Running   0             121m
productcatalogservice-64697cf69b-5bd6q            2/2     Running   0             117m
recommendationservice-669df46c9b-kfzwn            1/1     Running   0             110m
redis-cart-7ff8f4d6ff-zvqt7                       1/1     Running   0             121m
shippingservice-55879f889-rzgqx                   2/2     Running   0             117m

decsribe 명령어로 Pod를 확인하면, auto-instrumentation 구성에 사용된 Init Container와 OTEL 환경 변수가 설정된 애플리케이션 컨테이너를 확인할 수 있습니다.

6. Jaeger UI로 트래픽 흐름 추적

배포한 애플리케이션에서 정상적으로 트레이스 데이터가 수집 및 전송되면, Jaeger UI로 접속 시 Service 항목에서 각 애플리케이션 이름을 확인할 수 있습니다.

Jaeger UI 화면 상단의 System Architecture 탭의 Force Directed Graph에서 원 모양의 애플리케이션 위에 커서를 올리면 연결된 애플리케이션을 확인할 수 있습니다.

Jaeger UI - Force Directed Graph

System Architecture 탭의 DAG 메뉴에선 아래와 같이 구조를 확인할 수 있습니다.
각 숫자는 해당 경로를 통해 호출된 요청 수를 나타냅니다.

Jaeger UI - DAG

배포한 OnlineBoutique에서 트래픽을 발생시킨 이후, 최초 화면(Search 탭)에서 Service를 지정하고 Find Traces 버튼을 클릭하면 트레이스 정보를 확인할 수 있습니다.

애플리케이션 사이에서 발생한 요청의 흐름과 시간을 사진과 같이 확인할 수 있습니다.

Jaeger UI를 활용하면 애플리케이션 간 요청 흐름을 추적하여 서비스 장애 발생 시 문제 지점(병목 구간)을 신속히 식별할 수 있습니다. 또한, 지속적인 지연이 발생하는 서비스나 특정 경로를 분석하여 성능 개선 방향을 도출하는 데 도움을 줄 수 있습니다.

7. 결론

이번 포스트에서는 Kubernetes 클러스터에 OpenTelemetry Operator를 통해 Jaeger를 배포하고, OpenTelemetry Collector와 통합하여 Pod 간의 트래픽 흐름을 추적하는 방법을 알아봤습니다.
그리고 샘플 Online Boutique 애플리케이션을 배포하고, auto-instrumentation과 OpenTelemetry Collector을 통해 트레이스 데이터를 수집하고 Jaeger로 전송하는 방법을 알아봤습니다. 마지막으로 Jaeger UI를 통해 요청의 흐름을 어떤 식으로 확인할 수 있는지 알아봤습니다.

마이크로서비스 아키텍처에서는 애플리케이션이 여러 개의 서비스로 분리되어 있으며, 각 서비스는 서로 다른 Pod 또는 컨테이너에서 독립적으로 실행됩니다. 이러한 구조는 확장성과 유지보수성을 높이는 장점이 있지만, 서비스 간 호출 관계가 복잡해지고 장애 발생 시 원인을 추적하는 것이 어려워지는 문제를 야기합니다.

이러한 문제를 해결하기 위해 분산 트레이싱(Distributed Tracing)은 필수적인 모니터링 기법이 됩니다. Jaeger와 같은 트레이싱 솔루션을 활용하면 다음과 같은 이점을 얻을 수 있습니다.

  • 서비스 간 호출 관계 및 지연 시간 분석: 서비스가 요청을 처리하는 과정에서 발생하는 응답 속도각 호출의 소요 시간을 시각적으로 확인할 수 있어 성능 최적화에 도움이 됩니다.
  • 병목 현상 및 장애 지점 식별: 트랜잭션이 여러 마이크로서비스를 거치는 동안 지연이 발생하는 서비스오류가 발생한 경로를 빠르게 파악할 수 있습니다.
  • 실시간 모니터링 및 디버깅 용이성: 트레이스 데이터를 실시간으로 수집하여, 배포 이후에도 서비스의 상태를 모니터링하고 문제 발생 시 신속한 대응이 가능합니다.

운영 중인 클러스터에 Jaeger와 같은 분산 추적 구성이 필요하신가요? NGINX STORE를 통해 문의해 Kubernetes 기술 지원 서비스에 대해 상담해 보세요.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required