この記事は .NET Aspire に関する一連の記事の一部です。
- .NET Aspire って何? - 概要
- .NET Aspire を使ってみる
- .NET Aspire を デプロイする
- .NET Aspire で Prometheus, Jaeger, Grafana を使う
- Next.js + ASP.NET Core を .NET Aspire で構成する(with YARP)
- .NET Aspire でデータベースを扱う - PostgreSQL編
- .NET Aspire でデータベースを扱う - SQL Server編
- .NET Aspire のダッシュボードを単独で使う
.NET Aspire + Dapr についてはこちらをご覧ください。メインは Dapr についてですが、.NET Aspire を使用する場合についても記載があります。
.NET Aspire のダッシュボード
.NET Aspire は AppHost プロジェクトを起動するとダッシュボードという Web アプリケーションが立ち上がります。
ダッシュボードでは.NET Aspire 管理下にした.NETアプリケーションやコンテナ、実行ファイルなどのログ、環境変数などの詳細情報や、メトリクス、分散トレースなどのテレメトリデータを簡単に見ることができるため、非常に便利です。
.NET Aspire リリース直後からこのダッシュボードを単独で利用できないか、という issue が上がっていましたが、.NET Aspire Preview 4のリリース時にこの.NET Aspire ダッシュボードを単独で使用できるイメージの公開がアナウンスされました。そして .NET Aspire の GA とともに .NET Aspire ダッシュボードも GA しました。
.NET Aspire ダッシュボードのためのポータルサイトもオープンして、 Discord に .NET Aspire ダッシュボードのチャンネルも作成されました。
https://www.aspiredashboard.com/
何か質問あったら気軽に Discord で質問できるのは良いですね。
それでは.NET Aspire ダッシュボードを単独で使ってみましょう。
.NET Aspire ダッシュボードを動かす
.NET Aspire ダッシュボードは Docker イメージとして公開されています。次のコマンドでイメージを Pull して、実際に起動することができます。
docker run --rm -it -p 18888:18888 -p 4317:18889 -d --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:latest
18888 と 4317 の2つのポート番号を公開しています。18888 はダッシュボードの Web 画面で 4317 は OpenTelemetery のデータを受け付けます。
http://localhost:18888 を開くと次のようなログイン画面が表示されま
入力するトークンはコンテナログに表示されています。Docker Desktop の画面から稼働している aspire-dashboard コンテナをクリックしてログを表示します。
t= の後ろのトークンをコピーして、ログイン画面に貼り付けてログインすると、ダッシュボードが表示されます。
オプションに --rm がついていますので、コンテナを停止するとコンテナは削除されます。
この後、このダッシュボードにテレメトリデータを送信しますので、コンテナはそのまま稼働させておいてください。
テレメトリデータをダッシュボードに送信する
ではダッシュボードにテレメトリデータを送信してみましょう。OpenTelemetry のデータを送信するセットアップをする場合、.NET Aspire の サービスの既定値(Service Defaults) プロジェクトを利用するとあっという間です。.NET Aspire Starter アプリケーションを使った方がセットアップ済みなので簡単なのですが、今回は WebApi プロジェクトを作成して Service Default プロジェクトを適用することにします。
開発環境
- Windows 11
- Visual Studio 2022 17.11.0 Preview 2
- .NET Aspire 8.0.1
WebApi プロジェクトを作成し、OpenTelemetry をセットアップする
Visual Studio の新しいプロジェクトの作成画面で、ASP.NET Core Web API テンプレートを選択します。右上の検索ボックスで API を選ぶと探しやすいです。
追加情報画面では、HTTPS 用の構成のチェックボックスを外しておきましょう。
プロジェクトが作成できたら、実行してみましょう。
ランダムに生成された天気予報データが json 形式で取得できます。
では、.NET Aspire の サービスの既定値(Service Defaults) プロジェクトを追加します。ソリューションファイルを右クリック→追加→新しいプロジェクト を選択します。
プロジェクトの種類に「.NET Aspire」を選択すると、.NET Aspire サービスの既定値 がすぐ見つかるはずです。これを選択してソリューションに追加します。
ソリューションに2つのプロジェクトが存在する状態になります。
WebApi プロジェクトから、今追加した ServiceDefaults プロジェクトをプロジェクト参照します。ServiceDefaults プロジェクトファイルを WebApi プロジェクトにドラッグ&ドロップしてください。すると、WebApi プロジェクトにプロジェクト参照が追加されます。ちゃんと参照が追加されたかを確認するためには、プロジェクトファイルを選択します。(選択すると自動的に開きます)
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DashboardDemo.ServiceDefaults\DashboardDemo.ServiceDefaults.csproj" />
</ItemGroup>
</Project>
ProjectReference 要素で サービスの既定値(ServiceDefaults) プロジェクトのパスを参照していることがわかります。
では、WebApi プロジェクトで サービスの既定値(ServiceDefaults) を利用するためのセットアップをします。WebApi プロジェクトの Program.cs を開き、次の2行を追加します。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
+ builder.AddServiceDefaults();
var app = builder.Build();
// Configure the HTTP request pipeline.
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
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
});
+ app.MapDefaultEndpoints();
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
最後にダッシュボードに対して OpenTelemetry のテレメトリデータを送信します。OpenTelemetryに関する設定はすべて環境変数で行うことができます。最低限、次の2つを設定すればOKです。
- OTEL_EXPORTER_OTLP_ENDPOINT
- OTEL_SERVICE_NAME
WebApi プロジェクトの appsettings.Deveopment.json を開いて、次のように環境変数を2つ追加します。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
- }
+ },
+ "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317",
+ "OTEL_SERVICE_NAME": "WebApi"
}
実行します。すると、立ち上げておいたダッシュボードに構造化ログ・メトリクス・トレースが表示されます。
Blazor Web プロジェクトを作成し、トレースを確認する
プロジェクトが1つしかないと、複数スパンについてトレース情報がしっかりと表示されるのかがわかりません。これを確認するために、 Web プロジェクトを作成し、 WebApi を呼び出すようにしてみましょう。
ソリューションファイルを右クリック→追加→新しいプロジェクト を選択します。 ダイアログの検索条件で「Web」を選択し、表示された一覧から Blazor Web App を選びます。
「Include sample page」にチェックを入っていることを確認して、作成します。
Web プロジェクトでも サービスの既定値(ServiceDefaults)プロジェクト について WebApi プロジェクトと同様のセットアップをします。サービスの既定値(ServiceDefaults) プロジェクトファイルを Web プロジェクトファイルにドラッグ&ドロップしてプロジェクト参照するようにセットアップしてから、Program.csファイルを開いて次のように2行追加します。
using DashboardDemo.Web.Components;
var builder = WebApplication.CreateBuilder(args);
+ builder.AddServiceDefaults();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
+ app.MapDefaultEndpoints();
app.Run();
appsettings.Development.json ファイルにも WebApi プロジェクトと全く同じ設定をします。ただし、サービス名は「Web」に変更しておきます。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
- }
+ },
+ "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317",
+ "OTEL_SERVICE_NAME": "Web"
}
これで OpenTelemetry の準備は終わりました。次に Web プロジェクトから WebApi を呼び出して天気予報データを取得するように実装します。 Web プロジェクトに WeatherApiClient.cs ファイルを追加して、次の実装に入れ替えてください。ただし namespcae は作成時のままにしておいてください。
namespace DashboardDemo.Web;
public class WeatherApiClient(HttpClient httpClient)
{
public async Task<WeatherForecast[]> GetWeatherAsync()
{
return await httpClient.GetFromJsonAsync<WeatherForecast[]>("/weatherforecast") ?? [];
}
}
public record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
今作成した WeatherApiClient を DI する設定を Program.cs に追加実装します。
using DashboardDemo.Web;
using DashboardDemo.Web.Components;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
+ builder.Services.AddHttpClient<WeatherApiClient>(client =>
+ {
+ client.BaseAddress = new Uri("http://localhost:5028");
+ });
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.MapDefaultEndpoints();
app.Run();
localhost の ポート番号に 5028 を指定していますが、これは WebApi のポート番号です。みなさんの環境では異なる数字のはずですので、適宜修正してください。 WebApi/Properties/launchSettings.json を開き、profiles が http の中にある applicationUrl を確認してください。
最後に Weather ページを修正して、WebApi にアクセスしてデータを取得するようにします。次の内容に丸ごと変更します。
@page "/weather"
@attribute [StreamRendering]
@inject WeatherApiClient WeatherApi
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await WeatherApi.GetWeatherAsync();
}
}
これで実装はおしまいです。今のままだと実行しても WebApi プロジェクトしか起動しませんので、WebApi プロジェクトが起動した後に Web プロジェクトが起動するようにスタートアップ設定を変更します。
ソリューションファイルを右クリック→スタートアップ プロジェクトの構成 を選択します。表示されたダイアログで、次のように設定します。
- マルチスタートアップ プロジェクトを選ぶ
- WebApi プロジェクトが1番上になるようにして、アクションに開始を選ぶ
- Web プロジェクトが上から2番目になるようにして、アクションに開始を選ぶ
実行してみましょう。WebApi と Web ページが立ち上がったはずです。Weather ページを開いてみましょう。
問題なく天気予報データが取得できています。では、ダッシュボードを見てみましょう。トレース画面を見てみると、Web と WebApi のトレースがあります。 詳細列の表示リンクをクリックします。
トレースが各スパンと共に表示されています。良い感じですね。
Azure Container Apps でダッシュボードを動かす
ダッシュボードを本番環境で運用することはできるでしょうか。先に結論を言うと、可能です。状況によっては便利かもしれません。まずはやってみましょう。
まず、.NET Aspire Starter アプリケーションを何も修正せずそのまま Azure Container Apps にデプロイします。すると、webfrontend と apiservice という2つのアプリケーションが稼働します。
この2つアプリケーションそれぞれについて、環境変数を次のように設定します。
- OTEL_EXPORTER_OTLP_ENDPOINT: http://dashboard:4317
- OTEL_SERVICE_NAME: webfrontend または apiservice
http://dashboard となっているのは、これからデプロイするダッシュボードのアプリ名を dashboard にするからです。
ここに、ダッシュボードをデプロイします。コンテナアプリの作成画面で、コンテナアプリ名を dashboard と入力して、コンテナーをクリックします。
次のように選択・入力してからイングレスをクリックします。
- イメージのソースを「Docker Hub またはその他のレジストリ」
- レジストリログインサーバーを mcr.microsoft.com
- イメージとタグを dotnet/aspire-dashboard:latest
次のように選択・入力してから確認・作成をクリックします。
- イングレスを「有効」
- イングレス トラフィックを「どこからでもトラフィックを受け入れます」にする
- クライアント証明書モードを「無視」
- ターゲットポートを 18888
- 追加のTCPポートでターゲットポートを 18889、公開されたポートを 4317
デプロイが成功したら、ダッシュボードを開きます。ログインを求められますので、dashboard アプリのリビジョンからコンソールログを開きます。
トークンをコピーして、ログインします。その後、Webfrontend を開いて Weather ページに何度かアクセスしてみましょう。
無事に.NET Aspire ダッシュボードをデプロイし、テレメトリをダッシュボードで見ることができるようになりました。
しかし、残念ながらこの方法ではダッシュボードに送信されたテレメトリデータを保存することができません。dashboard コンテナを再起動したら、それまで送信されたテレメトリデータは消えてしまいます。それを理解した上での運用であれば本番環境での運用も良いと思います。
まとめ
.NET Aspire のダッシュボードを単独で動かすことで、ZipKin や Prometheus、Grafana などのツール類をセットアップすることなく、ログ・メトリクス・トレースを画面で簡単に確認することができます。OpenTelemetryは Java, Python, Node.js にも対応していますので、.NET Aspire ダッシュボードは言語を問わず使用可能です。
メインの用途はローカル開発時の状況確認ですが、本番運用でもデータ保存を気にしないのであれば採用できる場合があると思います。今回 Azure Container Apps へのダッシュボードのデプロイ時にはイングレスをインターネット公開しましたが、セキュリティに十分注意するのであれば仮想ネットワーク内部のみからのアクセスのみに限定するのが良いでしょう。