Akka.NET 관리 및 서비스 디스커버리
이 스킬을 사용해야 할 때
이 스킬은 다음과 같은 상황에 사용하세요:
- Akka.NET 클러스터를 Kubernetes 또는 클라우드 환경에 배포할 때
- 정적 시드 노드를 동적 서비스 디스커버리로 대체할 때
- 자동 구성을 위한 클러스터 부트스트래핑을 설정할 때
- 로드 밸런서를 위한 상태 확인 엔드포인트를 설정할 때
- Azure Table Storage, Kubernetes API 또는 Config 기반 디스커버리와 통합할 때
참조 파일
- discovery-providers.md: Config, Kubernetes 및 Azure 디스커버리 설정 (전체 코드 및 배포 YAML 포함)
- configuration-reference.md: 강력한 타입의 구성 모델 클래스
개요
Akka.Management는 클러스터 관리를 위한 HTTP 엔드포인트를 제공하며, Akka.Cluster.Bootstrap와 통합되어 정적 시드 노드 대신 서비스 디스커버리를 사용하여 동적 클러스터 구성을 가능하게 합니다.
Akka.Management를 사용하는 이유
| 방식 | 장점 | 단점 |
|---|---|---|
| 정적 시드 노드 | 간단함, 종속성 없음 | 확장 어려움, IP를 미리 알아야 함 |
| Akka.Management | 동적 디스커버리, N개 노드로 확장 가능 | 추가 구성 필요, 외부 종속성 |
정적 시드 노드는 다음과 같은 경우에 사용하세요: 개발, 단일 노드 배포, 고정된 인프라.
Akka.Management는 다음과 같은 경우에 사용하세요: Kubernetes, 자동 확장 그룹, 동적 환경, 프로덕션 클러스터.
아키텍처
┌─────────────────────────────────────────────────────────────┐
│ 클러스터 부트스트래핑 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 노드 1 │ │ 노드 2 │ │ 노드 3 │ │
│ │ │ │ │ │ │ │
│ │ Management │◄──►│ Management │◄──►│ Management │ │
│ │ HTTP :8558 │ │ HTTP :8558 │ │ HTTP :8558 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └──────────────────┼──────────────────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ 디스커버리 │ │
│ │ 제공자 │ │
│ └───────────────┘ │
│ │ │
└────────────────────────────┼────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼─────┐ ┌─────▼──────┐
│ Kubernetes│ │ Azure │ │ Config │
│ API │ │ Tables │ │ (HOCON) │
└───────────┘ └────────────┘ └────────────┘
필요한 NuGet 패키지
<ItemGroup>
<!-- 핵심 관리 -->
<PackageReference Include="Akka.Management" />
<PackageReference Include="Akka.Management.Cluster.Bootstrap" />
<!-- 다음 중 하나의 디스커버리 제공자를 선택 -->
<PackageReference Include="Akka.Discovery.KubernetesApi" /> <!-- Kubernetes -->
<PackageReference Include="Akka.Discovery.Azure" /> <!-- Azure -->
<PackageReference Include="Akka.Discovery.Config.Hosting" /> <!-- 정적 설정 -->
</ItemGroup>
Akka.Hosting 구성
모드 선택을 포함한 기본 설정
public static class AkkaConfiguration
{
public static IServiceCollection ConfigureAkka(
this IServiceCollection services,
Action<AkkaConfigurationBuilder, IServiceProvider>? additionalConfig = null)
{
services.AddOptions<AkkaSettings>()
.BindConfiguration("AkkaSettings")
.ValidateDataAnnotations()
.ValidateOnStart();
return services.AddAkka("MySystem", (builder, sp) =>
{
var settings = sp.GetRequiredService<IOptions<AkkaSettings>>().Value;
var configuration = sp.GetRequiredService<IConfiguration>();
ConfigureNetwork(builder, settings, configuration);
ConfigureHealthChecks(builder);
additionalConfig?.Invoke(builder, sp);
});
}
private static void ConfigureNetwork(
AkkaConfigurationBuilder builder,
AkkaSettings settings,
IConfiguration configuration)
{
if (settings.ExecutionMode == AkkaExecutionMode.LocalTest)
return;
builder.WithRemoting(settings.RemoteOptions);
if (settings.ClusterBootstrapOptions.Enabled)
ConfigureAkkaManagement(builder, settings, configuration);
else
builder.WithClustering(settings.ClusterOptions);
}
}
Akka.Management 구성
private static void ConfigureAkkaManagement(
AkkaConfigurationBuilder builder,
AkkaSettings settings,
IConfiguration configuration)
{
var mgmtOptions = settings.AkkaManagementOptions;
var bootstrapOptions = settings.ClusterBootstrapOptions;
// 중요: Akka.Management 사용 시 시드 노드 지우기
settings.ClusterOptions.SeedNodes = [];
builder
.WithClustering(settings.ClusterOptions)
.WithAkkaManagement(setup =>
{
setup.Http.HostName = mgmtOptions.HostName;
setup.Http.Port = mgmtOptions.Port;
setup.Http.BindHostName = "0.0.0.0";
setup.Http.BindPort = mgmtOptions.Port;
})
.WithClusterBootstrap(options =>
{
options.ContactPointDiscovery.ServiceName = bootstrapOptions.ServiceName;
options.ContactPointDiscovery.PortName = bootstrapOptions.PortName;
options.ContactPointDiscovery.RequiredContactPointsNr = bootstrapOptions.RequiredContactPointsNr;
options.ContactPointDiscovery.Interval = bootstrapOptions.ContactPointProbingInterval;
options.ContactPointDiscovery.StableMargin = bootstrapOptions.StableMargin;
options.ContactPointDiscovery.ContactWithAllContactPoints = bootstrapOptions.ContactWithAllContactPoints;
options.ContactPoint.FilterOnFallbackPort = bootstrapOptions.FilterOnFallbackPort;
options.ContactPoint.ProbeInterval = bootstrapOptions.BootstrapperDiscoveryPingInterval;
});
// 디스커버리 제공자 구성
ConfigureDiscovery(builder, settings, configuration);
}
전체 Config, Kubernetes 및 Azure 디스커버리 설정 코드는 discovery-providers.md를 참조하세요.
전체 강력한 타입의 구성 모델 클래스는 configuration-reference.md를 참조하세요.
상태 확인 엔드포인트
Akka.Management는 로드 밸런서와 오케스트레이터를 위한 상태 확인 엔드포인트를 노출합니다:
| 엔드포인트 | 목적 | 200 반환 조건 |
|---|---|---|
/alive | 활성 확인 | ActorSystem 실행 중 |
/ready | 준비 확인 | 클러스터 멤버가 Up 상태 |
/cluster/members | 디버그 | 클러스터 멤버십 반환 |
ASP.NET Core Health Check 통합
// Akka 상태 확인 등록
builder.Services.AddHealthChecks();
// Akka 구성
builder
.WithActorSystemLivenessCheck() // "akka-liveness" 상태 확인 추가
.WithAkkaClusterReadinessCheck(); // "akka-cluster-readiness" 상태 확인 추가
// 엔드포인트 매핑
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("liveness")
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("readiness")
});
문제 해결
클러스터가 구성되지 않음
증상: 노드가 각각 분리된 단일 노드 클러스터로 유지됩니다.
확인 목록:
- 모든 노드가 동일한
ServiceName을 사용하는지 확인 RequiredContactPointsNr가 실제 복제본 수와 일치하는지 확인- 디스커버리 제공자가 올바르게 구성되었는지 확인
- 네트워크가 관리 포트(8558)에서 트래픽을 허용하는지 확인
- Kubernetes의 경우: RBAC 권한이 설정되었는지 확인
Split Brain
증상: 하나의 클러스터 대신 여러 클러스터가 형성됩니다.
해결 방법:
ContactWithAllContactPoints = true설정- 느린 환경의 경우
StableMargin증가 - Aspire의 경우:
FilterOnFallbackPort = false설정 (동적 포트) - Kubernetes의 경우:
FilterOnFallbackPort = true설정 (고정 포트)
Azure 디스커버리 문제
증상: Azure Tables를 통해 노드가 서로를 찾을 수 없습니다.
확인 목록:
- 연결 문자열이 유효한지 확인
- 스토리지 계정이 테이블 작업을 허용하는지 확인
- 모든 노드가 동일한
ServiceName을 사용하는지 확인 - 방화벽이 Azure Storage에 대한 액세스를 허용하는지 확인
Aspire 통합
자세한 Aspire 관련 패턴은 akka-net-aspire-configuration 스킬을 참조하세요.
Aspire 빠른 참조:
// AppHost에서
appBuilder
.WithEndpoint(name: "remote", protocol: ProtocolType.Tcp,
env: "AkkaSettings__RemoteOptions__Port")
.WithEndpoint(name: "management", protocol: ProtocolType.Tcp,
env: "AkkaSettings__AkkaManagementOptions__Port")
.WithEnvironment("AkkaSettings__ClusterBootstrapOptions__Enabled", "true")
.WithEnvironment("AkkaSettings__ClusterBootstrapOptions__DiscoveryMethod", "AzureTableStorage")
.WithEnvironment("AkkaSettings__ClusterBootstrapOptions__FilterOnFallbackPort", "false");
요약: 언제 무엇을 사용할지
| 시나리오 | 디스커버리 방법 | FilterOnFallbackPort |
|---|---|---|
| 로컬 개발 (단일 노드) | 없음 (시드 노드 사용) | N/A |
| Aspire 다중 노드 | AzureTableStorage | false |
| Kubernetes | Kubernetes | true |
| Azure VM/VMSS | AzureTableStorage | true |
| 고정 인프라 | Config | true |
| AWS ECS/EC2 | AWS 디스커버리 플러그인 | true |