C# Tokyoでの企画でOpenTelemetryの初心者向けハンズオンを行いました。
OpenTelemetry .NETについての発表資料はこちらです。
www.slideshare.net
ハンズオン課題はこちらのリポジトリのhandson1フォルダに配置しています。
当日参加者の方にはすぐに使えるNew Relicのアカウントを提供しましたが、もし試す場合はこちらの記事を参考にNew Relicの無償アカウントをセットアップするか、別のバックエンドに送信できます。
C# Tokyoでの企画でOpenTelemetryの初心者向けハンズオンを行いました。
OpenTelemetry .NETについての発表資料はこちらです。
www.slideshare.net
ハンズオン課題はこちらのリポジトリのhandson1フォルダに配置しています。
当日参加者の方にはすぐに使えるNew Relicのアカウントを提供しましたが、もし試す場合はこちらの記事を参考にNew Relicの無償アカウントをセットアップするか、別のバックエンドに送信できます。
今回からは実際にOpenTelemetry .NETを使って計測してみることにします。まずは、ASP.NET Core (.NET 6)を題材にします。
OpenTelemetryのドキュメントと重なる部分も多いのであわせて参照してみてください。
まずは、計測対象の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>
またこれらのパッケージは、OpenTelemetryとOpenTelemetry.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の標準的な機能で計測することができます。ただ、これだけだと計測したデータをどこにも送信していません。まずはコンソールに出力して動作確認することにします。
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
で設定したアプリの名前やタグも確認できます。
ほとんどのケースではコンソールではなく、別のどこかに送信し、最終的には記録・利用のためのオブザーバビリティバックエンドに送信することになります。前回の記事の「OpenTelemetery で計測するために必要なもの」の図ではコレクターを経由して送信しています。実用的にはコレクターを経由するケースが多いと思うのですが、ひとまず検証するという意味ではアプリケーションのOpenTelemetryコードから直接オブザーバビリティバックエンドに送信することができます。その場合、バックエンドがOpenTelemetry .NET向けにExporterライブラリを提供していれば利用できます。特に、OTLP形式のエンドポイントをサポートしているバックエンドであればOpenTelemetry .NETが提供しているOTLPExporterが利用できます。そして、New RelicはOTLP形式のエンドポイントをサポートしているので試しに利用できます。
New Relic側の準備については公式ブログを参照してください。具体的にはライセンスキーとエンドポイントをメモしておいてください。
それは実装してみます。まずは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の場合、送信するだけでアプリケーションごとに以下のようなダッシュボードに表示されます。
また、先程コンソールに出力したようなトレースの詳細は分散トレース(Distributed Tracing)のUIで確認できます。
このようにOpenTelemetry .NETはよく使われるフレームワークであれば、まず使い始めるのは非常にかんたんになっています。
先日、カオスエンジニアリングをマネージドサービスとして提供するAzure Chaos Studioが発表され、プレビューとして利用できるようになっています。
せっかくなので試してみようと思い、いろいろやった結果をまとめておきます。
ドキュメントでいうとここからのセクションに説明されています。
一番大きな単位が「カオス実験」で、どんな障害をどんなリソースに実行するかが記述されています。 どんな障害を「ロジック」として定義し、どんなリソースにを「セレクター」として定義します。この2つがカオス実験を構成する単位です。 ロジックは1つずつ逐次実行する「ステップ」から構成され、ステップは同時に実行される「分岐(Branch)」から構成されます。 分岐は対象となるリソースをさらに定義するセレクターと、なにを実行するかの「アクション」で構成されます。 アクションは、障害もしくは待機の2種類があります。 これらの組み合わせで1つの実験を定義し、実行することになります。
現在利用できる障害は以下の3種類にわけられるようです。
まず、サービス直接ターゲットを使ったものを試してみました。
試したアクションはCosmosDBをFailoverさせるものです。基本的にはこのドキュメントに沿って設定すればOKです。が、いくつかはまったところがあるのと、実は最終的に実験の結果がFailedになってしまい未解決です。
まずはCosmosDBを用意します。Failoverさせることから、読み取りリージョンを1つ以上追加しておかないとあとで設定できなくなります。
空だとさみしいので、コンテナとアイテムを用意しておきました。実験を実行するだけの場合、なくてもかまいません。
まず、Azure Chaos Studioのページから対象となるリソースを選択します。そして、大事なことですが、ポータルの言語を英語にしておきます。Previewのせいなのか、日本語だとロジックがうまく実行できませんでした。最初日本語で作成していたので画面キャプチャは日本語になっています。
実験を作成します。このときのリソースグループとリージョンは対象と一致していなくてもよいはずですが、今回は同じものを選んでいます。
実験を作成したら、ロジックを作成します。ステップとブランチ1つの中に、障害アクションを1つ作成します。
翻訳の表記ゆれをしていますが、フォールトの選択で障害の種類を選びます。CosmosDB Failverを選びます。
どのリージョンにFailoverさせるかを選択します。ここで注意しないといけないのは、Failover先は対象のCosmosDBの読み取りリージョンとして追加されたものでないといけません。そして、ポータルの言語が日本語のときの問題はここで発生しました。例えばここで「英国西部」を選択していますが、すると「英国西部」という値がアクションに設定されてしまい、実験の実行時に「英国西部」なんていうリージョンはないとエラーになります。正しくは「UK West」ですが、これを設定するためにはポータルを英語表示にする必要がありました*1。
どのCosmosDBを対象にするかを選びます。
これで実験は作成できました。この後、セキュリティーのため、対象となるCosmosDBにこの実験を操作してもよいという許可設定をする必要があります。カオス実験はマネージドIDを利用しているので、CosmosDBのIAMの設定でロールを追加します。「Cosmos DB 演算子」というロールを選びます。Cosmos DB Operatorの訳のようです。
マネージドIDを選びます。カオス実験につけた名前でIDが作られています。
実験の実行はChaos Studioのページから行います。実行履歴も確認できます。
実行すると、実際にフェールオーバーが起きたことを確認できます。
10分経過後、フェールバックして元のリージョンに戻ったところまで確認できたのですが、なぜか実験はFailedになっており、詳細メッセージもありません。
まだプレビューということもあり、動作が不安定なのでフィードバックしています。次回はエージェント方式の障害を試してみようと思います。
*1:実際に設定されている内容はJSONビューで確認できます。