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

feat: Add .NET Aspire AppHost and observability infrastructure #143

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,5 @@ __pycache__/
*.pyc

.vscode

.DS_Store
appsettings.*.json
3 changes: 3 additions & 0 deletions Lectures/Lecture_13/IW5_Lecture13-Deployment_Aspire.pptx
Git LFS file not shown
25 changes: 25 additions & 0 deletions src/CookBook/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
7 changes: 7 additions & 0 deletions src/CookBook/CookBook.Api.App/CookBook.Api.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<UserSecretsId>70e22f70-c609-4d6a-9be8-b462915b90e5</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>

<ItemGroup>
Expand All @@ -20,8 +21,14 @@
<ProjectReference Include="..\CookBook.Api.BL\CookBook.Api.BL.csproj" />
<ProjectReference Include="..\CookBook.Api.DAL.EF\CookBook.Api.DAL.EF.csproj" />
<ProjectReference Include="..\CookBook.Api.DAL.Memory\CookBook.Api.DAL.Memory.csproj" />
<ProjectReference Include="..\CookBook.Hosting.ServiceDefaults\CookBook.Hosting.ServiceDefaults.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="CookBook.Api.App.EndToEndTests" />
</ItemGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
</Project>
31 changes: 31 additions & 0 deletions src/CookBook/CookBook.Api.App/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY Directory.Build.props .
COPY ["CookBook.Api.App/CookBook.Api.App.csproj", "CookBook.Api.App/"]
COPY ["CookBook.Api.BL/CookBook.Api.BL.csproj", "CookBook.Api.BL/"]
COPY ["CookBook.Api.DAL.Common/CookBook.Api.DAL.Common.csproj", "CookBook.Api.DAL.Common/"]
COPY ["CookBook.Common/CookBook.Common.csproj", "CookBook.Common/"]
COPY ["CookBook.Common.BL/CookBook.Common.BL.csproj", "CookBook.Common.BL/"]
COPY ["CookBook.Common.Models/CookBook.Common.Models.csproj", "CookBook.Common.Models/"]
COPY ["CookBook.Api.DAL.EF/CookBook.Api.DAL.EF.csproj", "CookBook.Api.DAL.EF/"]
COPY ["CookBook.Api.DAL.Memory/CookBook.Api.DAL.Memory.csproj", "CookBook.Api.DAL.Memory/"]
COPY ["CookBook.Hosting.ServiceDefaults/CookBook.Hosting.ServiceDefaults.csproj", "CookBook.Hosting.ServiceDefaults/"]
RUN dotnet restore "CookBook.Api.App/CookBook.Api.App.csproj"
COPY . .
WORKDIR "/src/CookBook.Api.App"
RUN dotnet build "CookBook.Api.App.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "CookBook.Api.App.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CookBook.Api.App.dll"]
15 changes: 4 additions & 11 deletions src/CookBook/CookBook.Api.App/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Globalization;
using AutoMapper;
using AutoMapper.Internal;
using CookBook.Api.App.Extensions;
using CookBook.Api.App.Processors;
using CookBook.Api.BL.Facades;
Expand All @@ -15,18 +12,13 @@
using CookBook.Common.Extensions;
using CookBook.Common.Models;
using CookBook.Common.Resources;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using CookBook.Hosting.ServiceDefaults;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;

var builder = WebApplication.CreateBuilder();
builder.AddServiceDefaults();

ConfigureCors(builder.Services);
ConfigureLocalization(builder.Services);
Expand All @@ -36,6 +28,7 @@
ConfigureAutoMapper(builder.Services);

var app = builder.Build();
app.MapDefaultEndpoints();

ValidateAutoMapperConfiguration(app.Services);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using CookBook.Api.DAL.Common.Entities;
using CookBook.Api.DAL.Common.Repositories;

namespace CookBook.API.DAL.IntegrationTests;
namespace CookBook.Api.DAL.IntegrationTests;

public interface IDatabaseFixture
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using CookBook.Common.Enums;
using Newtonsoft.Json;

namespace CookBook.API.DAL.IntegrationTests;
namespace CookBook.Api.DAL.IntegrationTests;

public class InMemoryDatabaseFixture : IDatabaseFixture
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using CookBook.Common.Enums;
using Xunit;

namespace CookBook.API.DAL.IntegrationTests;
namespace CookBook.Api.DAL.IntegrationTests;

public class RecipeRepositoryTests
{
Expand Down
12 changes: 12 additions & 0 deletions src/CookBook/CookBook.Hosting.AppHost/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": false,
"tools": {
"aspirate": {
"version": "8.0.7",
"commands": [
"aspirate"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>65e3f962-7661-40d1-b440-1a322d8d8dba</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CookBook.Api.App\CookBook.Api.App.csproj" />
<ProjectReference Include="..\CookBook.Hosting.Gateway\CookBook.Hosting.Gateway.csproj" />
<ProjectReference Include="..\CookBook.Web.App\CookBook.Web.App.csproj" />
</ItemGroup>

</Project>
92 changes: 92 additions & 0 deletions src/CookBook/CookBook.Hosting.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Microsoft.Extensions.Configuration;

var builder = DistributedApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables();

var prometheus = builder.AddContainer("prometheus", "prom/prometheus", "v2.53.2")
.WithHttpEndpoint(targetPort: 9090, isProxied: false)
.WithBindMount("./configs/prometheus/config.yaml", "/mnt/config/local-config.yaml", isReadOnly: true)
.WithArgs("--config.file=/mnt/config/local-config.yaml", "--web.enable-remote-write-receiver");

var grafanaLoki = builder.AddContainer("grafanaLoki", "grafana/loki", "3.2.0")
.WithHttpEndpoint(targetPort: 3100, isProxied: false)
.WithBindMount("./configs/loki/config.yaml", "/mnt/config/local-config.yaml", isReadOnly: true)
.WithArgs("-config.file=/mnt/config/local-config.yaml");

var grafanaTempo = builder.AddContainer("grafanaTempo", "grafana/tempo", "2.6.0")
.WithHttpEndpoint(targetPort: 3200, isProxied: false)
.WithEndpoint(targetPort: 9097, name: "grpc", isProxied: false)
.WithBindMount("./configs/tempo/config.yaml", "/mnt/config/local-config.yaml", isReadOnly: true)
.WithArgs("-config.file=/mnt/config/local-config.yaml");

var grafana = builder.AddContainer("grafana", "grafana/grafana", "11.2.0")
.WithHttpEndpoint(targetPort: 3000)
.WithBindMount("./configs/grafana/config", "/etc/grafana", isReadOnly: true)
.WithBindMount("./configs/grafana/dashboards", "/var/lib/grafana/dashboards", isReadOnly: true)
.WithVolume("grafana-data", "/var/lib/grafana")
.WithReference(grafanaTempo.GetEndpoint("http"))
.WithReference(prometheus.GetEndpoint("http"))
.WithReference(grafanaLoki.GetEndpoint("http"));

var otelCollector = builder.AddContainer("otel-collector", "otel/opentelemetry-collector", "0.109.0")
.WithEndpoint(targetPort: 4317, name: "grpc", scheme: "http", isProxied: false)
.WithEndpoint(targetPort: 4318, name: "http", scheme: "http", isProxied: false)
.WithBindMount("./configs/otel-collector/config.yaml", "/etc/otelcol/config.yaml", isReadOnly: true)
.WithEnvironment("GRAFANA_TEMPO_GRPC", () =>
{
var endpoint = grafanaTempo.GetEndpoint("grpc");
return $"{endpoint.ContainerHost}:{endpoint.TargetPort}";
})
.WithEnvironment(context =>
{
if (builder.Configuration.GetValue<string>("DOTNET_DASHBOARD_OTLP_ENDPOINT_URL") is {} otlpEndpoint)
{
var endpointUrl = new HostUrl(otlpEndpoint);
context.EnvironmentVariables["DOTNET_DASHBOARD_OTLP_ENDPOINT"] = endpointUrl;
}
})
.WithReference(grafanaTempo.GetEndpoint("grpc"))
.WithReference(prometheus.GetEndpoint("http"))
.WithReference(grafanaLoki.GetEndpoint("http"));

var api = builder.AddProject<Projects.CookBook_Api_App>("api")
.WithHttpEndpoint();

var web = builder.AddProject<Projects.CookBook_Web_App>("web");

var gateway = builder.AddProject<Projects.CookBook_Hosting_Gateway>("gateway")
.WithReference(api)
.WithReference(web)
.WithHttpEndpoint()
.WithExternalHttpEndpoints();


if (builder.ExecutionContext.IsPublishMode)
{
web.WithHttpEndpoint(targetPort: 8080, env: "HTTP_PORT");
}
else
{
web.WithHttpEndpoint();
}

ConfigureOpenTelemetryExporter(api);
ConfigureOpenTelemetryExporter(gateway);

builder.Build().Run();
return;

void ConfigureOpenTelemetryExporter<T>(IResourceBuilder<T> resourceBuilder) where T : IResourceWithEnvironment
{
if (builder.Configuration.GetValue<bool>("SendMetricsToCollector"))
{
var grpcEndpoint = otelCollector.GetEndpoint("grpc");
resourceBuilder.WithEnvironment("OTEL_EXPORTER_OTLP_ENDPOINT", grpcEndpoint);
resourceBuilder.WithEnvironment("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc");
}
else
{
// sends metrics to dashboard
resourceBuilder.WithOtlpExporter();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15079",
"environmentVariables": {
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true",
// "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS": "true",
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19066",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20096"
}
}
}
}
20 changes: 20 additions & 0 deletions src/CookBook/CookBook.Hosting.AppHost/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# .NET Aspire AppHost

## Install / Restore
```sh
dotnet workload install aspire
dotnet tool install -g aspirate
```

```sh
dotnet workload update
dotnet tool restore
```

## Generate output
Based on already existing `aspirate-state.json` this command will generate a Docker Compose file along with program images.
These will be stored locally.

```shell
aspirate generate
```
15 changes: 15 additions & 0 deletions src/CookBook/CookBook.Hosting.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"SendMetricsToCollector": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
},
"Dashboard": {
"Otlp": {
"AuthMode": "Unsecured"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
api:
container_name: "api"
image: "iw5/api:latest"
environment:
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
HTTP_PORTS: "8080"
ports:
- target: 8080
published: 10000
restart: unless-stopped
web:
container_name: "web"
image: "iw5/web:latest"
environment:
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
HTTP_PORT: "8080"
ports:
- target: 8080
published: 10001
restart: unless-stopped
gateway:
container_name: "gateway"
image: "iw5/gateway:latest"
environment:
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true"
OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true"
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
HTTP_PORTS: "8080"
services__api__http__0: "http://api:8080"
services__web__http__0: "http://web:8080"
ports:
- target: 8080
published: 10002
restart: unless-stopped
Loading