Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#64 : Support for Scheduler Service #65

Merged
merged 2 commits into from
Sep 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions all.sln
Original file line number Diff line number Diff line change
@@ -101,6 +101,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PlacementSample", "Placemen
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlacementSample", "samples\AspNetCore\PlacementSample\PlacementSample\PlacementSample.csproj", "{2FC86574-6A81-4E2B-A0D4-78D46528A917}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SchedulerSample", "SchedulerSample", "{8FD8AFF0-A56A-4BDC-B40E-F498AA147790}"
ProjectSection(SolutionItems) = preProject
samples\AspNetCore\SchedulerSample\README.md = samples\AspNetCore\SchedulerSample\README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SchedulerSample", "samples\AspNetCore\SchedulerSample\SchedulerSample\SchedulerSample.csproj", "{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -315,6 +322,18 @@ Global
{2FC86574-6A81-4E2B-A0D4-78D46528A917}.Release|x64.Build.0 = Release|Any CPU
{2FC86574-6A81-4E2B-A0D4-78D46528A917}.Release|x86.ActiveCfg = Release|Any CPU
{2FC86574-6A81-4E2B-A0D4-78D46528A917}.Release|x86.Build.0 = Release|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Debug|x64.ActiveCfg = Debug|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Debug|x64.Build.0 = Debug|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Debug|x86.ActiveCfg = Debug|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Debug|x86.Build.0 = Debug|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Release|Any CPU.Build.0 = Release|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Release|x64.ActiveCfg = Release|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Release|x64.Build.0 = Release|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Release|x86.ActiveCfg = Release|Any CPU
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -348,6 +367,8 @@ Global
{9F4DA8E9-F253-4312-A0BB-E2873A21C41A} = {7F6A5D8C-9780-4824-8284-CC3149683C70}
{AE430C04-78BD-4CAE-86D7-EBC599774D9C} = {DA3D8137-F2DD-465D-81AA-3CA5C75087D2}
{2FC86574-6A81-4E2B-A0D4-78D46528A917} = {AE430C04-78BD-4CAE-86D7-EBC599774D9C}
{8FD8AFF0-A56A-4BDC-B40E-F498AA147790} = {DA3D8137-F2DD-465D-81AA-3CA5C75087D2}
{C0B2943F-3EA8-43A9-8714-7D6B84AD788E} = {8FD8AFF0-A56A-4BDC-B40E-F498AA147790}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E906E97D-7D56-4E02-A13F-1C48AEB47A88}
1 change: 1 addition & 0 deletions all.v3.ncrunchsolution
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
<AutoEnable>True</AutoEnable>
<CurrentEngineMode>Run Solution Unit Tests automatically [SnapshotShared]</CurrentEngineMode>
<InstrumentationMode>Optimised</InstrumentationMode>
<RdiConfigured>True</RdiConfigured>
<SolutionConfigured>True</SolutionConfigured>
</Settings>
<EngineModes>
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
"Serilog": {
"MinimumLevel": "Debug"
},
"AllowedHosts": "*"
}
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
4 changes: 2 additions & 2 deletions samples/AspNetCore/PlacementSample/README.md
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ builder.Services.AddDaprSidekick(builder.Configuration)
.AddPlacement();
```

Typically when installing Dapr in self-hosted mode, a Placement service container is added to Docker exposing the default port 6500. If this samle is run
Typically when installing Dapr in self-hosted mode, a Placement service container is added to Docker exposing the default port 6050. If this same is run
while that container is up it will be unable to start due to a port conflict. Instead a different port 6501 is assigned to Placement in configuration:

```json5
@@ -30,7 +30,7 @@ unless a specific remote address is defined. For example the following specifies

```json5
"Sidecar": {
"PlacementHostAddress": "remote-host-1:6050,remote-host-2:6050,remote-host-3:6050"
"PlacementHostAddress": "remote-host-1:50005,remote-host-2:50005,remote-host-3:50005"
}
```

42 changes: 42 additions & 0 deletions samples/AspNetCore/SchedulerSample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Scheduler example

This sample shows how Dapr Sidekick can be used to host the Dapr Scheduler service alongside a Dapr Sidecar instance. Sidekick will launch the Scheduler service and wait for it
to enter a healthy running state, then will launch the Sidecar alongside. The Sidecar will be configured to use the launched Scheduler service by default.
As with the Sidecar, the Scheduler service will be continually monitored and maintained for the lifetime of the application.

## How Dapr Sidekick was added

This is an ASP.NET Core minimal Web API project. Dapr Sidekick was added to the [Program.cs](SchedulerSample\Program.cs) file as follows:

```csharp
// Add Dapr Sidekick with Scheduler
builder.Services.AddDaprSidekick(builder.Configuration)
.AddScheduler();
```

Typically when installing Dapr in self-hosted mode, a Scheduler service container is added to Docker exposing the default port 6060. If this same is run
while that container is up it will be unable to start due to a port conflict. Instead a different port 6061 is assigned to Scheduler in configuration:

```json5
"Scheduler": {
"RuntimeDirectory": "scheduler",
"Id": "dapr-scheduler-server-0", // Optional unique identifier when used in a cluster
"Port": 6061 // To avoid conflicts with local Dapr Scheduler container. Sidecar will use this automatically as well.
}
```

By default the Sidecar that is launched alongside the Scheduler service will look for the Scheduler service locally on this custom port,
unless a specific remote address is defined. For example the following specifies a three-host remote Scheduler cluster:

```json5
"Sidecar": {
"SchedulerHostAddress": "remote-host-1:50006,remote-host-2:50006,remote-host-3:50006"
}
```

## Running the sample

To run the sample simply set `SchedulerSample` as the startup project and run it in Visual Studio,
it will launch first the Scheduler service then the Dapr sidecar, then open a browser and display
the configured launch options for both.

39 changes: 39 additions & 0 deletions samples/AspNetCore/SchedulerSample/SchedulerSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Man.Dapr.Sidekick;
using Serilog;

// Add Serilog for enhanced console logging.
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// Add Dapr Sidekick with Scheduler
builder.Services.AddDaprSidekick(builder.Configuration)
.AddScheduler();

builder.Host.UseSerilog();

var app = builder.Build();

app.MapGet("/status", (IDaprSidecarHost sidecarHost, IDaprSchedulerHost schedulerHost) => Results.Ok(new
{
sidecar = new
{
process = sidecarHost.GetProcessInfo(), // Information about the sidecar process such as if it is running
options = sidecarHost.GetProcessOptions() // The sidecar options if running, including ports and locations
},
scheduler = new
{
process = schedulerHost.GetProcessInfo(), // Information about the sentry process such as if it is running
options = schedulerHost.GetProcessOptions() // The sentry options if running, including ports and locations
},
}));

// For Dapr
app.MapHealthChecks("/health");

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"SchedulerSample": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "status",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Man.Dapr.Sidekick.AspNetCore\Man.Dapr.Sidekick.AspNetCore.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="dapr\**\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="scheduler\**\*.*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"DaprSidekick": {
"Sidecar": {
"RuntimeDirectory": "dapr"
},
"Scheduler": {
"RuntimeDirectory": "scheduler",
"Id": "dapr-scheduler-server-0", // Optional unique identifier when used in a cluster
"Port": 6061 // To avoid conflicts with local Dapr Scheduler container. Sidecar will use this automatically as well.
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprsystem
namespace: default
spec:
mtls:
enabled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprsystem
namespace: default
spec:
17 changes: 17 additions & 0 deletions src/Man.Dapr.Sidekick.AspNetCore/DaprSidekickBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq;
using Man.Dapr.Sidekick.AspNetCore.Metrics;
using Man.Dapr.Sidekick.AspNetCore.Placement;
using Man.Dapr.Sidekick.AspNetCore.Scheduler;
using Man.Dapr.Sidekick.AspNetCore.Sentry;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -33,6 +34,22 @@ public IDaprSidekickBuilder AddPlacement()
return this;
}

public IDaprSidekickBuilder AddScheduler()
{
// Add the scheduler host
_services.TryAddSingleton<IDaprSchedulerHost, DaprSchedulerHost>();
_services.TryAddHostedService<DaprSchedulerHostedService>();

// Add the health checks and metrics
_services.AddHealthChecks().AddDaprScheduler();
_services.AddSingleton<IPrometheusCollector, DaprSchedulerMetricsCollector>();

// Override the default sidecar hosted service, to one that only starts when the Scheduler service is available.
ReplaceSidecarHostedService<DaprSchedulerSidecarHostedService>();

return this;
}

public IDaprSidekickBuilder AddSentry()
{
// Add the Sentry host
6 changes: 6 additions & 0 deletions src/Man.Dapr.Sidekick.AspNetCore/IDaprSidekickBuilder.cs
Original file line number Diff line number Diff line change
@@ -11,6 +11,12 @@ public interface IDaprSidekickBuilder
/// <returns>This instance to allow calls to be chained.</returns>
public IDaprSidekickBuilder AddPlacement();

/// <summary>
/// Adds the Dapr Scheduler service.
/// </summary>
/// <returns>This instance to allow calls to be chained.</returns>
public IDaprSidekickBuilder AddScheduler();

/// <summary>
/// Adds the Dapr Sentry service.
/// </summary>
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ public static class DaprMetricsConstants

public static readonly string DaprSidecarLabel = "dapr-sidecar";
public static readonly string DaprPlacementLabel = "dapr-placement";
public static readonly string DaprSchedulerLabel = "dapr-scheduler";
public static readonly string DaprSentryLabel = "dapr-sentry";

public static readonly string ExporterContentType = "text/plain; charset=utf-8";
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Man.Dapr.Sidekick.AspNetCore.Scheduler
{
public class DaprSchedulerHealthCheck : DaprProcessHealthCheck
{
public DaprSchedulerHealthCheck(
IDaprSchedulerHost daprSchedulerHost)
: base(daprSchedulerHost)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using Man.Dapr.Sidekick.AspNetCore.Metrics;
using Man.Dapr.Sidekick.AspNetCore.Scheduler;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Microsoft.Extensions.DependencyInjection
{
public static class DaprSchedulerHealthCheckBuilderExtensions
{
/// <summary>
/// Add a health check for the Dapr Scheduler.
/// </summary>
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'dapr_Scheduler' will be used for the name.</param>
/// <param name="failureStatus">
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
/// </param>
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/> to allow calls to be chained.</returns>
public static IHealthChecksBuilder AddDaprScheduler(
this IHealthChecksBuilder builder,
string name = default,
HealthStatus? failureStatus = default,
IEnumerable<string> tags = default)
{
builder.AddCheck<DaprSchedulerHealthCheck>(name ?? DaprMetricsConstants.DaprSchedulerLabel, failureStatus, tags);
return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Threading;
using Man.Dapr.Sidekick.AspNetCore.Metrics;
using Microsoft.Extensions.Options;

namespace Man.Dapr.Sidekick.AspNetCore.Scheduler
{
public class DaprSchedulerHostedService : DaprHostedService<IDaprSchedulerHost, DaprSchedulerOptions>
{
public DaprSchedulerHostedService(
IDaprSchedulerHost daprSchedulerHost,
IOptionsMonitor<DaprOptions> optionsAccessor)
: base(daprSchedulerHost, optionsAccessor)
{
}

protected override void OnStarting(DaprOptions options, CancellationToken cancellationToken)
{
// Assign metrics
options.Scheduler ??= new DaprSchedulerOptions();
options.Scheduler.Metrics ??= new DaprMetricsOptions();
options.Scheduler.Metrics.SetLabel(DaprMetricsConstants.ServiceLabelName, options.Sidecar?.AppId);
options.Scheduler.Metrics.SetLabel(DaprMetricsConstants.AppLabelName, DaprMetricsConstants.DaprSchedulerLabel);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Man.Dapr.Sidekick.AspNetCore.Metrics;

namespace Man.Dapr.Sidekick.AspNetCore.Scheduler
{
public class DaprSchedulerMetricsCollector : DaprProcessHostPrometheusCollector
{
public DaprSchedulerMetricsCollector(IDaprSchedulerHost daprSchedulerHost)
: base(daprSchedulerHost)
{
}
}
}
Loading
Loading