ㅡ.ㅡ

[K8S] Opentelemetry 본문

Observabillity

[K8S] Opentelemetry

ekwkqk12 2023. 12. 20. 19:06

OpenTracing

분산 시스템 및 마이크로서비스 아키텍처에서 애플리케이션 간 트레이싱을 구현하기 위한 벤더 중립적인 API와 도구의 표준으로 애플리케이션의 다양한 구성 요소에서 분산된 트랜잭션의 흐름을 추적하기 위한 추상화 계층을 제공하여 여러 서비스 간의 요청-응답 흐름을 따라가고, 각 단계에서 걸리는 시간을 측정하며, 이를 통해 성능 문제를 분석하고 최적화할 수 있다.

Trace
분산 시스템 전체에 걸쳐 이동하는 하나의 트랜잭션의 값으로 여러개의 Span으로 구성된다.

Span
trace의 논리적 실행 단위로 하나의 서비스의 작업을 의미하며 한 Span 내 작업을 처리하는 함수와 같은것들을 Child Span으로 구성된다.

  • Operation Name : 실행되는 Span의 이름
  • TimeStamp : Span의 시작과 종료되는 시간
  • Tags : 트레이싱되는 정보를 필터링 하거나, 쿼리하기 위한 Key:Value 형태의 값
  • Logs : 로그 메시지와, 디버깅 및 정보를 출력하는데 사용되는 Key:Value 형태의 값
  • Context : 다음 서비스로의 네트워크 혹은 메시지 버스를 통해 전달하는 정보의 집합체로 Trace ID, SpaniD, Baggage(Key/Value 형태의 데이터 저장 공간)로 구성된다

HTTP Header(W3C/Trace Context)
Traceparent 헤더는 분산 추적을 위한 표준 방법으로 W3C에서 제안된 헤더로 HTTP, AMQP, gRPC 등 다양한 프로토콜에서 사용된다. trace에 대한 고유 ID 및 trace당 span에 대한 unique ID를 갖고 있어 클라이언트의 요청이 타 시스템을 경유 할때마다 Traceparent 헤더를 포함하여 전송하며 각 시스템에서는 Traceparent 헤더에 포함된 정보를 기반으로 request에 대한 trace를 생성한다.

Traceparent: {version}-{trace-id}-{span-id}-{trace-flags}

  • version: 현재 버전 정보
  • trace-id: 16진수 인코딩으로 된 고유 Trace ID입니다. 32자로 구성되어 있으며, 대소문자와 숫자로 이루어져 있습니다.
  • span-id: 16진수 인코딩으로 된 고유 Span ID입니다. 16자로 구성되어 있으며, 대소문자와 숫자로 이루어져 있습니다.
  • trace-flags: trace 활성화 관련 flag를 표시합니다

No Collector Pattern

이 패턴은 텔레메트리 신호(추적, 메트릭, 로그)를 백엔드로 직접 내보내는 OpenTelemetry SDK로 계측하는 애플리케이션으로 구성

장점

  • 간편한 사용(특히 개발/테스트 환경에서)
  • 작동하기 위해 추가로 움직이는 부품이 없음(프로덕션 환경)

단점

  • 수집, 처리, 수집이 변경되면 코드를 변경해야 함
  • 애플리케이션 코드와 백엔드 간의 강력한 결합
  • 언어 구현당 내보내기의 수가 제한됨

Node.js

APP(package.json)
트레이스값 추출 및 전달에사용되는 모듈 추가

{
  ...
  "dependencies": {
    ...
    "@opentelemetry/auto-instrumentations-node": "^0.37",
    "@opentelemetry/api": "^1.4"
  }
} 

Logging
TraceID관련 로깅 필드 추가

import * as winston from "winston";
import { format } from "winston";

const logger = winston.createLogger({
  format: format.combine(
    format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }),
    format.printf(
      ({ level, message, timestamp, trace_id, trace_flags, span_id }) => {
        const logData = {
          timestamp,
          level,
          trace_id,
          trace_flags,
          span_id,
          message,
        };
        return JSON.stringify(logData);
      }
    )
  ),
  transports: [new winston.transports.Console()],
});

Helm(app)
앱 배포시 환경변수로 opentelemetry agent 활성화 및 설정값들 추가

extraEnv:
  - name: OTEL_TRACES_EXPORTER
    value: "otlp"
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: <Grafana Tempo>
  - name: OTEL_NODE_RESOURCE_DETECTORS
    value: "env,host,os"
  - name: OTEL_SERVICE_NAME
    value: "test-app"
  - name: NODE_OPTIONS
    value: "--require @opentelemetry/auto-instrumentations-node/register"

Java

Dockerfile
앱에서 트레이스값을 생성하고 전달하기위한 에이전트(https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) 추가

COPY lib /home
COPY --from=builder /root/.gradle/caches ./gradle-caches
COPY --from=jar-layer application/dependencies .
COPY --from=jar-layer application/spring-boot-loader .
COPY --from=jar-layer application/snapshot-dependencies .
COPY --from=jar-layer application/application . 

Logging
TraceID관련 로깅 필드 추가

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
    <property name="LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread, %X{traceId:-}, %X{spanId:-}] [%logger{0}:%line] - %msg%n"/>

helm(app)
앱 배포시 환경변수로 opentelemetry agent 활성화 및 설정값들 추가

extraEnv:
  - name: JAVA_TOOL_OPTIONS
    value: "-javaagent:/home/opentelemetry-javaagent.jar"
  - name: OTEL_TRACES_EXPORTER
    value: "otlp"
  - name: OTEL_EXPORTER_OTLP_ENDPOINT
    value: <Grafana Tempo>
  - name: OTEL_SERVICE_NAME
    value: "traceapp1"
  - name: OTEL_EXPORTER_OTLP_PROTOCOL
    value: "http/protobuf"
  - name: OTEL_TRACES_SAMPLER
    value: "traceidratio"
  - name: OTEL_TRACES_SAMPLER_ARG
    value: "0.3"

Agent Collector Deployment Pattern

애플리케이션과 함께 실행되는 수집기 인스턴스 또는 애플리케이션과 동일한 호스트(예: 사이드카 또는 데몬 세트)에서 텔레메트리 신호를 전송하는 기타 수집기(OTLP 내보내기 사용)로 구성된 애플리케이션(OpenTelemetry 프로토콜을 사용하는 OpenTelemetry SDK로 계측) 또는 애플리케이션과 함께 실행되는 수집기 인스턴스로 구성됩니다.

장점

  • 간편한 시작
  • 애플리케이션과 수집기 간의 명확한 1:1 매핑

단점

  • 확장성(인력 및 부하 측면)
  • 유연하지 않음

Opentelemetry-Operator

OpenTelemetry 프로젝트를 쿠버네티스(Kubernetes) 환경에서 CRD를 사용하여 OpenTelemetry의 구성 요소를 배포 및 관리하기 쉽게 설치 및 관리하는 도구이다.

OpenTelemetry 구성 요소

Helm Install

helm install --namespace opentelemetry-operator-system \   opentelemetry-operator open-telemetry/opentelemetry-operator 

Opentelemetry-Collector

application이 보낸 데이터를 수집(receivers)하고 가공(processors)하여 저장소로 내보내는(expporters) 역할로 수집과 저장의 라이브러리 간 결합도를 없애고 표준화된 API를 사용하기 때문에 수집을 위한 라이브러리와 저장을 위한 라이브러리 각각의 변경에 부담이 없어지게 된다.

배포 방식
DaemonSet, Sidecar, Deployment 모드 지원가능 하며 LB를 붙여 gateway 패턴으로도 사용할 수 있다

주요 구성 요소
Receivers
데이터를 수집하는 소스로 다양한 형식의 데이터를 수신할 수 있다.

receivers:
  otlp:
    protocols:
      grpc:
      http:
  • OTLPReceiver
    OpenTelemetry 프로토콜을 사용하여 데이터를 수신합니다.
  • JaegerReceiver
    Jaeger 형식의 추적 데이터를 수신합니다.
  • PrometheusReceiver
    Prometheus 메트릭 데이터를 수신합니다.

Processors
수신된 데이터에 대해 변환, 조작 또는 필터링을 수행한다.

  • BatchProcessor
    데이터를 일정 기간동안 누적한 후, 한 번에 전송합니다.

    • timeout: 배치를 전송하기 전까지 대기하는 최대 시간
    • send_batch_size: 한 번에 보낼 수 있는 스팬의 최대 개수
    processors:
      batch:
        send_batch_size: 10000 # 한 번에 보낼 데이터의 최대 개수
        timeout: 10s # 데이터를 기다리는 최대 시간
        retry_on_failure: true # 
    
  • MemoryLimiter
    메모리 사용량을 조정하여 리소스 할당을 통제합니다.

    processors:
      memory_limiter:
        check_interval: 5s
        limit_mib: 4000
        limit_percentage: 75
        spike_limit_mib: 500
        spike_limit_percentage: 15
    
    • check_interval: 메모리 사용량을 확인하는 데 사용되는 간격
    • limit_(mib/percentage) : 메모리 사용량의 최대 값
    • spike_limit_(mib/percentage): 메모리 사용량이 급격하게 증가했을 때 허용되는 값
  • AttributeProcessor
    추적 데이터의 속성을 추가, 업데이트, 복제, 삭제하는 작업을 처리합니다.

    • actions: 수행할 작업 목록 (add, update, delete 등)과 관련 있는 키-값 쌍
    processors:
      attributes:
        actions:
          - key: environment
            value: production
            action: insert
          - key: db.statement
            action: delete
          - key: email
            action: hash
    
      # Data sources: traces
      span:
        name:
          to_attributes:
            rules:
              - ^\/api\/v1\/document\/(?P<documentId>.*)\/update$
          from_attributes: [db.svc, operation]
          separator: '::'
    
  • ResourceProcessor
    데이터를 애플리케이션에 연결된 리소스 속성으로 변환하여 동일한 시스템에서 받은 메트릭을 추적하는 것이 용이하게 합니다.
    attributes: 측정한 리소스에 매핑될 속성 목록을 포함합니다.

    processors:
        resource:
      attributes:
        - key: cloud.zone
          value: zone-1
          action: upsert
        - key: k8s.cluster.name
          from_attribute: k8s-cluster
          action: insert
        - key: redundant-attribute
          action: delete
    
  • Tail Sampling Processor
    각 추적의 완전한 추적 정보가 수집된 후에 필터링을 적용하여 자식 span까지 제외 시킬 수 있다.

    tail_sampling:
      decision_wait: 30s
      policies:
      - name: disabled-healthckeck1
        type: string_attribute
        string_attribute:
          key: http.route
          values: ['/health', '/metrics', '/healthcheck']
          enabled_regex_matching: true
          invert_match: true # 매칭되지 않는것만 수집
      - name: disabled-healthckeck3
        type: string_attribute
        string_attribute:
          key: http.user_agent
          values: ['ELB-HealthChecker.*','kube-probe.*']
          enabled_regex_matching: true
          invert_match: true
    
  • 기타 프로세서 리스트
    https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor

Exporters
수집한 데이터를 다른 시스템에 전달하는 역할을 합니다.

  # Data sources: traces, metrics, logs
    otlp:
    endpoint: otelcol2:4317
    tls:
      cert_file: cert.pem
      key_file: cert-key.pem

  # Data sources: traces, metrics
  otlphttp:
    endpoint: https://example.com:4318

  # Data sources: metrics
  prometheus:
    endpoint: prometheus:8889
    namespace: default
  • OTLPExporter
    GRPC/OpenTelemetry 프로토콜을 사용하여 데이터를 다른 시스템으로 내보냅니다.
  • OTLPHttpExporter
  • HTTP/OpenTelemetry 프로토콜을 사용하여 데이터를 다른 시스템으로 내보냅니다.
  • JaegerExporter
    Jaeger 형식의 추적 데이터를 다른 시스템으로 내보냅니다.
  • PrometheusExporter
    Prometheus 메트릭 데이터를 다른 시스템으로 내보냅니다.

Connectors
한 파이프라인의 끝에서 수집된 데이터를 내보내는 exporter로 동작하고 다른 파이프라인의 시작점에서 데이터를 수신하는 receiver로 동작하여 두 파이프라인을 연결하는 역할을 합니다.

receivers:
  foo:
exporters:
  bar:
connectors:
  count:

service:
  pipelines:
    traces:
      receivers: [foo]
      exporters: [count]
    metrics:
      receivers: [count]
      exporters: [bar]
  • forward
    입력된 데이터를 그대로 전달하는 기본 커넥터입니다

    connectors:
      forward:
    
  • count
    주어진 조건에 따라 데이터(event)를 세는 작업을 수행하는 커넥터입니다.
    spanevents : 이벤트에 대한 카운터 이름 및 설명, 그리고 조건들을 설정합니다

    connectors:
      count:
      spanevents:
        my.prod.event.count:
          description: The number of span events from my prod environment.
          conditions:
            - 'attributes["env"] == "prod"'
            - 'name == "prodevent"'
    
  • spanmetrics
    분산 추적 데이터에서 히스토그램 값을 생성하여 메트릭으로 전환하는 커넥터입니다.

    connectors:
      spanmetrics:
      histogram:
        explicit:
          buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms]
      dimensions:
        - name: http.method
          default: GET
        - name: http.status_code
      dimensions_cache_size: 1000
      aggregation_temporality: 'AGGREGATION_TEMPORALITY_CUMULATIVE'
    
    • histogram: 히스토그램 유형과 버킷을 설정합니다.
    • dimensions: 메트릭 데이터의 차원 및 기본값을 설정합니다.
    • dimensions_cache_size: 캐시 크기를 프로세서에 설정합니다.
    • aggregation_temporality: 데이터 집계의 시간 속성을 설정합니다.
  • servicegraph
    서비스 간 관계를 모니터링하고 추적하는 워크플로를 표현하는 커넥터입니다.

    connectors:
      servicegraph:
      latency_histogram_buckets: [1, 2, 3, 4, 5]
      dimensions:
        - dimension-1
        - dimension-2
      store:
        ttl: 1s
        max_items: 10
    
    • latency_histogram_buckets: 지연 시간 히스토그램의 버킷 값을 설정합니다.
    • dimensions: 사용할 차원을 설정합니다.
    • store: 저장 구성에 대한 TTL 및 최대 항목 수를 설정합니다.

Extensions
Collector의 기능을 확장하거나 도와주는 부가적인 서비스입니다.

extensions:
  health_check:
  pprof:
  zpages:
  memory_ballast:
    size_mib: 512
  • health_check
    OpenTelemetry Collector의 상태를 확인하는 기능을 제공하며, 주로 로드 밸런서에서 사용됩니다.
  • pprof
    Collector 프로세스의 Golang 프로파일링 데이터를 제공합니다.
    • endpoint: pprof 엔드포인트를 설정합니다(예: 127.0.0.1:5555)
    • block_profile_fraction: 블록 프로파일 작성을 위한 세분화 정도를 설정합니다.
  • zpages
    특정 웹 페이지 내에 프로세스의 로그 및 메트릭 정보를 표시하는 서비스입니다.
    • endpoint: zpages 엔드포인트를 설정합니다(예: 127.0.0.1:5678)
  • memory_ballast
    Collector 프로세스의 메모리 사용량에 미리 할당된 블록을 추가하여 메모리 할당 및 해제(GC)와 관련한 오버헤드를 줄여 프로세스의 성능을 향상시키는 역할을 합니다.

service
Collector의 실행 및 구성을 관리하는 데 사용되는 구성 요소로 전반적인 동작 및 구성을 정의합니다.

service:
    extensions:
   - health_check
   - pprof
   - zpages
  pipelines:
    traces:
      receivers: [otlp, zipkin]
      processors: [batch, queued_retry]
      exporters: [jaeger, otlp]
    metrics:
      receivers: [otlp, prometheus]
      processors: [batch]
      exporters: [prometheus, otlphttp]
  • extensions
    Collector의 부가적인 기능을 향상시키며, 추가 작업을 수행합니다.
  • pipelines
    traces, metrics, 및 logs와 같은 데이터 유형에 대해 파이프라인을 정의하고, receivers, processors, 및 exporters 사이의 데이터 흐름을 구성합니다.

opentelemetry-collector(daemonset)
daemonset으로 배포하는 CRD

apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
  name: otel-daemonset
  namespace: opentelemetry
spec:
  mode: daemonset
  config: |
    receivers:
      otlp:
        protocols:
          grpc:
          http:
    processors:
      # 파일 시스템 스팬 드랍
      filter/spans:
        spans:
          exclude:
            match_type: regexp
            span_names: ['.*fs.*']
      # 헬스체크 스팬 드랍
      tail_sampling:
        decision_wait: 30s
        policies:
        - name: disabled-healthckeck
          type: string_attribute
          string_attribute:
            key: http.route
            values: ['/health', '/metrics', '/healthcheck']
            enabled_regex_matching: true
            invert_match: true        
      groupbytrace/custom:
        wait_duration: 1s
        num_traces: 5000
        num_workers: 4
      memory_limiter:
        check_interval: 1s
        limit_percentage: 75
        spike_limit_percentage: 30
      # 배치 및 그룹핑
      batch:
        send_batch_size: 512
        send_batch_max_size: 1024
      groupbyattrs:
        keys:
          - host.name
      resource:
        attributes:
        - key: k8s.cluster.name
          value: ${opentelemetryConfig.name}
          action: insert
    exporters:
      otlphttp:
        endpoint: <Grafana Tempo>
    service:
      telemetry:
        logs:
          level: "info"
      pipelines:
        traces:
          receivers: [otlp]
          processors: [memory_limiter, groupbytrace/custom, batch, groupbyattrs, filter/spans, tail_sampling, resource]
          exporters: [otlphttp]

servicemonitor
opentelemetry-collector의 메트릭을 수집하는 Service-monitor

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: otel-collector-service-monitor
  namespace: opentelemetry
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: otel-daemonset-collector-monitoring
      app.kubernetes.io/instance: opentelemetry.otel-daemonset
      app.kubernetes.io/component: opentelemetry-collector
  namespaceSelector:
    matchNames:
    - opentelemetry
  endpoints:
  - interval: 30s
    port: monitoring

Opentelemetry-Instrumentation

init container를 통해 자동으로 에이전트를 실행시키는 CRD

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: ${eksenv}-otel-instrumentation-grpc
  namespace: opentelemetry
spec:
  exporter:
    endpoint: <Opentelemetry-Collector>
  propagators:
    - tracecontext
    - baggage
  sampler:
    type: parentbased_traceidratio
    argument: '1'
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:1.30.0
    env:
      - name: OTEL_ENV
        value: ${eksenv}
      - name: OTEL_APP_NAME
        valueFrom:
          fieldRef:
            fieldPath: metadata.labels['app.kubernetes.io/name']
      - name: OTEL_SERVICE_NAME
        value: $(OTEL_ENV)-$(OTEL_APP_NAME)
      - name: OTEL_METRICS_EXPORTER
        value: 'prometheus'
      - name: OTEL_EXPORTER_PROMETHEUS_HOST
        value: '0.0.0.0'
      - name: OTEL_EXPORTER_PROMETHEUS_PORT
        value: '9090'
      - name: OTEL_LOGS_EXPORTER
        value: 'none'
      - name: OTEL_LOG_LEVEL
        value: info
  nodejs:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:0.41.1
    env:
      - name: OTEL_ENV
        value: ${eksenv}
      - name: OTEL_APP_NAME
        valueFrom:
          fieldRef:
            fieldPath: metadata.labels['app.kubernetes.io/name']
      - name: OTEL_SERVICE_NAME
        value: $(OTEL_ENV)-$(OTEL_APP_NAME)
      - name: OTEL_METRICS_EXPORTER
        value: 'prometheus'
      - name: OTEL_EXPORTER_PROMETHEUS_HOST
        value: '0.0.0.0'
      - name: OTEL_EXPORTER_PROMETHEUS_PORT
        value: '9090'
      - name: OTEL_LOGS_EXPORTER
        value: 'none'
      - name: OTEL_LOG_LEVEL
        value: info

propagators
서로 다른 시스템 간의 일관된 추적을 유지할 수 있게 컨텍스트 전파를 담당하며, 분산 추적에 사용되는 tracing headers와 관련된 정보를 전달합니다.

  • B3: Zipkin과 호환되는 전파 형식이다.
  • Baggage : 사용자 정의 데이터를 분산된 시스템 전반에 걸쳐 전달할 수 있게 해준다.
  • Jaeger : B3 전파 형식을 기반으로 하며, Jaeger와 함께 사용될 때 사용한다.
  • TraceContext : W3C Trace Context 표준을 준수하는 전파 형식이다.

sampler
추적 데이터를 샘플링하여 일부 데이터만 저장하거나 전송하는 역할을 담당한다.

  • AlwaysOn
  • AlwaysOff
  • Parent
  • TraceIdRatio

exporter
수집한 추적 데이터, 메트릭 데이터 또는 로그 데이터를 원격 시스템에 전송하는 역할을 담당한다.

Pod Trace 값 수집 활성화 설정(Annotation)

  • 지원 종류
    go, dotnet, java, nodejs, python, apacheHttp, ,nginx(디폴트 설정에는 있음)
  • CRD와 수집 대상이 동일 네임스페이스일 경우
    instrumentation.opentelemetry.io/inject-<지원종류>: "true"
  • CRD와 수집 대상이 다른 네임스페이스일 경우
    instrumentation.opentelemetry.io/inject-<지원종류>: "<CRD 네임스페이스/CRD이름>"

Reference

'Observabillity' 카테고리의 다른 글

[Tracing] Grafana Tempo  (0) 2023.12.20
[Monitoring] JVM Exporter(Micrometer)  (0) 2022.01.20
[Monitoring] Prometheus-Stack  (0) 2021.10.31
[Logging] APP Logging/Fluentd&Fluentbit  (0) 2021.10.17
[K8S] Kubernetes Dashboard  (0) 2021.01.30