OpenTelemetry 마이크로서비스 이해하기 자습서

이 자습서에서는 OpenTelemetry 로 마이크로서비스 애플리케이션에 대한 매우 중요한 관찰 가능성 유형인 추적(tracing)을 강조합니다. 시작하기 전에, 관찰 가능성에 대해 논의할 때 일반적으로 사용되는 일부 용어를 정의해보겠습니다:

마이크로서비스 아키텍처는 팀의 자율성과 확장 및 배포의 유연성을 포함하여 여러 이점을 가지고 있습니다. 그러나 시스템 내에 있는 서비스가 많을수록(마이크로서비스 애플리케이션은 수십 개 또는 수백 개의 서비스를 가질 수 있음) 전반적인 시스템 운영의 명확한 그림을 유지하는 것이 더 어려워집니다. 복잡한 소프트웨어 시스템의 작성자 및 유지 관리자로서, 그 명확한 그림을 가지는 것의 중요성을 알고 있습니다. 관찰 도구는 다양한 서비스와 지원 인프라 전체에 걸쳐 그림을 구축하는 데 필요한 기능을 제공합니다.

  • Observability – 복잡한 시스템(예: 마이크로서비스 애플리케이션)의 내부 상태나 조건을 외부 출력물(추적, 로그, 지표 등)의 지식만을 통해 이해하는 능력을 말합니다.
  • Monitoring – 일정 기간 동안 객체의 진행 상황이나 상태를 관찰하고 확인하는 능력입니다. 예를 들어, 최대 이용 시간에 앱으로 들어오는 트래픽을 모니터링하고 해당 정보를 사용하여 적절하게 스케일링하는 데 활용할 수 있습니다.
  • Telemetry – 지표, 추적, 로그 등을 수집하고 저장 및 분석을 위해 원점으로부터 다른 시스템으로 전송하는 작업입니다. 또한, 이러한 데이터 자체를 가리킵니다.
  • Tracing/Traces – 요청이나 동작이 분산 시스템의 모든 노드를 통과하는 과정을 기록한 것입니다.
  • Span – 작업과 관련된 메타데이터를 포함하는 추적 내의 레코드입니다. 추적은 여러 개의 중첩된 범위로 구성됩니다.
  • Event Logging/Logs – 시간 기록과 메타데이터를 포함하는 텍스트 레코드입니다.
  • Metric – 실행 시점에서 측정된 측정치입니다. 예를 들어, 특정 시점에서 애플리케이션이 사용하는 메모리양 등이 측정될 수 있습니다.

이러한 모든 개념을 활용하여 마이크로서비스의 성능에 대한 통찰력을 얻을 수 있습니다. 추적은 관찰 가능성 전략의 특히 유용한 부분입니다. 추적은 요청이 이루어질 때 여러 개의 종종 느슨하게 결합된 구성 요소 전체에 대한 “전체 그림”을 제공하기 때문입니다. 또한, 성능 병목 현상을 식별하는 데 특히 효과적인 방법입니다. 추적을 통해 각 구성 요소에서 소요되는 시간을 추적하고, 병목 현상이 발생하는 지점을 식별하여 성능을 개선할 수 있습니다.

이 자습서에서는 OpenTelemetry (OTel)의 추적 도구 킷을 사용합니다. OpenTelemetry 은 벤더 중립적인 오픈 소스 표준으로, OpenTelemetry 수집, 처리 및 내보내는 데 사용되며, 현재 많은 인기를 얻고 있습니다. OpenTelemetry 에서는 추적이 데이터 흐름을 여러 서비스를 포함한 일련의 연대순으로 나누어주는 개념을 가지고 있습니다. 이렇게 나누어진 추적은 “조각(chunk)”이라고도 부르며, 다음과 같은 내용을 쉽게 이해할 수 있도록 도와줍니다.

  • “조각(chunk)”에 발생한 모든 단계
  • 각 단계가 소요한 시간
  • 각 단계에 대한 메타데이터

목차

1. OpenTelemetry 자습서 개요
2. 자습서 아키텍처와 OpenTelemetry 목표
2-1. OpenTelemetry 아키텍처와 사용자 흐름
2-2. Telemetry 목표
3. OpenTelemetry자습서 전제 조건 및 설정
3-1. OpenTelemetry 전제 조건
3-2. 설정
4. 과제 1: 기본 OTel 계측 설정
4-1. 메신저 서비스 시작
4-2. 콘솔로 전송되는 OTel 자동 계측 구성
5. 과제 2: 모든 서비스에 대한 OTel 계측 및 추적 시각화 설정
5-1. 외부 수집기로 전송되는 OTel 자동 계측 구성
5-2. 알림 서비스의 OTel 자동 계측 구성
5-3. NGINX의 OTel 계측 구성
6. 과제 3: OTel 추적을 읽는 방법 배우기
6-1. 추적 데이터 생성
6-2. 흔적을 읽을 준비를 하십시오
6-3. 추적의 NGINX(messenger-lb) 섹션 검사
6-4. 추적의 메신저 섹션을 검사하십시오.
6-5. 추적의 알리미 섹션 검사
6-6. 결론
7. OpenTelemetry 과제 4: 추적 판독값을 기반으로 계측 최적화
7-1. 불필요한 스팬 제거
7-2. 커스텀 스팬 설정
7-3. 메신저와 알리미가 동일한 이벤트를 처리하고 있는지 확인
8. OpenTelemetry 리소스 정리
9. OpenTelemetry 다음 단계

1. OpenTelemetry 자습서 개요

이 자습서는 OpenTelemetry 을 사용하여 마이크로서비스 앱의 작업을 추적하는 데 중점을 둡니다. 이 자습서의 네 가지 과제에서는 시스템을 통해 요청을 추적하고 마이크로서비스에 대한 질문에 답하는 방법을 배웁니다.

이러한 문제는 처음으로 추적을 설정할 때 권장되는 프로세스를 보여줍니다. 단계는 다음과 같습니다.

  1. 시스템과 조사하는 특정 작업을 이해합니다.
  2. 실행 중인 시스템으로부터 알아야 할 정보를 결정합니다.
  3. 시스템을 “순진하게” 계측합니다. 즉, 필요하지 않은 정보를 제거하거나 사용자 정의 데이터 포인트를 수집하는 대신에 기본 구성을 사용하고, 계측이 질문에 대한 답변을 도와주는지를 평가합니다.
  4. 질문에 더 빠르게 답변할 수 있도록 보고되는 정보를 조정합니다.

참고: 이 자습서에서는 OpenTelemetry 에 대한 핵심 개념을 설명하기 위해 마이크로서비스를 실제로 배포하는 올바른 방법을 보여주기 위한 것이 아닙니다. 실제 “마이크로서비스” 아키텍처를 사용하지만 몇 가지 중요한 주의사항이 있습니다.

  • 이 자습서는 Kubernetes나 Nomad와 같은 컨테이너 오케스트레이션 프레임워크를 사용하지 않습니다. 이는 특정 프레임워크의 세부 사항에 국한되지 않고 마이크로서비스 개념을 학습할 수 있도록 하기 위함입니다. 여기에서 소개되는 패턴은 이러한 프레임워크 중 하나를 사용하는 시스템으로 이식 가능합니다.
  • 서비스는 소프트웨어 엔지니어링 엄격함보다 이해하기 쉽도록 최적화되었습니다. 핵심은 시스템 내에서 서비스의 역할과 통신 패턴을 살펴보는 것이며, 코드의 세부 사항보다 중요합니다. 자세한 정보는 개별 서비스의 README 파일을 참조하십시오.

2. 자습서 아키텍처와 OpenTelemetry 목표

2-1. OpenTelemetry 아키텍처와 사용자 흐름

이 다이어그램은 자습서에서 사용되는 마이크로서비스 및 기타 요소 간의 전체 아키텍처 및 데이터 흐름을 보여줍니다.

OpenTelemetry

두 마이크로서비스는 다음과 같습니다.

  • 메신저 서비스(Messenger service) – 메시지 저장 기능이 있는 간단한 채팅 API
  • 알림 서비스(notifier service) – 기본 설정에 따라 사용자에게 경고하는 이벤트를 트리거하는 리스너

세 가지 지원 인프라는 다음과 같습니다.

  • NGINX Open Source메신저 서비스 및 시스템 전반에 대한 진입점
  • RabbitMQ – 서비스가 비동기식으로 통신할 수 있도록 하는 인기 있는 오픈 소스 메시지 브로커
  • Jaeger – 원격 측정을 생성하는 시스템 구성 요소에서 원격 측정을 수집하고 시각화하기 위한 오픈 소스 end-to-end 분산 추적 시스템

OpenTelemetry 을 잠시 사진에서 제외하면 우리가 추적하고 있는 일련의 이벤트에 집중할 수 있습니다.

OpenTelemetry 2

흐름은 다음과 같이 나뉩니다.

  1. 사용자가 메신저 서비스에 메시지를 보냅니다. NGINX 리버스 프록시는 메시지를 가로채 메신저 서비스의 여러 인스턴스 중 하나로 전달합니다.
  2. 메신저 서비스는 데이터베이스에 새 메시지를 씁니다.
  3. 메신저 서비스는 메시지가 전송되었음을 나타내기 위해 chat_queue라는 RabbitMQ 메시지 대기열에서 이벤트를 생성합니다.
  4. 동시에:
    • 4a. 메신저 서비스는 메시지가 성공적으로 전송되었음을 보고하는 발신자에게 응답을 return 합니다.
    • 4b. 알림 서비스는 chat_queue의 새 이벤트를 인식하고 이를 사용합니다.
  5. 알림 서비스는 데이터베이스에서 새 메시지 수신자의 알림 기본 설정을 확인합니다.
  6. 알림 서비스는 수신자가 선호하는 방법을 사용하여 하나 이상의 알림을 보냅니다(이 자습서에서 방법 선택은 SMS 및 이메일입니다).

2-2. OpenTelemetry 목표

원격 분석 계측을 설정할 때 “모든 것을 보내고 통찰력을 얻기”보다 더 정의된 일련의 계측 목표로 시작하는 것이 가장 좋습니다. 이 자습서에는 세 가지 주요 원격 분석 목표가 있습니다.

  1. 세 메시지 흐름 중에 요청이 거치는 모든 단계 이해
  2. 흐름이 정상적인 조건에서 5초 이내에 end-to-end 실행되고 있다는 확신을 가지십시오.
  3. 알림 서비스가 메신저 서비스에서 발송한 이벤트를 처리하기 시작하는 데 걸리는 시간을 확인합니다(지연 시간이 너무 길면 알림 서비스가 이벤트 대기열에서 읽는 데 문제가 있고 이벤트가 백업되고 있음을 의미할 수 있습니다).

이러한 목표는 시스템의 기술적 작동 및 사용자 경험 모두와 관련되어 있습니다.

3. OpenTelemetry 자습서 전제 조건 및 설정

3-1. OpenTelemetry 전제 조건

자체 환경에서 자습서를 완료하려면 다음이 필요합니다.

  • Linux/Unix 호환 환경
    참고: 이 자습서에서 NGINX 추적과 관련된 작업은 ARM 기반 프로세스에서 작동하지 않습니다. 이는 NGINX의 OpenTelemetry 모듈이 호환되지 않기 때문입니다. (Linux aarch64 아키텍처와 M1 또는 M2 칩이 탑재된 Apple 기기를 포함합니다.) 메신저알림 서비스와 관련된 작업은 모든 아키텍처에서 작동합니다.
  • Linux command line, JavaScript 및 bash에 대한 기본 지식(그러나 모든 코드와 명령이 제공되고 설명되므로 제한된 지식으로도 성공할 수 있음)
  • DockerDocker Compose
  • Node.js 19.x 이상
    • 여기서는 버전 19.x를 테스트했지만 최신 버전의 Node.js도 작동할 것으로 예상합니다.
    • Node.js 설치에 대한 자세한 내용은 메신저 서비스 저장소의 README를 참조하세요. asdf를 설치하여 자습서에서 사용된 것과 정확히 동일한 Node.js 버전을 얻을 수도 있습니다.
  • curl(대부분의 시스템에 이미 설치되어 있음)
  • 아키텍처 및 사용자 흐름에 나열된 기술: 메신저 및 알리미(다음 섹션에서 다운로드), NGINX 오픈 소스, JaegerRabbitMQ.

참고: 이 자습서는 메신저알림 서비스가 Node.js로 작성되었기 때문에 JavaScript SDK를 사용합니다. 또한 OpenTelemetry 의 자동 계측 기능을 설정하여 OpenTelemetry 에서 제공되는 정보 유형을 실제로 확인할 수 있습니다. 이 튜토리얼은 ORel Node.js SDK에 대해 알아야 할 모든 내용을 설명하지만, 더 자세한 내용은 OTel 문서를 참조하십시오.

3-2. 설정

  1. 터미널 세션을 시작합니다.
  2. 홈 디렉토리에서 microservices-march 디렉토리를 만들고 이 자습서의 GitHub 리포지토리를 여기에 복제합니다. (다른 디렉토리 이름을 사용하고 그에 따라 지침을 조정할 수도 있습니다.)

참고: 자습서 전체에서 Linux command line의 프롬프트는 생략되어 명령을 터미널에 더 쉽게 복사하고 붙여넣을 수 있습니다. 물결표(~)는 홈 디렉토리를 나타냅니다.

mkdir ~/microservices-march
cd ~/microservices-march
git clone https://github.com/microservices-march/messenger --branch mm23-metrics-start
git clone https://github.com/microservices-march/notifier --branch mm23-metrics-start
git clone https://github.com/microservices-march/platform --branch mm23-metrics-start

4. 과제 1: 기본 OTel 계측 설정

이 과제에서는 메신저 서비스를 시작하고 원격 측정을 콘솔로 보내도록 OTel 자동 계측을 구성합니다.

4-1. 메신저 서비스 시작

1. 플랫폼 리포지토리로 변경하고 Docker Compose를 시작합니다.

cd ~/microservices-march/platform
docker compose up -d --build

이렇게 하면 후속 챌린지에서 사용될 RabbitMQ 및 Jaeger가 시작됩니다.

  • ‑d 플래그는 컨테이너가 시작되면 컨테이너에서 분리하도록 Docker Compose에 지시합니다(그렇지 않으면 컨테이너가 터미널에 연결된 상태로 유지됨).
  • –build 플래그는 시작 시 모든 이미지를 다시 빌드하도록 Docker Compose에 지시합니다. 이렇게 하면 실행 중인 이미지가 잠재적인 파일 변경 사항을 통해 업데이트된 상태로 유지됩니다.

2. 메신저 저장소의 디렉토리로 변경하고 Node.js를 설치합니다(원하는 경우 다른 방법으로 대체 가능).

cd ~/microservices-march/messenger/app
asdf install

3. 종속성 설치

npm install

4. 메신저 서비스용 PostgreSQL 데이터베이스를 시작합니다.

docker compose up -d

5. 데이터베이스 스키마와 테이블을 생성하고 일부 시드 데이터를 삽입합니다.

npm run refresh-db

4-2. 콘솔로 전송되는 OTel 자동 계측 구성

OTel 자동 계측을 사용하면 추적을 설정하기 위해 메신저 코드베이스에서 어떤 것도 수정할 필요가 없습니다. 애플리케이션 코드 자체로 가져오는 대신 모든 추적 구성은 런타임 시 Node.js 프로세스로 가져오는 스크립트에서 정의됩니다.

여기에서 가장 기본적인 추적 대상인 콘솔을 사용하여 메신저 서비스의 자동 계측을 구성합니다. 과제 2에서는 추적을 외부 수집기로 Jaeger에 보내도록 구성을 변경합니다.

1. 여전히 메신저 저장소의 디렉토리에서 작업하면서 핵심 OTel Node.js 패키지를 설치합니다.

npm install @opentelemetry/sdk-node@0.36.0 \
            @opentelemetry/auto-instrumentations-node@0.36.4

이러한 라이브러리는 다음 기능을 제공합니다.

  • @opentelemetry/sdk-node – OTel 데이터 생성 및 내보내기
  • @opentelemetry/auto-instrumentations-node – 가장 일반적인 모든 Node.js 계측의 기본 구성으로 자동 설정

참고: OTel의 JavaScript SDK가 아주 아주 작은 조각으로 나뉘는 것은 OTel의 특이한 점입니다. 따라서 이 자습서의 기본 예제에 대한 몇 가지 추가 패키지를 설치하게 됩니다. 이 튜토리얼에서 다루는 것 이상으로 계측 작업을 수행하는 데 필요한 패키지를 이해하려면 (아주 좋은) OTel 시작 안내서를 정독하고 OTel GitHub 리포지토리를 살펴보십시오.

2. OTel 추적을 위한 설정 및 구성 코드를 포함하는 tracing.mjs라는 새 파일을 만듭니다.

touch tracing.mjs

3. 원하는 텍스트 편집기에서 tracing.mjs를 열고 다음 코드를 추가합니다.

//1
import opentelemetry from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";

//2
const sdk = new opentelemetry.NodeSDK({
  traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
  instrumentations: [getNodeAutoInstrumentations()],
});

//3
sdk.start();

코드는 다음을 수행합니다.

  1. OTel SDK에서 필요한 기능과 객체를 가져옵니다.
  2. NodeSDK의 새 인스턴스를 생성하고 다음과 같이 구성합니다.
    • 스팬을 콘솔(ConsoleSpanExporter)로 보냅니다.
    • 자동 계측기를 기본 계측 세트로 사용합니다. 이 계측은 가장 일반적인 자동 계측 라이브러리를 모두 로드합니다. 튜토리얼에서 관련 항목은 다음과 같습니다.
      • Postgres 데이터베이스 라이브러리(pg)용 @opentelemetry/instrumentation-pg
      • Node.js Express 프레임워크용 @opentelemetry/instrumentation-express
      • RabbitMQ 라이브러리용 @opentelemetry/instrumentation-amqplib(amqplib)
  3. SDK를 시작합니다.

4. 메신저 서비스를 시작하고 3단계에서 생성한 자동 계측 스크립트를 가져옵니다.

node --import ./tracing.mjs index.mjs

잠시 후 추적과 관련된 많은 출력이 콘솔(터미널)에 나타나기 시작합니다.

...
{
  traceId: '9c1801593a9d3b773e5cbd314a8ea89c',
  parentId: undefined,
  traceState: undefined,
  name: 'fs statSync',
  id: '2ddf082c1d609fbe',
  kind: 0,
  timestamp: 1676076410782000,
  duration: 3,
  attributes: {},
  status: { code: 0 },
  events: [],
  links: []
}
...

참고: 챌린지 2에서 재사용할 수 있도록 터미널 세션을 열어 둡니다.

5. 과제 2: 모든 서비스에 대한 OTel 계측 및 추적 시각화 설정

추적을 보고 분석하는 데 사용할 수 있는 많은 도구가 있지만 이 자습서에서는 Jaeger를 사용합니다. Jaeger는 범위 및 기타 추적 데이터를 볼 수 있는 웹 기반 사용자 인터페이스가 내장된 간단한 오픈 소스 end-to-end 분산 추적 프레임워크입니다. 플랫폼 리포지토리에서 제공하는 인프라에는 Jaeger(챌린지 1의 1단계에서 시작한)가 포함되어 있으므로 복잡한 툴링을 처리하는 대신 데이터 분석에 집중할 수 있습니다.

Jaeger는 브라우저의 http://localhost:16686 엔드포인트에서 액세스할 수 있지만 지금 엔드포인트에 액세스하면 시스템에 대해 아무것도 표시되지 않습니다. 현재 수집 중인 추적이 콘솔로 전송되기 때문입니다! Jaeger에서 추적 데이터를 보려면 OpenTelemetry 프로토콜(OTLP) 형식을 사용하여 추적을 내보내야 합니다.

이 챌린지에서는 다음에 대한 계측을 구성하여 핵심 사용자 흐름을 계측합니다.

5-1. 외부 수집기로 전송되는 OTel 자동 계측 구성

참고로, OTel 자동 계측을 사용한다는 것은 추적을 설정하기 위해 메신저 코드베이스에서 어떤 것도 수정하지 않는다는 것을 의미합니다. 대신 모든 추적 구성은 런타임 시 Node.js 프로세스로 가져오는 스크립트에 있습니다. 여기에서 콘솔에서 외부 수집기(이 자습서의 Jaeger)로 메신저 서비스에서 생성된 추적의 대상을 변경합니다.

1. 과제 1과 동일한 터미널과 메신저 저장소의 디렉토리에서 계속 작업하면서 OTLP 내보내기 Node.js 패키지를 설치합니다.

npm install @opentelemetry/exporter-trace-otlp-http@0.36.0

@opentelemetry/exporter-trace-otlp-http 라이브러리는 HTTP를 통해 추적 정보를 OTLP 형식으로 내보냅니다. OTel 외부 수집기로 원격 측정을 보낼 때 사용됩니다.

2. tracing.mjs(챌린지 1에서 만들고 편집한)를 열고 다음과 같이 변경합니다.

  • 파일 맨 위에 있는 가져오기 문 집합에 다음 줄을 추가합니다.
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
  • 챌린지 1에서 사용된 콘솔 내보내기에서 OTel SDK에 제공하는 “내보내기”를 HTTP를 통해 OTLP 호환 수집기로 OTLP 데이터를 보낼 수 있는 내보내기로 변경합니다.
traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),

와 함께:

traceExporter: new OTLPTraceExporter({ headers: {} }),

참고: 단순화를 위해 자습서에서는 수집기가 기본 위치인 http://localhost:4318/v1/traces에 있다고 가정합니다. 실제 시스템에서는 위치를 명시적으로 설정하는 것이 좋습니다.

3. Ctrl+c를 눌러 콘솔에 전송된 OTel 자동 계측 구성의 4단계에서 이 터미널에서 시작한 메신저 서비스를 중지합니다. 그런 다음 다시 시작하여 2단계에서 구성한 새 내보내기를 사용합니다.

^c node --import ./tracing.mjs index.mjs

4. 별도의 두 번째 터미널 세션을 시작합니다. (이후 지침에서는 이를 클라이언트 터미널이라고 하고 원래 터미널(1단계와 3단계에서 사용됨)을 메신저 터미널이라고 합니다.) 10초 정도 기다린 다음 메신저 서비스에 상태 확인 요청을 보냅니다. 여러 추적을 보고 싶은 경우):

curl -X GET http://localhost:4000/health

요청을 보내기 전에 10초를 기다리면 서비스가 시작될 때 자동 계측이 생성하는 많은 추적 후에 추적이 오기 때문에 추적을 더 쉽게 찾을 수 있습니다.

5. 브라우저에서 http://localhost:16686의 Jaeger UI에 액세스하고 OTLP 내보내기가 예상대로 작동하는지 확인합니다. 제목 표시줄에서 검색을 클릭하고 서비스 필드의 드롭다운 메뉴에서 이름이 unknown_service로 시작하는 서비스를 선택합니다. 추적 찾기 버튼을 클릭합니다.

OpenTelemetry 3

6. 창 오른쪽에 있는 트레이스를 클릭하면 그 안의 스팬 목록이 표시됩니다. 각 범위는 추적의 일부로 실행된 작업(경우에 따라 여러 서비스를 포함함)을 설명합니다. 스크린샷의 jsonParser 범위는 메신저 서비스 요청 처리 코드의 jsonParser 부분을 실행하는 데 걸린 시간을 보여줍니다.

OpenTelemetry 4

7. 5단계에서 언급한 바와 같이 OTel SDK에서 내보낸 서비스 이름(unknown_service)은 의미가 없습니다. 이 문제를 해결하려면 메신저 터미널에서 Ctrl+c를 눌러 메신저 서비스를 중지합니다. 그런 다음 Node.js 패키지를 몇 개 더 설치합니다.

^c 
npm install @opentelemetry/semantic-conventions@1.10.0 \
            @opentelemetry/resources@1.10.0

이 두 라이브러리는 다음 기능을 제공합니다.

  • @opentelemetry/semantic-conventions – OTel 사양에 정의된 추적에 대한 표준 속성을 정의합니다.
  • @opentelemetry/resources – OTel 데이터(이 튜토리얼에서는 메신저 서비스)를 생성하는 소스를 나타내는 객체(리소스)를 정의합니다.

8. 텍스트 편집기에서 tracing.mjs를 열고 다음과 같이 변경합니다.

  • 파일 맨 위에 있는 가져오기 문 집합에 다음 줄을 추가합니다.
import { Resource } from "@opentelemetry/resources"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
  • 마지막 가져오기 문 뒤에 다음 줄을 추가하여 OTel 사양의 올바른 키 아래에 메신저라는 리소스를 만듭니다.
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
});
  • 검은색 줄 사이에 주황색으로 강조 표시된 줄을 추가하여 리소스 객체를 NodeSDK 생성자에 전달합니다.
const sdk = new opentelemetry.NodeSDK({ resource, traceExporter: new OTLPTraceExporter({ headers: {} }), instrumentations: [getNodeAutoInstrumentations()], });

9. 메신저 서비스를 다시 시작합니다.

node --import ./tracing.mjs index.mjs

10. 10초 정도 기다린 다음 클라이언트 터미널(4단계에서 연)에서 다른 상태 확인 요청을 서버로 보냅니다(여러 추적을 보려면 명령을 몇 번 실행할 수 있음).

curl -X GET http://localhost:4000/health

참고: 다음 섹션에서 재사용할 수 있도록 클라이언트 단말기를 열어두고 챌린지 3에서 재사용할 수 있도록 메신저 단말기를 열어 둡니다.

11. 메신저라는 새 서비스가 브라우저의 Jaeger UI에 나타나는지 확인합니다(몇 초 정도 걸릴 수 있으며 Jaeger UI를 새로 고쳐야 할 수 있음).

OpenTelemetry 5

12. 서비스 드롭다운 메뉴에서 메신저를 선택하고 추적 찾기 버튼을 클릭하여 메신저 서비스에서 발생한 모든 최근 추적을 확인합니다(스크린샷은 20개 중 가장 최근 2개를 보여줍니다).

OpenTelemetry 6

13. 트레이스를 클릭하면 그 안에 스팬이 표시됩니다. 각 스팬은 메신저 서비스에서 발생한 것으로 적절하게 태그가 지정됩니다.

OpenTelemetry 7

5-2. 알림 서비스의 OTel 자동 계측 구성

이제 메신저 서비스에 대한 이전 두 섹션에서와 기본적으로 동일한 명령을 실행하여 알림 서비스에 대한 자동 계측을 시작하고 구성합니다.

1. 새 터미널 세션을 엽니다(이후 단계에서 알림 터미널이라고 함). 알림 리포지토리의 디렉터리로 변경하고 Node.js를 설치합니다(원하는 경우 다른 방법으로 대체할 수 있음).

cd ~/microservices-march/notifier/app
asdf install

2. 종속성 설치:

npm install

3. 알림 서비스에 대한 PostgreSQL 데이터베이스를 시작합니다.

docker compose up -d

4. 데이터베이스 스키마와 테이블을 생성하고 일부 시드 데이터를 삽입합니다.

npm run refresh-db

5. OTel Node.js 패키지를 설치합니다(패키지가 수행하는 작업에 대한 설명은 콘솔에 전송되는 OTel 자동 계측 구성의 1단계와 3단계 참조).

npm install @opentelemetry/auto-instrumentations-node@0.36.4 \
  @opentelemetry/exporter-trace-otlp-http@0.36.0 \
  @opentelemetry/resources@1.10.0 \
  @opentelemetry/sdk-node@0.36.0 \
  @opentelemetry/semantic-conventions@1.10.0

6. tracing.mjs라는 새 파일을 만듭니다.

touch tracing.mjs

7. 원하는 텍스트 편집기에서 tracing.mjs를 열고 다음 스크립트를 추가하여 OTel SDK를 시작하고 실행합니다.

import opentelemetry from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "notifier",
});

const sdk = new opentelemetry.NodeSDK({
  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

참고: 이 스크립트는 SemanticResourceAttributes.SERVICE_NAME 필드의 값이 notifier라는 점을 제외하면 메신저 서비스의 스크립트와 완전히 동일합니다.

8. OTel 자동 계측으로 알림 서비스를 시작합니다.

node --import ./tracing.mjs index.mjs

9. 10초 정도 기다린 다음 클라이언트 터미널에서 알림 서비스에 상태 확인 요청을 보냅니다. 이 서비스는 포트 4000에서 수신 대기 중인 메신저 서비스와의 충돌을 방지하기 위해 포트 5000에서 수신 대기 중입니다.

curl http://localhost:5000/health

참고: 과제 3에서 재 사용할 수 있도록 클라이언트 및 notifier 터미널을 열어 둡니다.

10. 브라우저의 Jaeger UI에 notifier 라는 새 서비스가 나타나는지 확인합니다.

범위에 대한 심층 검사에 사용할 수 있는 서비스 목록에 알리미를 표시하는 Jaeger GUI의 스크린샷

5-3. NGINX의 OTel 계측 구성

NGINX의 경우 OTel 자동 계측 방법을 사용하는 대신 수동으로 추적을 설정합니다. 현재 OTel을 사용하여 NGINX를 계측하는 가장 일반적인 방법은 C로 작성된 모듈을 사용하는 것입니다. Third-Party 모듈은 NGINX 생태계의 중요한 부분이지만 설정하는 데 약간의 작업이 필요합니다. 이 자습서에서는 설정을 수행합니다. 배경 정보는 블로그에서 NGINX 및 NGINX Plus용 Third-Party 모듈 컴파일을 참조하십시오.

1. 새 터미널 세션(NGINX 터미널)을 시작하고 디렉터리를 메신저 리포지토리의 루트로 변경하고 load-balancer라는 새 디렉터리와 Dockerfile, nginx.confopentelemetry_module.conf라는 새 파일을 만듭니다.

cd ~/microservices-march/messenger/
mkdir load-balancer
cd load-balancer
touch Dockerfile
touch nginx.conf
touch opentelemetry_module.conf

2. 선호하는 텍스트 편집기에서 Dockerfile을 열고 다음을 추가합니다(주석은 각 줄의 기능을 설명하지만 모든 내용을 이해하지 않고도 Docker 컨테이너를 빌드하고 실행할 수 있음).

FROM --platform=amd64 nginx:1.23.1

# Replace the nginx.conf file with our own
COPY nginx.conf /etc/nginx/nginx.conf

# Define the version of the NGINX OTel module
ARG OPENTELEMETRY_CPP_VERSION=1.0.3

# Define the search path for shared libraries used when compiling and running NGINX
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib

# 1. Download the latest version of Consul template and the OTel C++ web server module, otel-webserver-module
ADD https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/webserver%2Fv${OPENTELEMETRY_CPP_VERSION}/opentelemetry-webserver-sdk-x64-linux.tgz /tmp

RUN apt-get update \
  && apt-get install -y --no-install-recommends dumb-init unzip \
# 2. Extract the module files
  && tar xvfz /tmp/opentelemetry-webserver-sdk-x64-linux.tgz -C /opt \
  && rm -rf /tmp/opentelemetry-webserver-sdk-x64-linux.tgz \
# 3. Install and add the 'load_module' directive at the top of the main NGINX configuration file
  && /opt/opentelemetry-webserver-sdk/install.sh \
  && echo "load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.23.1/ngx_http_opentelemetry_module.so;\n$(cat /etc/nginx/nginx.conf)" > /etc/nginx/nginx.conf

# 4. Copy in the configuration file for the NGINX OTel module
COPY opentelemetry_module.conf /etc/nginx/conf.d/opentelemetry_module.conf

EXPOSE 8085

STOPSIGNAL SIGQUIT

3. nginx.conf를 열고 다음을 추가합니다.

events {}

http {
    include /etc/nginx/conf.d/opentelemetry_module.conf;

    upstream messenger {
        server localhost:4000;
    }

    server {
        listen 8085;

        location / {
            proxy_pass http://messenger;
        }
    }
}

이 매우 기본적인 NGINX 구성 파일은 NGINX에 다음을 지시합니다.

  • 메신저 서비스 인스턴스 그룹을 대표하는 메신저라는 upstream그룹 설정
  • 포트 8085에서 HTTP 요청 수신 대기
  • /로 시작하는 경로에 대해 들어오는 모든 요청(즉, 들어오는 모든 요청)을 메신저 upstream으로 전달

참고: 이는 프로덕션 환경에서 리버스 프록시 및 로드 밸런서로서의 NGINX의 실제 구성과 매우 유사합니다. 유일한 주요 차이점은 upstream 블록의 server 지시문에 대한 인수가 일반적으로 localhost가 아닌 도메인 이름 또는 IP 주소라는 점입니다.

4. opentelemetry_module.conf를 열고 다음을 추가합니다.

NginxModuleEnabled ON;
NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName messenger-lb;
NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;

5. NGINX 및 NGINX OTel 모듈을 포함하는 Docker 이미지를 빌드합니다.

docker build -t messenger-lb .

6. NGINX 리버스 프록시 및 로드 밸런서에 대한 Docker 컨테이너를 시작합니다.

docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb

7. 클라이언트 터미널에서 NGINX 리버스 프록시 및 로드 밸런서를 통해 메신저 서비스에 상태 확인 요청을 보냅니다(이 요청을 보내기 전에 기다릴 필요가 없음).

curl http://localhost:8085/health

참고: 과제 3에서 재사용할 수 있도록 NGINX 및 클라이언트 터미널을 열어 둡니다.

8. 브라우저에서 새 messenger-lb 서비스가 이전에 시작한 서비스와 함께 Jaeger UI에 나열되는지 확인합니다. 브라우저에서 Jaeger UI를 다시 로드해야 할 수도 있습니다.

이제 messenger-lb를 포함하여 범위에 대한 심층 검사에 사용할 수 있는 서비스 목록을 보여주는 Jaeger GUI의 스크린샷

6. 과제 3: OTel 추적을 읽는 방법 배우기

아키텍처 및 사용자 흐름에서 우리는 사용자 흐름의 단계를 설명했지만 요약하자면 다음과 같습니다.

  1. 사용자는 다른 사용자에게 메시지를 보내 대화를 시작합니다.
  2. NGINX 리버스 프록시는 메시지를 가로채 메신저 서비스로 전달합니다.
  3. 메신저 서비스는 데이터베이스에 메시지를 작성한 다음 RabbitMQ를 통해 이벤트를 발송합니다.
  4. 알림 서비스는 해당 이벤트를 사용하고 수신자(두 번째 사용자)의 알림 기본 설정을 조회하고 선호하는 방법을 통해 수신자에게 알림을 보냅니다.

원격 분석 구현의 목표는 다음과 같습니다.

  1. 새 메시지 흐름을 수행하기 위해 요청이 거치는 모든 단계를 이해합니다.
  2. 흐름이 정상적인 조건에서 5초 이내에 end-to-end 실행되고 있다는 확신을 가지십시오.
  3. 알림 서비스가 메신저 서비스에서 발송한 이벤트를 처리하기 시작하는 데 걸리는 시간을 확인하십시오.

이 챌린지에서는 OTel 계측에서 생성된 추적이 앞서 언급한 목표를 충족하는지 여부를 평가하는 방법을 배웁니다. 먼저 시스템을 회전시키고 몇 가지 추적을 생성합니다. 그런 다음 NGINX, 메신저 서비스알림 서비스에서 생성된 메시지 흐름 및 섹션에 대한 추적을 검사합니다.

6-1. 추적 데이터 생성

클라이언트 터미널에서 대화를 설정하고 두 사용자 간에 몇 가지 메시지를 보냅니다.

curl -X POST \
    -H "Content-Type: application/json" \
    -d '{"participant_ids": [1, 2]}' \
    'http://localhost:8085/conversations'

curl -X POST \
    -H "User-Id: 1" \
    -H "Content-Type: application/json" \
    -d '{"content": "This is the first message"}' \
    'http://localhost:8085/conversations/1/messages'

curl -X POST \
    -H "User-Id: 2" \
    -H "Content-Type: application/json" \
    -d '{"content": "This is the second message"}' \
    'http://localhost:8085/conversations/1/messages'

다음과 같은 출력이 알림 서비스에 의해 생성되고 알림 터미널에 표시됩니다.

Received new_message: {"type":"new_message","channel_id":1,"user_id":1,"index":1,"participant_ids":[1,2]}
Sending notification of new message via sms to 12027621401

Received new_message:  {"type":"new_message","channel_id":1,"user_id":2,"index":2,"participant_ids":[1,2]}

Sending notification of new message via email to the_hotstepper@kamo.ze

Sending notification of new message via sms to 19147379938

6-2. 흔적을 읽을 준비를 하십시오

브라우저에서 Jaeger UI를 열고 Service 드롭다운 메뉴에서 messenger-lb를 선택한 다음 Find Traces 버튼을 클릭합니다. 흐름의 맨 처음부터 추적 목록이 나타납니다. 이 스크린샷과 같이 추적에 대한 세부 정보를 표시하려면 아무 추적이나 클릭하십시오.

흐름의 전체 범위 세트를 보여주는 Jaeger GUI의 스크린샷

주위를 클릭하고 조금 탐색하십시오. 그런 다음 계속 진행하기 전에 추적의 정보가 과제 3 소개에 나열된 계측 목표를 어떻게 지원하는지 고려하십시오. 관련 질문은 다음과 같습니다.

  • 목표 달성에 도움이 되는 정보는 무엇입니까?
  • 누락된 정보는 무엇입니까?
  • 관련이 없는 정보는 무엇입니까?

6-3. 추적의 NGINX(messenger-lb) 섹션 검사

목표 1: 새 메시지 흐름에서 요청이 거치는 모든 단계 보기

상위 범위 내에 11개의 하위 범위가 있는 NGINX 범위로 시작하십시오. 현재 NGINX 구성은 매우 단순하기 때문에 하위 범위는 그다지 흥미롭지 않으며 단순히 NGINX 요청 처리 수명 주기의 각 단계에 소요된 시간을 보여줍니다. 그러나 상위 스팬(첫 번째 스팬)에는 몇 가지 흥미로운 통찰력이 포함되어 있습니다.

추적의 NGINX(messenger-lb) 섹션에서 상위 스팬을 보여주는 Jaeger GUI의 스크린샷
  • 태그 아래에 다음 속성이 표시됩니다.
    • http.method 필드 – POST(REST 용어로 이것은 생성을 의미함)
    • http.status_code 필드 – 201(성공적인 생성을 나타냄)
    • http.target 필드 – conversations/1/messages(메시지 엔드포인트)

종합하면 이 세 가지 정보가 결합되어 “POST 요청이 /conversations/1/messages로 전송되었고 응답은 201(성공적으로 생성됨)”이었습니다. 이는 아키텍처 및 사용자 흐름의 1단계 및 4a단계에 해당합니다.

  • Process 아래의 webengine.name 필드는 이것이 요청의 NGINX 부분임을 보여줍니다.

또한 메신저알림에 대한 스팬이 messenger-lb conversations/1 스팬 내에 중첩되어 있기 때문에(흔적을 읽을 준비를 하십시오 의 스크린샷 참조) NGINX를 통해 메신저 서비스로 전송된 요청이 반대임을 알 수 있습니다. 프록시는 흐름에서 예상되는 모든 구성 요소에 도달합니다.

이 정보는 NGINX 리버스 프록시가 흐름의 일부임을 알 수 있으므로 목표를 충족합니다.

목표 2: 흐름이 5초 이내에 실행되는지 확인

messenger-lb 레이블이 지정된 스팬 목록에서 가장 최근 스팬(목록 맨 아래에 있음)을 보고 요청의 NGINX 부분이 얼마나 오래 걸렸는지 확인합니다. 스크린샷에서 스팬은 589마이크로초(µs)에서 시작하여 24µs 동안 지속되었습니다. 즉, 완전한 리버스 프록시 작업에는 약 0.6밀리초(ms)인 613µs만 소요되었습니다. (물론 정확한 값은 튜토리얼을 직접 실행하면 다를 수 있습니다.)

추적의 NGINX(messenger-lb) 섹션에서 범위를 보여주는 Jaeger GUI의 스크린샷

이와 같은 설정에서 대부분의 경우 값은 다른 측정과 관련하여 유용하며 시스템마다 다릅니다. 하지만 이 경우 이 작업의 길이가 5초에 근접할 위험은 없습니다.

NGINX 작업이 거의 5초도 걸리지 않았음을 알 수 있으므로 이 정보는 목표를 충족합니다. 흐름에 매우 느린 작업이 있는 경우 나중에 발생해야 합니다.

목표 3: 알림 서비스가 메신저 서비스에서 전달한 이벤트를 읽는 데 걸리는 시간 확인

NGINX 리버스 프록시 레이어에는 이에 대한 정보가 포함되어 있지 않으므로 메신저 스팬으로 이동할 수 있습니다.

6-4. 추적의 메신저 섹션을 검사하십시오.

목표 1: 새 메시지 흐름에서 요청이 거치는 모든 단계 보기

추적의 메신저 서비스 섹션에는 또 다른 11개의 스팬이 포함되어 있습니다. 다시 말하지만 대부분의 하위 범위는 Express 프레임워크가 요청을 처리할 때 사용하는 기본 단계와 관련이 있으며 별로 흥미롭지 않습니다. 그러나 상위 스팬(첫 번째 스팬)에는 몇 가지 흥미로운 통찰력이 포함되어 있습니다.

Screenshot of Jaeger GUI showing the parent span in the messenger section of the trace

태그 아래에 다음 속성이 표시됩니다.

  • http.method 필드 – POST(다시 REST 용어로 이것은 생성을 의미함)
  • http.route 필드 – /conversations/:conversationId/messages(메시지 경로)
  • http.target 필드 – /conversations/1/messages(메시지 엔드포인트)

이 정보는 메신저 서비스가 흐름의 일부이고 엔드포인트 적중이 새 메시지 엔드포인트임을 보여주기 때문에 목표를 충족합니다.

목표 2: 흐름이 5초 이내에 실행되는지 확인

다음 스크린샷에 표시된 대로 트레이스의 메신저 부분은 1.28ms에서 시작하여 36.28ms에서 끝났으며 전체 시간은 35ms입니다. 대부분의 시간은 JSON(미들웨어 – jsonParser)을 구문 분석하고 훨씬 더 많은 시간을 데이터베이스(pg-pool.connect 및 tcp.connect)에 연결하는 데 사용했습니다.

이는 메시지를 작성하는 과정에서 여러 SQL 쿼리가 생성된다는 점에서 이치에 맞습니다. 그러면 이러한 쿼리의 타이밍을 캡처하기 위해 자동 계측 구성을 보강할 수 있습니다. (튜토리얼에서는 이 추가 계측을 보여주지 않지만 Challenge 4에서는 데이터베이스 쿼리를 래핑하는 데 사용할 수 있는 스팬을 수동으로 생성합니다.)

추적의 메신저 섹션에 있는 스팬과 소요 시간을 보여주는 Jaeger GUI의 스크린샷

이 정보는 메신저 작업이 거의 5초도 걸리지 않는다는 것을 보여주기 때문에 목표를 충족합니다. 흐름에 매우 느린 작업이 있는 경우 나중에 발생해야 합니다.

목표 3: 알림 서비스가 메신저 서비스에서 전달한 이벤트를 읽는 데 걸리는 시간 확인

NGINX 스팬과 마찬가지로 메신저 스팬에는 이 정보가 포함되어 있지 않으므로 알림 스팬으로 이동할 수 있습니다.

6-5. 추적의 알리미 섹션 검사

목표 1: 새 메시지 흐름에서 요청이 거치는 모든 단계 보기

추적의 알림 섹션에는 두 개의 범위만 포함됩니다.

추적의 알리미 섹션에 있는 두 스팬을 보여주는 Jaeger GUI의 스크린샷
  • chat_queue 프로세스 범위 – 알림 서비스가 chat_queue 메시지 대기열의 이벤트를 처리했는지 확인합니다.
  • pg-pool.connect 범위 – 이벤트를 처리한 후 알림 서비스가 데이터베이스에 일종의 연결을 설정했음을 보여줍니다.

이러한 범위에서 사용할 수 있는 정보는 모든 단계를 이해한다는 목표를 부분적으로만 충족합니다. 알림 서비스가 대기열에서 이벤트를 소비하는 지점에 도달했음을 알 수 있지만 다음과 같은 경우에는 알 수 없습니다.

  • 이 서비스에서 보낸 메시지 알림은 메신저 서비스에서 발송한 이벤트에 해당합니다.
  • 관련 메시지 알림이 메시지 수신자에게 올바르게 전송되었습니다.

알림 서비스 흐름을 완전히 이해하려면 다음을 수행해야 함을 나타냅니다.

  • 전송 중인 알림을 표시하는 스팬을 수동으로 계측
  • 추적 ID의 형태로 메신저 서비스에서 발송한 이벤트와 알림 서비스에서 소비한 이벤트 사이에 명시적 연결이 있는지 확인합니다.

목표 2: 흐름이 5초 이내에 실행되는지 확인

알림 서비스 범위의 전체 타이밍을 보면 요청이 흐름의 알림 섹션에서 30.77ms를 소비했음을 알 수 있습니다. 그러나 전체 흐름의 “종료”(수신자에게 알림 전송)를 알리는 범위가 없기 때문에 흐름의 이 섹션의 총 타이밍이나 작업의 전체 완료 시간을 결정할 수 없습니다.

목표 3: 알림 서비스가 메신저 서비스에서 전달한 이벤트를 읽는 데 걸리는 시간 확인

그러나 메신저 서비스에 대한 chat_queue 전송 범위가 4.12ms에 시작된 후 알림 서비스에 대한 chat_queue 프로세스 범위가 6.12ms에 시작된 것을 볼 수 있습니다. 2ms입니다.

메신저 서비스에서 발송한 이벤트를 소비하는 알림 서비스를 보여주는 Jaeger GUI의 스크린샷

이 목표는 알림메신저 서비스에 의해 발송된 후 2ms 동안 이벤트를 소비했다는 것을 알고 있기 때문에 달성됩니다. 목표 2와 달리 이 목표를 달성하기 위해 이벤트가 완전히 처리되었는지 또는 얼마나 걸렸는지 알 필요가 없습니다.

6-6. 결론

현재 OTel 자동 계측에 의해 생성된 흔적에 대한 분석을 기반으로 다음이 분명합니다.

  • 이러한 범위의 대부분은 현재 형식에서 유용하지 않습니다.
    • NGINX는 권한 부여 확인 및 파일 제공과 같은 기능과 관련된 스팬을 생성하고 있으며 관심 있는 역할인 리버스 프록시와 관련이 없습니다. 그러나 이 시점에서 NGINX용 OTel 계측은 관련 없는 범위를 생략하는 것을 허용하지 않으므로 아무 것도 할 수 없습니다.
    • Node.js 서비스(메신저알림 서비스)의 범위 중 일부는 JSON 구문 분석, 요청 처리기 및 모든 데이터베이스 작업에 대한 범위와 같이 목표와 관련이 있는 것으로 보입니다. 일부 미들웨어 범위(예: expressInit 및 corsMiddleware)는 관련이 없어 보이며 제거할 수 있습니다.
  • 다음에 대한 중요한 범위가 누락되었습니다.
    • 알림 서비스에서 보낸 알림
    • 메신저 서비스에서 발송된 RabbitMQ 이벤트와 알림 서비스에서 처리한 이벤트 간의 명확한 매핑

이는 기본 계측이 마지막 목표를 충족함을 의미합니다.

  • 알림 서비스가 메신저 서비스에서 발송한 이벤트 처리를 시작하는 데 걸리는 시간 확인

그러나 처음 두 가지 목표를 달성하기에는 정보가 충분하지 않습니다.

  • 새 메시지 흐름 중에 요청이 거치는 모든 단계 이해
  • 흐름이 정상적인 상황에서 5초 이내에 end-to-end 실행되고 있음을 확신합니다.

7. OpenTelemetry 과제 4: 추적 판독값을 기반으로 계측 최적화

이 챌린지에서는 챌린지 3에서 수행한 추적 분석을 기반으로 OTel 계측을 최적화합니다. 여기에는 불필요한 스팬 제거, 새 사용자 지정 스팬 생성, 알리미 서비스에서 사용 중인 이벤트가 메신저에서 생성된 이벤트인지 확인이 포함됩니다.

7-1. 불필요한 스팬 제거

1. 원하는 텍스트 편집기에서 메신저 리포지토리의 디렉터리에 있는 tracing.mjs 파일을 열고 상단의 가져오기 문 목록 끝에 다음을 추가합니다.

const IGNORED_EXPRESS_SPANS = new Set([
  "middleware - expressInit",
  "middleware - corsMiddleware",
]);

이것은 Jaeger UI의 다음 스크린샷에 표시된 스팬 목록에서 파생된 스팬 이름 세트를 정의하며 이 흐름에 유용한 정보를 제공하지 않기 때문에 추적에서 생략됩니다. 스크린샷에 나열된 다른 범위도 필요하지 않다고 판단하고 IGNORED_EXPRESS_SPANS 목록에 추가할 수 있습니다.

관련 정보를 제공할 수 있으므로 추적에서 생략할 수 있는 메신저 서비스의 여러 범위 목록을 보여주는 Jaeger GUI의 스크린샷

2. 주황색으로 강조 표시된 줄을 변경하여 자동 계측 구성에 필터를 추가하여 원하지 않는 범위를 생략합니다.

const sdk = new opentelemetry.NodeSDK({
  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [getNodeAutoInstrumentations()],
});

이에:

const sdk = new opentelemetry.NodeSDK({
  resource,
  traceExporter: new OTLPTraceExporter({ headers: {} }),
  instrumentations: [
    getNodeAutoInstrumentations({
      "@opentelemetry/instrumentation-express": {
        ignoreLayers: [
          (name) => {
            return IGNORED_EXPRESS_SPANS.has(name);
          },
        ],
      },
    }),
  ],
});

getNodeAutoInstrumentations 함수는 @opentelemetry/instrumentation-express에 의해 생성된 추적에서 필터링하기 위해 1단계에서 정의된 범위 세트를 참조합니다. 즉, return 문은 IGNORED_EXPRESS_SPANS에 속하는 범위에 대해 true로 확인하고 ignoreLayers 문은 추적에서 해당 범위를 제거합니다.

3. 메신저 단말기에서 Ctrl+c를 눌러 메신저 서비스를 중지합니다. 그런 다음 다시 시작합니다.

^c
node --import ./tracing.mjs index.mjs

4. 10초 정도 기다린 다음 클라이언트 터미널에서 새 메시지를 보냅니다.

curl -X POST \
	-H "User-Id: 2" \
	-H "Content-Type: application/json" \
	-d '{"content": "This is the second message"}' \
	'http://localhost:8085/conversations/1/messages'

5. Jaeger UI에서 메신저 스팬을 다시 확인하십시오. 두 개의 미들웨어 범위인 expressInit 및 corsMiddleware가 더 이상 표시되지 않습니다(과제 3에서 추적의 메신저 섹션 검사의 목표 2에 대한 스크린샷과 비교할 수 있습니다.

Screenshot of Jaeger GUI showing that the trace no longer includes two spans after you change the instrumentation to filter them out of the trace

7-2. 커스텀 스팬 설정

이 섹션에서는 처음으로 애플리케이션 코드를 터치합니다. 자동 계측은 애플리케이션을 변경하지 않고도 상당한 양의 정보를 생성하지만 일부 통찰력은 비즈니스 로직의 특정 부분을 계측해야만 가능합니다.

계측 중인 새 메시지 흐름의 경우 메시지 수신자에게 알림 전송을 추적하는 것이 그 예입니다.

  1. 알림 리포지토리의 디렉터리에서 index.mjs를 엽니다. 이 파일에는 서비스에 대한 모든 비즈니스 논리가 포함되어 있습니다. 파일 맨 위에 있는 가져오기 문 목록 끝에 다음 줄을 추가합니다.
import { trace } from "@opentelemetry/api";

2. 이 코드를 교체합니다(파일의 약 91번째 줄).

for (let pref of preferences) {
  console.log(
    `Sending notification of new message via ${pref.address_type} to ${pref.address}`
  );
}

와:

const tracer = trace.getTracer("notifier"); // 1
tracer.startActiveSpan( // 2
  "notification.send_all",
  {
    attributes: {
      user_id: msg.user_id,
    },
  },
  (parentSpan) => {
    for (let pref of preferences) {
      tracer.startActiveSpan(  // 3
        "notification.send",
        {
          attributes: { // 4
            notification_type: pref.address_type,
            user_id: pref.user_id,
          },
        },
        (span) => {
          console.log(
            `Sending notification of new message via ${pref.address_type} to ${pref.address}`
          );
          span.end(); // 5
        }
      );
    }
    parentSpan.end(); // 6
  }
);

새 코드는 다음을 수행합니다.

  1. OTel 추적과 상호 작용하기 위한 전역 객체인 추적자를 가져옵니다.
  2. notification.send_all이라는 새 상위 스팬을 시작하고 user_id 속성을 설정하여 메시지 발신자를 식별합니다.
  3. 수신자의 알림 기본 설정이 열거되고 notification.send_all 아래에 notification.send라는 새 하위 범위가 생성되는 루프에 들어갑니다. 모든 알림은 새로운 스팬을 생성합니다.
  4. 하위 범위에 대한 추가 속성을 설정합니다.
    • notification_type – SMS 또는 이메일 중 하나
    • user_id – 알림을 받는 사용자의 ID
  5. 각 자식 알림을 닫습니다. 차례로 범위를 보냅니다.
  6. 상위 notification.send_all 범위를 닫습니다.

상위 스팬이 있으면 사용자에 대한 알림 기본 설정이 발견되지 않은 경우에도 각 “알림 보내기” 작업이 보고됩니다.

3. 알림 터미널에서 Ctrl+c를 눌러 알리미 서비스를 중지합니다. 그런 다음 다시 시작합니다.

^c
node --import ./tracing.mjs index.mjs

4. 10초 정도 기다린 다음 클라이언트 터미널에서 새 메시지를 보냅니다.

curl -X POST \
	-H "User-Id: 2" \
	-H "Content-Type: application/json" \
	-d '{"content": "This is the second message"}' \
	'http://localhost:8085/conversations/1/messages'

5. Jaeger UI에서 알림 스팬을 다시 확인하십시오. 각각 “알림 보내기” 작업이 있는 상위 범위와 두 개의 하위 범위가 표시됩니다.

알림 서비스에 대한 코드에서 3개의 새 스팬을 정의한 결과를 보여주는 Jaeger GUI의 스크린샷

새 메시지 흐름 중에 요청이 거치는 모든 단계를 볼 수 있으므로 이제 첫 번째 목표와 두 번째 목표를 완전히 달성할 수 있습니다. 각 스팬의 타이밍은 이러한 단계 사이의 지연을 노출합니다.

7-3. 메신저와 알리미가 동일한 이벤트를 처리하고 있는지 확인

흐름에 대한 완전한 통찰력을 위해 필요한 것이 하나 더 있습니다. 알림 서비스에서 처리 중인 이벤트가 실제로 메신저 서비스에서 발송한 이벤트입니까?

두 트레이스를 연결하기 위해 명시적으로 변경할 필요는 없지만 자동 계측의 마법을 맹목적으로 신뢰하고 싶지는 않습니다.

이를 염두에 두고 몇 가지 빠른 디버그 코드를 추가하여 NGINX 서비스에서 시작하는 추적이 실제로 알림 서비스에서 사용하는 추적과 동일한지(동일한 추적 ID를 가짐) 확인합니다.

1. 메신저 리포지토리의 디렉터리에서 index.mjs 파일을 열고 다음과 같이 변경합니다.

  • 상단의 가져오기 문 목록 끝에 다음 줄을 추가합니다.
import { trace } from "@opentelemetry/api";
  • 기존 검은색 줄 아래에 주황색으로 강조 표시된 줄을 추가합니다.
async function createMessageInConversation(req, res) {
  const tracer = trace.getActiveSpan();
  console.log("TRACE_ID: ", tracer.spanContext().traceId);

새 줄은 새 메시지 생성을 처리하는 메신저의 함수 내부에서 TRACE_ID를 출력합니다.

2. 알림 리포지토리의 디렉터리에서 index.mjs 파일을 열고 주황색으로 강조 표시된 줄을 기존 검은색 줄 아래에 추가합니다.

export async function handleMessageConsume(channel, msg, handlers) {
  console.log("RABBIT_MQ_MESSAGE: ", msg);

새 행은 알림 서비스에서 수신한 AMQP 이벤트의 전체 내용을 인쇄합니다.

3. 메신저알림 터미널 모두에서 다음 명령을 실행하여 메신저 및 알리미 서비스를 중지하고 다시 시작합니다.

^c
node --import ./tracing.mjs index.mjs

4. 10초 정도 기다린 다음 클라이언트 터미널에서 메시지를 다시 보냅니다.

curl -X POST \
	-H "User-Id: 2" \
	-H "Content-Type: application/json" \
	-d '{"content": "This is the second message"}' \
	'http://localhost:8085/conversations/1/messages'

5. 메신저알림 서비스에 대한 로그를 확인하십시오. 메신저 서비스 로그에는 메시지의 추적 ID를 보고하는 다음과 같은 줄이 포함되어 있습니다(실제 ID는 자습서를 실행할 때 다릅니다).

TRACE_ID:  29377a9b546c50be629c8e64409bbfb5

6. 마찬가지로 알림 서비스 로그는 다음과 같은 출력에서 추적 ID를 보고합니다.

_spanContext: {
  traceId: '29377a9b546c50be629c8e64409bbfb5',
  spanId: 'a94e9462a39e6dbf',
  traceFlags: 1,
  traceState: undefined
},

7. 추적 ID는 콘솔에서 일치하지만 최종 단계에서 Jaeger UI의 추적 ID와 비교할 수 있습니다. 전체 추적을 보려면 관련 추적 ID 끝점에서 UI를 엽니다(귀하의 것과 다르지만 이 예에서는 http://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5). Jaeger 추적은 다음을 확인합니다.

  • 메신저 서비스의 AMQP 자동 계측은 이벤트가 발송될 때 이 추적 ID를 메타데이터의 일부로 추가합니다.
  • 알림 서비스의 AMQP 자동 계측은 해당 메타데이터를 예상하고 추적 컨텍스트를 적절하게 설정합니다.

참고: 실제 프로덕션 시스템에서는 흐름이 예상대로 작동하는지 확인한 후 이 섹션에 추가한 코드를 제거합니다.

8. OpenTelemetry 리소스 정리

이 자습서 전체에서 몇 가지 컨테이너와 이미지를 만들었습니다! 제거하려면 다음 지침을 따르십시오.

  • 실행 중인 Docker 컨테이너를 제거하려면:
docker rm $(docker stop messenger-lb)
  • 플랫폼 서비스와 메신저알림 데이터베이스 서비스를 제거하려면:
cd ~/microservices-march/platform && docker compose down
cd ~/microservices-march/notifier && docker compose down
cd ~/microservices-march/messenger && docker compose down

9. OpenTelemetry 다음 단계

축하합니다. OpenTelemetry 튜토리얼을 완료했습니다!

  • NGINX 리버스 프록시와 두 개의 Node.js 서비스에서 OTel 계측을 설정합니다.
  • OTel 자동 계측에서 제공하는 데이터를 중요한 시각으로 살펴보고 OTel 실험실 목표를 달성하기 위해 누락된 일부 원격 측정을 추가했습니다.
    • 애플리케이션 코드를 직접 변경하지 않고 메시징 시스템을 통해 특정 요청 흐름에 대한 합리적인 보기를 달성했습니다.
    • 정상적인 상황에서 흐름이 5초 이내에 end-to-end 실행되고 있음을 확인했습니다.

그러나 이상적인 추적 구성이 어떻게 생겼는지에 대한 표면을 간신히 긁었습니다! 프로덕션 환경에서는 각 서비스의 컨테이너 ID와 같은 런타임 세부 정보를 설명하는 모든 범위에 대한 각 데이터베이스 쿼리 및 추가 메타데이터에 대한 사용자 지정 범위와 같은 항목을 추가할 수 있습니다. 다른 두 가지 유형의 OTel 데이터(메트릭 및 로깅)를 구현하여 시스템 상태에 대한 완전한 그림을 제공할 수도 있습니다.

NGINX Plus를 직접 사용해 보시려면 30일 무료 평가판을 신청하거나 NGINX STORE에 연락하여 문의하십시오.

아래 뉴스레터를 구독하고 NGINX의 최신 정보들을 빠르게 전달 받아보세요.