本文へスキップ
【.NET Aspire入門】第6回 本番運用のベストプラクティスのアイキャッチ画像
Architecture

【.NET Aspire入門】第6回 本番運用のベストプラクティス

公開: 更新: 約11分で読めます

はじめに

本シリーズはこれまで、Aspire によるクラウドネイティブ開発を、構成モデルの基礎から、データベースとキャッシュの統合、メッセージングによる疎結合な通信、可観測性、そしてデプロイとスケーリングまで順を追って扱ってきました。最終回となる第6回では、開発環境で動いていたアプリケーションを本番で安定して運用し続けるために欠かせない実践を整理します。

本番運用の難しさは、機能を実装することそのものよりも、構成の管理、権限の設計、障害時の振る舞い、コストの制御といった、平常時には目立たない部分に宿ります。.NET 10 で正式版となった Aspire は、これらの関心事を構成モデルとして一箇所に集約できる点に強みがあります。以下では、構成とシークレット、セキュリティ、回復性と可観測性、リソースとコスト、Integration の扱い、テストと CI/CD という順に、本番を見据えた勘所を C# のコード例とともに解説し、最後にシリーズ全体を振り返ります。

.NET Aspire の本番運用ベストプラクティスを示す図。中央の本番稼働アプリを囲むように、構成とシークレット、セキュリティ(マネージドID)、回復性、可観測性、コストとリソース、テスト、CI/CD の各柱がラベル付きで配置され、それぞれが中央のアプリに接続されている様子を表す
Aspire の本番運用は構成とシークレット・セキュリティ・回復性・可観測性・コスト・テスト・CI/CD を一貫した設計として組み立てることで安定します

構成とシークレットの管理

Aspire では、AppHost がアプリケーション全体の構成を集約する単一の起点になります。接続文字列やパラメータをここで宣言し、各サービスには参照として配線するのが基本方針です。環境ごとに異なる値を扱うとき、その差分を AppHost の外側に押し出せるため、サービスのコードは環境に依存しないまま保てます。

シークレットは、コードにもリポジトリにも直接置かないことが原則です。AddParameter に secret: true を付けて宣言した値は、開発時にはユーザーシークレット、本番では環境変数や azd のパラメータストアから解決されます。データベースのパスワードのような機微な値は、この仕組みを通して外部から注入します。

// AppHost/AppHost.cs
var builder = DistributedApplication.CreateBuilder(args);

// 開発時は user-secrets、本番は環境変数や azd のパラメータから解決される
var sqlPassword = builder.AddParameter("sql-password", secret: true);

var sql = builder.AddSqlServer("sql", password: sqlPassword)
    .AddDatabase("appdb");

// 本番のシークレットは Key Vault に集約し、参照として配線する
var secrets = builder.AddAzureKeyVault("secrets");

var api = builder.AddProject<Projects.Api>("api")
    .WithReference(sql)
    .WithReference(secrets);

builder.Build().Run();

ここで重要なのは、サービス側が接続先の具体的な値を知らずに済むという点です。WithReference によって Aspire が接続文字列やエンドポイントを環境変数として注入するため、各サービスは名前で参照するだけで対象へつながります。開発と本番で構成の解決先が変わっても、サービスのコードは同一のまま動き続けます。

セキュリティ ― マネージドIDと最小権限

本番のセキュリティで最初に徹底したいのは、資格情報をアプリケーションが保持しないことです。接続文字列にパスワードを埋め込む代わりに、Azure のマネージドIDを用いれば、認証情報そのものをアプリの外へ追い出せます。DefaultAzureCredential は、ローカルでは開発者の Azure CLI ログインに、本番ではアプリに割り当てたマネージドIDに、それぞれ自動でフォールバックします。同じコードで環境をまたげるため、開発と本番の分岐が不要になります。

// Api/Program.cs
using Azure.Identity;

var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

// 資格情報はコードに持たず、マネージドID を通して取得する
var credential = new DefaultAzureCredential();

// Aspire が注入した Key Vault のエンドポイントを構成に取り込む
var vaultUri = new Uri(builder.Configuration["ConnectionStrings:secrets"]!);
builder.Configuration.AddAzureKeyVault(vaultUri, credential);

var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();

マネージドIDに与える権限は、最小権限の原則に沿って絞り込みます。アプリが必要とするのが Key Vault のシークレット読み取りだけであれば、割り当てるロールも Key Vault Secrets User にとどめ、書き込みや管理の権限は付与しません。ストレージやデータベースへのアクセスも同様に、操作に必要な範囲だけをロールとして割り当てます。権限を広く取ってしまうと、資格情報が漏れなくても、侵害されたコンポーネントを起点に被害が広がりやすくなります。

あわせて、全エンドポイントでの HTTPS の強制、認証と認可の一貫した適用、そして機微な情報をログへ出力しない運用を、標準の構成として組み込んでおきます。これらは個別のサービスごとに判断するのではなく、後述する ServiceDefaults のような共通基盤にまとめておくと、実装のばらつきを防げます。

回復性と可観測性を運用の前提にする

分散システムでは、ネットワークの一時的な失敗や依存先の遅延は例外ではなく前提です。回復性は、これらを織り込んだうえで全体が破綻しないように設計する考え方を指します。Aspire のプロジェクトテンプレートに含まれる ServiceDefaults は、この土台を各サービスへ一括で適用する仕組みです。HttpClient には標準の回復性ハンドラーが構成され、リトライ、タイムアウト、サーキットブレーカーが既定で有効になります。

// ServiceDefaults/Extensions.cs(抜粋)
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
    builder.ConfigureOpenTelemetry();
    builder.AddDefaultHealthChecks();

    // すべての HttpClient にリトライ・タイムアウト・サーキットブレーカーを適用
    builder.Services.ConfigureHttpClientDefaults(http =>
    {
        http.AddStandardResilienceHandler();
        http.AddServiceDiscovery();
    });

    return builder;
}

リトライは、あくまで一時的な失敗に対して有効な手段です。冪等でない操作へ無条件にリトライを重ねると、二重処理を招きます。書き込み系の呼び出しでは、リトライの可否をユースケースごとに見極め、必要なら冪等性キーを併用します。タイムアウトは、遅い依存先に呼び出し元が引きずられないための防波堤であり、既定値のまま放置せず、対象の応答特性に合わせて調整します。

ヘルスチェックは、オーケストレーターが不健全なインスタンスを切り離すための判断材料です。ServiceDefaults では、稼働そのものを示す /alive と、依存先を含めて要求を受けられる状態かを示す /health を分けて公開します。AppHost 側からは、各サービスのヘルスチェックのエンドポイントを明示して、起動順序や依存関係の解決に用います。

// AppHost/AppHost.cs
var api = builder.AddProject<Projects.Api>("api")
    .WithReference(sql)
    .WithReference(secrets)
    .WithHttpHealthCheck("/health");

可観測性については第4回で詳しく扱いましたが、本番運用の観点から改めて強調しておきます。回復性の仕組みが実際に効いているかは、トレースとメトリクスを通して初めて確認できます。リトライの発生頻度、サーキットブレーカーが開いた回数、依存先ごとのレイテンシを継続的に観測し、異常をアラートとして受け取れる状態にしておくことが、回復性を作り込むことと表裏一体の取り組みになります。

リソース割り当てとコスト管理

本番では、各サービスに割り当てる計算資源とレプリカ数が、性能と月額コストの両方を直接左右します。Aspire では、レプリカ数を AppHost の構成として宣言できます。負荷の高いサービスには複数のレプリカを持たせ、単一障害点を避けつつ処理を分散します。

// AppHost/AppHost.cs
var api = builder.AddProject<Projects.Api>("api")
    .WithReplicas(3)
    .WithHttpHealthCheck("/health");

// キャッシュのようなステートフルなリソースは単一に保つ
var cache = builder.AddRedis("cache");

コストの制御は、割り当てを絞ることだけを意味しません。過小な割り当てはレイテンシの悪化やスケールアウトの多発を招き、かえって非効率になります。要点は、実測にもとづいて適正値へ寄せていくことです。CPU とメモリの使用率、リクエストあたりの処理時間、スケーリングの発生状況を観察し、余剰があれば削り、逼迫していれば増やします。Azure Container Apps へ azd でデプロイする構成では、スケーリングルールやリソースの上限は生成されるインフラ定義側で調整するため、アプリのコードには手を入れずに運用パラメータを変えられます。

加えて、使われていないリソースを放置しないことがコスト管理の基本になります。検証用に立ち上げた環境、開発が終わった機能に紐づくリソース、想定より低い最小レプリカ数で足りるサービスなどを定期的に棚卸しし、日次コストが平常時の水準から大きく外れたときにアラートで気づける仕組みを用意しておきます。

Integration の選定と自作

Aspire の Integration は、外部リソースを構成モデルへ組み込むための部品です。大きく二種類あり、AppHost からリソースを追加するホスティング Integration(AddRedis や AddAzureKeyVault など)と、各サービス側でクライアントを登録するクライアント Integration(AddRedisClient など)に分かれます。まずは公式に提供されている Integration を優先して使うのが原則です。接続の配線、ヘルスチェック、計装、回復性の既定が織り込まれており、自前で用意する手間と抜け漏れを避けられます。

公式の Integration が存在しない外部サービスに対しては、自作します。ただし多くの場合、専用のリソース型を一から実装する必要はなく、共通の構成をまとめる拡張メソッドを用意するだけで十分です。次の例は、社内 API 用のクライアントを、接続名から解決したエンドポイントと標準の回復性ハンドラー付きで登録する拡張メソッドです。

// ServiceDefaults/WeatherClientExtensions.cs
public static class WeatherClientExtensions
{
    public static void AddWeatherClient(
        this IHostApplicationBuilder builder, string connectionName)
    {
        var endpoint = builder.Configuration.GetConnectionString(connectionName)
            ?? throw new InvalidOperationException(
                $"接続文字列 '{connectionName}' が構成されていません");

        builder.Services.AddHttpClient<WeatherClient>(client =>
            {
                client.BaseAddress = new Uri(endpoint);
            })
            .AddStandardResilienceHandler();
    }
}

このように既存の仕組みへ寄せて自作すると、公式 Integration と同じ流儀で扱えるコンポーネントになります。接続名で参照し、回復性と計装を標準で備えるという一貫性が保たれるため、利用側のコードに特別扱いが生まれません。

テスト・CI/CD と避けるべきアンチパターン

Aspire アプリケーションの統合テストには、Aspire.Hosting.Testing パッケージを使います。DistributedApplicationTestingBuilder は、AppHost で定義した構成をそのまま起動し、実際にリソースが立ち上がった状態でテストを実行できます。モックでは検証しづらい、配線や起動、ヘルスチェックの成否まで含めて確認できる点が利点です。

// IntegrationTests/ApiTests.cs
[Fact]
public async Task Api_ヘルスチェックが正常を返す()
{
    var appHost = await DistributedApplicationTestingBuilder
        .CreateAsync<Projects.AppHost>();

    await using var app = await appHost.BuildAsync();
    await app.StartAsync();

    // 対象リソースが健全になるまで待ってから検証する
    await app.ResourceNotifications
        .WaitForResourceHealthyAsync("api");

    using var client = app.CreateHttpClient("api");
    var response = await client.GetAsync("/health");

    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

CI/CD は、この統合テストをパイプラインへ組み込み、ビルドとテストを通過したものだけをデプロイする流れに整えます。Azure Container Apps を対象とする場合、azd と GitHub Actions を組み合わせ、OIDC によるフェデレーション資格情報で認証すると、長期のシークレットをパイプラインに保持せずにデプロイできます。マニフェストとインフラ定義がコードとして管理されるため、環境の再現性が保たれ、変更の履歴も追えます。

最後に、本番運用で避けたいアンチパターンを整理します。

  • シークレットのハードコード — 接続文字列やキーをコードや設定ファイルに直接書き込む。パラメータとマネージドIDへ移す。
  • 過大な権限の付与 — 「とりあえず動かす」ために広いロールを割り当てたまま放置する。最小権限に絞る。
  • 無防備な外部呼び出し — タイムアウトも回復性も設定せずに依存先を呼び、一箇所の遅延が全体へ波及する。標準の回復性ハンドラーを通す。
  • 冪等でない処理へのリトライ — 書き込み系に無条件でリトライを重ね、二重処理を招く。冪等性を確認してから適用する。
  • 可観測性の後回し — 障害が起きてから計装を足す。開発の初期からトレースとメトリクスを前提にする。
  • リソースの放置 — 検証環境や不要なレプリカを止めずにコストを膨らませる。定期的に棚卸しする。

まとめ

本シリーズでは、Aspire を用いたクラウドネイティブ開発を、構成モデルの基礎から本番運用まで六回にわたって辿ってきました。第1回で構成の考え方をつかみ、第2回と第3回でデータとメッセージングの統合を、第4回で可観測性を、第5回でデプロイとスケーリングを扱い、この最終回で本番運用のベストプラクティスを整理しました。通して見えてくるのは、Aspire が接続や計装、回復性、デプロイといった横断的な関心事を構成モデルへ集約し、アプリケーションのコードを本質的な価値の実装に集中させるという一貫した姿勢です。

本番運用で成果を左右するのは、シークレットを外へ追い出すこと、権限を最小に絞ること、依存先の失敗を織り込むこと、状態を観測し続けること、そして資源とコストを実測にもとづいて調整することです。いずれも派手さはありませんが、これらを標準の構成として最初から組み込んでおくことが、長く安定して動くシステムの条件になります。

エンハンスド株式会社では、Aspire を用いたクラウドネイティブ開発と、本番運用を見据えたアーキテクチャ設計を、構成やセキュリティの検討から可観測性の整備、Azure へのデプロイと CI/CD の構築まで一貫して支援しています。既存システムのモダナイゼーションや、本番運用に耐える基盤づくりを具体化したい方は、お気軽にお問い合わせください。

本連載「.NET Aspire 入門」全6回

  1. 第1回 クラウドネイティブ開発の全体像とセットアップ
  2. 第2回 データベースとキャッシュの統合
  3. 第3回 メッセージングとイベント駆動アーキテクチャ
  4. 第4回 可観測性とモニタリング
  5. 第5回 デプロイメントとスケーリング
  6. 第6回 本番運用のベストプラクティス(本記事)

実務目線の総論は .NET Aspire で実現するクラウドネイティブ開発の実践ガイド もあわせてご覧ください。

この記事をシェア

コピーしました

関連記事