サービス検出
サービス検出は、サービス同士が互いを見つけるための仕組みです。http://localhost:5001 のような URL をハードコーディングする代わりに、フロントエンドは catalog サービスを名前で参照するだけで、実際のアドレス解決は Aspire が担当します。
サービス検出の仕組み
Section titled “サービス検出の仕組み”WithReference() を使ってサービスを接続すると、Aspire は自動的にサービス検出を構成します。
- AppHost がリソースを宣言 してエンドポイントを割り当てる
- 構成が注入される 各サービスに環境変数経由で構成が渡される
- コードは論理名を使用
https+http://catalogのような論理名を使う - Aspire のリゾルバーが解決 実行時に論理名を実アドレスへ変換する
重要なポイント: コードでは catalog のような論理サービス名を使い、Aspire の構成ベースのエンドポイント リゾルバーが実行時に実アドレスへ変換します。
参照による暗黙的なサービス検出
Section titled “参照による暗黙的なサービス検出”サービス検出用の構成は、あるプロジェクトから参照されているサービスに対してのみ追加されます。たとえば、次の AppHost プログラムを見てみましょう。
var builder = DistributedApplication.CreateBuilder(args);
var catalog = builder.AddProject<Projects.CatalogService>("catalog");var basket = builder.AddProject<Projects.BasketService>("basket");
var frontend = builder.AddProject<Projects.MyFrontend>("frontend") .WithReference(basket) .WithReference(catalog);import { function createBuilder(): IDistributedApplicationBuilder
Creates a new distributed application builder
createBuilder } from './.aspire/modules/aspire.mjs';
const const builder: IDistributedApplicationBuilder
builder = await function createBuilder(): IDistributedApplicationBuilder
Creates a new distributed application builder
createBuilder();
const const catalog: ProjectResource
catalog = await const builder: IDistributedApplicationBuilder
builder.IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: { launchProfileOrOptions?: ProjectResourceOptions;}): ProjectResource (+1 overload)
Adds a .NET project resource
addProject("catalog", "./CatalogService/CatalogService.csproj");const const basket: ProjectResource
basket = await const builder: IDistributedApplicationBuilder
builder.IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: { launchProfileOrOptions?: ProjectResourceOptions;}): ProjectResource (+1 overload)
Adds a .NET project resource
addProject("basket", "./BasketService/BasketService.csproj");
const const frontend: ProjectResource
frontend = await const builder: IDistributedApplicationBuilder
builder.IDistributedApplicationBuilder.addProject(name: string, projectPath: string, options?: { launchProfileOrOptions?: ProjectResourceOptions;}): ProjectResource (+1 overload)
Adds a .NET project resource
addProject("frontend", "./MyFrontend/MyFrontend.csproj") .ProjectResource.withReference(source: EndpointReference | string | uri, options?: { connectionName?: string; optional?: boolean; name?: string;} | undefined): ProjectResource (+1 overload)
Adds a reference to another resource
withReference(const basket: ProjectResource
basket) .ProjectResource.withReference(source: EndpointReference | string | uri, options?: { connectionName?: string; optional?: boolean; name?: string;} | undefined): ProjectResource (+1 overload)
Adds a reference to another resource
withReference(const catalog: ProjectResource
catalog);
await const builder: IDistributedApplicationBuilder
builder.IDistributedApplicationBuilder.build(): DistributedApplication
Builds the distributed application
build().DistributedApplication.run(cancellationToken?: cancellationToken): void
Runs the distributed application
run();この例では、frontend プロジェクトが catalog プロジェクトと basket プロジェクトを参照しています。2 つの WithReference 呼び出しは、Aspire に次のことを指示します。
- 構成を注入する:
catalogとbasketのサービス検出情報をfrontendに渡す - 解決を有効にする:
frontendでhttps+http://catalogのような URI をHttpClientから利用できるようにする
名前付きエンドポイント
Section titled “名前付きエンドポイント”一部のサービスは、複数の「名前付きエンドポイント」を公開します。名前付きエンドポイントは、HTTP リクエスト URI のホスト部分にエンドポイント名を指定することで解決できます。形式は scheme://_endpointName.serviceName です。たとえば、「basket」という名前のサービスが「dashboard」というエンドポイントを公開している場合、https+http://_dashboard.basket という URI でそのエンドポイントを指定できます:
builder.Services.AddHttpClient<BasketServiceClient>( static client => client.BaseAddress = new("https+http://basket"));
builder.Services.AddHttpClient<BasketServiceDashboardClient>( static client => client.BaseAddress = new("https+http://_dashboard.basket"));package main
import ( "net/http" "os")
func main() { // Aspire は環境変数としてエンドポイント URL を注入します。 // 変数名を組み立てるにはサービス名を使用します。 basketURL := os.Getenv("services__basket__https__0") dashboardURL := os.Getenv("services__basket__dashboard__0") _ = dashboardURL
// 解決された URL を直接使用します resp, err := http.Get(basketURL + "/api/items") if err != nil { panic(err) } defer resp.Body.Close() // ...}import asyncioimport osimport httpx
async def main(): # Aspire は環境変数としてエンドポイント URL を注入します。 # 変数名を組み立てるにはサービス名を使用します。 basket_url = os.getenv("services__basket__https__0") dashboard_url = os.getenv("services__basket__dashboard__0")
async with httpx.AsyncClient() as client: response = await client.get(f"{basket_url}/api/items")
asyncio.run(main())// Aspire は環境変数としてエンドポイント URL を注入します。// 変数名を組み立てるにはサービス名を使用します。const basketUrl = process.env.services__basket__https__0;const dashboardUrl = process.env.services__basket__dashboard__0;
const response = await fetch(`${basketUrl}/api/items`);この例では、basket サービス用と、basket サービスのダッシュボード用の 2 つの HttpClient クラスが追加されています。
構成を使用した名前付きエンドポイント
Section titled “構成を使用した名前付きエンドポイント”構成ベースのエンドポイント リゾルバーを使用すると、エンドポイント名の前に _endpointName を付けることで、構成内に名前付きエンドポイントを指定できます。ここで endpointName はエンドポイント名です。たとえば、次の appsettings.json では、名前なしの既定エンドポイントと、「dashboard」という名前のエンドポイントが定義されています:
{ "Services": { "basket": { "https": "https://10.2.3.4:8080" /* https://basket で要求される HTTPS エンドポイント */, "dashboard": "https://10.2.3.4:9999" /* https://_dashboard.basket で要求される "dashboard" エンドポイント */ } }}この JSON では、次のように解決されます:
- 既定エンドポイントとして
https://basketを解決すると10.2.3.4:8080になります。 https://_dashboard.basketで解決される「dashboard」エンドポイントは10.2.3.4:9999になります。
Aspire における名前付きエンドポイント
Section titled “Aspire における名前付きエンドポイント”名前付きエンドポイントは、AppHost のコードから公開することもできます。たとえば、先ほどの例は次のように表現できます。
var basket = builder.AddProject<Projects.BasketService>("basket") .WithHttpsEndpoint(port: 9999, name: "dashboard");const basket = await builder.addProject("basket", "./BasketService/BasketService.csproj") .withHttpsEndpoint({ port: 9999, name: "dashboard" });DNS SRV を使用した Kubernetes 上の名前付きエンドポイント
Section titled “DNS SRV を使用した Kubernetes 上の名前付きエンドポイント”Kubernetes にデプロイする場合、DNS SRV サービス エンドポイント リゾルバーを使用して名前付きエンドポイントを解決できます。たとえば、次のリソース定義では、「basket」というサービスに対して、「default」と「dashboard」という 2 つのエンドポイント用の DNS SRV レコードが作成されます。
apiVersion: v1kind: Servicemetadata: name: basketspec: selector: name: basket-service clusterIP: None ports: - name: default port: 8080 - name: dashboard port: 9999「basket」サービスの「dashboard」エンドポイントを解決するように構成するには、次のように DNS SRV サービス エンドポイント リゾルバーをホスト ビルダーに追加します。
builder.Services.AddServiceDiscoveryCore();builder.Services.AddDnsSrvServiceEndpointProvider();// Go では、DNS SRV ルックアップを使用してサービス エンドポイントを解決しますimport "net"
_, addrs, err := net.LookupSRV("default", "tcp", "basket")# Python では、SRV レコードのルックアップに dnspython を使用しますimport dns.resolver
answers = dns.resolver.resolve("_default._tcp.basket", "SRV")// Node.js では、SRV ルックアップに dns モジュールを使用しますimport { resolveSrv } from 'node:dns/promises';
const records = await resolveSrv('_default._tcp.basket');詳しくは、AddServiceDiscoveryCore および AddDnsSrvServiceEndpointProvider をご参照ください。
特別なポート名である「default」は、https://basket という URI で解決される既定のエンドポイントを指定するために使われます。
先ほどと同様に、バスケット サービス用の HttpClient にサービス ディスカバリーを追加するには、次のようにします:
builder.Services.AddHttpClient<BasketServiceClient>( static client => client.BaseAddress = new("https://basket"));basketURL := os.Getenv("services__basket__https__0")resp, err := http.Get(basketURL + "/api/items")basket_url = os.getenv("services__basket__https__0")response = await client.get(f"{basket_url}/api/items")const basketUrl = process.env.services__basket__https__0;const response = await fetch(`${basketUrl}/api/items`);同様に、「dashboard」エンドポイントは次のように指定できます。
builder.Services.AddHttpClient<BasketServiceDashboardClient>( static client => client.BaseAddress = new("https://_dashboard.basket"));dashboardURL := os.Getenv("services__basket__dashboard__0")resp, err := http.Get(dashboardURL + "/api/stats")dashboard_url = os.getenv("services__basket__dashboard__0")response = await client.get(f"{dashboard_url}/api/stats")const dashboardUrl = process.env.services__basket__dashboard__0;const response = await fetch(`${dashboardUrl}/api/stats`);トラブルシューティング
Section titled “トラブルシューティング”サービスが見つからない / “No endpoints resolved”
Section titled “サービスが見つからない / “No endpoints resolved””症状: HttpClient が “No endpoints resolved for service ‘myservice’” のような例外を投げる、または接続拒否が発生する。
よくある原因:
WithReference()の不足: 呼び出し元サービスが AppHost で対象サービスに接続されていない- サービス名の不一致: URI 内の名前が
AddProject()またはAddContainer()の名前と一致していない - サービス未起動: 対象サービスが起動に失敗している、または起動中である
対処方法: AppHost でサービス同士が接続されていることを確認します。
var api = builder.AddProject<Projects.Api>("api");var frontend = builder.AddProject<Projects.Frontend>("frontend") .WithReference(api); // ← これが必要ですconst api = await builder.addProject("api", "./Api/Api.csproj");const frontend = await builder.addProject("frontend", "./Frontend/Frontend.csproj") .withReference(api); // ← これは必須です!名前付きエンドポイントを解決できない
Section titled “名前付きエンドポイントを解決できない”症状: https://_endpointName.serviceName を使うとエラーになる。
対処方法: AppHost でそのエンドポイントに名前が設定されていることを確認します。
var api = builder.AddProject<Projects.Api>("api") .WithHttpEndpoint(port: 9999, name: "dashboard"); // ← 名前を一致させるconst api = await builder.addProject("api", "./Api/Api.csproj") .withHttpEndpoint({ port: 9999, name: "dashboard" }); // ← 名前は一致させる必要があります次に、アンダースコア付きプレフィックスで正確に指定します。
client.BaseAddress = new("https://_dashboard.api");dashboardURL := os.Getenv("services__api__dashboard__0")dashboard_url = os.getenv("services__api__dashboard__0")const dashboardUrl = process.env.services__api__dashboard__0;URI 形式を理解する
Section titled “URI 形式を理解する”https+http:// プレフィックスは「HTTPS を優先し、失敗時は HTTP にフォールバックする」ことを意味します。
| URI 形式 | 意味 |
|---|---|
https://catalog | 既定エンドポイントへ HTTPS のみで接続 |
http://catalog | 既定エンドポイントへ HTTP のみで接続 |
https+http://catalog | HTTPS を優先し、HTTP にフォールバック |
https://_admin.catalog | 名前付き “admin” エンドポイントへ HTTPS で接続 |