Post

관찰 가능성 엔지니어링 - Chapter2 (OpenTelemetry 시그널 - 분산 추적, 메트릭, 로그)

관찰 가능성 엔지니어링 - Chapter2 (OpenTelemetry 시그널 - 분산 추적, 메트릭, 로그)

실습 환경

아래 이미지와 같은 구조를 마련하기 위해 도커 컴포즈를 사용한다.

서비스포트
Jagger (Trace)16686
Prometheus (Metric)9090
Loki (Log)3100
Grafana (Visualization)3000
Opentelemetry Collector13133
Web Applicaiton8080

실습 환경을 위한 docker compose는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
version: "3.7"
services:
    shopper:
        image: codeboten/shopper:chapter2
        container_name: shopper
        environment:
            - OTEL_EXPORTER_OTLP_ENDPOINT=opentelemetry-collector:4317
            - OTEL_EXPORTER_OTLP_INSECURE=true
            - GROCERY_STORE_URL=http://grocery-store:8080/products
        networks:
            - cloud-native-observability
        depends_on:
            - grocery-store
            - opentelemetry-collector
        stop_grace_period: 1s
    grocery-store:
        image: codeboten/grocery-store:chapter2
        container_name: grocery-store
        environment:
            - OTEL_EXPORTER_OTLP_ENDPOINT=http://opentelemetry-collector:4317
            - OTEL_SERVICE_NAME=grocery-store
            - INVENTORY_URL=http://legacy-inventory:5001/inventory
        networks:
            - cloud-native-observability
        depends_on:
            - legacy-inventory
            - opentelemetry-collector
        stop_grace_period: 1s
        ports:
            - 8080:5000
        deploy:
            resources:
                limits:
                    cpus: "0.50"
                    memory: 80M
    legacy-inventory:
        image: codeboten/legacy-inventory:chapter2
        container_name: inventory
        environment:
            - OTEL_EXPORTER_OTLP_ENDPOINT=http://opentelemetry-collector:4317
            - OTEL_SERVICE_NAME=inventory
        networks:
            - cloud-native-observability
        depends_on:
            - opentelemetry-collector
        stop_grace_period: 1s
        ports:
            - 5001:5001
        deploy:
            resources:
                limits:
                    cpus: "0.50"
                    memory: 80M
    jaeger:
        image: jaegertracing/all-in-one:1.29.0
        container_name: jaeger
        ports:
            - 6831:6831/udp
            - 16686:16686
        networks:
            - cloud-native-observability
    prometheus:
        image: prom/prometheus:v2.29.2
        container_name: prometheus
        volumes:
            - ./config/prometheus/config.yml/:/etc/prometheus/prometheus.yml
        command:
            - "--config.file=/etc/prometheus/prometheus.yml"
            - "--enable-feature=exemplar-storage"
        ports:
            - 9090:9090
        networks:
            - cloud-native-observability
    opentelemetry-collector:
        image: otel/opentelemetry-collector-contrib:0.43.0
        container_name: opentelemetry-collector
        volumes:
            - ./config/collector/config.yml/:/etc/opentelemetry-collector.yml
            - /var/run/docker.sock:/var/run/docker.sock
        command:
            - "--config=/etc/opentelemetry-collector.yml"
        networks:
            - cloud-native-observability
        ports:
            - 4317:4317
            - 13133:13133
            - 8889:8889
        stop_grace_period: 1s
    loki:
        image: grafana/loki:2.3.0
        container_name: loki
        ports:
            - 3100:3100
        command: -config.file=/etc/loki/local-config.yaml
        networks:
            - cloud-native-observability
    promtail:
        image: grafana/promtail:2.3.0
        container_name: promtail
        volumes:
            - /var/log:/var/log
        command: -config.file=/etc/promtail/config.yml
        networks:
            - cloud-native-observability
    grafana:
        image: grafana/grafana:8.3.3
        container_name: grafana
        ports:
            - 3000:3000
        volumes:
            - ./config/grafana/provisioning:/etc/grafana/provisioning
        networks:
            - cloud-native-observability
        environment:
            - GF_AUTH_ANONYMOUS_ENABLED=true
            - GF_AUTH_ORG_ROLE=Editor
            - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
            - GF_AUTH_DISABLE_LOGIN_FORM=true

networks:
    cloud-native-observability:

분산 추적

추적은 전체 시스템에 대한 고유한 요청을 나타내며 동기, 비동기로 구분된다.

또한 각 추적에 기록된 작업은 시스템에서 처리된 작업의 단위를 의미하는 스팬으로 표기된다.

Span

단일 메서드 호출이나 메서드 내에서 호출되는 코드의 일부분을 나타낸다.

추적 내에서의 여러 스팬은 부모-자식 관계로 연결되어있고 각 자식 스팬은 부모 스팬에 대한 정보를 갖고 있다.

추적 내에 있는 첫 번째 스팬을 루트 스팬이라고 하며 당연하게도 부모 스팬에 대한 식별자를 가지고 있지 않다.

아래는 추적, 스팬의 관계를 도식화한 그림이다.

그리고 Jagger에서 실제 추적 정보를 조회하면 아래와 같이 볼 수 있다.


Span Context

W3C의 권고에 따라 정의된 Span의 구성요소는 아래와 같다.

추적 ID

고유 식별자로, 전체 시스템에서 요청을 식별할 수 있는 요소

스팬 ID

컨텍스트와 상호 작용한 최종 스팬과 연결된다.

하위 스팬이 존재하는 경우 상위 스팬의 스팬 ID는 부모 식별자로 불린다.

추적 플래그

추적 수준(Trace Level), 샘플링 여부(Sampling Decision)등 추적에 관한 메타데이터를 담고있다.

추적 상태

개별 벤더가 각자의 시스템에서 필요로 하는 정보를 전파, 추적 데이터를 해석할 수 있도록 한다. ex) vendorA=123456와 같은 형태로 추적 상태 필드에 값을 넣는다.


실제 Span의 조회


메트릭

메트릭이 갖는 필드는 아래와 같다.

메트릭은 단순한 리소스 사용량을 측정할 수 있는 것 뿐만 아니라 아래처럼 Request Counter등 다양한 요소를 얻을 수 있다.

이런 다양한 메트릭 지표를 통해 On-Call 알림을 보내도록 설정하여 엔지니어에게 현재 서비스의 대한 경고를 보낸다.

메트릭 이라는 용어 자체는 서로 다른 측정값들을 캡슐화한 용어이다. 구체적인 데이터는 데이터 포인트라는 데이터 타입을 포착한다.

데이터 포인트 - 카운터(Counter)

단순히 증가만 하는 누적 값이다.

1
2
3
4
5
6
7
# HTTP 요청 카운터
http_requests_total{method="GET", endpoint="/api/users"} 23421
http_requests_total{method="POST", endpoint="/api/users"} 7832

# 시스템 에러 카운터
system_errors_total{type="connection_timeout"} 382
system_errors_total{type="internal_error"} 89

데이터 포인트 - 게이지(Gauge)

현재 상태를 나타내는 순간값이다.

1
2
3
4
5
6
# CPU 사용률
cpu_usage_percent{core="0"} 78.5
cpu_usage_percent{core="1"} 92.3

# 메모리 사용량
memory_usage_bytes{pod="backend-prod-1"} 1.28e+9

데이터 포인트 - 히스토그램(Histogram)

값의 분포를 구간(bucket)별로 측정하는 관측값이다. 구간은 보통 le(less equal) 레이블로 표시한다.

1
2
3
4
5
# HTTP 응답시간 분포
http_request_duration_seconds_bucket{le="0.1"} 12323  # 100ms 이하
http_request_duration_seconds_bucket{le="0.3"} 14236  # 300ms 이하
http_request_duration_seconds_bucket{le="1.0"} 15564  # 1s 이하
http_request_duration_seconds_bucket{le="+Inf"} 15600 # 전체

데이터 포인트 - 요약(Summary)

히스토그램과 유사하나 서버측에서 백분위를 계산하여 클라이언트 부하는 적으나 정확도는 떨어진다.

1
2
3
4
# API 응답시간 요약
api_response_latency_seconds{quantile="0.5"} 0.042  # 중앙값
api_response_latency_seconds{quantile="0.9"} 0.087  # 90th 백분위
api_response_latency_seconds{quantile="0.99"} 0.184 # 99th 백분위

OpenTelemetry 데이터 포인트 구조

OpenTelemetry가 활성 스팬 정보를 메트릭에 포함시키도록하여 더 많은 정보를 얻게 해준다.

OpenTelemetry에서 정의된 데이터 포인트는 아래와 같은 정보들을 갖는다.

1. 추적 ID (Trace ID)

  • 전체 트랜잭션의 고유 식별자
  • 형식: 16바이트 hex 문자열
    1
    
    trace_id: "4bf92f3577b34da6a3ce929d0e0e4736"
    

2. 스팬 ID (Span ID)

  • 개별 작업 단위의 식별자
  • 형식: 8바이트 hex 문자열
    1
    
    span_id: "00f067aa0ba902b7"
    

3. 타임스탬프 (Timestamp)

  • 이벤트 발생 시각 (나노초 단위)
    1
    
    timestamp: "2024-01-26T09:00:00.123456789Z"
    

4. 표준 속성 (Standard Attributes)

서비스 식별

1
2
3
4
5
service:
  name: "payment-service"
  version: "1.2.3"
  environment: "production"
  instance_id: "pod-xyz-123"

HTTP 요청 정보

1
2
3
4
5
http:
  method: "POST"
  url: "/api/v1/users"
  status_code: 200
  user_agent: "Mozilla/5.0..."

데이터베이스 작업

1
2
3
4
5
database:
  system: "postgresql"
  name: "users"
  operation: "SELECT"
  statement: "SELECT * FROM users WHERE id = ?"

클라우드/인프라 정보

1
2
3
4
5
6
7
cloud:
  provider: "aws"
  region: "us-east-1"
  zone: "us-east-1a"
kubernetes:
  pod_name: "backend-pod-123"
  namespace: "production"

사용자 정의 속성

1
2
3
4
custom:
  customer_id: "12345"
  transaction_id: "tx_789"
  error_type: "connection_timeout"

로그

로그는 공통 로그 형식이 있지만 이를 모두가 완벽하게 지킬 순 없을것이다. 그럼에도 다음 두 항목은 반드시 구성되어야하는 요소이다.

  • 이벤트가 발생한 타임스탬프
  • 이벤트를 나타내는 메세지

구조화된 로깅에서는 Key-Value로 표현하기도하고, 구분자와 정의된 순서를 이용한 로그를 만들어내기도한다.

This post is licensed under CC BY 4.0 by the author.