Aspire Service Defaults
이 스킬을 사용해야 하는 경우
다음과 같은 경우 이 스킬을 사용하세요:
- Aspire 기반 분산 애플리케이션을 구축할 때
- 서비스 전반에 걸쳐 일관된 관찰 가능성(로깅, 추적, 메트릭)이 필요할 때
- 공유 상태 확인 구성을 원할 때
- HttpClient 복원력 및 서비스 검색을 구성할 때
ServiceDefaults란?
ServiceDefaults는 Aspire 애플리케이션의 모든 서비스에 공통 구성을 제공하는 공유 프로젝트입니다:
- OpenTelemetry - 로깅, 추적, 메트릭
- Health Checks - 준비성 및 활성 엔드포인트
- Service Discovery - 자동 서비스 확인
- HTTP Resilience - 재시도 및 차단기 정책
모든 서비스는 이 프로젝트를 참조하고 AddServiceDefaults()를 호출합니다.
프로젝트 구조
src/
MyApp.ServiceDefaults/
Extensions.cs
MyApp.ServiceDefaults.csproj
MyApp.Api/
Program.cs # AddServiceDefaults() 호출
MyApp.Worker/
Program.cs # 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>
/// 공통 Aspire 서비스(OpenTelemetry, 상태 확인,
/// 서비스 검색, HTTP 복원력)를 추가합니다.
/// </summary>
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// 복원력: 재시도, 차단기, 시간 제한
http.AddStandardResilienceHandler();
// 서비스 검색: 서비스 이름을 주소로 확인
http.AddServiceDiscovery();
});
return builder;
}
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
// 로깅
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
// 메트릭
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
// 추적
.WithTracing(tracing =>
{
tracing
.AddSource(builder.Environment.ApplicationName)
.AddAspNetCoreInstrumentation(options =>
// 상태 확인을 추적에서 제외
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
{
// OTLP 엔드포인트가 구성된 경우 OTLP 내보내기 사용(Aspire 대시보드, Jaeger 등)
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>
/// 상태 확인 엔드포인트를 매핑합니다. UseRouting() 이후에 호출하세요.
/// </summary>
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// 개발 환경에서만 노출 - 아래 보안 참고 사항 확인
if (app.Environment.IsDevelopment())
{
// 준비성: 모든 상태 확인이 통과해야 함
app.MapHealthChecks(HealthEndpointPath);
// 활성: "live" 태그가 있는 확인만
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}
서비스에서의 사용법
API 서비스
var builder = WebApplication.CreateBuilder(args);
// 모든 서비스 기본값 추가
builder.AddServiceDefaults();
// 서비스 추가
builder.Services.AddControllers();
var app = builder.Build();
// 상태 엔드포인트 매핑
app.MapDefaultEndpoints();
app.MapControllers();
app.Run();
Worker 서비스
var builder = Host.CreateApplicationBuilder(args);
// 비웹 호스트에서도 작동
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()
// 기본 활성 확인
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
// 데이터베이스 준비성
.AddNpgSql(
builder.Configuration.GetConnectionString("postgres")!,
name: "postgres",
tags: ["ready"])
// Redis 준비성
.AddRedis(
builder.Configuration.GetConnectionString("redis")!,
name: "redis",
tags: ["ready"])
// 사용자 정의 확인
.AddCheck<MyCustomHealthCheck>("custom", tags: ["ready"]);
return builder;
}
사용자 정의 추적 소스 추가
Akka.NET 또는 사용자 정의 ActivitySource의 경우:
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddSource(builder.Environment.ApplicationName)
// Akka.NET 추적
.AddSource("Akka.NET")
// 사용자 정의 소스
.AddSource("MyApp.Orders")
.AddSource("MyApp.Payments")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});
return builder;
}
프로덕션 상태 확인
프로덕션에서는 상태 엔드포인트를 보호하거나 다른 경로를 사용하세요:
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Kubernetes 프로브를 위해 항상 매핑하지만 다음을 고려하세요:
// - 내부 전용 포트 사용
// - 인증 추가
// - 속도 제한
app.MapHealthChecks("/health", new HealthCheckOptions
{
// 상태만 반환, 세부 정보는 반환하지 않음
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 대시보드로 텔레메트리를 전송합니다.
모범 사례
| 사례 | 이유 |
|---|---|
| 하나의 ServiceDefaults 프로젝트 | 모든 서비스에서 일관된 구성 |
| 상태 확인을 추적에서 필터링 | 관찰 가능성 데이터의 노이즈 감소 |
| 상태 확인에 태그 지정 | 활성 상태와 준비성 구분 |
| StandardResilienceHandler 사용 | 내장된 재시도, 차단기, 시간 제한 |
| 사용자 정의 추적 소스 추가 | 도메인별 스팬 캡처 |
리소스
- Aspire Service Defaults: https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/service-defaults
- OpenTelemetry .NET: https://opentelemetry.io/docs/languages/net/
- Health Checks: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks