内部ループ ネットワークの概要

Aspire で開発する利点の 1 つは、クラウドネイティブ アプリをローカルで開発、テスト、デバッグできることです。内部ループ ネットワークは、開発環境でアプリ同士を通信させる Aspire の重要な要素です。この記事では、プロキシ、エンドポイント、エンドポイント構成、コンテナー ネットワークを通じて、Aspire がさまざまなネットワーク シナリオをどう処理するかを学びます。

Aspire のネットワークをホテルのフロントにたとえると理解しやすくなります:

  • クライアント(API を呼び出すあなたのコード)は、常に既知で安定したアドレスにある フロント(プロキシ)と通信します
  • フロントは、リクエストを 実際の部屋(あなたのサービス)が現在いる場所へルーティングします
  • 複数の部屋(レプリカ)がある場合は、どこに接続するかをフロントが処理します

この設計は、次のような問題を解決します:

問題プロキシが役立つ理由
ポート競合2 つのサービスは同じ 5000 番ポートを使えませんが、プロキシならそれぞれに別ポートを割り当てられます
レプリカサービスの複数インスタンスには負荷分散が必要ですが、プロキシがこれを自動処理します
サービス再起動サービスが新しいポートで再起動しても、プロキシのアドレスは変わりません
flowchart LR
    Client["Your Code"]
    Proxy["Proxy :5000"]
    App1["Service :52847"]
    App2["Service :52848"]
    
    Client --> Proxy
    Proxy --> App1
    Proxy --> App2

内部ループにおけるネットワーク

Section titled “内部ループにおけるネットワーク”

内部ループとは、アプリをターゲット環境にデプロイする前に、ローカルで開発とテストを進めるプロセスです。Aspire は、内部ループでのネットワーク体験を簡素化して高めるために、次のようなツールと機能を提供します:

  • エンドポイント / エンドポイント構成: エンドポイントは、データベース、メッセージ キュー、API など、アプリが依存するサービスとの接続です。エンドポイントには、サービス名、ホスト ポート、スキーム、環境変数などの情報が含まれます。Aspire はリソース構成から自動でエンドポイントを作成でき、WithEndpointwithEndpoint の呼び出しで明示的に追加することもできます。
  • プロキシ: Aspire は、アプリに追加した各サービス バインディングごとにプロキシを自動起動し、そのプロキシが待ち受けるポートを割り当てます。プロキシは、アプリが待ち受けるポート(プロキシ ポートと異なる場合があります)にリクエストを転送します。これにより、ポート競合を避けつつ、アプリとサービスに一貫した予測可能な URL でアクセスできます。
  • コンテナー ネットワーク: Aspire はコンテナー リソース用の専用ネットワークを作成・管理し、ローカル開発中にコンテナー同士が相互に検出して通信できるようにします。

Aspire のサービス バインディングは 2 つの統合から成り立ちます。1 つは、アプリが必要とする外部リソース(たとえばデータベース、メッセージ キュー、API)を表す サービス、もう 1 つは、アプリとサービスの接続を確立し必要な情報を提供する バインディング です。

Aspire はリソース構成からサービス バインディングを自動作成でき、WithEndpoint を使って追加のバインディングを明示的に作成することもできます。

バインディングが暗黙・明示のどちらで作成された場合でも、Aspire は指定ポートで軽量なリバース プロキシを起動し、アプリからサービスへのリクエストのルーティングと負荷分散を処理します。プロキシは Aspire の実装詳細であり、設定や管理を意識する必要はありません。

エンドポイントの仕組みを視覚的に把握するために、Aspire スターター テンプレートの内部ループ ネットワーク図を見てみましょう:

Aspire Starter Application テンプレートの内部ループ ネットワーク図。

コンテナー ネットワークの管理方法

Section titled “コンテナー ネットワークの管理方法”

1 つ以上のコンテナー リソースを追加すると、Aspire はコンテナー間のサービス検出を有効にする専用のコンテナー ブリッジ ネットワークを作成します。このブリッジ ネットワークは、コンテナー同士を通信させる仮想ネットワークであり、DNS 名を使ったコンテナー間のサービス検出向けに DNS サーバーも提供します。

ネットワークのライフタイムはコンテナー リソースに依存します:

  • すべてのコンテナーがセッション ライフタイムの場合、ネットワークもセッション ベースになり、AppHost プロセスの終了時にクリーンアップされます。
  • いずれかのコンテナーが永続ライフタイムの場合、ネットワークは永続化され、AppHost プロセス終了後も稼働し続けます。Aspire は後続の実行でこのネットワークを再利用するため、AppHost が動いていない間でも永続コンテナー同士は通信を継続できます。

コンテナーのライフタイムの詳細は、永続コンテナーのライフタイム を参照してください。

コンテナー ネットワークの命名規則は次のとおりです:

  • セッション ネットワーク: aspire-session-network-<unique-id>-<app-host-name>
  • 永続ネットワーク: aspire-persistent-network-<project-hash>-<app-host-name>

各 AppHost インスタンスは独自のネットワーク リソースを取得します。違いはネットワークのライフタイムと名前だけで、サービス検出の仕組み自体は両者で同じです。

コンテナーはリソース名を使ってネットワークに登録されます。Aspire はこの名前をコンテナー間のサービス検出に使います。たとえば pgadmin コンテナーは、postgres という名前のデータベース リソースへ postgres:5432 で接続できます。

コンテナー ネットワーク エイリアス

Section titled “コンテナー ネットワーク エイリアス”

既定では、コンテナーは DNS エイリアスとして リソース名 を使ってコンテナー ネットワーク上からアクセスできます。たとえば AddContainer("mydb", ...) で追加したコンテナーは、他のコンテナーから mydb:5432 で到達できます。

サードパーティ ツールが特定のホスト名を期待する場合や、既存の Docker Compose 構成から移行する場合など、追加のエイリアスが必要になることがあります。カスタム DNS 名を追加するには WithContainerNetworkAliaswithContainerNetworkAlias を使います:

AppHost.cs
var redis = builder.AddRedis("cache")
.WithContainerNetworkAlias("redis-primary")
.WithContainerNetworkAlias("session-store");

これで他のコンテナーは、次のいずれの名前でも Redis に接続できます:

  • cache:6379(既定のリソース名)
  • redis-primary:6379(カスタム エイリアス)
  • session-store:6379(カスタム エイリアス)

コンテナーからホスト ベースのサービスに到達する必要がある場合、Aspire は コンテナー トンネル を使って信頼性の高い接続を提供します。詳細は コンテナー ネットワーク を参照してください。

一部のエンドポイント動作はリソース種別に依存します。そうした詳細は、この概要に重複して書くのではなく、各リソース種別のドキュメントに置くようにしてください。

.NET 固有のエンドポイント動作については、C# 起動プロファイル と プロジェクト リソース を参照してください。

サービス バインディングを定義するとき、ホスト ポートは 常に サービス手前のプロキシに割り当てられます。これにより、サービスが単一レプリカでも複数レプリカでも同様に動作できます。さらに、WithReferencewithReference API を使うすべてのリソース依存関係は、環境変数経由で渡されるプロキシ エンドポイントに依存します。

サービスが複数レプリカで動作する場合にも、同じプロキシ パターンが適用されます。ブラウザーは引き続き 1 つの安定したホスト ポートに接続し、プロキシが利用可能なレプリカへトラフィックを振り分けます:

前述のコードにより、次のネットワーク図になります:

特定のホスト ポートと 2 つのレプリカを持つ Aspire フロントエンド アプリのネットワーク図。

前述の図は次の内容を示しています:

  • アプリのエントリ ポイントである Web ブラウザー。
  • 5066 のホスト ポート。
  • Web ブラウザーとフロントエンド サービス レプリカの間にあり、ポート 5066 で待ち受けるフロントエンド プロキシ。
  • ランダム割り当てのポート 65001 で待ち受ける frontend_0 フロントエンド サービス レプリカ。
  • ランダム割り当てのポート 65002 で待ち受ける frontend_1 フロントエンド サービス レプリカ。

環境変数から待ち受けポートを読み取る JavaScript アプリでは、次のように安定したホスト ポートを公開できます:

AppHost.cs
builder.AddJavaScriptApp("frontend", "./frontend")
.WithHttpEndpoint(port: 5066, env: "PORT");

定義されるポートは 2 つあります:

  • 5066 のホスト ポート。
  • 基盤サービスがバインドされるランダムなプロキシ ポート。
特定のホスト ポートとランダム ポートを持つ Aspire フロントエンド アプリのネットワーク図。

前述の図は次の内容を示しています:

  • アプリのエントリ ポイントである Web ブラウザー。
  • 5066 のホスト ポート。
  • Web ブラウザーとフロントエンド サービスの間にあり、ポート 5066 で待ち受けるフロントエンド プロキシ。
  • ランダム ポート 65001 で待ち受けるフロントエンド サービス。

基盤サービスは引き続き独自のポートで待ち受け、Aspire は割り当てられたそのポートを PORT 環境変数経由でアプリに提供します。

ホスト ポートを省略すると、Aspire はホスト ポートとサービス ポートの両方にランダム ポートを生成します。これは、ポート競合を避けたいが、ホスト ポートやサービス ポートを気にしない場合に便利です。次のコードを見てみましょう:

AppHost.cs
builder.AddJavaScriptApp("frontend", "./frontend")
.WithHttpEndpoint(env: "PORT");

このシナリオでは、次の図のようにホスト ポートとサービス ポートの両方がランダムになります:

ランダムなホスト ポートとプロキシ ポートを持つ Aspire フロントエンド アプリのネットワーク図。

前述の図は次の内容を示しています:

  • アプリのエントリ ポイントである Web ブラウザー。
  • ランダムなホスト ポート 65000。
  • Web ブラウザーとフロントエンド サービスの間にあり、ポート 65000 で待ち受けるフロントエンド プロキシ。
  • ランダム ポート 65001 で待ち受けるフロントエンド サービス。

コンテナー リソースを追加すると、Aspire はコンテナーにランダム ポートを自動割り当てします。コンテナー ポートを指定するには、希望するポートでコンテナー リソースを構成します:

AppHost.cs
builder.AddContainer("frontend", "mcr.microsoft.com/dotnet/samples", "aspnetapp")
.WithHttpEndpoint(port: 8000, targetPort: 8080);

前述のコードは次のことを行います:

  • mcr.microsoft.com/dotnet/samples:aspnetapp イメージから frontend という名前のコンテナー リソースを作成します。
  • ホストをポート 8000 にバインドし、コンテナーのポート 8080 にマップして http エンドポイントを公開します。

次の図を参照してください:

Docker ホストを含む Aspire フロントエンド アプリのネットワーク図。

IResourceWithEndpoints インターフェイスを実装する任意のリソースは、WithEndpoint 拡張メソッドを使用できます。この拡張には複数のオーバーロードがあり、スキーム、コンテナー ポート、ホスト ポート、環境変数名、エンドポイントをプロキシ化するかどうかを指定できます。

さらに、エンドポイントを構成するデリゲートを指定できるオーバーロードもあります。これは、環境やその他の要因に応じてエンドポイントを構成したい場合に便利です。次のコードを見てみましょう:

AppHost.cs
builder.AddContainer("apiService", "nginx")
.WithEndpoint(
endpointName: "admin",
callback: static endpoint =>
{
endpoint.Port = 17003;
endpoint.UriScheme = "http";
endpoint.Transport = "http";
});

前述のコードは、エンドポイント構成用のコールバック デリゲートを提供しています。エンドポイント名は admin で、http スキームとトランスポート、およびホスト ポート 17003 を使うように構成されています。コンシューマーは http://_admin.apiservice のような URI で、このエンドポイントを名前指定で参照できます。_ センチネルは、admin セグメントが apiservice サービスに属するエンドポイント名であることを示します。詳細は サービス検出 を参照してください。

WithEndpoint 拡張メソッドを呼び出すとき、callback オーバーロードでは生の EndpointAnnotation が公開されるため、利用者はエンドポイントの多くの側面をカスタマイズできます。

AllocatedEndpoint プロパティでは、サービスのエンドポイントを取得または設定できます。IsExternal と IsProxied プロパティは、エンドポイントをどう管理して公開するかを決定します。IsExternal は公開アクセス可能にするかどうかを決め、IsProxied は DCP に管理させることで、内部ポート差異やレプリケーションを可能にします。

Name プロパティはサービスを識別し、Port と TargetPort プロパティは、それぞれ希望ポートと待ち受けポートを指定します。

ネットワーク通信では、Protocol プロパティが TCP と UDP をサポートしており、将来さらに増える可能性があります。また Transport プロパティはトランスポート プロトコル(HTTP、HTTP2、HTTP3)を示します。最後に、サービスが URI でアドレス指定可能な場合、UriScheme プロパティがサービス URI 構築用の URI スキームを提供します。

詳細は EndpointAnnotation のプロパティ を参照してください。

プロジェクト リソース固有のエンドポイント フィルタリングについては、プロジェクト リソース を参照してください。

サービス検出からエンドポイントを除外する

Section titled “サービス検出からエンドポイントを除外する”

既定では、別のリソースが WithReference(resource) で参照するとき、あるリソース上のすべてのエンドポイントが含まれます。管理ダッシュボードやヘルスチェック ポートのような補助エンドポイントを公開するリソースでは、コンシューマー サービスがそれらを自動検出すべきでない場合があります。既定の参照セットからエンドポイントを除外するには、EndpointAnnotation の ExcludeReferenceEndpoint プロパティを使います:

AppHost.cs
builder.AddContainer("myservice", "myimage")
.WithHttpEndpoint(name: "management", port: 8080)
.WithEndpoint("management", ep => ep.ExcludeReferenceEndpoint = true);

ExcludeReferenceEndpoint が true のとき、そのエンドポイントは通常の WithReference(resource) 呼び出しでは依存サービスに 注入されません。それでも、名前指定なら明示的に参照できます:

AppHost.cs
var myService = builder.AddContainer("myservice", "myimage")
.WithHttpEndpoint(name: "api", port: 5000)
.WithHttpEndpoint(name: "management", port: 8080)
.WithEndpoint("management", ep => ep.ExcludeReferenceEndpoint = true);
// コンシューマーは "api" エンドポイントのみを取得し、"management" は除外される
var api = builder.AddProject<Projects.Api>("api")
.WithReference(myService);
// 管理エンドポイントが実際に必要な場合だけ明示的にオプトインする
var adminApp = builder.AddProject<Projects.Admin>("admin")
.WithReference(myService.GetEndpoint("management"));

次の Aspire 組み込み統合では、補助エンドポイントにすでにこのパターンが適用されています:

リソース除外されるエンドポイント
Keycloak管理ダッシュボード (management)
Azure Cosmos DB Emulatorヘルスチェック エンドポイント (emulatorHealth)
Azure Event Hubs Emulatorヘルスチェック エンドポイント (emulatorHealth)
Azure Service Bus Emulatorヘルスチェック エンドポイント (emulatorHealth)

症状: Address already in use や Failed to bind to port のようなエラー メッセージ

よくある原因:

  • アプリの別インスタンスがまだ実行中
  • 以前の Aspire セッションが正常に終了しなかった
  • 別のアプリケーションが同じポートを使用している

解決策:

  1. ⌃+CControl + CControl + C で実行中の Aspire セッションを停止するか、ダッシュボードを閉じる
  2. そのポートを使っているプロセスを確認する: netstat -ano | findstr :5000(Windows)または lsof -i :5000(macOS/Linux)
  3. WithEndpoint から明示的なポート番号を外し、Aspire にランダム ポートを割り当てさせる

症状: コンテナー リソースへの接続時にタイムアウトまたは接続拒否が発生する

よくある原因:

  • コンテナーの起動がまだ完了していない
  • WaitForwaitFor 依存関係が不足している
  • コンテナー ネットワーク上のコンテナーに、ホスト側から接続しようとしている

解決策:

  1. 依存サービスより先にコンテナーの準備完了を保証するため、.WaitFor(container) を追加する
  2. コンテナー リソースに .WithHttpHealthCheck() または .WithHealthCheck() を追加する
  3. ホスト サービスが 公開ポート(コンテナー内部ポートではない)を使っていることを確認する:
// コンテナーはポート 5432 をホストに公開する
var db = builder.AddPostgres("db");
// ホスト サービスは公開ポート経由で接続する(WithReference が処理)
var api = builder.AddJavaScriptApp("api", "./api")
.WithHttpEndpoint(env: "PORT")
.WithReference(db); // ✅ 正しい: 公開ポートを使用

症状: サービス検出が「service not found」や DNS 解決エラーで失敗する

解決策:

  1. プロデューサーとコンシューマーの間で WithReference() が設定されていることを確認する
  2. URI 内のエンドポイント名が完全一致(大文字と小文字を区別)していることを確認する
  3. サービス検出のトラブルシューティング を確認する