銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

OpenTelemetry .NETを理解する (2) ASP.NET CoreにOpenTelemetryをまずいれてみる

今回からは実際にOpenTelemetry .NETを使って計測してみることにします。まずは、ASP.NET Core (.NET 6)を題材にします。

OpenTelemetryのドキュメントと重なる部分も多いのであわせて参照してみてください。

opentelemetry.io

最初の計装

まずは、計測対象のASP.NET Coreプロジェクトを作ります。dotnet new web で作れますが、.NET 6だと1ファイルで完結する形でかんたんなWebアプリが作れるので以下のコードをスタートにします。

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
    
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
       new WeatherForecast
       (
           DateTime.Now.AddDays(index),
           Random.Shared.Next(-20, 55),
           summaries[Random.Shared.Next(summaries.Length)]
       ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

ここから計測コードを実装する計装作業を行います。まず、必要なライブラリはNuGetで提供されているので、以下の2つをまず追加します。

  <ItemGroup>
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc8" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc8" />
  </ItemGroup>

またこれらのパッケージは、OpenTelemetryOpenTelemetry.Apiに依存しています。この2つは計装のためのOpenのAPIとSDKの言語ごとの実装であり、計装するために最低限必要なライブラリという位置づけになっています。APIとSDKについては、ライブラリ開発者がOpenTelemetry .NETを使うユースケースで説明しようと思っています。

OpenTelemetry.Extensions.HostingとOpenTelemetry.Instrumentation.AspNetCoreは計装の手間をへらすために、よく使われるライブラリ(ここではASP.NET Core)を計装する処理を提供します。前回の記事の自動計装(automatic instrumentation)と呼ばれる機能です。APIとSDKは安定版ですが、自動計装部分についてはRC版となっています。

実際の計装コードは次のように記述します。先程のコードの先頭部分に次のように追加します。

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

var tags = new Dictionary<string, object>
        {
            { "Environment", "Production" },
            { "Level", 99 },
        };

builder.Services.AddOpenTelemetryTracing((builder) => builder
        .AddAspNetCoreInstrumentation()
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Hello-Otel").AddAttributes(tags))
    );

SetResourceBuilderメソッドの中の「Hello-Otel」はこのアプリケーションを識別するための名前をつけます。またアプリケーションを分類するためのタグを追加しています。これだけでASP.NET CoreアプリケーションをOpenTelemetryの標準的な機能で計測することができます。ただ、これだけだと計測したデータをどこにも送信していません。まずはコンソールに出力して動作確認することにします。

ConsoleExporterの追加

Exporterは出力先ごとにNuGetライブラリが提供されています。Consoleに出力する場合、以下のライブラリを追加します。

  <ItemGroup>
    <PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.1.0" />
  </ItemGroup>

Exporterを設定するのは、先程のbuilder.Services.AddOpenTelemetryTracingメソッドの中で、次のように設定します。

builder.Services.AddOpenTelemetryTracing((builder) => builder
        .AddConsoleExporter()
        .AddAspNetCoreInstrumentation()
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Hello-Otel").AddAttributes(tags))
    );

これだけで完了です。実際に実行してエンドポイントにアクセスすると次のようなデータがコンソールに出力されるはずです。

Activity.Id:          00-016a34b6053f5d51737b3419a41e4604-c08de677265e1e72-01
Activity.ActivitySourceName: OpenTelemetry.Instrumentation.AspNetCore
Activity.DisplayName: WeatherForecast
Activity.Kind:        Server
Activity.StartTime:   2022-01-06T06:33:00.9914510Z
Activity.Duration:    00:00:00.0496200
Activity.TagObjects:
    http.host: localhost:7174
    http.method: GET
    http.target: /WeatherForecast
    http.url: https://localhost:7174/WeatherForecast
    http.user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
    http.route: WeatherForecast
    http.status_code: 200
    otel.status_code: UNSET
Resource associated with Activity:
    Environment: Production
    Level: 99
    service.name: Hello-Otel
    service.instance.id: 509603d5-39e6-4395-9c71-884e27f61072

実行された処理がトレースとして計測されたものです。トレースを識別するためのIdや名前、時刻、経過時間、タグなどが記録されていることがわかります。また、「Resource associated with Activity」ではSetResourceBuilderで設定したアプリの名前やタグも確認できます。

OTLPExporterでNew Relicに送信する

ほとんどのケースではコンソールではなく、別のどこかに送信し、最終的には記録・利用のためのオブザーバビリティバックエンドに送信することになります。前回の記事の「OpenTelemetery で計測するために必要なもの」の図ではコレクターを経由して送信しています。実用的にはコレクターを経由するケースが多いと思うのですが、ひとまず検証するという意味ではアプリケーションのOpenTelemetryコードから直接オブザーバビリティバックエンドに送信することができます。その場合、バックエンドがOpenTelemetry .NET向けにExporterライブラリを提供していれば利用できます。特に、OTLP形式のエンドポイントをサポートしているバックエンドであればOpenTelemetry .NETが提供しているOTLPExporterが利用できます。そして、New RelicはOTLP形式のエンドポイントをサポートしているので試しに利用できます。

New Relic側の準備については公式ブログを参照してください。具体的にはライセンスキーとエンドポイントをメモしておいてください。

newrelic.com

それは実装してみます。まずはOTLPExporterのライブラリを追加します。先程のConsoleExpoterは不要ですが、残しておいても問題はありません。

  <ItemGroup>
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.1.0" />
  </ItemGroup>

AddConsoleExporterの代わりにAddOtlpExporterを呼びます。そして、以下のようにエンドポイントと認証のためのヘッダーを設定します。ここではエンドポイントをOTEL_ENDPOINTという名前の、認証ヘッダーのキーがapi-keyで値がOTEL_API_KEYという名前の、環境変数に設定しています。

builder.Services.AddOpenTelemetryTracing((builder) => builder
        .AddAspNetCoreInstrumentation()
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Hello-Otel").AddAttributes(tags))
        .AddOtlpExporter(options => 
        {
            options.Endpoint = new Uri(Environment.GetEnvironmentVariable("OTEL_ENDPOINT"));
            options.Headers = $"api-key={Environment.GetEnvironmentVariable("OTEL_API_KEY")}";
        })
    );

エンドポイントや認証のために利用するヘッダーのキーの名前はOTLPをサポートしているバックエンドごとに提供されています。なのでほぼ同じ書き方で設定できるはずです。また、その他のExpoterも設定はこのような書き方でできるものが多いです。これでOTLPフォーマットで送信する設定は完了です。再度アプリケーションを起動し、アクセスすると送信されるはずです。

送信されたデータをどのように表示するかはオブザーバビリティバックエンドの機能に依存します。New Relicの場合、送信するだけでアプリケーションごとに以下のようなダッシュボードに表示されます。

f:id:tanaka733:20220119225731p:plain

また、先程コンソールに出力したようなトレースの詳細は分散トレース(Distributed Tracing)のUIで確認できます。

f:id:tanaka733:20220119231437p:plain

このようにOpenTelemetry .NETはよく使われるフレームワークであれば、まず使い始めるのは非常にかんたんになっています。

Azure Chaos Studio を試してみる サービス直接ターゲット編

f:id:tanaka733:20220118233433p:plain 先日、カオスエンジニアリングをマネージドサービスとして提供するAzure Chaos Studioが発表され、プレビューとして利用できるようになっています。

azure.microsoft.com

docs.microsoft.com

せっかくなので試してみようと思い、いろいろやった結果をまとめておきます。

Chaos Studioの概念

ドキュメントでいうとここからのセクションに説明されています。

docs.microsoft.com

一番大きな単位が「カオス実験」で、どんな障害をどんなリソースに実行するかが記述されています。 どんな障害を「ロジック」として定義し、どんなリソースにを「セレクター」として定義します。この2つがカオス実験を構成する単位です。 ロジックは1つずつ逐次実行する「ステップ」から構成され、ステップは同時に実行される「分岐(Branch)」から構成されます。 分岐は対象となるリソースをさらに定義するセレクターと、なにを実行するかの「アクション」で構成されます。 アクションは、障害もしくは待機の2種類があります。 これらの組み合わせで1つの実験を定義し、実行することになります。

現在利用できる障害は以下の3種類にわけられるようです。

  • サービス直接ターゲット: Azureのサービスに直接作用する
  • エージェントベース: OSにインストールしたエージェントが作用する
  • Chaos Mesh: AKSクラスター上のkubernetesリソースにChaos Meshを利用して作用する

まず、サービス直接ターゲットを使ったものを試してみました。

CosmosDBにサービス直接ターゲットを設定して障害を起こす

試したアクションはCosmosDBをFailoverさせるものです。基本的にはこのドキュメントに沿って設定すればOKです。が、いくつかはまったところがあるのと、実は最終的に実験の結果がFailedになってしまい未解決です。

docs.microsoft.com

まずはCosmosDBを用意します。Failoverさせることから、読み取りリージョンを1つ以上追加しておかないとあとで設定できなくなります。 f:id:tanaka733:20220118232721p:plain f:id:tanaka733:20220118191744p:plain

空だとさみしいので、コンテナとアイテムを用意しておきました。実験を実行するだけの場合、なくてもかまいません。

f:id:tanaka733:20220118185203p:plain

まず、Azure Chaos Studioのページから対象となるリソースを選択します。そして、大事なことですが、ポータルの言語を英語にしておきます。Previewのせいなのか、日本語だとロジックがうまく実行できませんでした。最初日本語で作成していたので画面キャプチャは日本語になっています。

f:id:tanaka733:20220118185259p:plain

実験を作成します。このときのリソースグループとリージョンは対象と一致していなくてもよいはずですが、今回は同じものを選んでいます。

f:id:tanaka733:20220118185748p:plain

実験を作成したら、ロジックを作成します。ステップとブランチ1つの中に、障害アクションを1つ作成します。

f:id:tanaka733:20220118185839p:plain

翻訳の表記ゆれをしていますが、フォールトの選択で障害の種類を選びます。CosmosDB Failverを選びます。

f:id:tanaka733:20220118185903p:plain

どのリージョンにFailoverさせるかを選択します。ここで注意しないといけないのは、Failover先は対象のCosmosDBの読み取りリージョンとして追加されたものでないといけません。そして、ポータルの言語が日本語のときの問題はここで発生しました。例えばここで「英国西部」を選択していますが、すると「英国西部」という値がアクションに設定されてしまい、実験の実行時に「英国西部」なんていうリージョンはないとエラーになります。正しくは「UK West」ですが、これを設定するためにはポータルを英語表示にする必要がありました*1

f:id:tanaka733:20220118192036p:plain

どのCosmosDBを対象にするかを選びます。

f:id:tanaka733:20220118190102p:plain

これで実験は作成できました。この後、セキュリティーのため、対象となるCosmosDBにこの実験を操作してもよいという許可設定をする必要があります。カオス実験はマネージドIDを利用しているので、CosmosDBのIAMの設定でロールを追加します。「Cosmos DB 演算子」というロールを選びます。Cosmos DB Operatorの訳のようです。

f:id:tanaka733:20220118190416p:plain

マネージドIDを選びます。カオス実験につけた名前でIDが作られています。

f:id:tanaka733:20220118190507p:plain

実験の実行はChaos Studioのページから行います。実行履歴も確認できます。

f:id:tanaka733:20220118233010p:plain

実行すると、実際にフェールオーバーが起きたことを確認できます。

f:id:tanaka733:20220118193006p:plain

10分経過後、フェールバックして元のリージョンに戻ったところまで確認できたのですが、なぜか実験はFailedになっており、詳細メッセージもありません。

f:id:tanaka733:20220118233149p:plain

まだプレビューということもあり、動作が不安定なのでフィードバックしています。次回はエージェント方式の障害を試してみようと思います。

*1:実際に設定されている内容はJSONビューで確認できます。

OpenTelemetry .NETを理解する (1) OpenTelemetryとは

f:id:tanaka733:20220106001746p:plain OpenTelemetry .NETを使って.NETアプリケーションを計測する方法について複数回にわたって紹介してみたいと思います。まず .NET向けの話に行く前に、OpenTelemetryそのものについてかんたんにまとめてみます。

OpenTelemetryの誕生

アプリケーションのパフォーマンスを計測し監視するためには、それまであったプロセスやネットワークの死活監視では不十分でした。Webアプリケーションの例では、1つのHTTPリクエストに対しどのくらいのレスポンスで返しているのか、そこでのボトルネックはコードのどの部分に対応するのか、あるアプリケーションは時間あたりどの程度のリクエストを処理しているのか、といったデータを計測する必要があります。そのようなデータを計測し、監視するためのツールとして、New Relicなどのベンダーが提供するツールが出てきました。New Relicの例で言えば、New Relicの公開が2008年、.NET に対応したのが2012年になっています。

ベンダーが提供するツールは、どんなデータをどのような形式でどのように計測するかといった仕様が多くの場合公開されていません。そのため、たとえ同じデータ、例えば1つのHTTPリクエストに対する平均レスポンス時間、を計測する場合でも、あるツールから別のツールに入れ替えると計測の方法そのものをセットアップしなおす必要がありました。

GitHub上をはじめとしたオープンソースとして公開されるライブラリが増えていく中、ベンダー依存が強いツールは使う場所を選ぶようになってきました。特にライブラリ開発者にとっては、計測ツールへの対応が求められることがあっても、特定の計測ツールに依存したコードを含めてしまうのは避けたいものです。そのような事情もあり、計測ツールのオープンな標準プロジェクトが求められるようになりました。

当初(2018年ごろより)OpenCensusとOpenTracingというオープンなプロジェクトがありましたが、2019年に両者が発展的に合流したのがOpenTelemetryプロジェクトです。

opentelemetry.io

OpenTelemetryとは何なのか

OpenTelemetryのDocsの最初に記載があります。

opentelemetry.io

アプリケーションの状態を知るためのデータをテレメトリーデータ(アプリケーションから離れた場所から収集できるデータ)とし、テレメトリーデータの形式、収集方法、バックエンドへの送信方法をベンダーに依存せず標準化するためのものです。

f:id:tanaka733:20220106001214p:plain

テレメトリーデータは現在のところ、メトリクス、トレース、ログの3種類は想定されていますが、3種類に限定されたわけではありません。今後追加される可能性もあります。

opentelemetry.io

メトリクス、ログ、トレースとはなんだ?という定義については、実際にOpenTelemetryを使いながら説明することにします。今すぐ知りたいという場合は、まずこのページをおすすめします。

www.oreilly.com

また、OpenTelemetryでは対象となるアプリケーションからどのように収集し、どのように送信するか、までを設計しており、送信したあとどのように保存し活用するかについては対象外となります。送信されたテレメトリーデータを保存しUIなどに表示して活用するツールやサービスを「バックエンド」と呼んでいます。OpenTelemetryではどのバックエンドに送信するかを「エクスポーター」と呼ばれるプラグ可能な機能として提供しています。JaegerやPrometheusといったオープンソースなツールからNew Relicのような商用サービスまでさまざまなバックエンドに送信することが可能です。また、これらのツールやサービス側もOpenTelemetry標準の形式でのエクスポーターによるデータ送信が可能になるよう対応を進めています。

OpenTelemetery で計測するために必要なもの

f:id:tanaka733:20220105234322p:plain

計測するためのツールを導入していない状況で新規に導入するケース(図の左下)では、まずアプリケーションのOpenTelemetryの定める形式と方法でテレメトリーデータを収集するための処理を入れる必要があります。多くの場合、ライブラリを参照しコードを追加する必要があるのですが、このことを「Instrumentation(計装=測を実する)」とよんでいます。この部分は、アプリケーションが実装された言語・ランタイムによって最適な方法が異なります。そのためOpenTelemetryプロジェクトでは対応する言語ごとに必要なツールやライブラリを実装しています。また、必要最小限のコードで計装できるようになる自動計装(automatic instrumentation)と、細かい挙動まで実装者が制御できる手動計装(manual instrumentation)が用意されています。いずれかの方法あるいは両方の方法で計装を行います。

opentelemetry.io

計装したことでテレメトリーデータを集められるようになったあとは、バックエンドにデータを送信する必要があり、これを「コレクター(Collector)」と呼んでいます。一番単純な方法としては、アプリケーションで計装したものを直接エクスポーターで送信する仕組みが考えられます。これは図の左下のCollector(Agent方式)が直接バックエンドに送信することを意味します。加えて、アプリケーションで計装したテレメトリーデータをフィルタリングしたり、加工したりすることもできます。アプリケーションプロセスが大量にあるので注目したいデータのみに特化する場合や、ホスト名といった環境情報を付加する場合などです。これは図の中央のCollector(Standalone)に相当し、その中でデータを受信する「レシーバー(receiver)」、加工する「プロセッサー(processor)」、送信するエクスポーターの3つのコンポーネントから構成されます。

opentelemetry.io

計装は言語ごとに用意されていますが、その形式は標準化されているため、コレクターはアプリケーションの言語を問わず利用できます。またJaegarなどの既存のツールを利用している場合、ツールが出力するテレメトリーデータをOpenTelemetry形式に解釈するアダプターとなるレシーバーを介することでOpenTelemetryのコレクターに取り込むことが可能になっています。

f:id:tanaka733:20220106001325p:plain

OpenTelemetryのステータスとOpenTelemetry .NET

OpenTelemetryプロジェクトのステータスはここで公開されています。

opentelemetry.io

メトリクスとトレースの仕様については安定(Stable)となっています。ロギングについてはドラフト状態ですが、メトリクスやトレースと比べてログは既存のログライブラリが多数存在し、いかにそれらとつなぎこむかの設計に時間がかかっているのではないかと想像しています。

また言語ごとに用意されている計装についてはこちらに一覧があります。

github.com

念の為最新の状況は言語ごとのリポジトリで確認しましょう。OpenTelemetry .NETはこちらです。

github.com

これを見ると、TraceとExporterに関しては .NETはかなり対応が進んでいます。Metricsについても対応が進みつつあります

次回以降、実際にOpenTelemetry .NETを使う方法を説明します。なお、バックエンドにはNew Relicを使いますが、その理由はOpenTelemetryネイティブのOpenTelemetry Protocol (OTLP)フォーマットでデータ送信できるという点と、私が一番詳しくしっているバックエンドだからです。