티스토리 뷰

개발/공부

[OpenTelemetry] OpenTelemetry Intro

heoseogi 2024. 11. 10. 21:07

Introduction

애플리케이션을 운영하다 보면 요청이 느려지거나, 심한 경우 에러가 나는 경우를 경험한다. 이런 문제를 해결하기 위해서 여러 가지 로그와, CS 지식을 활용하여 추측을 해나가면서 문제를 진단하고 해결해 나가는 과정을 겪게 된다. 모든 소프트웨어 엔지니어가 겪는 문제라고 생각하고, 조금 더 쉽고 빠르게 문제를 진단하게 하기 위해서 Observability라는 개념이 나왔고, 이런 Observability를 높이기 위해서 사용하는 다양한 툴들이 존재한다. CNCF의 프로젝트 중 하나인 OpenTelemetry라는 프로젝트는 개발자가 OpenTelemetry를 사용하여 Observability를 높일 수 있는 환경을 제공하고 있다.

OpenTelemetry가 있는데, 직접 만들면.. 상상도 https://medium.com/@bansalbhavik9/reinvent-the-wheel-or-not-5013f6d1ac2c

OpenTelemetry란?

공식 홈페이지에 정의되어 있는 한 줄 정의는 아래와 같다.

OpenTelemetry, also known as OTel, is a vendor-neutral open source Observability framework for instrumenting, generating, collecting, and exporting telemetry data such as traces, metrics, and logs.

 

OpenTelemetry의 전체적인 구조. 출처:  https://opentelemetry.io/docs/

 

그림에서 보이는 것처럼, Microservice에서 데이터를 전송하고, 수집하며, 수집한 데이터를 어딘가로 전송하는 것까지가 OpenTelemetry에서 다루는 영역이다.

전송된 데이터를 어떤 형태로 보여주는지에 대한 영역은 각기 다른 서비스가 책임지는 구조이며, Elasticsearch, Prometheus, Jaeger 등 데이터 종류에 따라서 선택지가 다양하고, 회사마다 상황에 따라 다양한 선택지를 제공한다.

비록 보여주는 부분이 빠져있지만, 그림에서 보이는 것처럼 생각보다 OpenTelemetry에서 커버하고 있는 영역이 넓다. 이번 글에서는 OpenTelemetry에서 가장 먼저 알아야 한다고 생각하는 개념들과 Component들의 이름을 정리하려고 한다.

너무나 많은 OpenTelemetry에서 다루는 영역들. 출처:  https://opentelemetry.io/docs/what-is-opentelemetry/

 

OpenTelemetry를 이해하기 위한 주요 개념들

Observability

Observability란 시스템 내부를 알지 않더라도, 시스템을 바깥에서 알 수 있게 해주는 것. 그래서 새로운 문제가 생겼을 때, 쉽게 문제를 파악할 수 있게 도와주는 것을 말한다.

이를 위해서는 애플리케이션이 적절히 계측(properly Instrumented)되어야 한다. 그러기 위해서는 적절히 signal(trace, metrics, log)을 보내야 한다. properly instrumented라는 것은 문제를 해결하기 위해 추가적으로 계측을 달지 않아도 되는 상태를 말한다.

OpenTelemetry는 observability를 위한 framework로, signal들을 생성하고, 수집하는 영역에 대한 부분을 신경쓰는 프로젝트이다.

Signals

OpenTelemetry의 Signal은 애플리케이션의 성능과 동작에 대한 주요 데이터를 수집하는 기능을 의미하며, Signal로는 Trace, Metric, Log가 있다.

Traces

The path of a request through your application.

트레이스를 통해 애플리케이션의 요청이 어떻게 처리되고 있는지에 대한 큰 그림을 제공할 수 있다. 트레이스는 하나 이상의 스팬으로 구성되어 있으며, OpenTelemetry에서 제공하는 예시를 보면 쉽게 트레이스가 무엇인지 이해할 수 있다.

hello span:

{
  "name": "hello",
  "context": {
    "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "051581bf3cb55c13"
  },
  "parent_id": null,
  "start_time": "2022-04-29T18:52:58.114201Z",
  "end_time": "2022-04-29T18:52:58.114687Z",
  "attributes": {
    "http.route": "some_route1"
  },
  "events": [
    {
      "name": "Guten Tag!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

이것은 루트 스팬으로, 전체 작업의 시작과 끝을 나타낸다. trace_id 필드가 이 스팬이 속한 트레이스를 나타내며, parent_id가 없는 것을 통해 루트 스팬임을 알 수 있다.

hello-greetings span:

{
  "name": "hello-greetings",
  "context": {
    "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "5fb397be34d26b51"
  },
  "parent_id": "051581bf3cb55c13",
  "start_time": "2022-04-29T18:52:58.114304Z",
  "end_time": "2022-04-29T22:52:58.114561Z",
  "attributes": {
    "http.route": "some_route2"
  },
  "events": [
    {
      "name": "hey there!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    },
    {
      "name": "bye now!",
      "timestamp": "2022-04-29T18:52:58.114585Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

이 스팬은 특정 작업(예: 인사)에 대한 정보를 담고 있으며, 그 부모 스팬은 hello 스팬이다. 이 스팬은 루트 스팬과 동일한 trace_id를 공유하여 동일한 트레이스의 일부임을 나타내며, parent_id가 hello 스팬의 span_id와 일치한다.

hello-salutations span:

{
  "name": "hello-salutations",
  "context": {
    "trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
    "span_id": "93564f51e1abe1c2"
  },
  "parent_id": "051581bf3cb55c13",
  "start_time": "2022-04-29T18:52:58.114492Z",
  "end_time": "2022-04-29T18:52:58.114631Z",
  "attributes": {
    "http.route": "some_route3"
  },
  "events": [
    {
      "name": "hey there!",
      "timestamp": "2022-04-29T18:52:58.114561Z",
      "attributes": {
        "event_attributes": 1
      }
    }
  ]
}

이 스팬은 이 트레이스의 세 번째 작업을 나타내며, 이전 스팬과 마찬가지로 hello 스팬의 자식이다. 따라서 hello-greetings 스팬의 형제 스팬이기도 하다.

이 세 개의 JSON 블록은 모두 동일한 trace_id를 공유하며, parent_id 필드는 계층 구조를 나타낸다. 이것이 바로 트레이스이다!

또한 각 스팬이 구조화된 로그와 비슷하다는 것을 알 수 있다. 실제로 트레이스는 컨텍스트, 상관관계, 계층 구조 등을 포함한 구조화된 로그 모음으로 생각할 수 있다. 이러한 “구조화된 로그”는 서로 다른 프로세스, 서비스, VM, 데이터 센터 등에서 생성될 수 있다. 이것이 트레이싱이 시스템의 끝에서 끝까지의 뷰를 나타낼 수 있게 해주는 이유이다

Spans

스팬은 작업 또는 작업의 단위를 나타낸다. 스팬은 요청이 수행하는 특정 작업을 추적하여 해당 작업이 실행된 시간 동안 무슨 일이 일어났는지 그림을 그린다. 스팬에는 이름, 시간 관련 데이터, 구조화된 로그 메시지 및 기타 메타데이터(즉, 속성)가 포함되어 추적하는 작업에 대한 정보를 제공한다.

예시 Trace. 네모 하나하나가 Span이다. (Client, /api, 등등)

 

Span에 남길 수 있는 정보들은 아래와 같다.

  • 이름
  • 상위 스팬 ID (루트 스팬인 경우 비어 있음)
  • 시작 및 종료 타임스탬프
  • 스팬 컨텍스트: Trace ID, SpanID를 포함하며, Trace Flag, Trace State를 가지고 있을 수 있다.
  • 속성: Key-valu로 metadata를 같이 기록할 수 있는 값
  • 스팬 이벤트: 스팬 내에서 특정 시점을 기록할 때 사용하는 필드.
  • 스팬 링크: 한 스팬과 다른 스팬을 연동시키기 위한 기능. 비동기 작업을 연관 시킬 때 사용할 수 있다.
  • 스팬 상태: Unset, Error, Ok 중 하나로 default는 Unset이다. 현재 span의 상태를 표시할 수 있다.
  • 스팬 종류: Span을 남기는 주체가 어떤 종류(Client, Server, Internal, Producer, Consumer)인지를 표기하여, 보여줄 때 더 시각화할 수 있게 해주기 위한 필드

Metrics

메트릭은 서비스의 측정치를 런타임에 캡처한 것이다. 측정이 이루어진 순간을 메트릭 이벤트라고 하며, 측정치뿐만 아니라 이를 캡처한 시간과 관련된 메타데이터도 포함한다.

애플리케이션 및 요청 메트릭은 가용성과 성능의 중요한 지표이다. 사용자 정의 메트릭을 통해 가용성 지표가 사용자 경험이나 비즈니스에 미치는 영향을 파악할 수 있다. 수집된 데이터는 장애에 대한 경고를 하거나 높은 수요가 있을 때 배포를 자동으로 확장하기 위한 스케줄링 결정을 트리거하는 데 사용할 수 있다.

OpenTelemetry에서 메트릭은 아래 4가지로 이루어져 있다.

  • 이름
  • 종류
  • 단위 (Optional)
  • 설명 (Optional)

메트릭의 종류

  • Counter: 시간이 지남에 따라 축적되는 값으로, 자동차의 주행 거리계처럼 생각할 수 있다. 값은 오직 증가만 한다.
  • Asynchronous Counter: Counter와 동일하지만, 각 내보내기 시 한 번 수집된다. 연속적인 증가에 접근할 수 없고 집계된 값만 있는 경우 사용할 수 있다.
  • UpDownCounter: 시간이 지남에 따라 축적되며, 다시 감소할 수도 있는 값이다. 예를 들어 대기열의 길이는 대기열에 있는 작업 항목 수에 따라 증가하고 감소할 수 있다.
  • Asynchronous UpDownCounter: UpDownCounter와 동일하지만, 각 내보내기 시 한 번 수집된다. 연속적인 변화에 접근할 수 없고 집계된 값만 있는 경우 사용할 수 있다(예: 현재 대기열 크기).
  • Gauge: 읽는 시점의 현재 값을 측정한다. 예를 들어 차량의 연료 게이지가 이에 해당한다. 게이지는 비동기적이다.
  • Histogram: 요청 지연 시간과 같은 값의 클라이언트 측 집계이다. 히스토그램은 값 통계를 알고 싶을 때 적합하다. 예: 얼마나 많은 요청이 1초 이내에 완료되었는가? (추가로 이걸 사용해서 latency의 95percentile 지표를 측정할 수 있다.

Logs

로그는 타임스탬프가 있는 텍스트 기록으로, 구조화된(권장) 또는 비구조화된 형식이며 선택적 메타데이터를 가질 수 있다. 모든 텔레메트리 신호 중에서 로그는 가장 큰 유산을 가지고 있다. 대부분의 프로그래밍 언어에는 기본적인 로깅 기능이 있거나 잘 알려져 있고 널리 사용되는 로깅 라이브러리가 있다.

OpenTelemetry 시스템은 새로운 로깅 시스템을 만들기보다는 기존의 로깅 시스템을 사용해 쉽게 수집할 수 있는 방향으로 설계되었다.

 

MISC (세상과 합의되어 있는 로그의 구조들)

1. JSON

{ "timestamp": "2024-08-04T12:34:56.789Z", "level": "INFO", "service": "user-authentication", "environment": "production", "message": "User login successful", "context": { "userId": "12345", "username": "johndoe", "ipAddress": "192.168.1.1", "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" }, "transactionId": "abcd-efgh-ijkl-mnop", "duration": 200, "request": { "method": "POST", "url": "/api/v1/login", "headers": { "Content-Type": "application/json", "Accept": "application/json" }, "body": { "username": "johndoe", "password": "******" } }, "response": { "statusCode": 200, "body": { "success": true, "token": "jwt-token-here" } } }

2. Common Log Format

127.0.0.1 - johndoe [04/Aug/2024:12:34:56 -0400] "POST /api/v1/login HTTP/1.1" 200 1234

3. Extended Log Format

192.168.1.1 - johndoe [04/Aug/2024:12:34:56 -0400] "POST /api/v1/login HTTP/1.1" 200 1234 "<http://example.com>" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36" {"transactionId": "abcd-efgh-ijkl-mnop", "responseTime": 150, "requestBody": {"username": "johndoe"}, "responseHeaders": {"Content-Type": "application/json"}}

 

Baggage

OpenTelemetry에서 Baggage는 컨텍스트 옆에 있는 컨텍스트 정보이다. Baggage는 키-값 저장소로, 컨텍스트와 함께 원하는 데이터를 전파할 수 있다. Baggage를 사용하면 서비스와 프로세스 간에 데이터를 전달하여 해당 서비스에서 트레이스, 메트릭 또는 로그에 추가할 수 있다.

Baggage는 일반적으로 요청 시작 시에만 사용할 수 있는 정보를 후속 처리 단계까지 포함하는 데 가장 적합하다. 예를 들어 계정 식별, 사용자 ID, 제품 ID, 원본 IP 등을 포함할 수 있다.

Baggage를 사용하여 이러한 정보를 전파하면 백엔드에서 텔레메트리에 대한 심층적인 분석이 가능해진다. 예를 들어, 사용자 ID와 같은 정보를 데이터베이스 호출을 추적하는 스팬에 포함시키면 “어떤 사용자가 가장 느린 데이터베이스 호출을 경험하고 있는가?”와 같은 질문에 훨씬 더 쉽게 답할 수 있다. 또한 하위 작업에 대한 정보를 로깅하고 동일한 사용자 ID를 로그 데이터에 포함시킬 수 있다.

Baggage에 대해 알아야 할 중요한 점은 이것이 별도의 키-값 저장소라는 점이며, 명시적으로 추가하지 않으면 스팬, 메트릭 또는 로그의 속성과 연관되지 않는다는 것이다. Baggage 항목을 속성에 추가하려면 Baggage에서 데이터를 명시적으로 읽어 스팬, 메트릭 또는 로그의 속성으로 추가해야 한다. Baggage의 일반적인 사용 사례 중 하나는 전체 트레이스에 걸쳐 스팬 속성에 데이터를 추가하는 것이며, 여러 언어에서 Baggage 스팬 프로세서를 사용하여 스팬 생성 시 Baggage 데이터를 속성으로 추가할 수 있다.

 

회고

  1. properly instrumented를 달성하려고 노력하는 것이 생산성을 높이고 싶은 소프트웨어 엔지니어에게 필요하다.
  2. properly instrumented를 달성하려면 어떤 부분을 더 잘 챙겨야 할지를 생각해봤는데
    • process, thread, 외부 시스템(DB, API Call) 등의 변화가 있을 때는 trace 안에서 분리해서 볼 수 있게 span을 추가하자.
    • 특정 문제 상황을 겪고, 다음번에는 조금 더 빨리 진단할 수 있는 observability를 갖추는 것이 properly instrumented를 보완하기 위한 방법일 것 같다.
  3. 대략적인 이해만 하고 있었으나, 개념을 제대로 공부하는 것만으로도 회사에 세팅되어 있는 OpenTelemetry와 관련된 Observability 세팅들을 더 잘 활용할 수 있겠다는 생각이 듦. (Log와 Trace를 연결하는 기능은 쓰지 않고 있었는데, 이번에 공부하면서 알게 되었음, Baggage를 통해서 span 간의 context를 쉽게 전달할 수 있음을 알게 되었음.)
반응형