다음을 통해 공유


예: Prometheus, Grafana 및 Jaeger와 함께 OpenTelemetry 사용

이 예제에서는 메트릭 수집에 Prometheus, 대시보드 생성에 Grafana, 분산 추적 표시에 Jaeger를 사용합니다.

1. 프로젝트 만들기

Visual Studio에서 ASP.NET Core Empty 템플릿을 사용하거나 다음 .NET CLI 명령을 사용하여 간단한 웹 API 프로젝트를 만듭니다.

dotnet new web

2. 메트릭 및 활동 정의 추가

다음 코드는 API가 호출된 횟수에 대한 새 메트릭(greetings.count)과 새 활동 소스(OtPrGrYa.Example)를 정의합니다.

// Custom metrics for the application
var greeterMeter = new Meter("OtPrGrYa.Example", "1.0.0");
var countGreetings = greeterMeter.CreateCounter<int>("greetings.count", description: "Counts the number of greetings");

// Custom ActivitySource for the application
var greeterActivitySource = new ActivitySource("OtPrGrJa.Example");

3. API 엔드포인트 만들기

app.MapGet("/", SendGreeting);
async Task<String> SendGreeting(ILogger<Program> logger)
{
    // Create a new Activity scoped to the method
    using var activity = greeterActivitySource.StartActivity("GreeterActivity");

    // Log a message
    logger.LogInformation("Sending greeting");

    // Increment the custom counter
    countGreetings.Add(1);

    // Add a tag to the Activity
    activity?.SetTag("greeting", "Hello World!");

    return "Hello World!";
}

참고 항목

API 정의는 OpenTelemetry와 관련된 항목을 사용하지 않습니다. 가시성에 대해 .NET API를 사용합니다.

4. OpenTelemetry 패키지 참조

NuGet 패키지 관리자 또는 명령줄을 사용하여 다음 NuGet 패키지를 추가합니다.

<ItemGroup>
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />
    <PackageReference Include="OpenTelemetry.Exporter.Zipkin" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
</ItemGroup>

참고 항목

OTel API가 계속해서 발전하고 있으므로 최신 버전을 사용합니다.

5. 올바른 공급자를 사용하여 OpenTelemetry 구성

var tracingOtlpEndpoint = builder.Configuration["OTLP_ENDPOINT_URL"];
var otel = builder.Services.AddOpenTelemetry();

// Configure OpenTelemetry Resources with the application name
otel.ConfigureResource(resource => resource
    .AddService(serviceName: builder.Environment.ApplicationName));

// Add Metrics for ASP.NET Core and our custom metrics and export to Prometheus
otel.WithMetrics(metrics => metrics
    // Metrics provider from OpenTelemetry
    .AddAspNetCoreInstrumentation()
    .AddMeter(greeterMeter.Name)
    // Metrics provides by ASP.NET Core in .NET 8
    .AddMeter("Microsoft.AspNetCore.Hosting")
    .AddMeter("Microsoft.AspNetCore.Server.Kestrel")
    .AddPrometheusExporter());

// Add Tracing for ASP.NET Core and our custom ActivitySource and export to Jaeger
otel.WithTracing(tracing =>
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSource(greeterActivitySource.Name);
    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =>
         {
             otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
         });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});

이 코드는 ASP.NET Core 계측을 사용하여 ASP.NET Core에서 메트릭과 활동을 가져옵니다. 또한 메트릭과 추적에 대한 Metrics 공급자와 ActivitySource 공급자를 각각 등록합니다.

이 코드는 ASP.NET Core를 사용하여 엔드포인트를 호스트하는 메트릭에 Prometheus 내보내기를 사용하므로 다음을 추가해야 합니다.

// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();

6. 프로젝트 실행

프로젝트를 실행한 다음, 브라우저 또는 curl을 사용하여 API에 액세스합니다.

curl -k http://localhost:7275

페이지를 요청할 때마다 만들어진 인사말 수의 수가 증가합니다. 경로 /metrics를 통해 동일한 기본 URL을 사용하여 메트릭 엔드포인트에 액세스할 수 있습니다.

6.1 로그 출력

코드의 로깅 문은 ILogger를 사용하여 출력됩니다. 출력이 콘솔로 전달되도록 콘솔 공급자는 기본적으로 활성화됩니다.

.NET에서 로그를 송신하는 방법에는 몇 가지 옵션이 있습니다.

  • stdoutstderr 출력은 Kubernetes와 같은 컨테이너 시스템에 의해 로그 파일로 리디렉션됩니다.
  • ILogger와 통합할 로깅 라이브러리를 사용하면 Serilog 또는 NLog가 포함됩니다.
  • 아래에 표시된 OTLP 또는 Azure Monitor 내보내기와 같은 OTel에 대한 로깅 공급자를 사용합니다.

6.2 메트릭에 액세스

/metrics 엔드포인트를 사용하여 메트릭에 액세스할 수 있습니다.

curl -k https://localhost:7275/
Hello World!

curl -k https://localhost:7275/metrics
# TYPE greetings_count counter
# HELP greetings_count Counts the number of greetings
greetings_count 1 1686894204856

# TYPE current_connections gauge
# HELP current_connections Number of connections that are currently active on the server.
current_connections{endpoint="127.0.0.1:7275"} 1 1686894204856
current_connections{endpoint="[::1]:7275"} 0 1686894204856
current_connections{endpoint="[::1]:5212"} 1 1686894204856
...

메트릭 출력은 엔드포인트가 요청된 시점에 메트릭의 스냅샷입니다. 결과는 Prometheus 설명 형식으로 제공되며, 이는 사람이 읽을 수 있지만 Prometheus에서 더 잘 이해할 수 있습니다. 해당 항목은 다음 단계에서 다룹니다.

6.3 추적에 액세스

서버에 대한 콘솔을 보면 콘솔 추적 내보내기에서 출력이 표시되는데, 이는 사람이 읽을 수 있는 형식으로 정보를 출력합니다. 여기에는 사용자 지정 ActivitySource 및 ASP.NET Core의 두 가지 활동이 표시됩니다.

Activity.TraceId:            2e00dd5e258d33fe691b965607b91d18
Activity.SpanId:             3b7a891f55b97f1a
Activity.TraceFlags:         Recorded
Activity.ParentSpanId:       645071fd0011faac
Activity.ActivitySourceName: OtPrGrYa.Example
Activity.DisplayName:        GreeterActivity
Activity.Kind:               Internal
Activity.StartTime:          2023-06-16T04:50:26.7675469Z
Activity.Duration:           00:00:00.0023974
Activity.Tags:
    greeting: Hello World!
Resource associated with Activity:
    service.name: OTel-Prometheus-Grafana-Jaeger
    service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.5.0

Activity.TraceId:            2e00dd5e258d33fe691b965607b91d18
Activity.SpanId:             645071fd0011faac
Activity.TraceFlags:         Recorded
Activity.ActivitySourceName: Microsoft.AspNetCore
Activity.DisplayName:        /
Activity.Kind:               Server
Activity.StartTime:          2023-06-16T04:50:26.7672615Z
Activity.Duration:           00:00:00.0121259
Activity.Tags:
    net.host.name: localhost
    net.host.port: 7275
    http.method: GET
    http.scheme: https
    http.target: /
    http.url: https://localhost:7275/
    http.flavor: 1.1
    http.user_agent: curl/8.0.1
    http.status_code: 200
Resource associated with Activity:
    service.name: OTel-Prometheus-Grafana-Jaeger
    service.instance.id: e1afb619-bc32-48d8-b71f-ee196dc2a76a
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.5.0

첫 번째는 생성된 내부 사용자 지정 활동입니다. 두 번째는 요청에 대한 ASP.NET에서 생성되며 HTTP 요청 속성에 대한 태그를 포함합니다. 둘 다 동일한 TraceId를 가지고 있다는 것을 알 수 있으며, 이는 단일 트랜잭션을 식별하고 분산 시스템에서 트랜잭션과 관련된 각 서비스의 추적을 상호 연결하는 데 사용할 수 있습니다. ID는 HTTP 헤더로 전송됩니다. ASP.NET Core는 요청을 받을 때 아무것도 없는 경우 TraceId를 할당합니다. HttpClient에는 기본적으로 아웃바운드 요청에 헤더가 포함됩니다. 각 활동에는 각 활동을 고유하게 식별하는 TraceIdSpanId의 조합인 SpanId가 있습니다. Greeter 활동은 HTTP 활동의 SpanId에 매핑되는 ParentSpanId를 통해 HTTP 활동에 부모로 지정됩니다.

이후 단계에서는 이 데이터를 Jaeger에 공급하여 분산 추적을 시각화합니다.

7. Prometheus를 사용하여 메트릭 수집

Prometheus는 메트릭 수집, 집계, 시계열 데이터베이스 시스템입니다. 각 서비스에 대한 메트릭 엔드포인트를 사용하여 이를 구성하면 주기적으로 값을 스크랩하여 시계열 데이터베이스에 저장합니다. 그런 다음, 필요에 따라 분석하고 처리할 수 있습니다.

Prometheus 형식으로 노출되는 메트릭 데이터는 프로세스 메트릭의 지정 시간 스냅샷입니다. 메트릭 엔드포인트에 대한 요청이 있을 때마다 현재 값을 보고합니다. 현재 값이 흥미로울 수 있지만, 이를 과거 값과 비교하여 추세를 확인하고 값이 이례적인지 감지할 때 더 가치가 있습니다. 일반적으로 서비스는 하루 중 특정 시간대나 연말 쇼핑과 같은 세계 이벤트에 따라 사용량이 급증합니다. 과거 추세와 비교하여 값이 비정상적인지 아니면 시간이 지남에 따라 메트릭이 서서히 악화되는지 감지할 수 있습니다.

이 프로세스는 이러한 메트릭 스냅샷의 기록을 저장하지 않습니다. 프로세스에 해당 기능을 추가하는 것은 리소스 집약적일 수 있습니다. 또한 분산 시스템에서는 일반적으로 각 노드의 인스턴스가 여러 개 있으므로, 모든 노드에서 메트릭을 수집한 다음 집계하고 이를 과거 값과 비교하고자 할 것입니다.

7.1 Prometheus 설치 및 구성

https://prometheus.io/download/에서 플랫폼에 대한 Prometheus를 다운로드하고 다운로드의 콘텐츠를 추출합니다.

실행 중인 서버의 출력 상단을 확인하여 http 엔드포인트의 포트 번호를 가져옵니다. 예시:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7275
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5212

Prometheus YAML 구성 파일을 수정하여 HTTP 스크래핑 엔드포인트의 포트를 지정하고 더 낮은 스크래핑 간격을 설정합니다. 예시:

  scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    scrape_interval: 1s # poll very quickly for a more responsive demo
    static_configs:
      - targets: ["localhost:5212"]

Prometheus를 시작하고 실행 중인 포트에 대한 출력(일반적으로 9090)을 확인합니다.

>prometheus.exe
...
ts=2023-06-16T05:29:02.789Z caller=web.go:562 level=info component=web msg="Start listening for connections" address=0.0.0.0:9090

브라우저에서 이 URL을 엽니다. 이제 Prometheus UI에서 메트릭을 쿼리할 수 있습니다. 다음 이미지에서 강조 표시된 단추를 사용하여 사용 가능한 모든 메트릭을 표시하는 메트릭 탐색기를 엽니다.

Prometheus 메트릭 탐색기

greetings_count 메트릭을 선택하여 값 그래프를 확인합니다.

greetings_count 그래프

8. Grafana를 사용하여 메트릭 대시보드 만들기

Grafana는 Prometheus 또는 기타 데이터 원본을 기반으로 대시보드와 경고를 만들 수 있는 대시보드 제품입니다.

플랫폼에 대한 지침에 따라 https://grafana.com/oss/grafana/에서 Grafana의 OSS 버전을 다운로드하고 설치합니다. 설치되면 Grafana는 일반적으로 포트 3000에서 실행되므로 브라우저에서 http://localhost:3000를 엽니다. 로그인해야 합니다. 기본 사용자 이름과 암호는 모두 admin입니다.

햄버거 메뉴에서 연결을 선택한 다음, 텍스트 prometheus를 입력하여 엔드포인트 형식을 선택합니다. Prometheus 데이터 원본 만들기를 선택하여 새 데이터 원본을 추가합니다.

Prometheus에 대한 Grafana 연결

다음 속성을 설정해야 합니다.

  • Prometheus 서버 URL: 해당하는 포트를 변경하는 http://localhost:9090/

저장 & 테스트를 선택하여 구성을 확인합니다.

성공 메시지가 표시되면 대시보드를 구성할 수 있습니다. 성공 메시지 팝업에 표시된 대시보드 빌드 링크를 클릭합니다.

시각화 추가를 선택한 다음, 방금 데이터 원본으로 추가한 Prometheus 데이터 원본을 선택합니다.

대시보드 패널 디자이너가 표시됩니다. 화면 아래쪽에서 쿼리를 정의할 수 있습니다.

greetings_count를 사용하는 Grafana 쿼리

greetings_count 메트릭을 선택한 다음, 쿼리 실행을 선택하여 결과를 확인합니다.

Grafana를 사용하면 다양한 메트릭을 추적하는 정교한 대시보드를 디자인할 수 있습니다.

.NET의 각 메트릭은 데이터를 분할하는 데 사용할 수 있는 키-값 쌍인 추가 차원이 있을 수 있습니다. ASP.NET 메트릭은 모두 카운터에 적용할 수 있는 여러 차원을 특징으로 합니다. 예를 들어 Microsoft.AspNetCore.Hostingcurrent-requests 카운터는 다음과 같은 차원을 갖습니다.

Attribute Type 설명 예제 현재 상태
method string HTTP 요청 메서드입니다. Always
scheme string 사용된 프로토콜을 식별하는 URI 체계입니다. http: https Always
host string 요청을 받은 로컬 HTTP 서버의 이름입니다. localhost Always
port int 요청을 받은 로컬 HTTP 서버의 포트입니다. 8080 기본값이 아닌 경우 추가됨(http의 경우 80, https의 경우 443)

Grafana의 그래프는 일반적으로 각각의 고유한 차원 조합에 따라 분할됩니다. Grafana 쿼리에서 차원을 사용하여 데이터를 필터링하거나 집계할 수 있습니다. 예를 들어 그래프 current_requests를 그리면 각 차원 조합에 따라 분할된 값이 표시됩니다. 호스트만을 기준으로 필터링하려면 Sum 작업을 추가하고 host를 레이블 값으로 사용합니다.

호스트의 Grafana current_requests

9. Jaeger를 사용한 분산 추적

6단계에서는 분산 추적 정보가 콘솔에 노출되는 것을 확인했습니다. 이 정보는 활동의 작업 단위를 추적합니다. 요청 처리를 나타내기 위해 ASP.NET 활동과 같은 일부 활동은 플랫폼에서 자동으로 생성되며 라이브러리와 앱 코드도 활동을 만들 수 있습니다. 인사말 예제에는 Greeter 활동이 있습니다. 활동은 TraceId, SpanId, ParentId 태그를 사용하여 상관 관계가 지정됩니다.

분산 시스템의 각 프로세스는 자체적인 활동 정보 스트림을 생성하며, 메트릭과 마찬가지로 각 트랜잭션에 대해 수행된 작업을 시각화할 수 있도록 활동을 수집, 저장, 상호 연결하는 시스템이 필요합니다. Jaeger는 이러한 수집과 시각화를 가능하게 하는 오픈 소스 프로젝트입니다.

https://www.jaegertracing.io/download/에서 플랫폼을 위한 Jaeger의 최신 바이너리 배포 아카이브를 다운로드합니다.

그런 다음, 쉽게 액세스할 수 있는 로컬 위치에 다운로드를 추출합니다. jaeger-all-in-one(.exe) 실행 파일을 실행합니다.

./jaeger-all-in-one --collector.otlp.enabled

콘솔 출력을 확인하여 gRPC를 통해 OTLP 트래픽을 수신 대기하는 포트를 찾습니다. 예시:

{"level":"info","ts":1686963686.3854616,"caller":"otlpreceiver@v0.78.2/otlp.go:83","msg":"Starting GRPC server","endpoint":"0.0.0.0:4317"}

이 출력은 0.0.0.0:4317에서 수신 대기 중임을 알려 주므로 해당 포트를 OTLP 내보내기 대상으로 구성할 수 있습니다.

프로젝트에 대한 AppSettings.json 파일을 열고 다음 줄을 추가하여 해당하는 경우 포트를 변경합니다.

"OTLP_ENDPOINT_URL" :  "http://localhost:4317/"

속성 변경을 선택하고 추적 정보를 Jaeger로 보낼 수 있도록 인사말 프로세스를 다시 시작합니다.

이제 웹 브라우저의 http://localhost:16686/에서 Jaeger UI를 볼 수 있습니다.

추적에 대한 Jaeger 쿼리

추적 목록을 보려면 서비스 드롭다운에서 OTel-Prometheus-grafana-Jaeger를 선택합니다. 추적을 선택하면 추적의 일부로 활동의 간트 차트가 표시되어야 합니다. 각 작업을 클릭하면 활동에 대한 자세한 내용이 표시됩니다.

Jaeger 작업 세부 정보

분산 시스템에서 모든 프로세스에서 동일한 Jaeger 설치로 추적을 전송하여 시스템 전체에서 트랜잭션의 상관 관계를 지정하고자 할 수 있습니다.

앱이 자신에 대한 HTTP 호출을 만들게 하면 앱을 좀 더 흥미롭게 만들 수 있습니다.

  • 애플리케이션에 HttpClient 팩터리 추가

    builder.Services.AddHttpClient();
    
  • 중첩된 인사말 호출을 만들기 위한 새로운 엔드포인트 추가

    app.MapGet("/NestedGreeting", SendNestedGreeting);
    
  • 추적할 수도 있는 HTTP 호출을 만들도록 엔드포인트를 구현합니다. 이 경우 인공 루프에서 자신을 다시 호출합니다(실제로는 데모 시나리오에만 적용 가능).

    async Task SendNestedGreeting(int nestlevel, ILogger<Program> logger, HttpContext context, IHttpClientFactory clientFactory)
    {
        // Create a new Activity scoped to the method
        using var activity = greeterActivitySource.StartActivity("GreeterActivity");
    
        if (nestlevel <= 5)
        {
            // Log a message
            logger.LogInformation("Sending greeting, level {nestlevel}", nestlevel);
    
            // Increment the custom counter
            countGreetings.Add(1);
    
            // Add a tag to the Activity
            activity?.SetTag("nest-level", nestlevel);
    
            await context.Response.WriteAsync($"Nested Greeting, level: {nestlevel}\r\n");
    
            if (nestlevel > 0)
            {
                var request = context.Request;
                var url = new Uri($"{request.Scheme}://{request.Host}{request.Path}?nestlevel={nestlevel - 1}");
    
                // Makes an http call passing the activity information as http headers
                var nestedResult = await clientFactory.CreateClient().GetStringAsync(url);
                await context.Response.WriteAsync(nestedResult);
            }
        }
        else
        {
            // Log a message
            logger.LogError("Greeting nest level {nestlevel} too high", nestlevel);
            await context.Response.WriteAsync("Nest level too high, max is 5");
        }
    }
    

이렇게 하면 각 수준이 이전 호출의 응답을 대기하므로 요청에 대한 피라미드 모양의 더 흥미로운 그래프가 생성됩니다.

Jaeger 중첩 종속성 결과