Skip to content

Commit 7deefb5

Browse files
authored
Add cluster bootstrap via Aspire sample (#270)
* Add cluster bootstrap via Aspire sample * Fix readme * Install .NET 9.0 SDK in CI/CD * Link example to main README
1 parent 52c831a commit 7deefb5

File tree

11 files changed

+244
-1
lines changed

11 files changed

+244
-1
lines changed

.github/workflows/pr_validation.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ jobs:
4242
- uses: actions/setup-dotnet@v4
4343
with:
4444
dotnet-version: 8.0.*
45+
- uses: actions/setup-dotnet@v4
46+
with:
47+
dotnet-version: 9.0.*
4548
- name: Cache .nuke/temp, ~/.nuget/packages
4649
uses: actions/cache@v4
4750
with:
@@ -68,6 +71,9 @@ jobs:
6871
- uses: actions/setup-dotnet@v4
6972
with:
7073
dotnet-version: 8.0.*
74+
- uses: actions/setup-dotnet@v4
75+
with:
76+
dotnet-version: 9.0.*
7177
- name: Cache .nuke/temp, ~/.nuget/packages
7278
uses: actions/cache@v4
7379
with:

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ The goals of this repository are to provide users with shovel-ready Akka.NET sam
88
* Integrating Akka.NET with other popular technologies: RabbitMQ, Apache Kafka, and more; and
99
* Deploying Akka.NET into popular and common deployment environments: Kubernetes, Azure, AWS, and more.
1010

11-
These samples aren't designed to teach how to model complex domains using actors - they are pimarily designed to demosntrate copy-and-pasteable approaches to running AKka.NET infrastructure correctly and succinctly.
11+
These samples aren't designed to teach how to model complex domains using actors - they are primarily designed to demonstrate copy-and-pasteable approaches to running AKka.NET infrastructure correctly and succinctly.
1212

1313
## Current Samples:
1414

1515
1. [Akka.NET Cluster.Sharding with Akka.Persistence.SqlServer and Razor Pages](https://github.com/petabridge/akkadotnet-code-samples/tree/master/src/clustering/sharding-sqlserver)
1616
2. [Akka.Streams.Amqp.RabbitMQ with Akka.Cluster.Sharding - Reliable Delivery + Backpressure Support](https://github.com/petabridge/akkadotnet-code-samples/tree/master/src/reliability/rabbitmq-backpressure)
1717
3. [Event-Sourcing and CQRS with Akka.Persistence and Akka.Persistence.Query](https://github.com/petabridge/akkadotnet-code-samples/tree/master/src/cqrs/cqrs-sqlserver)
1818
4. [Autofac And Akka.Hosting Integration](https://github.com/petabridge/akkadotnet-code-samples/tree/master/src/dependency-injection/AutofacIntegration)
19+
5. [Akka.NET Cluster Setup Using Microsoft .NET Aspire](https://github.com/petabridge/akkadotnet-code-samples/tree/master/src/clustering/cluster-bootstrap/Aspire)
1920

2021
## Contributing
2122

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCluster.Node", "SimpleCluster.Node\SimpleCluster.Node.csproj", "{73586BA4-9B6F-4470-AF2A-B7A3B0F94E2F}"
4+
EndProject
5+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCluster.AppHost", "SimpleCluster.AppHost\SimpleCluster.AppHost.csproj", "{77720513-442B-4CF3-A278-827328B69D80}"
6+
EndProject
7+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4781FC82-B4BD-472B-BFC2-2EBBF52CB763}"
8+
ProjectSection(SolutionItems) = preProject
9+
README.md = README.md
10+
EndProjectSection
11+
EndProject
12+
Global
13+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
14+
Debug|Any CPU = Debug|Any CPU
15+
Release|Any CPU = Release|Any CPU
16+
EndGlobalSection
17+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
18+
{73586BA4-9B6F-4470-AF2A-B7A3B0F94E2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19+
{73586BA4-9B6F-4470-AF2A-B7A3B0F94E2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
20+
{73586BA4-9B6F-4470-AF2A-B7A3B0F94E2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
21+
{73586BA4-9B6F-4470-AF2A-B7A3B0F94E2F}.Release|Any CPU.Build.0 = Release|Any CPU
22+
{77720513-442B-4CF3-A278-827328B69D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{77720513-442B-4CF3-A278-827328B69D80}.Debug|Any CPU.Build.0 = Debug|Any CPU
24+
{77720513-442B-4CF3-A278-827328B69D80}.Release|Any CPU.ActiveCfg = Release|Any CPU
25+
{77720513-442B-4CF3-A278-827328B69D80}.Release|Any CPU.Build.0 = Release|Any CPU
26+
EndGlobalSection
27+
EndGlobal
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Akka.NET Cluster Setup Using Microsoft .NET Aspire
2+
3+
The goal of this sample is to demonstrate how a three-node cluster can be set-up in a single machine using Microsoft .NET Aspire
4+
5+
## Technology
6+
7+
This solution is built with:
8+
9+
- Minimal APIs
10+
- Docker container
11+
- [Akka.NET v1.5 w/ Akka.Cluster](https://github.com/akkadotnet/akka.net)
12+
- [Akka.Hosting](https://github.com/akkadotnet/Akka.Hosting) - which minimizes the amount of configuration for Akka.NET to practically zero.
13+
- [Microsoft .NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/)
14+
15+
## Domain
16+
17+
There is no domain for this example, it is set up to show a bare-bone minimum code required to run a three node cluster using Aspire.
18+
19+
## Running Sample
20+
21+
Load up Rider or Visual Studio and launch `SimpleCluster.AppHost`
22+
23+
You should see
24+
1. Aspire dashboard opened in your browser.
25+
* If the dashboard does not open automatically, go to this address: `http://localhost:15258`
26+
2. All resource status shown in the dashboard
27+
* The `azure` resource should be in "Unhealthy" state until Azurite Docker container is successfully downloaded and run, where it should switch to the "Running" state
28+
* Three `akka-node` resource which should be in "Waiting" state and switches to "Running" state after the `azure` resource is running.
29+
3. Cluster bootstrap creates the cluster
30+
* Cluster should form in a few seconds. View the console log of one of the `akka-node` resource to see the cluster forming, you should see something similar to the logs below.
31+
32+
```
33+
[INFO][06/09/2025 19:40:14.779Z][Thread 0020][akka.tcp://SimpleCluster@localhost:56545/system/bootstrapCoordinator] Looking up [Lookup(akka-discovery, , tcp)]
34+
[INFO][06/09/2025 19:40:14.785Z][Thread 0004][akka.tcp://SimpleCluster@localhost:56545/system/bootstrapCoordinator] Located service members based on: [Lookup(akka-discovery, , tcp)]: [ResolvedTarget(localhost, 56543, ), ResolvedTarget(localhost, 56544, ), ResolvedTarget(localhost, 56549, )], filtered to [localhost:56543, localhost:56544, localhost:56549]
35+
[INFO][06/09/2025 19:40:15.155Z][Thread 0004][akka.tcp://SimpleCluster@localhost:56545/system/bootstrapCoordinator] Contact point [akka.tcp://SimpleCluster@localhost:56548] returned [1] seed-nodes [akka.tcp://SimpleCluster@localhost:56548]
36+
[INFO][06/09/2025 19:40:15.156Z][Thread 0004][akka.tcp://SimpleCluster@localhost:56545/system/bootstrapCoordinator] Joining [akka.tcp://SimpleCluster@localhost:56545] to existing cluster [akka.tcp://SimpleCluster@localhost:56548]
37+
[INFO][06/09/2025 19:40:15.295Z][Thread 0012][Cluster (akka://SimpleCluster)] Cluster Node [akka.tcp://SimpleCluster@localhost:56545] - Welcome from [akka.tcp://SimpleCluster@localhost:56548]
38+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Microsoft.Extensions.Hosting;
2+
3+
var builder = DistributedApplication.CreateBuilder(args);
4+
5+
#region Setup Azure
6+
7+
var azureStorage = builder.AddAzureStorage("azure");
8+
9+
// use Azurite docker container to emulate Azure if we're in a development environment
10+
if (builder.Environment.IsDevelopment())
11+
azureStorage.RunAsEmulator();
12+
13+
var azureTables = azureStorage.AddTables("azure-tables");
14+
15+
#endregion
16+
17+
#region Setup Akka
18+
19+
builder.AddProject<Projects.SimpleCluster_Node>("akka-node")
20+
// Spin up 3 replicas
21+
.WithReplicas(3)
22+
// Let Aspire inject the Azure table connection string
23+
.WithReference(azureTables)
24+
// Let Aspire assign a random port for Akka.Remote
25+
.WithEndpoint(name: "remote", env: "Akka__Remote_Port")
26+
// Let Aspire assign a random port for Akka.Management
27+
.WithEndpoint(name: "management", env: "Akka__Management_Port")
28+
// Make sure that Azure Tables is running before starting Akka nodes
29+
.WaitFor(azureTables);
30+
31+
#endregion
32+
33+
builder.Build().Run();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<Sdk Name="Aspire.AppHost.Sdk" Version="9.3.0"/>
4+
5+
<PropertyGroup>
6+
<OutputType>Exe</OutputType>
7+
<TargetFramework>net9.0</TargetFramework>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<Nullable>enable</Nullable>
10+
<UserSecretsId>b5a49694-6be3-4d9c-96ed-034cb40960f9</UserSecretsId>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.0"/>
15+
<PackageReference Include="Aspire.Hosting.Azure.Storage" Version="9.3.0" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="..\SimpleCluster.Node\SimpleCluster.Node.csproj" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning",
6+
"Aspire.Hosting.Dcp": "Warning"
7+
}
8+
}
9+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Microsoft.Extensions.Configuration;
2+
3+
namespace SimpleCluster.Node;
4+
5+
public class AkkaOptions
6+
{
7+
[ConfigurationKeyName("Host_Name")]
8+
public string HostName { get; set; } = "localhost";
9+
10+
[ConfigurationKeyName("Remote_Port")]
11+
public int RemotePort { get; set; } = 14884;
12+
13+
[ConfigurationKeyName("Management_Port")]
14+
public int ManagementPort { get; set; } = 18558;
15+
16+
[ConfigurationKeyName("Discovery_Service_Name")]
17+
public string DiscoveryServiceName { get; set; } = "akka-discovery";
18+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using Akka.Cluster.Hosting;
2+
using Akka.Discovery.Azure;
3+
using Akka.Hosting;
4+
using Akka.Management;
5+
using Akka.Management.Cluster.Bootstrap;
6+
using Akka.Remote.Hosting;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.Hosting;
9+
using SimpleCluster.Node;
10+
11+
await Host.CreateDefaultBuilder(args)
12+
.ConfigureServices((context, services) =>
13+
{
14+
// bind environment configuration passed in from Aspire
15+
var akkaOptions = new AkkaOptions();
16+
context.Configuration.GetSection("Akka").Bind(akkaOptions);
17+
18+
services.AddAkka("SimpleCluster", builder =>
19+
{
20+
// Setup Akka.Remote
21+
builder.WithRemoting(opt =>
22+
{
23+
opt.PublicHostName = akkaOptions.HostName;
24+
opt.PublicPort = akkaOptions.RemotePort;
25+
opt.HostName = "0.0.0.0";
26+
opt.Port = akkaOptions.RemotePort;
27+
});
28+
29+
// Setup Akka.Cluster
30+
builder.WithClustering();
31+
32+
// Setup Akka.Management
33+
builder.WithAkkaManagement(opt =>
34+
{
35+
opt.Http.HostName = akkaOptions.HostName;
36+
opt.Http.Port = akkaOptions.ManagementPort;
37+
opt.Http.BindHostName = "0.0.0.0";
38+
opt.Http.BindPort = akkaOptions.ManagementPort;
39+
});
40+
41+
// Setup Akka.Management.Cluster.Bootstrap
42+
builder.WithClusterBootstrap(opt =>
43+
{
44+
opt.ContactPointDiscovery.ServiceName = akkaOptions.DiscoveryServiceName;
45+
opt.ContactPointDiscovery.RequiredContactPointsNr = 3;
46+
47+
// NOTE: It is important to set this to false
48+
// see https://getakka.net/articles/discovery/akka-management.html#running-clustered-nodes-on-a-single-machine
49+
opt.ContactPoint.FilterOnFallbackPort = false;
50+
});
51+
52+
// Setup Akka.Discovery.Azure
53+
var connectionString = context.Configuration.GetConnectionString("azure-tables") ?? "UseDevelopmentStorage=true";
54+
builder.WithAzureDiscovery(opt =>
55+
{
56+
opt.ConnectionString = connectionString;
57+
opt.HostName = akkaOptions.HostName;
58+
opt.Port = akkaOptions.ManagementPort;
59+
opt.ServiceName = akkaOptions.DiscoveryServiceName;
60+
});
61+
});
62+
})
63+
.UseConsoleLifetime()
64+
.RunConsoleAsync();

0 commit comments

Comments
 (0)