OpenTelemetry와 Jaeger를 사용하여 추적하기

추적(Tracing)은 중요한 コンポーネント로, 複雑なシステム을 거쳐 リクエストのトラッキングを行います。この視覚性により、瓶颈とエラーを揭示し、より速く解決することができます。私たちのGoウェブサービスシリーズの前回の記事では、可視性(observability)の重要性を探りました。今日は、追跡に焦点を当てます。Jaegerは、分散型システムのトラックを集め、保存し、視覚化します。これは、サービス間のリクエスト流を crucial insights を提供します。開発者は、JaegerをOpenTelemetryと統合することで、トラッキング手法を统一させ、一貫性と完全な視覚性を保証します。この統合は、パフォーマンス問題の診断を簡略化し、システムの信頼性を向上させます。この記事では、Jaegerを設定し、アプリケーションでOpenTelemetryと統合し、より深い洞察を得るためにトラックの視覚化を探ります。

动机

私たちが向かうのは、以下のようなJaegerダッシュボードです。

アプリのいろいろな部分(Onehubフロントエンド)に行くと、各リクエストのトラックが(grpc-gatewayに触れると)集められ、それぞれの概要を提供します。また、一つのトラックに钻頭込みにより詳細なビューを取得することもできます。POSTリクエストの最初のもの(トピックにメッセージを作成/送信する)を見てください。

ここで、Createリクエストが触れるすべてのコンポーネントと、それぞれの进入/退出時間やメソッド内での時間を表示しています。とても強力です。

始める

TL;DR: これを実際に動かし、ブログの残りを検証するには:

  1. 이 소스는 PART11_TRACING 브랜치에 있습니다.
  2. 필요한 모든 것을 빌드하십시오(브랜치를 체크아웃 한 후):
make build

  1. 우리는 docker-compose를 두 部分로 분할했습니다(이에 대한 자세한 설명은 나중에 하겠습니다), 따라서 두 개의 창을 실행하는 것을 확인하십시오.
  • terminal 1: make updb dblogs
  • terminal 2: make up logs
  1. localhost:7080으로 이동하여 시작하십시오.

高层次 概览

현재 我们的系统는 다음과 같습니다:

OpenTelemetry를 이용한 인스펙션으로 我们的系统는 다음과 같이 발전하게 될 것입니다:

지금까지 앞서 말했듯이, 각 서비스가 별도의 클라이언트를 사용하여 특정 제조업체로 보내는 것은 상당히 onerous합니다. 대신 별도로 실행되는 OTel 수집기를 사용하면 모든 (관심 있는) 서비스가 merecklessly 지속적으로 이러한 수집기에 메트릭스/로그/트레이스를 보낼 수 있습니다. 이러한 수집기는 요구되는 모든 백端에 대응하여 export 할 수 있습니다. 이번 경우, Jaeger를 트레이스 목적으로 사용합니다.

시작해봅시다.

OTel 수집기 세팅

첫 번째 단계는 우리의 OTel 수집기를 Docker 환경에서 실행하며 Jaeger를 함께 사용하여 이를 사용할 수 있도록 만들기 입니다.

이유: 우리의 원래의 모든 것을 포함하는 docker-compose.yml 구성을 두 部分로 분할했습니다:

  1. db-docker-compose.yml: databases (Postgres, Typesense)과 모니터링 서비스 (OTel 수집기, Jaeger, Prometheus 등)과 같은 인프RASTRUKTURE (non-application) related components를 포함합니다.
  2. docker-compose.yml: 응용 프로그램 관련 서비스(Nginx, gRPC Gateway, dbsync, frontend 등)를 포함합니다.

두 개의 도커 컴포즈 환경은 공유 네트워크(onehubnetwork)를 통해 연결되며 서비스는이 네트워크를 통해 서로 통신할 수 있습니다.이로 인해 변경 사항이 발생할 때 서비스의 하위 집합만 다시 시작하면 되므로 개발 속도가 빨라집니다.

우리의 설정으로 돌아가서 db-docker-compose.yml에 다음 서비스를 추가합니다:

YAML

 

간단하게도 도커 환경에 두 가지 서비스를 설정합니다.

  1. otel-collector: 모니터링되는 다양한 서비스(이 목록은 시간이 지남에 따라 계속 추가할 예정임)에 의해 전송된 모든 신호(메트릭/로그/추적)의 싱크입니다. OTel 표준 이미지를 사용하며, OTel 사용자 지정 구성(아래 참조)을 사용하여 다양한 관찰 가능성 파이프라인(즉, 신호가 어떻게 수신, 처리 및 다양한 방식으로 내보내져야 하는지)을 설명합니다.
  2. jaeger: 추적을 받고 저장할 Jaeger 인스턴스입니다(otel-collector에서 내보낸 추적). 이것은 저장소와 Nginx를 통해 액세스할 수 있는 대시보드(UI)를 모두 호스팅합니다(/jaeger HTTP 경로 접두사에서 내보냄).
  3. prometheus: 이 게시물에서는 자세히 다루지 않지만 메트릭을 내보내서 Prometheus에서 스크랩할 수 있도록 합니다.

몇 가지 注意事項:

  • 이 게시물에서는 필수는 아니지만 Postgres 건강 메트릭을 스크랩할 수 있도록 Postgres 연결 세부 정보(환경 변수)를 otel-collector에 전달합니다.
  • Jaeger (v1.35 이후)는 내적 OTLP를 기본적으로 지원합니다.
  • OTLP의 장점은 OTel 수집기를 쌍하여 OTEI 수집기/프로세서/전달기/ルーTER 등의 네트워크를 형성할 수 있다는 것입니다.
  • OTLP는 GRPC 또는 HTTP 엔드포인트( respective ports 4317 and 4318)를 통해 제공할 수 있습니다.
  • 기본적으로, OTLP 서비스는 localhost:4317/4318에서 시작합니다. 이렇게 되면, Jaeger가 지정한 하스(host/pod)에서 실행되는 서비스(모니터링 대상)와 같이 동일한 하스에 실행되는 것이기 때문에 문제가 없습니다. 그러나 Jaeger가 별도의 팩에 실행되는 경우, 이를 외부 주소(0.0.0.0)에 바인딩해야합니다. 이러한 사항은 文档에서 明确하게 说明되어 있지 않아 중요한 부분이었습니다.
  • COLLECTOR_OTLP_ENABLED: true이 現在 기본값이 되었고 명시적으로 지정할 필요가 없습니다.

OTel 구성

OTel도 特定的한 수신기, 프로세서, eks포트를 지정해야 합니다. 우리는 그것을 configs/otel-collector.yaml에서 하겠습니다.

수신기 추가

OTel 수집기에게 어떤 수신기가 활성화 되어야 하는지 알려줘야 합니다. 이것은 receivers 섹션에 지정되어 있습니다.:

YAML

 

이것은 포트 4317과 4318(grpc, http respectively)에 OTLP 리시버를 활성화하는 것입니다. 시작할 수 있는 리시버의 종류는 많습니다. 예를 들어, 이 글에 pertinent하지 않지만 Postgresql에 대한 지속적인 메트릭스 스크레이ping을 行う ” postgresql” 리시버를 추가하도록 되어 있습니다. 리시버는 엑스ポート 또는 펀치 기반으로 구성할 수 있습니다. 펀치 기반의 리시버는 周期적으로 특정 대상(예: postgres)을 스크레이ping합니다. 엑스ポー트 기반의 리시버는 OTel 클라이언트 SDK을 사용하여 응용 프로그램에서 메트릭스/로그/迹й를 보냄을 대신 듣고 수신합니다.

그렇다. 이제 我们的 수집기가 적절한 메트릭스를 수신(또는 스크레이ping)하기 위해 준비되었습니다.

프로세서 추가

OTel의 프로세서는 수집기로 들어오는 신호를 변형, 지정, 묶음, 필터링하고/또는 豐富的하게하고 이를 내보내는 것을 도와줍니다. 例如, 프로세서는 메트릭스를 サンプ렙하고, 로그를 필터링하거나 효율성을 위해 신호를 묶음할 수 있습니다. 기본적으로 프로세서를 추가하지 않으므로 수집기는 passthrough입니다. 지금은 이를 무시하겠습니다.

エクスポータ 추가

이제 singals이 어디에 exported 되어야 하는지 identifier를 가져올 때이다: 각 singals에 가장 적절한 backends에게。 리시버와 마찬가지로, exporter도 pull-based이나 push-based이 될 수 있습니다. push-based exporter는 다른 receiver에게 signals를 emit하는 것을 사용합니다. 이들은 outbound입니다. pull-based exporter는 다른 pull-based receiver가 (예를 들어 prometheus) scrape할 수 있는 endpoint을 노출합니다. 우리는 각 kind의 exporter을 추가할 것입니다: tracing의 한 가지와 Prometheus가 scrape하기 위한 한 가지 (尽管 Prometheus가 이 글의 주제가 아닌지 여기에서 추가할 것입니다):

YAML

 

여기에는 otlp 수집器를 실행하는 Jaeger로 exporter가 있습니다. 이를 otlp/jaeger로 나타냅니다. 이 exporter는 주기적으로 Jaeger로 traces를 push할 것입니다. 우리는 또한 9090 포트에 “scraper” endpoint을 추가하여 Prometheus가 주기적으로 그 것을 scrape할 수 있도록 만듭니다.

“debug” exporter는 simply standard output/error streams로 signals를 倾印하는 것만을 사용합니다.

Pipelines 정의

Receiver, Processor, 및 Exporter 节은 수집器로 활성화 될 모듈을 정의하는 것뿐이며, 실제로 활성화 될 것은 아닙니다. 실제로 활성화하거나 시작하려면 “pipelines”로 引用해야 합니다. Pipelines는 신호가 수집器를 통과하고 처리되는 방법을 정의합니다. 우리의 Pipeline 정의 (services 部分)는 이를 명확하게 하게 될 것입니다:

YAML

 

여기에 두 가지 Pipeline을 정의하고 있습니다. 두 가지가 서로 비슷하지만 두 가지 다른 exporting mode(Jaeger와 Prometheus)를 허용합니다. 이제 OTel을 통해 pipelines를 생성하는 力量的을 보고 있습니다.

  1. traces:

  • client SDKs에서 signals를 받기
  • 처리하지 않음
  • 콘솔과 Jaeger
  1. metrics:
  • 클라이언트 SDK에서 的信号을 받기
  • 처리하지 않음
  • 콘솔과 Prometheus로 信号线索를 输出하기 (Prometheus가 스크랩하기 위한 端点을 노출하는 것)。

Nginx를 통해 대시보드 노출하기

Jaeger는 모든 我们的 요청에 대한 信号线索 dataviz를 제공합니다. 이를 브라우저에서 信号线索 찾기를 사용하여 我们的 Nginx 설정에서 다음을 사용하여 보입니다. 다시 한 번, 이 포스트의 주제가 아닌지만 보며 – 또한 Nginx를 통해 /prometheus HTTP 경로 접두사로 Prometheus UI를 노출하고 있습니다。

YAML

 

Jaeger에서 信号线索 시각화

Jaeger UI는 pretty comprehensive하며 여러 기능을 탐구할 수 있습니다. 브라우저에서 Jaeger UI로 이동합니다. 信号线索를 찾고 분석하는 thoroughly comprehensive 인터페이스를 볼 수 있습니다. 주요 섹션들을 familiarize yourself with하고, 包括 search barTrace list를 includes filtering by service, time durations, components, 등을 사용하여 다양한 信号线索를 search for。

다양한 요청에 대한 信号线索 시간 線索를 분석하여 操作의 sequence를 이해하는 것。 각 span은 작업의 単位를 나타내며, 시작과 끝 시간, 시간 이후, 관련 metadata를 보여줍니다. 이 상세한 보기는 信号线索 내에서 성능 문제점과 에러를 快速 揪出하기 매우 도울 수 있습니다。

Client SDK를 통합하는 것。

まず、私たちはシステムを設定して、信号の可視化、消費などを行いました。しかし、私たちのサービスは、まだOTelに信号を送信する更新を受けていません。ここでは、コードの様々な部分において(Golang)クライアントSDKと統合することになります。SDKの文書は、いくつかの概念について初めて熟悉するのに最適な場所です。

以下に記載されている关键的な概念とともに対応することになります。

リソース

リソースは、信号を生成する实体です。私たちの場合、リソースのスコープはサービスをホストしているバイナリです。現在、Onehubサービス全体に対する单一のリソースを持っていますが、これは後で分割される可能性があります。

これはcmd/backend/obs.goで定義されています。クライアントSDKは、リソース定義の詳細に入りたくない場合もありますが、この点については注意してください。標準のヘルパー(sdktrace.WithResource)を使用することで、実行時に最も有用的な部分(如Process Name、Pod Nameなど)を推測してリソース定義を作成することができます。

変更する必要があったのは、docker-compose.ymlonehubサービス用のOTEL_RESOURCE_ATTRIBUTES: service.name=onehub.backend,service.version=0.0.1環境変数だけです。

지시 전달

지시 전달은 관찰性问题에 대한 매우 중요한 주제입니다. 여러 기능 기준이 인자로 들어가 시스템 문제를 식별할 수 있는 것처럼 지시 전달이 일관성이 있는지 여부를 연관성을 가진 singals로 관련づけ는 것입니다. 지시를 추가적인 데이터로 생각하면 이러한 singals에 유용하게 조인할 수 있는 것처럼, 예를 들어 지시가 특정 그룹(比如说 요청)에 대응되는 여러 singals을 관련づける 유일한 방법을 가질 수 있다는 의미입니다.

제공자/ экспор터

OTel은 각 지시에 대한 Provider 인터페이스를 제공합니다 (예를 들어, Span/Trace를 eks포트하기 위한 TracerProvider, Metric를 eks포트하기 위한 MeterProvider, Log를 eks포트하기 위한 LoggerProvider 등)。 각 이 interface에는 여러 구현이 있을 수 있습니다. 예를 들어, 표준 출력/에러 스트림으로 보낼 수 있는 디버그 제공자, 다른 OTel 엔드포인트로 eks포트하기 위한 OTel 제공자, Directly via variety of exporters 등이 있습니다. 그러나 우리의 사례에서는 서비스 外의 어느 제공자의 선택을 늦추고 대신 우리의 환경에서 실행 중인 OTel 수집기로 모든 지시를 보낼 것입니다.

이를 추상화 시키기 위해서는 “OTELSetup” 형식을 생성하여 사용하거나 교체하고자 하는 다양한 제공자에 대한 관리를 할 수 있는 것입니다. 이를 실제izaration하기 위해 cmd/backend/obs.go 에서는 다음과 같습니다:

Go

 

이것은 OTel SDK가 필요한 일반적인 측면을 추적하는 간단한 WRAPPER입니다. 여기에는 제공자(Logger, Tracer, Metric)가 있으며, 추적 대상을 위한 컨텍스트 제공 방법도 포함되어 있습니다. 모든 제공자가 사용하는 오버적 Resource도 여기에 지정되어 있습니다. 앱 시작하고 끝나는 것을 기다리는 함수입니다. 이 함수는 기반 eksporter가 종료되었음을 의미하며, 이를 gracefully 하거나 이를 나갔습니다. WRAPPER 자체는 일반적인 자료를 받는 것입니다.

REPO는 이를 구현한 두 가지 방법을 포함합니다.

우리는 어느 것을 실행할지 결정하지 않았습니다. 지정한 것들은 SDK의 examples를 복사하고 minors fixes 및 refactoring 실시했습니다. 특히 otel-collector 예제를 기반으로 생각하십시오.

OTelProviders를 초기화하십시오.

agg collector를 사용하는 것의 정신은 모든 “entry” 포인트에서 OTel과 관련된 “context”를 시작하는 것입니다. 이 context가 시작시에 생성되면 이곳에 呼び出され는 모든 대상으로 이를 보냄으로써 나중에 이를 전달할 수 있습니다(우리가 correct thing을 하면 이 과정이 이뤄집니다).

간단한 ListTopics API 호출(api/vi/topics)을 고려하면, 我们的 요청은 다음과 같은 경로로 이동하고 돌아가ます:

[ Browser ] ---> [ Nginx ] ---> [ gRPC Gateway ] ---> [ gRPC Service ] ---> [ Database ]

이 경우, entry points는 gRPC Gateway가 Nginx로부터 API 요청을 받는 시작时에 있습니다(HTTP 요청이 Nginx에 도달할 때부터 추적을 시작할 수 있으며, Nginx의 지연을 하나의 주요 원인으로 표시할 수 있지만, 일부러 기다릴 것입니다).

필요한 것은 다음과 같습니다:

  • gRPC Gateway가 요청을 받습니다.
  • OTel specifc context.Context 인스턴스를 “custom” 생성합니다.
  • 각 상세 gRPC 서비스(예: TopicService)에 대한 사용자 정의 연결을 생성하고 이 context를 기본 context보다 사용합니다.
  • 각 서비스는 이 context를 사용하여 跡跡를 발送합니다.

이 과정을 한 단계씩 안내하겠습니다.

OTel SDK를 사용하기 전에 초기화하고 준비하기

main.go에서, 우선 agg collector 연결을 초기화하겠습니다:

Go

 

  • 8-16行: 우리는 자신의 Docker 환경에서 실행 중인 otel-collector에 연결을 생성합니다.
  • 17-21行: 그 다음에, OTel 셋업을 트레이서와 지표 제공자로 구성하여 我们的收集器이 이제 모든 迹線과 지표를 pushes 할 수 있도록 합니다.(이전에 我們가 OTel 구성에서 수신기를 정의 했음을 기억하십시오).
  • 23-25行: 시utdown시 오픈 트レ이스 연결/제공자 등을 제거하기 위한 最終갱신자를 설정합니다.
  • 27行: 이전과 마찬가지로 우리의 DB와 연결을 설정합니다.
  • 29行 이상: 이전에는 배경에서 GRPC와 ゲート웨이 서비스를 시작하고 그 뿐입니다. 그들의 리턴 또는 退出 상태를 상의 하지 않았습니다. 更具 有弹性의 시스템을 위해서는 우리가 시작하는 서비스의 라이프 사이클에 더 많은 의미를 가진 것이 중요합니다. 따라서 이제는 우리가 시작하는 각 서비스에 대한 “콜백” 채널을 전달합니다. 서버가 退出하면 해당 메서드가 제공하는 이러한 채널에 대해 그들이 gracefully 退出한 것을 보고하는 것입니다. 우리의 전체 二进制 이 vent 하는 것은 이러한 서비스 중 하나가 退出하면 됩니다.

예를 들어서, 우리의 ゲート웨이 서비스가 이 채널을 사용하는 것을 보십시오.

HTTP 서버(grpc-gateway)를 시작하는 것을 대신하여

1    http.ListenAndServe(gw_addr, mux)

我们现在:

Go

 

주의를 기울여 9-14行에서 서버 중지를 따로 goroutine에서 감시하고 行 15에서 서버 退出시 에러가 있었다면, 이러한 “notification” 채널을 인자로 이 메서드에 전달하여 돌아가는 것입니다.

現在,我們服務的各个方面都已經有了一個“活躍”的OTLP連接,可以在任何时候傳送信號。

OTel 中間件用於gRPC 网關

在上面,用於启动gRPC 网關的http.Server實例使用了自定义處理程序:OTel HTTP 包中的http.Handler。這個處理程序取一個现有的http.Handler實例,用OTel 上下文裝飾它,並確保其傳送到任何其他被呼叫的下游。

Go

 

我們的HTTP處理程序很简单:

  • 行 4:我們創建一個新的OTel specific 包装器來處理HTTP請求。
  • 行 5:我們設置SpanFormatter選項,以便可以根据方法和HTTP 請求路徑唯一地識別追蹤。沒有這個SpanNameFormatter,网關中我們的追蹤默認“名稱”簡單地是"gateway",導致所有追蹤看起来像这样:

使用OTel將网關包装到gRPC調用

默認情況下,gRPC 网關庫在创建/管理對底層GRPC 服務的連接時會創建一個“普通”的上下文。畢竟,网關不知道任何事情关于OTel。在這種模式下,一個連接(從用戶/瀏覽器)到gRPC 网關以及网關到gRPC 服務的連接將被視為两条不同的追蹤。

所以,移除創建gRPC 連接的責任至关重要,將其從网關移除,並提供一個已經知道OTel的連接。我們現在將这样做。

OTel 통합 전에는 우리의 gRPC에 대한 Gateway ハンドラ를 등록하는 것을 시작했습니다.:

Go

 

이제 다른 연결을 전달하는 것은 간단합니다.:

Go

 

우리는 첫 번째로 client를 생성했습니다.(行 6),該 client 充当我們 gRPC 서버의 연결 팩토리。client 은 fairly 간단합니다。gRPC ClientHandler (otelgrpc.NewClientHandler)만을 사용하여 연결을 생성합니다。이는 현재 trace 내에 시작된 새 HTTP 요청의 context를 이 ハン들러를 통해 gRPC 서버로 이를 유지하는 것을 보장합니다.

그럼 就这样。现在我们应该开始看到Gateway的新请求以及Gateway->gRPC请求作为一个单一的汇总跟踪,而不是两个不同的跟踪。

Span 시작과 끝

我們已經差不多完成了。到目前为止:

  • 我們已经启用了OTel 수집器和Jaeger以接收和存储跟踪(span) 数据(在docker-compose中)。
  • 我們已经设置了基本的OTel 수집器(作为单独的pods运行)作为我们的“provider”of tracers,metrics,和logs(即,我們的应用程序的OTel 통합將使用此端點来投入所有信號)。
  • 我们将Gateway的HTTP Handler包裹起来使其可以支持OTel,以便创建和传递跟踪和其上下文。
  • 我们覆盖了网关中的(gRPC)客户端,使其现在使用我们OTel设置中的OTel上下文而不是默认上下文。
  • 我們创建了全局的tracer / meter / logger实例,因此我们可以使用它们发送实际的信号线索。

이제 ListTopics 메서드에 대해 span을 생성해야 합니다. (services/topics.go 내에서)

Go

 

우리는 数据库에서 주제를 가져와 그것을 반환합니다. 数据库 액세스 방법과 유사하게 (datastore/topicds.go 내에서):

Go

 

이곳에서 우리의 주목이 있는 것은 각 이러한 방법에서 지속시간의 量이다. 이러한 방법 각각에 대해 span을 생성하면 끝입니다. 우리는 서비스 및 데이터 저장소 방법에 대한 수정( respectivement)은 다음과 같습니다:

  • services/topics.go:
Go

 

  • datastore/topicds.go:
Go

 

일반적인 패턴은 다음과 같습니다:

1. span 생성:

ctx, span := Tracer.Start(ctx, "<span name>")

이 경우, 주어진 上下文(ctx)가 “감싸られ” 새로운 上下文이 리턴되는 것입니다. 우리는 (그리고 해야 하는 것)이 새로운 감싸린 上下文을 추가 메서드에 전달할 수 있습니다. 우리는 이러한 방법을 datastore ListTopics 메서드에 대해 하는 것과 같습니다.

2. span 종료:

defer span.End()

span을 종료하는 것(메서드가 리턴할 때)은 的正确한 종료 시간/코드 등이 기록되는 것을 보장합니다. 이러한 방법에서는 필요하면 기록하고 디버깅을 도울 수 있는 更多信息을 추가하고 상태를 적용할 수 있습니다.

그럼 그것이 끝입니다. 우리는 Jaeger에서 自己的 추적을 보고 시나리오를 통해 이제 더욱 더 深く 이해할 수 있습니다.

결론

이 포스트에서 많은 내용을 다룬 것이지만, OTel과 跡跡(tracing)의 모든 세부사항을 다 scratch하기는 어렵습니다. 이 포스트에 더 많은 내용을 쌓으면 아이디어가 太多하겠습니다. 그렇기 때문에, 나중에 포스트에서 새로운 개념과 세부 사항을 소개 할 것입니다. 지금은, 자신의 서비스에서 이를 시도하고 otel-contrib 저장소에서 다른 eksorter와 수신기를 experiment with하십시오.

Source:
https://dzone.com/articles/tracing-with-opentelemetry-and-jaeger