mcpField
A
aspire-service-defaults

aspire-service-defaults

github.com/aarononthewebクラウド・DevOps1.2k485 回インストール今日更新
リポジトリ

aspire-service-defaults について

分散アプリケーションの各サービスで設定を統一するための .NET Aspire スキルです。OpenTelemetry、ヘルスチェック、サービスディスカバリー、HTTP の耐障害性設定を扱います。

主な機能

  1. 01共有 OpenTelemetry 設定
  2. 02レディネス・ライブネスチェック
  3. 03サービスディスカバリー設定
  4. 04HTTP のリトライとサーキットブレーカー
  5. 05カスタムトレースソース

活用シーン

  1. 01API とワーカーのテレメトリー統一
  2. 02サービスプローブ用ヘルスチェックの公開
  3. 03アウトバウンド HTTP の耐障害性設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill aspire-service-defaults

インストール後、Claude Code で/aspire-service-defaultsを入力して読み込まれたか確認します。

詳細

Aspire サービスの既定値

このスキルを使用するタイミング

次の場合にこのスキルを使用します。

  • Aspire ベースの分散アプリケーションを構築する場合
  • サービス間で一貫した可観測性(ロギング、トレーシング、メトリクス)が必要な場合
  • ヘルスチェックの設定を共有したい場合
  • HttpClient のレジリエンスとサービスディスカバリーを設定する場合

ServiceDefaults とは?

ServiceDefaults は、Aspire アプリケーション内のすべてのサービスに共通の設定を提供する共有プロジェクトです。

  • OpenTelemetry - ロギング、トレーシング、メトリクス
  • ヘルスチェック - 準備状態と稼働状態のエンドポイント
  • サービスディスカバリー - サービスの自動解決
  • HTTP レジリエンス - 再試行とサーキットブレーカーのポリシー

すべてのサービスがこのプロジェクトを参照し、AddServiceDefaults() を呼び出します。


プロジェクト構成

src/
  MyApp.ServiceDefaults/
    Extensions.cs
    MyApp.ServiceDefaults.csproj
  MyApp.Api/
    Program.cs  # Calls AddServiceDefaults()
  MyApp.Worker/
    Program.cs  # Calls AddServiceDefaults()
  MyApp.AppHost/
    Program.cs

ServiceDefaults プロジェクト

プロジェクトファイル
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <IsAspireSharedProject>true</IsAspireSharedProject>
  </PropertyGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.Extensions.Http.Resilience" />
    <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
    <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
    <PackageReference Include="OpenTelemetry.Extensions.Hosting" />
    <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Http" />
    <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
  </ItemGroup>
</Project>
Extensions.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace Microsoft.Extensions.Hosting;

public static class Extensions
{
    private const string HealthEndpointPath = "/health";
    private const string AlivenessEndpointPath = "/alive";

    /// <summary>
    /// Adds common Aspire services: OpenTelemetry, health checks,
    /// service discovery, and HTTP resilience.
    /// </summary>
    public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder)
        where TBuilder : IHostApplicationBuilder
    {
        builder.ConfigureOpenTelemetry();
        builder.AddDefaultHealthChecks();

        builder.Services.AddServiceDiscovery();

        builder.Services.ConfigureHttpClientDefaults(http =>
        {
            // Resilience: retries, circuit breaker, timeouts
            http.AddStandardResilienceHandler();

            // Service discovery: resolve service names to addresses
            http.AddServiceDiscovery();
        });

        return builder;
    }

    public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
        where TBuilder : IHostApplicationBuilder
    {
        // Logging
        builder.Logging.AddOpenTelemetry(logging =>
        {
            logging.IncludeFormattedMessage = true;
            logging.IncludeScopes = true;
        });

        builder.Services.AddOpenTelemetry()
            // Metrics
            .WithMetrics(metrics =>
            {
                metrics
                    .AddAspNetCoreInstrumentation()
                    .AddHttpClientInstrumentation()
                    .AddRuntimeInstrumentation();
            })
            // Tracing
            .WithTracing(tracing =>
            {
                tracing
                    .AddSource(builder.Environment.ApplicationName)
                    .AddAspNetCoreInstrumentation(options =>
                        // Exclude health checks from traces
                        options.Filter = context =>
                            !context.Request.Path.StartsWithSegments(HealthEndpointPath) &&
                            !context.Request.Path.StartsWithSegments(AlivenessEndpointPath))
                    .AddHttpClientInstrumentation();
            });

        builder.AddOpenTelemetryExporters();

        return builder;
    }

    private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder)
        where TBuilder : IHostApplicationBuilder
    {
        // Use OTLP exporter if endpoint is configured (Aspire Dashboard, Jaeger, etc.)
        var useOtlp = !string.IsNullOrWhiteSpace(
            builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);

        if (useOtlp)
        {
            builder.Services.AddOpenTelemetry().UseOtlpExporter();
        }

        return builder;
    }

    public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
        where TBuilder : IHostApplicationBuilder
    {
        builder.Services.AddHealthChecks()
            .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);

        return builder;
    }

    /// <summary>
    /// Maps health check endpoints. Call after UseRouting().
    /// </summary>
    public static WebApplication MapDefaultEndpoints(this WebApplication app)
    {
        // Only expose in development - see security note below
        if (app.Environment.IsDevelopment())
        {
            // Readiness: all health checks must pass
            app.MapHealthChecks(HealthEndpointPath);

            // Liveness: only "live" tagged checks
            app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
            {
                Predicate = r => r.Tags.Contains("live")
            });
        }

        return app;
    }
}

サービスでの使用方法

API サービス
var builder = WebApplication.CreateBuilder(args);

// Add all service defaults
builder.AddServiceDefaults();

// Add your services
builder.Services.AddControllers();

var app = builder.Build();

// Map health endpoints
app.MapDefaultEndpoints();

app.MapControllers();
app.Run();
ワーカーサービス
var builder = Host.CreateApplicationBuilder(args);

// Works for non-web hosts too
builder.AddServiceDefaults();

builder.Services.AddHostedService<MyWorker>();

var host = builder.Build();
host.Run();

カスタムヘルスチェックの追加

public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
    where TBuilder : IHostApplicationBuilder
{
    builder.Services.AddHealthChecks()
        // Basic liveness
        .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])

        // Database readiness
        .AddNpgSql(
            builder.Configuration.GetConnectionString("postgres")!,
            name: "postgres",
            tags: ["ready"])

        // Redis readiness
        .AddRedis(
            builder.Configuration.GetConnectionString("redis")!,
            name: "redis",
            tags: ["ready"])

        // Custom check
        .AddCheck<MyCustomHealthCheck>("custom", tags: ["ready"]);

    return builder;
}

カスタムトレースソースの追加

Akka.NET またはカスタム ActivitySources の場合:

public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
    where TBuilder : IHostApplicationBuilder
{
    builder.Services.AddOpenTelemetry()
        .WithTracing(tracing =>
        {
            tracing
                .AddSource(builder.Environment.ApplicationName)
                // Akka.NET tracing
                .AddSource("Akka.NET")
                // Custom sources
                .AddSource("MyApp.Orders")
                .AddSource("MyApp.Payments")
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation();
        });

    return builder;
}

本番環境のヘルスチェック

本番環境では、ヘルスチェックエンドポイントを保護するか、異なるパスを使用します。

public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
    // Always map for Kubernetes probes, but consider:
    // - Using internal-only ports
    // - Adding authorization
    // - Rate limiting

    app.MapHealthChecks("/health", new HealthCheckOptions
    {
        // Only return status, not details
        ResponseWriter = (context, report) =>
        {
            context.Response.ContentType = "text/plain";
            return context.Response.WriteAsync(report.Status.ToString());
        }
    });

    app.MapHealthChecks("/alive", new HealthCheckOptions
    {
        Predicate = r => r.Tags.Contains("live"),
        ResponseWriter = (context, report) =>
        {
            context.Response.ContentType = "text/plain";
            return context.Response.WriteAsync(report.Status.ToString());
        }
    });

    return app;
}

AppHost との統合

AppHost は OTLP エンドポイントを自動的に設定します。

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

var postgres = builder.AddPostgres("postgres");
var redis = builder.AddRedis("redis");

var api = builder.AddProject<Projects.MyApp_Api>("api")
    .WithReference(postgres)
    .WithReference(redis);

builder.Build().Run();

サービスは OTEL_EXPORTER_OTLP_ENDPOINT を自動的に受け取り、テレメトリーを Aspire Dashboard に送信します。


ベストプラクティス

プラクティス理由
ServiceDefaults プロジェクトを 1 つにするすべてのサービスで一貫した設定を使用できます
トレースからヘルスチェックを除外する可観測性データのノイズを減らせます
ヘルスチェックにタグを付ける稼働状態と準備状態を分離できます
StandardResilienceHandler を使用する再試行、サーキットブレーカー、タイムアウトが組み込まれています
カスタムトレースソースを追加するドメイン固有のスパンを記録できます

リソース

コメント