본문 바로가기

Log,Monitorings

[Opentelemetry] spanmetrics connector에 대해 알아보기

반응형

Span Metrics Connector는 span data로부터 RED(Request, Error, Duration) 메트릭을 만들어낸다.

생성되는 메트릭들은 다음의 dimension들을 최소한 가지게 된다.

- service.name
- span.name
- span.kind
- status.code

 

 

1. 설정값

 

histogram : Span duration 측정기준으로부터 record를 계산하기 위한 히스토그램 (explicit, exponential)

dimensions : service.name, span.name, span.kind, status.code 외에 생성되는 메트릭들의 label로 등록하고 싶은 리스트

 

예를 들어 시계열 메트릭의 Label로 IP , Region 등을 추가하고 싶다면 dimensions에 추가하면 된다.

exclude_dimensions : service.name, span.name, span.kind, status.code 중 제외하고 싶은 dimension

dimensions_cache_size : Collector의 메모리 사용량을 향상시키기 위한 Dimension 전용 캐시 사이즈 (default : 1000)

resource_metrics_cache_size : 서비스를 위해 메트릭을 보관하는 캐시 사이즈이며 대부분 메모리 누수를 방지하고 메트릭 타임스탬프 재설정을 수정하기 위한 누적 시간과 관련이 있음 (default : 1000)

namespace : 생성된 메트릭들의 네임스페이스를 정의한다. 만약 네임스페이스가 제공되었다면 생성된 메트릭 네임들이 해당 네임스페이스에 추가된다.

metrics_flush_interval : 생성된 메트릭들을 flush 하는 주기 (default : 15s)

exemplars : examplars를 히스토그램에 어떻게 붙일지 설정하는 섹션
enabled: true | false (default : false)

events : events 메트릭을 설정
enabled: true | false (default : false)
dimensions : (true일 경우 필수값) event metric에 dimention으로써 추가하고 싶은 span event의 attribute 리스트

resource_metrics_key_attributes : 리소스 attribute를 필터링하며 resource metrics key map hash를 만들어내는데 사용한다.

 

이는 서비스 재시작 전반에 걸쳐 리소스 속성이 변경되어 메트릭 카운터가 중단(및 중복)될 수 있는 상황을 방지하는 데 사용된다.

모든 Attribute를 가질 필요는 없으며 고유 리소스를 적절하게 식별하거나 둘 이상의 서비스 및 스팬으로부터의 데이터를 집계하는 위험을 발생시키기에 충분한 속성을 포함하면 된다.

 

 

아래 Github issue에 관련 내용이 나오며, resource_metrics_key_attributes 설정을 통해 Counter 값이 초기화되는 이슈를 해결할 수 있다고 하니 꼭 설정해주자.

 

https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21101

 

Spanmetrics Connector are not giving correct metrics of spans · Issue #21101 · open-telemetry/opentelemetry-collector-contrib

Component(s) connector/spanmetrics What happened? Description I am using spanmetrics connector to generate metrics from span. The calls metrics is counter metrics so it's value should always increa...

github.com



전체 설정에 대한 정보는 아래 config.go에서 확인할 수 있다.

https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/connector/spanmetricsconnector/config.go

 

 


 

2. 예시

docker-compose.yaml

version: '2'

services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.92.0
    volumes:
      - ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
    ports:
      - 4318:4318 # OTLP http receiver
      - 55679:55679 # zpages

 

 

 

otel-collector-config.yaml

receivers:
  otlp:
    protocols:
      http:
      grpc:

exporters:
  logging:
    loglevel: debug

  # prometheusremotewrite:
  #   endpoint: http://localhost:9090/api/v1/write
  #   target_info:
  #     enabled: true

connectors:
  spanmetrics:
    histogram:
      explicit:
        buckets: [50ms, 100ms, 200ms, 500ms, 1s, 5s]
    aggregation_temporality: "AGGREGATION_TEMPORALITY_CUMULATIVE"
    metrics_flush_interval: 15s
    exemplars:
      enabled: false
    events:
      enabled: true
      dimensions:
        - name: exception.type
        - name: exception.message
    resource_metrics_key_attributes:
      - service.name
      - telemetry.sdk.language
      - telemetry.sdk.name

service:
  extensions: [zpages]
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [spanmetrics]
    metrics:
      receivers: [spanmetrics]
      exporters: [logging]

extensions:
  zpages:
    endpoint: 0.0.0.0:55679

 

 

위 설정을 좀 더 자세히 살펴보자.

 

우선 우리가 설정한 파이프라인이 어떻게 구성되어있는지 간단히 확인하기위해 zpages 라는 extention을 추가해주었다.

 

zpages는 otel pipeline을 구성하면서 디버깅할 때 사용될 수 있는 익스텐션이다.

 

파이프라인 구성은 아래와 같다. 로컬에서는 보통 디버깅을 위해 logging exporter를 많이 사용하게 되며 당연히 실제 k8s에 올릴 때는 prometheusremotewrite를 사용하게 된다.

 

- traces : otlp(receiver) -> spanmetrics(connector type exporter)

- metrics : spanmetrics(connector type receiver) -> logging(exporter)

zpages

 

참고로 zpages에 대한 경로는 아래와 같다.

 

 

 

3. 테스트

아래 명령을 통해 OTEL Javaagent의 경로 및 otel config 값 등을 주입해준다.

 

OTEL_LOGS_EXPORTER=none OTEL_METRICS_EXPORTER=none \

<JAVA_PATH> -Dspring.profiles.active=local -javaagent:./opentelemetry-javaagent.jar \

-jar <PATH/TO/my-app.jar> -Dotel.resource.attributes=service.name=MYAPP-LOCAL \

-Dotel.traces.exporter=otlp -Dotel.exporter.otlp.endpoint=localhost:4317

 

 

무슨 설정값을 넣어줘야 하는지는 아래에서 확인 가능함

https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md

 

 

 

기 만들어둔 springboot에 otel java autoinstrument로 otel javaagent를 설정해주었다.

아래 latency에 접근하면 1.1s동안 sleep 한 뒤 client에게 응답을 해주게 된다.

 

 

이렇게 하면 다음의 내용이 logging exporter를 통해 찍히는걸 확인할 수 있다.

 

총 3가지가 보이는데 calls, duration, events이다.

 

calls는 호출 별로 어떤 endpoint로 STATUS_CODE가 무엇인지 등 정보를 확인할 수 있으며 이 메트릭을 통해 다음의 정보들을 구할 수 있다. 

 

R (Request)

E (Error)

 

duration은 다음의 정보를 구할 수 있다.

D (Duration)

 

events는 어떤 dimension을 넣을거냐에 따라 다르지만 보통 Exeption이 발생할 때 어떤 Exception Type인지, Exception Message가 어떻게 되는지 등을 확인할 수 있다.

 

다만 Exception Message는 굉장히 긴 문자열로 구성되는게 일반적이기 때문에 Cardinality를 고려하면 Exception Type만 설정하는게 옳은 방향일 것으로 보인다.

 

otel-collector-1  | 2024-01-26T03:08:39.600Z    info    MetricsExporter {"kind": "exporter", "data_type": "metrics", "name": "logging", "resource metrics": 1, "metrics": 3, "data points": 2}
otel-collector-1  | 2024-01-26T03:08:39.602Z    info    ResourceMetrics #0
otel-collector-1  | Resource SchemaURL: 
otel-collector-1  | Resource attributes:
otel-collector-1  |      -> host.arch: Str(aarch64)
otel-collector-1  |      -> host.name: Str(XXXXXX)
otel-collector-1  |      -> os.description: Str(Mac OS X 14.2)
otel-collector-1  |      -> os.type: Str(darwin)
otel-collector-1  |      -> process.executable.path: Str(/Users/XXXXXX/Library/Java/JavaVirtualMachines/corretto-11.0.17/Contents/Home/bin/java)
otel-collector-1  |      -> process.pid: Int(97819)
otel-collector-1  |      -> process.runtime.description: Str(Amazon.com Inc. OpenJDK 64-Bit Server VM 11.0.17+8-LTS)
otel-collector-1  |      -> process.runtime.name: Str(OpenJDK Runtime Environment)
otel-collector-1  |      -> process.runtime.version: Str(11.0.17+8-LTS)
otel-collector-1  |      -> service.name: Str(xxxxxx-0.0.1-SNAPSHOT)
otel-collector-1  |      -> telemetry.distro.name: Str(opentelemetry-java-instrumentation)
otel-collector-1  |      -> telemetry.distro.version: Str(2.0.0)
otel-collector-1  |      -> telemetry.sdk.language: Str(java)
otel-collector-1  |      -> telemetry.sdk.name: Str(opentelemetry)
otel-collector-1  |      -> telemetry.sdk.version: Str(1.34.1)
otel-collector-1  | ScopeMetrics #0
otel-collector-1  | ScopeMetrics SchemaURL: 
otel-collector-1  | InstrumentationScope spanmetricsconnector 


otel-collector-1  | Metric #0
otel-collector-1  | Descriptor:
otel-collector-1  |      -> Name: calls
otel-collector-1  |      -> Description: 
otel-collector-1  |      -> Unit: 
otel-collector-1  |      -> DataType: Sum
otel-collector-1  |      -> IsMonotonic: true
otel-collector-1  |      -> AggregationTemporality: Cumulative
otel-collector-1  | NumberDataPoints #0
otel-collector-1  | Data point attributes:
otel-collector-1  |      -> service.name: Str(xxxxxxx-0.0.1-SNAPSHOT)
otel-collector-1  |      -> span.name: Str(GET /latency)
otel-collector-1  |      -> span.kind: Str(SPAN_KIND_SERVER)
otel-collector-1  |      -> status.code: Str(STATUS_CODE_UNSET)
otel-collector-1  | StartTimestamp: 2024-01-26 03:08:30.544939961 +0000 UTC
otel-collector-1  | Timestamp: 2024-01-26 03:08:39.599184507 +0000 UTC
otel-collector-1  | Value: 1

 


otel-collector-1  | Metric #1
otel-collector-1  | Descriptor:
otel-collector-1  |      -> Name: duration
otel-collector-1  |      -> Description: 
otel-collector-1  |      -> Unit: ms
otel-collector-1  |      -> DataType: Histogram
otel-collector-1  |      -> AggregationTemporality: Cumulative
otel-collector-1  | HistogramDataPoints #0
otel-collector-1  | Data point attributes:
otel-collector-1  |      -> service.name: Str(xxxxxxxxx-0.0.1-SNAPSHOT)
otel-collector-1  |      -> span.name: Str(GET /latency)
otel-collector-1  |      -> span.kind: Str(SPAN_KIND_SERVER)
otel-collector-1  |      -> status.code: Str(STATUS_CODE_UNSET)
otel-collector-1  | StartTimestamp: 2024-01-26 03:08:30.544939961 +0000 UTC
otel-collector-1  | Timestamp: 2024-01-26 03:08:39.599204423 +0000 UTC
otel-collector-1  | Count: 1
otel-collector-1  | Sum: 1113.759917
otel-collector-1  | ExplicitBounds #0: 50.000000
otel-collector-1  | ExplicitBounds #1: 100.000000
otel-collector-1  | ExplicitBounds #2: 200.000000
otel-collector-1  | ExplicitBounds #3: 500.000000
otel-collector-1  | ExplicitBounds #4: 1000.000000
otel-collector-1  | ExplicitBounds #5: 5000.000000
otel-collector-1  | Buckets #0, Count: 0
otel-collector-1  | Buckets #1, Count: 0
otel-collector-1  | Buckets #2, Count: 0
otel-collector-1  | Buckets #3, Count: 0
otel-collector-1  | Buckets #4, Count: 0
otel-collector-1  | Buckets #5, Count: 1
otel-collector-1  | Buckets #6, Count: 0

 


otel-collector-1  | Metric #2
otel-collector-1  | Descriptor:
otel-collector-1  |      -> Name: events
otel-collector-1  |      -> Description: 
otel-collector-1  |      -> Unit: 
otel-collector-1  |      -> DataType: Sum
otel-collector-1  |      -> IsMonotonic: true
otel-collector-1  |      -> AggregationTemporality: Cumulative
otel-collector-1  |     {"kind": "exporter", "data_type": "metrics", "name": "logging"}

 

 

 

이렇게 간단하게 Opentelemetry Collector의 spanmetrics connector를 알아보았다.

 

여기에 이제 prometheusremotewrite를 추가로 덧붙이면 시계열저장소에 저장한 뒤 그라파나에서 대시보드 및 Alert를 할 수 있게 된다.

 

 

 

정보 1)

여기서 이제 prometheusremotewrite exporter를 통해 보내게되는데 불필요한 RED 메트릭들이 다수 보내질 수 있다.

 

따라서 중간에 processor를 두어 불필요한 metric을 안보내게 할 수도 있음을 참고하자.

 

Ex. span.kind=server 인 메트릭만 보내기, ..

 

 

정보 2)

exemplars를 사용하려면 prometheus 호환 저장소에서도 exemplars 사용하겠다는 설정을 해줘야 한다.

ex. --enable-feature=exemplar-storage

반응형