Skip to content

Commit

Permalink
Sample: KubernetesApi cluster bootstraping via HOCON (#2641)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arkatufus authored Jul 10, 2024
1 parent e3d55ab commit 73b2684
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Akka.Management.sln
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Coordination.Azure.Tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.StressTest", "src\coordination\examples\azure\Azure.StressTest\Azure.StressTest.csproj", "{296A0309-2370-4153-B3B0-A4105622CC9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HoconKubernetesCluster", "src\cluster.bootstrap\examples\discovery\hocon-kubernetes\src\HoconKubernetesCluster\HoconKubernetesCluster.csproj", "{7015572E-6225-4F32-A00C-4D52280E6C94}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -180,6 +182,10 @@ Global
{296A0309-2370-4153-B3B0-A4105622CC9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{296A0309-2370-4153-B3B0-A4105622CC9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{296A0309-2370-4153-B3B0-A4105622CC9A}.Release|Any CPU.Build.0 = Release|Any CPU
{7015572E-6225-4F32-A00C-4D52280E6C94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7015572E-6225-4F32-A00C-4D52280E6C94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7015572E-6225-4F32-A00C-4D52280E6C94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7015572E-6225-4F32-A00C-4D52280E6C94}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -214,6 +220,7 @@ Global
{55BB0B68-1364-41EB-A9AA-AB054FB39329} = {00CE6BBE-BFAD-452A-82A5-826F7B99F5FB}
{296A0309-2370-4153-B3B0-A4105622CC9A} = {C4C8BE03-A7C0-4F2E-8922-92854FE7A5BF}
{24FEBA95-1FC2-4AC2-8386-6AB13AE87056} = {9B10ADF5-60D1-4EED-9E98-9CB2E1E84E98}
{7015572E-6225-4F32-A00C-4D52280E6C94} = {24FEBA95-1FC2-4AC2-8386-6AB13AE87056}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B99E6BB8-642A-4A68-86DF-69567CBA700A}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Akka.Management Cluster Bootstrap Setup Using Pure HOCON Configuration

This sample project shows how Akka.Management cluster bootstrapping can be achieved using pure HOCON configuration, no Akka.Hosting is used in this project.

## Running Sample

To run the sample, you must have Docker Desktop installed on your machine.

1. Open a terminal window and go to the sample directory
```powershell
PS C:\> cd ./Akka.Management/src/cluster.bootstrap/examples/discovery/hocon-kubernetes
```
2. Start by building the Docker image
```powershell
PS C:\> ./build-docker.ps1
```
3. Deploy the Kubernetes cluster
```powershell
PS C:\> ./k8s/deploy.cmd
```

There are several scripts in the `.\k8s` directory:

* `deploy.cmd`: Deploys the Kubernetes cluster
* `destroy.cmd`: Take down the Kubernetes cluster
* `events.cmd`: List all the events that happens inside the Kubernetes cluster
* `status.cmd`: Show the status of the Kubernetes cluster
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dotnet publish --os linux --arch x64 -c Release /t:PublishContainer ./src/HoconKubernetesCluster/HoconKubernetesCluster.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# namespace declaration
apiVersion: v1
kind: Namespace
metadata:
name: hocon-cluster-bootstrap
---


# RBAC declaration
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pod-reader
namespace: hocon-cluster-bootstrap
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: lease-access
namespace: hocon-cluster-bootstrap
rules:
- apiGroups: ["akka.io"]
resources: ["leases"]
verbs: ["get", "create", "update", "list"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-pods
namespace: hocon-cluster-bootstrap
subjects:
- kind: ServiceAccount
name: cluster-bootstrap
namespace: hocon-cluster-bootstrap
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: lease-access
namespace: hocon-cluster-bootstrap
subjects:
- kind: ServiceAccount
name: cluster-bootstrap
namespace: hocon-cluster-bootstrap
roleRef:
kind: Role
name: lease-access
apiGroup: rbac.authorization.k8s.io
---


# Service declaration
apiVersion: v1
kind: ServiceAccount
metadata:
name: cluster-bootstrap
namespace: hocon-cluster-bootstrap
labels:
app: cluster-bootstrap
---
apiVersion: v1
kind: Service
metadata:
name: cluster-bootstrap
namespace: hocon-cluster-bootstrap
labels:
app: cluster-bootstrap
spec:
publishNotReadyAddresses: true
clusterIP: None
ports:
- port: 8558
name: management
- port: 8081
name: akka-remote
- port: 5000
name: http
selector:
app: cluster-bootstrap
---

# Stateful set declaration
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cluster-bootstrap
namespace: hocon-cluster-bootstrap
labels:
app: cluster-bootstrap
spec:
serviceName: cluster-bootstrap
replicas: 10
selector:
matchLabels:
app: cluster-bootstrap
template:
metadata:
labels:
app: cluster-bootstrap
spec:
serviceAccountName: cluster-bootstrap
terminationGracePeriodSeconds: 35
containers:
- name: cluster-bootstrap-app
image: hocon-cluster-bootstrap:latest
imagePullPolicy: Never
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: STATEFULSET_NAME
value: "cluster-bootstrap" # Manually setting it, as there's no direct way to fetch it via downward API
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: AKKA_REMOTE_HOSTNAME
value: "$(POD_NAME).$(STATEFULSET_NAME).$(NAMESPACE)"
- name: AKKA_REMOTE_PORT
value: "8081"
- name: AKKA_MANAGEMENT_PORT
value: "8558"
- name: AKKA_BOOTSTRAP_SERVICE_NAME
value: "cluster-bootstrap"
- name: AKKA_BOOTSTRAP_PORT_NAME
value: "management"
readinessProbe:
tcpSocket:
port: 8558
ports:
- containerPort: 8558
protocol: TCP
name: management
- containerPort: 8081
protocol: TCP
name: akka-remote
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
set LOCAL=%~dp0
kubectl apply -f "%~dp0/clusterbootstrap.yaml"
kubectl get all -n hocon-cluster-bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kubectl delete ns hocon-cluster-bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kubectl get event -n hocon-cluster-bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kubectl get all -n hocon-cluster-bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Text;
using Akka.Configuration;

namespace HoconKubernetesCluster;

public record EnvironmentSettings(
string? RemotingHostName,
string? RemotingPort,
string? ManagementHostName,
string? ManagementPort,
string? BootstrapServiceName,
string? BootstrapPortName)
{
private const string RemoteHostNameKey = "AKKA_REMOTE_HOSTNAME";
private const string RemotePortKey = "AKKA_REMOTE_PORT";
private const string ManagementHostNameKey = "AKKA_MANAGEMENT_HOSTNAME";
private const string ManagementPortKey = "AKKA_MANAGEMENT_PORT";
private const string BootstrapServiceNameKey = "AKKA_BOOTSTRAP_SERVICE_NAME";
private const string BootstrapPortNameKey = "AKKA_BOOTSTRAP_PORT_NAME";

public Config ToConfig()
{
var sb = new StringBuilder();
if (RemotingHostName is not null)
sb.AppendLine($"akka.remote.dot-netty.tcp.public-hostname = {RemotingHostName}");

if(RemotingPort is not null)
sb.AppendLine($"akka.remote.dot-netty.tcp.port = {RemotingPort}");

if (ManagementHostName is not null)
sb.AppendLine($"akka.management.hostname = {ManagementHostName}");

if (ManagementPort is not null)
sb.AppendLine($"akka.management.port = {ManagementPort}");

if (BootstrapServiceName is not null)
sb.AppendLine($"akka.management.cluster.bootstrap.contact-point-discovery.service-name = {BootstrapServiceName}");

if (BootstrapPortName is not null)
sb.AppendLine($"akka.management.cluster.bootstrap.contact-point-discovery.port-name = {BootstrapPortName}");

return sb.Length == 0 ? Config.Empty : sb.ToString();
}

public static EnvironmentSettings Create()
=> new (
RemotingHostName: Environment.GetEnvironmentVariable(RemoteHostNameKey),
RemotingPort: Environment.GetEnvironmentVariable(RemotePortKey),
ManagementHostName: Environment.GetEnvironmentVariable(ManagementHostNameKey),
ManagementPort: Environment.GetEnvironmentVariable(ManagementPortKey),
BootstrapServiceName: Environment.GetEnvironmentVariable(BootstrapServiceNameKey),
BootstrapPortName: Environment.GetEnvironmentVariable(BootstrapPortNameKey)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
akka {
log-config-on-start = on

extensions = [
"Akka.Management.Dsl.AkkaManagementProvider, Akka.Management",
"Akka.Management.Cluster.Bootstrap.ClusterBootstrapProvider, Akka.Management"
]

actor.provider = cluster

management {
hostname = "<hostname>" # will be replaced from code
port = 8558 # will be replaced from code

cluster {
bootstrap {
contact-point-discovery {
service-name = "<service-name" # will be replaced from code
port-name = "" # will be replaced from code
}
}
}
}

discovery {
method = kubernetes-api
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
<ContainerRepository>hocon-cluster-bootstrap</ContainerRepository>
<ContainerTitle>hocon-cluster-bootstrap</ContainerTitle>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\..\..\discovery\kubernetes\Akka.Discovery.KubernetesApi\Akka.Discovery.KubernetesApi.csproj" />
<ProjectReference Include="..\..\..\..\..\..\management\Akka.Management\Akka.Management.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="HOCON.conf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Akka.Actor;
using Akka.Configuration;
using Akka.Discovery.KubernetesApi;
using Akka.Management.Cluster.Bootstrap;
using Akka.Management.Dsl;

namespace HoconKubernetesCluster;

public static class Program
{
public static async Task Main(string[] args)
{
#region Console shutdown setup

var exitEvent = new ManualResetEvent(false);
Console.CancelKeyPress += (_, eventArgs) =>
{
eventArgs.Cancel = true;
exitEvent.Set();
};
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
{
exitEvent.Set();
};

#endregion

var hocon = await File.ReadAllTextAsync("HOCON.conf");
var config = EnvironmentSettings.Create().ToConfig()
.WithFallback(ConfigurationFactory.ParseString(hocon))
.WithFallback(AkkaManagementProvider.DefaultConfiguration())
.WithFallback(ClusterBootstrap.DefaultConfiguration())
.WithFallback(KubernetesDiscovery.DefaultConfiguration());

var actorSystem = ActorSystem.Create("hocon-cluster-discovery", config);

exitEvent.WaitOne();
await actorSystem.Terminate();
}
}

0 comments on commit 73b2684

Please sign in to comment.