IMG-LOGO
공지사항 :

OpenTelemetry + Tempo를 이용한 Kubernetes 분산 추적

lmkfox - 2026-09-15 06:57:04 2 Views 0 Comment

OpenTelemetry + Tempo를 이용한 Kubernetes 분산 추적

FastAPI → Redis → PostgreSQL 요청 흐름을 Trace ID 하나로 추적하는 실전 Kubernetes Observability

Kubernetes 환경에서 Prometheus와 Loki를 구축하면 시스템 상태와 로그를 상당히 편리하게 확인할 수 있다.

하지만 한 가지 문제가 남는다.

예를 들어 사용자가 다음 API를 호출했다고 생각해보자.

POST /api/orders

요청이 다음과 같은 구조로 처리된다.

사용자
  |
  v
Nginx / Ingress
  |
  v
FastAPI
  |
  +------> Redis
  |
  +------> PostgreSQL

그런데 사용자가 500 오류를 받았다.

Prometheus에서는:

HTTP 500 증가

를 확인할 수 있고,

Loki에서는:

Database connection timeout

이라는 로그를 찾을 수 있다.

하지만 다음 질문에는 바로 답하기 어렵다.

이 요청이 정확히 어떤 Pod를 거쳤는가?

Redis 요청에는 몇 ms가 걸렸는가?

PostgreSQL 쿼리에 몇 ms가 걸렸는가?

전체 요청 시간 중 어디에서 가장 많은 시간이 발생했는가?

Nginx → FastAPI → Redis → PostgreSQL의 호출 관계는 어떻게 되는가?

이 문제를 해결하는 기술이 **Distributed Tracing(분산 추적)**이다.

이번 실습에서는 다음 구조를 구축한다.

                     Client
                       |
                       v
                  Ingress/Nginx
                       |
                       v
                    FastAPI
                  /         \
                 /           \
                v             v
             Redis       PostgreSQL
                \             /
                 \           /
                  \         /
                   v       v
                 OpenTelemetry
                       |
                       v
                 OpenTelemetry
                   Collector
                       |
                       v
                     Tempo
                       |
                       v
                    Grafana

1. Distributed Tracing이란?

Distributed Tracing은 하나의 요청이 여러 시스템을 거쳐 처리되는 과정을 추적하는 기술이다.

예를 들어 사용자가 다음 API를 호출한다고 하자.

GET /api/users/100

실제 내부에서는 다음과 같은 작업이 수행될 수 있다.

HTTP Request
     |
     v
FastAPI
     |
     +---- Redis GET
     |
     +---- PostgreSQL SELECT
     |
     v
Response

이때 전체 요청을 하나의 Trace라고 한다.

Trace 내부에는 여러 개의 Span이 존재한다.

Trace
 |
 +-- Span: HTTP Request
 |
 +-- Span: Redis GET
 |
 +-- Span: PostgreSQL SELECT
 |
 +-- Span: Response

즉:

Trace = 하나의 요청 전체

Span = 요청을 처리하는 각각의 작업

이라고 이해하면 쉽다.


2. Trace와 Span의 관계

분산 추적의 기본 개념은 반드시 이해해야 한다.

예를 들어 요청 하나가 다음과 같이 처리되었다고 하자.

Trace ID
abc123

그리고 내부 작업이:

FastAPI HTTP
Redis GET
PostgreSQL SELECT

로 구성된다.

그러면:

Trace: abc123
 |
 +-- Span A: FastAPI HTTP
 |
 +-- Span B: Redis GET
 |
 +-- Span C: PostgreSQL SELECT

형태가 된다.

Span에는 다음과 같은 정보가 들어갈 수 있다.

Span ID
Trace ID
Service Name
Operation
Start Time
End Time
Duration
Status
Attributes

예:

Trace ID: abc123
Span: PostgreSQL SELECT
Duration: 83ms
Status: OK

이 정보를 이용하면 어느 구간에서 시간이 오래 걸렸는지 확인할 수 있다.


3. OpenTelemetry란?

OpenTelemetry는 애플리케이션의 Metrics, Logs, Traces와 같은 Telemetry 데이터를 수집하고 전달하기 위한 오픈 표준 및 프로젝트다.

이번 실습에서는 특히 Trace에 집중한다.

Application
     |
     v
OpenTelemetry SDK
     |
     v
OpenTelemetry Collector
     |
     v
Tempo

OpenTelemetry의 장점은 특정 백엔드에 종속되지 않는다는 것이다.

예를 들어:

OpenTelemetry
      |
      +---- Tempo
      +---- Jaeger
      +---- 다른 Trace Backend

와 같이 구성할 수 있다.

따라서 애플리케이션에 OpenTelemetry를 적용해 놓으면 나중에 Trace 저장 시스템을 변경하기도 상대적으로 편하다.


4. Tempo란?

Tempo는 Grafana 생태계의 Distributed Tracing Backend다.

기존에 구축한 시스템과 비교하면 이해하기 쉽다.

역할 기술
Metrics Prometheus
Logs Loki
Traces Tempo
Visualization Grafana
Alerts Alertmanager
Telemetry Collection OpenTelemetry Collector / Alloy

전체 구조는 다음과 같다.

                 Observability
                       |
        +--------------+--------------+
        |              |              |
        v              v              v
     Metrics          Logs          Traces
        |              |              |
   Prometheus          Loki          Tempo
        |              |              |
        +--------------+--------------+
                       |
                       v
                    Grafana

이제 Kubernetes 환경에서 세 가지 데이터를 함께 볼 수 있게 된다.


5. 왜 로그만으로 부족한가?

예를 들어 Loki에서 다음 로그를 발견했다고 하자.

Database query timeout

로그만 보면 PostgreSQL 문제가 의심된다.

하지만 실제 요청을 Trace로 보면:

HTTP Request       500ms
 |
 +-- Redis GET       10ms
 |
 +-- PostgreSQL     450ms
 |
 +-- Response        40ms

PostgreSQL에서 대부분의 시간이 소요되고 있다는 사실을 바로 확인할 수 있다.

반대로:

HTTP Request       500ms
 |
 +-- Redis GET      470ms
 |
 +-- PostgreSQL      20ms

라면 Redis가 병목이다.

따라서 Distributed Tracing은 성능 병목 구간을 찾는 데 매우 유용하다.


6. 전체 Kubernetes 구조

이번 실습에서는 다음 환경을 기준으로 한다.

Kubernetes Cluster

namespace: tax-prod

              Internet
                  |
                  v
             Ingress/Nginx
                  |
                  v
               FastAPI
              /       \
             /         \
            v           v
         Redis       PostgreSQL


Observability

FastAPI
   |
   v
OpenTelemetry SDK
   |
   v
OTel Collector
   |
   v
Tempo
   |
   v
Grafana

그리고 기존 로그 시스템은:

FastAPI/Nginx
      |
      v
Grafana Alloy
      |
      v
     Loki

Metrics는:

FastAPI/Node/Pod
      |
      v
Prometheus
      |
      v
Grafana

로 구성한다.


7. Tempo 설치

먼저 Grafana Helm Repository를 등록한다.

helm repo add grafana https://grafana.github.io/helm-charts

Repository를 업데이트한다.

helm repo update

Tracing용 Namespace를 만든다.

kubectl create namespace tracing

환경에 맞는 Tempo values 파일을 작성한다.

vi tempo-values.yaml

학습 환경에서는 단일 인스턴스 형태로 시작할 수 있다.

예:

deploymentMode: SingleBinary

tempo:
  reportingEnabled: false

  resources:
    requests:
      cpu: 100m
      memory: 256Mi
    limits:
      cpu: 500m
      memory: 1Gi

persistence:
  enabled: true
  size: 20Gi

설치:

helm upgrade --install tempo grafana/tempo \
  -n tracing \
  -f tempo-values.yaml

상태 확인:

kubectl get pods -n tracing
kubectl get svc -n tracing

정상적으로 설치되었다면 Tempo Pod가 Running 상태인지 확인한다.


8. 운영 환경에서는 Storage가 중요하다

Trace 데이터도 계속 증가한다.

예를 들어:

1초에 100 requests
       |
       v
Trace 데이터 지속 증가

따라서 운영 환경에서는 Storage를 고려해야 한다.

개념적으로:

OpenTelemetry
      |
      v
     Tempo
      |
      v
Object Storage
      |
      +-- S3
      +-- MinIO
      +-- Cloud Storage

개발 환경에서는 Persistent Volume으로 시작할 수 있지만 운영 환경에서는 Object Storage 기반 설계를 검토하는 것이 좋다.


9. OpenTelemetry Collector란?

OpenTelemetry Collector는 Telemetry 데이터를 중간에서 수집하고 전달하는 역할을 한다.

구조는 다음과 같다.

FastAPI
   |
   | OTLP
   v
OpenTelemetry Collector
   |
   | OTLP
   v
Tempo

Collector를 사용하는 이유는 애플리케이션과 Backend를 직접 연결하지 않아도 되기 때문이다.

예를 들어:

Application
      |
      v
Collector
      |
      +---- Tempo
      +---- 다른 Trace Backend

형태로 확장할 수 있다.

Collector는 다음과 같은 기능을 수행할 수 있다.

Receive
Process
Filter
Batch
Export

10. OpenTelemetry Collector 설치

Collector도 Kubernetes 환경에서 Helm으로 구성할 수 있다.

OpenTelemetry Helm Repository를 등록한다.

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

업데이트:

helm repo update

Collector용 Namespace:

kubectl create namespace observability

Collector values 파일:

vi otel-values.yaml

예제:

mode: deployment

image:
  repository: otel/opentelemetry-collector-k8s

config:
  receivers:
    otlp:
      protocols:
        grpc:
        http:

  processors:
    batch:

  exporters:
    otlp:
      endpoint: tempo.tracing.svc.cluster.local:4317
      tls:
        insecure: true

  service:
    pipelines:
      traces:
        receivers:
          - otlp
        processors:
          - batch
        exporters:
          - otlp

설치:

helm upgrade --install otel-collector \
  open-telemetry/opentelemetry-collector \
  -n observability \
  -f otel-values.yaml

확인:

kubectl get pods -n observability

11. OTLP란?

OpenTelemetry에서 중요한 개념이 **OTLP(OpenTelemetry Protocol)**다.

Application에서 Collector로:

FastAPI
   |
   | OTLP
   v
Collector

Collector에서 Tempo로:

Collector
   |
   | OTLP
   v
Tempo

형태로 데이터를 전달한다.

OTLP는 gRPC와 HTTP 방식으로 사용할 수 있다.

대표적인 포트는:

4317 = OTLP gRPC
4318 = OTLP HTTP

따라서 Kubernetes Service를 구성할 때 이 포트를 확인해야 한다.


12. FastAPI에 OpenTelemetry 적용

이제 실제 애플리케이션에 OpenTelemetry를 적용한다.

Python 패키지를 설치한다.

pip install \
  opentelemetry-api \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp \
  opentelemetry-instrumentation-fastapi

FastAPI 자동 계측을 사용할 수 있다.

예:

from fastapi import FastAPI

from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

app = FastAPI()

FastAPIInstrumentor.instrument_app(app)

이렇게 하면 FastAPI 요청에 대한 Trace를 자동으로 생성할 수 있다.


13. OTLP Exporter 설정

Collector로 Trace를 보내도록 설정한다.

예:

from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

resource = Resource.create({
    "service.name": "fastapi-backend"
})

provider = TracerProvider(
    resource=resource
)

exporter = OTLPSpanExporter(
    endpoint="otel-collector.observability.svc.cluster.local:4317",
    insecure=True
)

provider.add_span_processor(
    BatchSpanProcessor(exporter)
)

trace.set_tracer_provider(provider)

그리고 FastAPI instrumentation:

from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

FastAPIInstrumentor.instrument_app(app)

구조는 다음과 같다.

FastAPI
   |
   v
TracerProvider
   |
   v
OTLP Exporter
   |
   v
Collector
   |
   v
Tempo

14. Kubernetes 환경에서는 환경변수로 관리

운영 환경에서는 Collector 주소를 코드에 직접 넣는 것보다 환경변수를 사용하는 것이 좋다.

예:

env:
  - name: OTEL_SERVICE_NAME
    value: "fastapi-backend"

  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: "http://otel-collector.observability.svc.cluster.local:4317"

  - name: OTEL_EXPORTER_OTLP_INSECURE
    value: "true"

그러면 환경별로 쉽게 변경할 수 있다.

개발:

otel-collector-dev

운영:

otel-collector-prod

와 같이 분리할 수 있다.


15. Redis Trace 계측

FastAPI에서 Redis를 사용하는 경우 Redis 작업도 Trace에 포함시키면 좋다.

OpenTelemetry에서는 Redis와 같은 라이브러리에 대한 instrumentation을 사용할 수 있다.

개념적으로:

FastAPI Span
      |
      +-- Redis GET Span
      |
      +-- Redis SET Span

이렇게 된다.

예:

Trace ID: abc123

FastAPI GET /users
 |
 +-- Redis GET user:100
 |
 +-- PostgreSQL SELECT

이제 Redis에서 지연이 발생하는지 쉽게 확인할 수 있다.


16. PostgreSQL Trace 계측

Database도 마찬가지다.

PostgreSQL을 사용하는 Python 애플리케이션에 Database instrumentation을 적용하면 SQL 관련 Span을 확인할 수 있다.

구조:

FastAPI
   |
   +-- Redis
   |
   +-- PostgreSQL
          |
          +-- SELECT
          +-- INSERT
          +-- UPDATE

예를 들어 PostgreSQL SELECT가:

Duration = 850ms

라고 나타난다면 Database query가 병목일 가능성이 높다.


17. 중요한 점 — SQL 정보와 보안

Database Trace에는 SQL 관련 정보가 포함될 수 있다.

따라서 운영 환경에서는 개인정보나 비밀번호 등의 민감한 데이터가 Trace에 들어가지 않도록 주의해야 한다.

좋지 않은 예:

SELECT * FROM users
WHERE password='123456'

Trace에 민감한 정보가 그대로 남으면 안 된다.

따라서 다음을 고려한다.

민감 데이터 제거
SQL parameter masking
Trace retention 설정
접근 권한 제한

18. 직접 Span 생성하기

자동 instrumentation만으로 부족한 경우 직접 Span을 만들 수 있다.

예:

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

그리고:

with tracer.start_as_current_span("calculate-tax"):
    result = calculate_tax()

이렇게 하면 다음과 같은 Span이 생성된다.

FastAPI Request
       |
       +-- calculate-tax
              |
              +-- DB Query

복잡한 비즈니스 로직을 추적할 때 매우 유용하다.


19. Trace ID란?

Distributed Tracing에서 가장 중요한 정보 중 하나가 Trace ID다.

예:

Trace ID:
4bf92f3577b34da6a3ce929d0e0e4736

하나의 요청에 동일한 Trace ID가 연결된다.

Nginx
 |
 | Trace ID = ABC
 v
FastAPI
 |
 | Trace ID = ABC
 +------> Redis
 |
 | Trace ID = ABC
 +------> PostgreSQL

따라서 로그에도 Trace ID를 기록하면 강력한 장애 분석이 가능해진다.


20. Logs와 Trace 연결

앞에서 구축한 Loki와 결합하면 더욱 강력해진다.

예:

Trace ID
ABC123

FastAPI 로그:

ERROR Database timeout
trace_id=ABC123

Grafana에서 해당 Trace를 클릭하면:

Trace
 |
 +-- FastAPI
 |
 +-- PostgreSQL

그리고 관련 로그를 바로 확인할 수 있다.

개념적으로:

             Trace ID
                |
       +--------+--------+
       |                 |
       v                 v
     Tempo              Loki
       |                 |
       +--------+--------+
                |
                v
             Grafana

이것이 **Logs와 Traces의 상관관계(Correlation)**다.


21. Metrics와 Trace까지 연결

이제 Prometheus까지 연결한다.

Prometheus
    |
    | Metrics
    v
 Grafana
    |
    +------ Loki
    |
    +------ Tempo

예를 들어 Dashboard에서:

HTTP Error Rate
      |
      v
500 증가
      |
      v
해당 시간대 Trace 확인
      |
      v
PostgreSQL Span 확인
      |
      v
Query 1.2초
      |
      v
관련 Loki 로그 확인
      |
      v
Database timeout

이런 분석이 가능해진다.


22. Grafana에 Tempo 연결

Grafana에서:

Connections
   ↓
Data Sources
   ↓
Add data source
   ↓
Tempo

를 선택한다.

Tempo Service 주소를 입력한다.

예:

http://tempo.tracing.svc.cluster.local:3200

환경에 따라 Service 이름과 포트는 실제 설치 결과를 확인해야 한다.

kubectl get svc -n tracing

정상 연결을 확인한다.


23. Grafana Explore에서 Trace 확인

Grafana:

Explore
   ↓
Tempo

를 선택한다.

Trace ID를 이용해 검색할 수 있다.

예:

Trace ID
4bf92f3577b34da6a3ce929d0e0e4736

결과는 다음과 같은 구조로 나타날 수 있다.

Trace
 |
 +-- GET /api/users
      |
      +-- Redis GET
      |
      +-- PostgreSQL SELECT

각 Span을 클릭하면:

Duration
Attributes
Status
Service
Operation

등을 확인할 수 있다.


24. Trace Waterfall 이해하기

Distributed Tracing에서 가장 중요한 화면 중 하나가 Waterfall이다.

예:

0ms                                      1000ms

FastAPI      |-----------------------------|
             |
Redis        |----|
             |
PostgreSQL   |              |---------------|
             |
Response     |------------------------------|

이 화면을 보면 어느 작업이 오래 걸렸는지 바로 알 수 있다.

예를 들어:

FastAPI       1000ms
Redis           20ms
PostgreSQL     850ms

라면 PostgreSQL이 병목이다.

반면:

FastAPI       1000ms
Redis         900ms
PostgreSQL     30ms

라면 Redis가 병목이다.


25. 장애 상황 분석 실습

이제 실제 장애를 만들어보자.

API:

GET /api/users/100

를 호출한다.

정상 상태:

FastAPI       100ms
Redis          10ms
PostgreSQL     30ms

그런데 PostgreSQL에 의도적으로 느린 Query를 발생시킨다.

결과:

FastAPI       1200ms
Redis          10ms
PostgreSQL   1100ms

Grafana Tempo에서 다음처럼 확인할 수 있다.

GET /api/users/100
|
+-- Redis GET             10ms
|
+-- PostgreSQL SELECT   1100ms

이것만으로도 Database가 병목이라는 것을 확인할 수 있다.


26. Loki와 함께 장애 분석

Tempo에서 느린 PostgreSQL Span을 찾았다.

Trace ID:

ABC123

이제 Loki에서 Trace ID를 검색한다.

{app="backend"} |= "ABC123"

결과:

Database connection timeout

그리고 PostgreSQL 로그:

{app="postgres"} |= "timeout"

결과:

connection pool exhausted

최종적으로:

HTTP 500
   |
   v
FastAPI
   |
   v
PostgreSQL Span 1100ms
   |
   v
Loki
   |
   v
connection pool exhausted

라는 장애 분석이 가능해진다.


27. 장애 테스트 — Redis 지연

이번에는 Redis를 느리게 만들어본다.

FastAPI
   |
   v
Redis
   |
   X
느린 응답

Trace:

FastAPI       1000ms
Redis          950ms
PostgreSQL      20ms

Waterfall에서 Redis Span이 길게 나타난다.

이런 방식으로 성능 병목을 빠르게 찾을 수 있다.


28. 장애 테스트 — PostgreSQL 장애

PostgreSQL Pod를 삭제한다.

kubectl delete pod <postgres-pod> -n tax-prod

FastAPI에서 DB API를 호출한다.

Trace에서는:

FastAPI
   |
   +-- PostgreSQL
          |
          +-- ERROR
          +-- Timeout

가 나타날 수 있다.

동시에 Loki에서는:

Database connection refused

를 확인할 수 있다.

Kubernetes에서는:

kubectl get pods -n tax-prod

로 PostgreSQL Pod 상태를 확인한다.

이렇게 세 가지 정보를 연결한다.

Tempo
 ↓
어디에서 실패했는가?

Loki
 ↓
무슨 오류가 발생했는가?

Kubernetes
 ↓
Pod/서비스 상태는 어떠한가?

29. Kubernetes에서 Trace Context 전달

Distributed Tracing에서 매우 중요한 개념이 Trace Context Propagation이다.

요청이:

Client
  ↓
Nginx
  ↓
FastAPI
  ↓
Redis
  ↓
PostgreSQL

로 이동할 때 Trace Context가 전달되어야 한다.

대표적으로 W3C Trace Context가 사용된다.

HTTP Header 예:

traceparent:
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

이 정보를 기반으로 하나의 요청을 하나의 Trace로 연결할 수 있다.


30. Trace ID를 로그에 남기는 이유

운영 환경에서 다음과 같은 로그가 있다고 하자.

ERROR Database timeout

이것만으로는 어떤 요청인지 알기 어렵다.

하지만:

ERROR Database timeout
trace_id=ABC123

라면 바로 Tempo에서 찾을 수 있다.

따라서 이상적인 구조는:

Application Log
       |
       +-- timestamp
       +-- level
       +-- service
       +-- trace_id
       +-- span_id
       +-- message

이다.


31. Grafana에서 완성되는 Observability

이제 지금까지 구축한 모든 시스템을 연결해보자.

                         Kubernetes
                              |
             +----------------+----------------+
             |                |                |
             v                v                v
          Metrics            Logs            Traces
             |                |                |
        Prometheus           Loki            Tempo
             |                |                |
             +----------------+----------------+
                              |
                              v
                           Grafana
                              |
                +-------------+-------------+
                |             |             |
                v             v             v
             Dashboard       Logs          Traces

Alertmanager까지 연결하면:

Prometheus
    |
    v
Alertmanager
    |
    +-- Email
    +-- Slack
    +-- Webhook

구조가 된다.


32. 실제 장애 대응 흐름

운영 중 다음 알림이 발생했다고 가정하자.

CRITICAL
FastAPI HTTP 500 rate > 5%

운영자는 다음 순서로 확인한다.

1단계 — Metrics

Prometheus에서:

HTTP 500 증가

확인.

2단계 — Logs

Loki에서:

{app="backend"} |= "ERROR"

검색.

결과:

Database timeout

3단계 — Trace

Tempo에서 해당 요청 Trace를 확인.

FastAPI
 |
 +-- Redis      10ms
 |
 +-- PostgreSQL 2.8s

4단계 — Kubernetes

kubectl get pods -n tax-prod

PostgreSQL 상태 확인.

5단계 — Database

PostgreSQL 로그에서:

connection pool exhausted

확인.

이제 단순히 "FastAPI 500 오류"가 아니라:

PostgreSQL Connection Pool 부족

이라는 근본 원인까지 좁혀갈 수 있다.


33. Metrics + Logs + Traces의 역할

세 가지 기술의 역할을 명확히 구분하면 이해하기 쉽다.

데이터 질문
Metrics 문제가 발생하고 있는가?
Logs 무슨 오류가 발생했는가?
Traces 요청이 어디에서 느려지거나 실패했는가?

예:

Metrics
HTTP 500 증가
      ↓
Logs
Database timeout
      ↓
Trace
PostgreSQL 2.8초
      ↓
Kubernetes
PostgreSQL Pod 상태 확인
      ↓
Root Cause
Connection Pool 문제

이것이 현대적인 Observability의 기본적인 장애 분석 방법이다.


34. Production 환경에서 주의할 점

Distributed Tracing을 운영 환경에 적용할 때는 몇 가지를 반드시 고려해야 한다.

34.1 모든 요청을 무조건 저장하지 않기

트래픽이 매우 많다면 모든 Trace를 저장하면 데이터 양이 증가한다.

따라서 Sampling을 사용할 수 있다.

예:

전체 요청 = 100%

Trace 저장 = 10%

또는:

정상 요청 = 일부 Sampling

오류 요청 = 높은 우선순위

와 같은 전략을 고려할 수 있다.


35. Trace Sampling

Sampling은 Trace 데이터의 양을 조절하는 방법이다.

예를 들어 초당:

10,000 requests

가 발생한다면 모든 Trace를 저장하는 것은 부담이 될 수 있다.

따라서:

10,000 requests
       |
       v
Sampling
       |
       v
1,000 traces

처럼 줄일 수 있다.

다만 장애 분석을 위해 오류 Trace는 최대한 보존하는 전략을 고려할 수 있다.


36. Trace 데이터 보존 기간

Trace도 로그와 마찬가지로 보존 기간이 필요하다.

예:

Hot Trace
7일

Archive
30일

Delete
90일

실제 보존 기간은 시스템 규모와 장애 분석 요구사항에 따라 결정한다.


37. Resource 제한

OpenTelemetry Collector도 Kubernetes Pod이므로 Resource 제한을 설정해야 한다.

예:

resources:
  requests:
    cpu: 100m
    memory: 128Mi

  limits:
    cpu: 500m
    memory: 512Mi

Trace가 증가하면 Collector 자체의 CPU와 Memory 사용량도 증가할 수 있다.

따라서 Prometheus에서 Collector를 모니터링하는 것이 좋다.

Collector CPU
Collector Memory
Dropped Spans
Export Errors
Queue Size

등을 확인해야 한다.


38. Collector 장애도 모니터링해야 한다

다음 상황을 생각해보자.

FastAPI
   |
   v
Collector
   X
   |
   v
Tempo

Collector가 장애가 발생하면 Trace가 유실될 수 있다.

따라서:

Application
     |
     v
Collector
     |
     v
Tempo

각 구간을 모니터링해야 한다.

특히 운영 환경에서는:

Collector replicas
Queue
Retry
Batch
Resource

등을 고려해야 한다.


39. 개발/운영 환경 분리

앞서 Kubernetes 애플리케이션에서 개발과 운영 환경을 분리했듯이 Observability 환경도 분리할 수 있다.

예:

Namespace
|
+-- tax-dev
|
+-- tax-prod
|
+-- observability
|
+-- tracing
|
+-- logging

또는:

dev
 └── Trace

prod
 └── Trace

환경을 구분할 수 있다.

Trace에 다음 정보를 넣는 것도 좋다.

deployment.environment=production

그러면 Grafana에서 개발/운영 데이터를 구분할 수 있다.


40. 최종 Kubernetes Observability Architecture

지금까지 작성한 Prometheus, Loki, Tempo를 모두 연결하면 다음과 같은 구조가 된다.

                         Internet
                            |
                            v
                     Ingress / Nginx
                            |
                            v
                         FastAPI
                       /    |    \
                      /     |     \
                     v      v      v
                  Redis PostgreSQL Other Service


                Kubernetes Observability
                         |
        +----------------+----------------+
        |                |                |
        v                v                v
     Metrics            Logs            Traces
        |                |                |
   Prometheus           Loki            Tempo
        |                |                |
        +----------------+----------------+
                         |
                         v
                      Grafana
                         |
                         v
                Unified Observability
                         |
              +----------+----------+
              |          |          |
              v          v          v
           Dashboard    Logs       Trace
                         |
                         v
                    Root Cause

여기에:

Prometheus
    |
    v
Alertmanager
    |
    +-- Email
    +-- Slack
    +-- Webhook

까지 추가하면 운영 환경에서 필요한 기본적인 Observability 구성이 완성된다.


41. 시스템 엔지니어가 반드시 기억해야 할 구조

전체 내용을 가장 간단하게 정리하면 다음과 같다.

Prometheus
    |
    +-- CPU
    +-- Memory
    +-- Request
    +-- Error Rate
    +-- Latency

Loki
    |
    +-- Application Log
    +-- Nginx Log
    +-- Kubernetes Log
    +-- Error Log

Tempo
    |
    +-- Request Trace
    +-- Service Span
    +-- Redis Span
    +-- Database Span

Grafana
    |
    +-- Metrics
    +-- Logs
    +-- Traces

그리고 장애가 발생하면:

1. Metrics
   "문제가 있는가?"

2. Logs
   "무슨 오류인가?"

3. Trace
   "어디에서 문제가 발생했는가?"

4. Kubernetes
   "Pod/Service/Node 상태는 정상인가?"

5. Root Cause
   "왜 문제가 발생했는가?"

순서로 접근하면 된다.


42. 최종 정리

Kubernetes 환경에서 애플리케이션이 복잡해질수록 단순한 kubectl logs만으로는 장애 원인을 찾기 어려워진다.

특히 다음과 같은 구조에서는 더욱 그렇다.

Ingress
   ↓
Nginx
   ↓
FastAPI
   ↓
Redis
   ↓
PostgreSQL

하나의 사용자 요청이 여러 서비스를 통과하기 때문이다.

이때 Distributed Tracing을 사용하면:

사용자 요청
   |
   v
Trace ID
   |
   +-- Nginx
   |
   +-- FastAPI
   |
   +-- Redis
   |
   +-- PostgreSQL

전체 요청 흐름을 하나의 Trace로 확인할 수 있다.

이번 실습에서 구축한 핵심 구조는 다음과 같다.

FastAPI
   |
   v
OpenTelemetry SDK
   |
   v
OpenTelemetry Collector
   |
   v
Tempo
   |
   v
Grafana

그리고 기존 시스템과 결합하면:

              Kubernetes
                   |
       +-----------+-----------+
       |           |           |
       v           v           v
    Metrics       Logs       Traces
       |           |           |
 Prometheus       Loki        Tempo
       |           |           |
       +-----------+-----------+
                   |
                   v
                Grafana
                   |
                   v
          장애 원인 분석

이제 Kubernetes Observability의 핵심 세 가지인 Metrics + Logs + Traces가 모두 연결되었다.

가장 중요한 것은 각각의 기술을 별도로 사용하는 것이 아니라 서로 연결하는 것이다.

Prometheus
"500 오류가 증가했다"
        ↓
Loki
"Database timeout 로그가 발생했다"
        ↓
Tempo
"PostgreSQL Span에서 2.8초가 소요되었다"
        ↓
Kubernetes
"PostgreSQL Pod와 연결 상태를 확인한다"
        ↓
Root Cause
"Database Connection Pool 부족"

이렇게 Metrics → Logs → Traces → Kubernetes → Root Cause로 연결해서 장애를 분석할 수 있다면 Kubernetes 운영에 필요한 Observability의 핵심 개념을 제대로 이해한 것이다.


댓글