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

Update metrics sample to use OTLP and OTEL collector #606

Merged
merged 9 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging;

namespace MetricsApp.AppHost.OpenTelemetryCollector;

internal sealed class CollectorLifecycleHook : IDistributedApplicationLifecycleHook
{
private readonly ILogger<CollectorLifecycleHook> _logger;

public CollectorLifecycleHook(ILogger<CollectorLifecycleHook> logger)
{
_logger = logger;
}

public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken)
{
var resources = appModel.GetProjectResources();
var collectorResource = appModel.Resources.OfType<CollectorResource>().FirstOrDefault();

if (collectorResource == null)
{
_logger.LogWarning("No collector resource found");
return Task.CompletedTask;
}

var endpoint = collectorResource.GetEndpoint(CollectorResource.OtlpGrpcEndpointName);
if (endpoint == null)
{
_logger.LogWarning("No endpoint for the collector");
return Task.CompletedTask;
}

if (resources.Count() == 0)
{
_logger.LogInformation("No resources to add Environment Variables to");
}

foreach (var resourceItem in resources)
{
_logger.LogDebug($"Forwarding Telemetry for {resourceItem.Name} to the collector");
if (resourceItem == null)
{
continue;
}

resourceItem.Annotations.Add(new EnvironmentCallbackAnnotation((EnvironmentCallbackContext context) =>
{
context.EnvironmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = endpoint;
}));
}

return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MetricsApp.AppHost.OpenTelemetryCollector;

public class CollectorResource(string name) : ContainerResource(name)
{
internal const string OtlpGrpcEndpointName = "grpc";
internal const string OtlpHttpEndpointName = "http";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.Extensions.Configuration;

namespace MetricsApp.AppHost.OpenTelemetryCollector;

public static class CollectorResourceBuilderExtensions
{
private const string DashboardOtlpUrlVariableName = "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL";
private const string DashboardOtlpApiKeyVariableName = "AppHost:OtlpApiKey";
private const string DashboardOtlpUrlDefaultValue = "http://localhost:18889";

public static IResourceBuilder<CollectorResource> AddCollector(this IDistributedApplicationBuilder builder, string name, string configFileLocation)
DamianEdwards marked this conversation as resolved.
Show resolved Hide resolved
{
builder.AddCollectorInfrastructure();

var url = builder.Configuration[DashboardOtlpUrlVariableName] ?? DashboardOtlpUrlDefaultValue;

var dashboardOtlpEndpoint = ReplaceLocalhostWithContainerHost(url, builder.Configuration);
var dashboardInsecure = url.StartsWith("https", StringComparison.OrdinalIgnoreCase) ? "false" : "true";

var resource = new CollectorResource(name);
return builder.AddResource(resource)
.WithImage("ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib", "latest")
.WithEndpoint(port: 4317, targetPort: 4317, name: CollectorResource.OtlpGrpcEndpointName, scheme: "http")
.WithEndpoint(port: 4318, targetPort: 4318, name: CollectorResource.OtlpHttpEndpointName, scheme: "http")
JamesNK marked this conversation as resolved.
Show resolved Hide resolved
.WithBindMount(configFileLocation, "/etc/otelcol-contrib/config.yaml")
.WithEnvironment("ASPIRE_ENDPOINT", dashboardOtlpEndpoint)
.WithEnvironment("ASPIRE_API_KEY", builder.Configuration[DashboardOtlpApiKeyVariableName])
.WithEnvironment("ASPIRE_INSECURE", dashboardInsecure);
}

private static string ReplaceLocalhostWithContainerHost(string value, IConfiguration configuration)
JamesNK marked this conversation as resolved.
Show resolved Hide resolved
{
var hostName = configuration["AppHost:ContainerHostname"] ?? "host.docker.internal";

return value.Replace("localhost", hostName, StringComparison.OrdinalIgnoreCase)
.Replace("127.0.0.1", hostName)
.Replace("[::1]", hostName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Aspire.Hosting.Lifecycle;

namespace MetricsApp.AppHost.OpenTelemetryCollector;

internal static class CollectorServiceExtensions
{
public static IDistributedApplicationBuilder AddCollectorInfrastructure(this IDistributedApplicationBuilder builder)
{
builder.Services.TryAddLifecycleHook<CollectorLifecycleHook>();

return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Aspire Hosting extension for the OpenTelemetry Collector

Based on source from https://github.com/practical-otel/opentelemetry-aspire-collector by @martinjt.
17 changes: 12 additions & 5 deletions samples/Metrics/MetricsApp.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
var builder = DistributedApplication.CreateBuilder(args);
using MetricsApp.AppHost.OpenTelemetryCollector;

var builder = DistributedApplication.CreateBuilder(args);

var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
.WithBindMount("../prometheus", "/etc/prometheus", isReadOnly: true)
.WithArgs("--web.enable-otlp-receiver", "--config.file=/etc/prometheus/prometheus.yml")
.WithHttpEndpoint(targetPort: 9090, name: "http");

var grafana = builder.AddContainer("grafana", "grafana/grafana")
.WithBindMount("../grafana/config", "/etc/grafana", isReadOnly: true)
.WithBindMount("../grafana/dashboards", "/var/lib/grafana/dashboards", isReadOnly: true)
.WithEnvironment(c => c.EnvironmentVariables["PROMETHEUS_PORT"] = $"{prometheus.GetEndpoint("http").Port}")
.WithHttpEndpoint(targetPort: 3000, name: "http");
JamesNK marked this conversation as resolved.
Show resolved Hide resolved

builder.AddCollector("otelcollector", "../otelcollector/config.yaml")
.WithEnvironment("PROMETHEUS_ENDPOINT", $"{prometheus.GetEndpoint("http")}/api/v1/otlp");

builder.AddProject<Projects.MetricsApp>("app")
.WithEnvironment("GRAFANA_URL", grafana.GetEndpoint("http"));

builder.AddContainer("prometheus", "prom/prometheus")
.WithBindMount("../prometheus", "/etc/prometheus", isReadOnly: true)
.WithHttpEndpoint(/* This port is fixed as it's referenced from the Grafana config */ port: 9090, targetPort: 9090);

using var app = builder.Build();

await app.RunAsync();
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19032",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20132"
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20132",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
}
},
"generate-manifest": {
Expand Down
8 changes: 0 additions & 8 deletions samples/Metrics/ServiceDefaults/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,6 @@ private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostAppli
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}

// The following lines enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
builder.Services.AddOpenTelemetry()
// BUG: Part of the workaround for https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/1617
.WithMetrics(metrics => metrics.AddPrometheusExporter(options => options.DisableTotalNameSuffixForCounters = true));

return builder;
}

Expand All @@ -85,9 +80,6 @@ public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicati

public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// The following line enables the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
app.MapPrometheusScrapingEndpoint();

// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (app.Environment.IsDevelopment())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ datasources:
type: prometheus
access: proxy
# Access mode - proxy (server in the UI) or direct (browser in the UI).
url: http://host.docker.internal:9090
url: http://host.docker.internal:$PROMETHEUS_PORT
JamesNK marked this conversation as resolved.
Show resolved Hide resolved
uid: PBFA97CFB590B2093
42 changes: 42 additions & 0 deletions samples/Metrics/otelcollector/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318

processors:
batch:

exporters:
debug:
verbosity: detailed
otlp/aspire:
endpoint: ${env:ASPIRE_ENDPOINT}
headers:
x-otlp-api-key: ${env:ASPIRE_API_KEY}
tls:
insecure: ${env:ASPIRE_INSECURE}
insecure_skip_verify: true
otlphttp/prometheus:
endpoint: ${env:PROMETHEUS_ENDPOINT}
tls:
insecure: true

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/aspire]
metrics:
receivers: [otlp]
processors: [batch]
exporters:
- otlp/aspire
- otlphttp/prometheus
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlp/aspire]
10 changes: 4 additions & 6 deletions samples/Metrics/prometheus/prometheus.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
global:
scrape_interval: 1s # makes for a good demo
storage:
tsdb:
out_of_order_time_window: 30m

scrape_configs:
- job_name: 'metricsapp'
static_configs:
- targets: ['host.docker.internal:5048'] # hard-coded port matches launchSettings.json
otlp:
Loading