OpenTelemetry

Monitoring next Level


28.09.2024

kubectl describe trainer fpommerening
{
  name: "Frank Pommerening",
  employer: {},
  skills: [
    "Senior-Softwareentwickler",
    "Consultant",
    "Trainer"
  ],
  communication:{
    email : "frank@pommerening-consulting.de",
    twitter: "@fpommerening"
  }
}

OpenTelemetry

Ãœberblick

Ãœberblick

High-quality, ubiquitous, and portable telemetry
to enable effective observability.

  • Ursprünglich aus OpenCensus und OpenTracing hervorgegangen
  • Seit August 2021 - Ein Projekt der CNCF in der Inkubationsphase
  • Verarbeitung von Telemetriedaten wie Logs, verteilte Traces und Metriken
  • Verfolgt offene Standards und bietet eine lose Kopplung
  • Herstellerunabhängig und interoperabel
  • Werkzeuge, APIs und SDKs zur Erzeugung, Erfassung
    und Export von Telemetriedaten

Bestandteile

OpenTelemetry Protocol (OTLP)

  • Mechanismen zum Kodierung, Transport und
    Ãœbertragung von Telemetriedaten
  • Gilt für Telemetriequellen, Zwischenknoten und Backends
  • Verwendet HTTP oder gRPC mit dem gleichen Protobuf-Schema
  • Bietet binäre oder JSON-Ãœbertragung (Experimentell)
  • Enthält Optionen zur Ãœbertragungssteuerung, einschließlich Throttling

OpenTelemetry Collector

  • Das zentrale Element von OpenTelemetry
  • Sammlung, Verarbeitung und Weiterleitung von Telemetriedaten
  • Verwendet eine Pipeline-Verarbeitung

APIs und SDKs

  • Gemeinsame Schnittstellen, einschließlich Protobuf
  • Bibliotheken zur Integration in eigene Anwendungen

Sprachunterstützung

Aktuell werden elf Programmiersprachen von OpenTelemetry unterstützt.
Der Entwicklungsstand variiert dabei erheblich.
C++  .NET  Erlang/Elixir  GO  
Java  JavaScript  PHP  Python  
Ruby  Rust  Swift  

OpenTelemetry

.NET

Ãœbersicht

  • Unterstützung für Full-Framework < 4.6.1 und .NET (Core)
  • Bereitstellung als Nuget-Pakete

Exporter

Die Exporter leiten die gesammelten Dateien zur Verarbeitung weiter.
Das Ziel muss dabei nicht zwangsläufig ein OpenTelemetry Collector bzw. OpenTelemetry Agent sein.
Auch Systeme wie Jaeger , Prometheus oder Zipkin sind möglich.

Instrumentation

OpenTelemetry liefert bereits Implementierungen der Telemetrie von
ASP.NET ASP.NET Core HTTP clients
Grpc.Net.Client Redis Client MS SQL Client

Integration

Dependency Injection

services.AddOpenTelemetry().ConfigureResource(rb => 
            rb.AddEnvironmentVariableDetector().AddService(ServiceName))
  .WithMetrics(metricsBuilder =>
  {
    metricsBuilder.AddAspNetCoreInstrumentation();
    metricsBuilder.AddHttpClientInstrumentation();
    metricsBuilder.AddOtlpExporter(otlpOptions =>{ otlpOptions.Endpoint = new Uri(...);}));
  })
  .WithTracing(WithTracing =>
  {
    traceBuilder.SetSampler(new AlwaysOnSampler());
    traceBuilder.AddAspNetCoreInstrumentation();
    traceBuilder.AddHttpClientInstrumentation();
    traceBuilder.AddOtlpExporter(otlpOptions =>{ otlpOptions.Endpoint = new Uri(...);}));
  });
  

Manuell

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .ConfigureResource(res => res.AddService(ServiceName))
    .AddAspNetCoreInstrumentation()
    .AddHttpClientInstrumentation()
    .AddOtlpExporter(otlpOptions =>{ otlpOptions.Endpoint = new Uri(...);
    .Build();
using var meterProvider = Sdk.CreateMeterProviderBuilder()
    .ConfigureResource(res => res.AddService(ServiceName))
    .AddAspNetCoreInstrumentation()
    .AddHttpClientInstrumentation()
    .AddOtlpExporter(otlpOptions =>{ otlpOptions.Endpoint = new Uri(...);
    .Build();

Eigene Instrumentation

Für die Integration in eigene Anwendungen bzw. Bibliotheken ist keine Abhängigkeit zu OpenTelemetry erforderlich.

Activity / ActivitySource

ActivitySource ActivitySource = new ActivitySource(ActivitySourceName, ...)
using var activity = ActivitySource.StartActivity(ActivityName);

Metriken

Meter Meter = new Meter(MeterName, ...)
var counter = Meter.CreateCounter<int>(CounterName, Unit, Description);
var gauge = Meter.CreateObservableGauge<int>(CounterName, ()=>GetValue(), Unit, Description);

Logging

Das Logging erfolgt über das Interface ILogger bzw. ILogger<T>.

OpenTelemetry

Collector

Ãœberblick

  • Der Collector ist das zentrale Element von OpenTelemetry.
  • Er sammelt, verarbeitet und leitet Telemetriedaten in einer Pipeline weiter.
  • Die Ein- und Ausgänge der Pipelines folgen etablierten Standards, darunter Prometheus, Jaeger und Fluent Bit.
  • Die Umstellung der Systeme auf den Collector kann schrittweise erfolgen.
  • Unterstützung verschiedener CPU Architekturen
    u.a. x86/x64, PPC und ARM7/ARM64.
  • Für verschiedene Betriebssysteme
    wie Windows, MacOS und Linux verfügbar.
  • Die Konfiguration erfolgt in YAML oder CRD für den Operator.

Bestandteile

Receiver

Der Receiver liefert die Eingangswerte, die entweder über
eine Schnittstelle empfangen (Push) oder abgerufen (Pull) werden können.
Es erfolgt eine Umsetzung in einen einheitlichen Datenstrom.
Die Parameter der Receiver sind spezifisch.

Exporter

Die Exporter stellen die Ausgabewerte bereit, die
entweder passiv über Schnittstellen abgerufen (Pull)
oder aktiv an ein System weitergeleitet (Push) werden.

Processor

Die Prozessoren verarbeiten den Datenstrom innerhalb der Pipeline. Dabei erfolgt die Filterung, Anreicherung und Umwandlung der Daten.

Extensions

Diese Erweiterungen erweitern die primären Funktionen des Collectors um Aspekte wie Monitoring (Health-Checks) und Speichermanagement.

Service / Pipelines

Die Pipeline beschreibt den Fluss der Telemetriedaten vom Receiver zu einem Exporter. Dabei können Prozessoren und Erweiterungen durchlaufen werden.

Konfiguration

receivers:
  otlp:
    protocols:
      grpc:{}
      http:{}
exporters:
  jaeger:
    endpoint: jaeger.fqdn:14250
extensions:
  health_check: {}
processors:
  batch: {}
service:
  extensions:
  - health_check
  pipelines:
    traces:
      exporters:
      - jaeger
      processors:
      - batch
      receivers:
      - otlp

Struktur

Beispielanwendung

Konfiguration

receivers:
  otlp:
    protocols:
      grpc: {}
processors:
  resource:
    attributes:
    - action: insert
      key: loki.resource.labels
      value: service.name
exporters:
  otlp/jeager:
    endpoint: jaeger-collector.jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: "${MY_POD_IP}:9090"
    send_timestamps: true
    metric_expiration: 15m
    resource_to_telemetry_conversion:
      enabled: true
  loki:
    endpoint: http://loki-write.loki:3100/loki/api/v1/push
  otlp/aspecto:
    endpoint: otelcol.aspecto.io:4317
    headers:
      Authorization: not-my-key
service:
  pipelines:
    logs:
      receivers:
        - otlp
      processors:
        - resource
      exporters:
        - loki
    metrics:
      receivers:
        - otlp
      exporters:
        - prometheus
    traces:
      receivers:
        - otlp
      exporters:
        - otlp/jeager
        - otlp/aspecto
Lokale Anwendungen GitHub
Externe Anwendungen GitHub

OpenTelemetry

Metriken mit .NET

Ãœberblick

  • Metriken sind quantitative Daten, die normalerweise aggregiert werden.
  • Sie dienen der Messung und Ãœberwachung der Leistung
    und des Verhaltens eines Systems.
  • Arten von Metriken sind u.a. Zähler, Messwerte und Histogramme.
  • Beispiele für Metriken sind Anfragen pro Sekunde und die CPU-Auslastung.
  • Metriken sind nützlich, um Trends, Muster und Engpässe
    in einer Anwendung zu erkennen.
  • Es gibt umfassende Instrumentierungen für die .NET-Runtime,
    ASP.NET, gRPC und andere Frameworks.
  • Die Konfiguration erfolgt über die Klasse MeterProviderBuilder.

Exporter

OpenTelemetry ermöglicht die Exportierung von Metriken
an den Otel-Collector, Otel-Agent oder andere Ziele
u.a. Prometheus oder TSDB wie InfluxDB.

OpenTelemetry Exporter

Der OpenTelemetry Exporter ermöglicht die Übertragung
von Metriken über das OpenTelemetry-Protokoll.
Services.AddOpenTelemetry()
  .WithMetrics(mb => mb.AddOtlpExporter(opt =>  opt.Endpoint = new Uri(...)));

Prometheus Exporter

ASP.NET
Die Metriken werden über die Pipeline von ASP.NET bereitgestellt.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry().WithMetrics(mb => mb.AddPrometheusExporter());
var app = builder.Build();
app.UseOpenTelemetryPrometheusScrapingEndpoint();
HTTP Listener
Der Prometheus Exporter bietet auch einen HTTP-Endpunkt,
der unabhängig von ASP.NET verwendet werden kann.
var meterProvider = Sdk.CreateMeterProviderBuilder()
  .AddMeter(MyMeter.Name)
  .AddPrometheusHttpListener(options => options.UriPrefixes =
        new string[] { "http://localhost:9090/" })
  .Build();

Meter

Die Meter-Instanz stellt Methoden zum Erstellen verschiedener Metriken zur Verfügung. Sie sollte nur einmal erstellt und bereinigt werden.
Der Name ist wichtig für die Registrierung bei OpenTelemetry.
public const string MeterName = "fp.monitoring.workshop.instrumentationlibrary";
using Meter meter = new Meter(MeterName);
Services.AddOpenTelemetry().WithMetrics(mb =>  mb.AddMeter(MeterName));

Counter

Counter-Metriken werden beim Start der Anwendung mit 0 initialisiert und ausschließlich inkrementiert. Daraus lassen sich Wachstumsraten ableiten. Für die Methode Add führt die Wertänderungen durch.
Negative Änderungen sind möglich, aber nicht vorgesehen.
Beispiel: Aufrufe einer API seit Start.
Counter<int> executionCounter = meter.CreateCounter<int>("executions");
executionCounter.Add(1);
Vergleichbar mit der Counter-Metrik, hält die ObservableCounter-Metrik den State (Wert) nicht selbst, sondern erhält ihn über eine Funktion.
ObservableCounter<int> executionCounter = 
        meter.CreateCounter<int>("executions", ()=> QueryExecutions());

UpDownCounter

UpDownCounter-Metriken sind mit den Counter-Metriken vergleich. Abweichend sind Inkrementierung und Dekrementierung vorgesehen.

Beispiel: Länge einer Warteschlange

UpDownCounter<int> queryItemCounter = meter.CreateUpDownCounter<int>("query.lenght");
  queryItemCounter.Add(5);
  queryItemCounter.Add(-1);
Die ObservableUpDownCounter-Metriken unterstützen
die externe Wertermittlung per Funktion.
ObservableUpDownCounter<int> queryLength = 
        meter.CreateObservableUpDownCounter<int>("query.lenght", ()=> MyQuery.Lenght);
Einige Exporter haben keine native Unterstützung für UpDownCounter-Metriken. Sie werden dann meist als Gauge-Metrik exportiert.

Gauge

Gauge-Metriken können beliebige Zahlenwerte annehmen. Die Ermittlung des aktuellen Wertes erfolgt bei der Veröffentlichung der Metriken.
Je nach Aufwand ist ein Cache zu empfehlen.

Beispiele: aktueller RAM-Verbrauch, Anzahl Objekte im Lager.

meter.CreateObservableGauge("my_sample_gauge", observeValue: () => ObserveMyValue());

Histogram

Histogram-Metriken stellen die Verteilung von Zahlenwerten in Buckets dar.

Beispiel: Verarbeitungszeit je Anfrage.

Histogram<double> mySampleHistogram = meter.CreateHistogram<double>("my_sample_histogram");
mySampleHistogram.Record(14.5d);
mySampleHistogram.Record(67.1d);

Bucket-Boundaries

OpenTelemetry liefert einen Default für die Bucketgrenzen der Histogramme. Eigene Werte können ggf. in diesen Grenzen unzureichend abgebildet werden. Die Konfiguration eines View definiert eigene Bucket-Boundaries.

Services.AddOpenTelemetry().WithMetrics(mb =>{ ...
    mb.AddView("my_sample_histogram", 
    new ExplicitBucketHistogramConfiguration
    {
        Boundaries = new double[] {1, 2, 5, 10, 50, 100}
    });
});

Einheiten / Beschreibungen

Einheiten (Units) und Beschreibung (Descriptions) sind optionale Metadaten. Diese werden bei der Erstellung der Metriken angegeben.

UpDownCounter<int> queryLength = 
        meter.CreateUpDownCounter<int>("query.lenght","wi", "count of unprocessed work items");
meter.CreateObservableGauge("system.ram.free", 
        ()=>> querySystemRam(), "MiB", "amount of free system memory"); 

Label

Labels sind optionale Metadaten, die den jeweiligen Metrik-Wert näher beschreiben. Die Angabe erfolgt als 1 .. n Key-Value-Pair.

MyIntSampleCounter.Add(2, KeyValuePair.Create<string, object>("label1", value));

MySampleHistogram.Record(14.5d, new TagList{{"label1", 42}});     

meter.CreateObservableGauge("my.sample.gauge",              
  observeValue: () => new Measurement<int>(42,
      KeyValuePair.Create<string, object>("label2", "value2")));

OpenTelemetry

Tracing mit .NET

Ãœberblick

  • Tracing beschäftigt sich mit der Ãœberwachung und Verfolgung
    von Anfragen und Transaktionen in Anwendungen und Diensten.
  • Ziel ist die Erfassung der zeitlichen Abläufe,
    um Leistungsprobleme zu identifizieren.
  • Es ermöglicht die Verfolgung von Anfragen über verschiedene Dienste hinweg und unterstützt die Fehlerdiagnose in verteilten Systemen.
  • Die Verknüpfung des Anfragekontexts dient dazu,
    die Anfragen zu korrelieren.
  • Spezieller Code muss in Anwendungen eingebettet werden,
    um das Tracing durchzuführen.
  • Tracing mit OpenTelemetry und C# nutzt bestehende Strukturen (ActivitySource / Activity).

Sampler

  • Sampler können dazu beitragen, den Overhead
    durch das Tracing zu reduzieren.
  • Die Ausführung erfolgt vor der Erstellung der Activity / des Spans.
  • Sampler verfügen nur über wenige Kontextinformationen zur Ausführung.
  • Standard-Sampler umfassen:
    • AlwaysOnSampler
    • AlwaysOffSampler
    • ParentBasedSampler
    • TraceIdRatioBasedSampler
  • Erstellung / Verwendung eigener Sampler kann performancekritisch sein.

Registrierung

OpenTelemetry exportiert Traces zum Otel-Collector bzw. Otel-Agent.
Die Auswahl eines Samplers ist verpflichtend.
Services.AddOpenTelemetry()
  .WithTracing(tb => 
  {
    tb.SetSampler(SAMPLER);
    tb.AddOtlpExporter(opt =>  opt.Endpoint = new Uri(...));
  });
Services.AddOpenTelemetry()
        .WithTracing(tb => 
        {
            ...
            tb.AddAspNetCoreInstrumentation();
            tb.AddHttpClientInstrumentation();
            tb.AddGrpcClientInstrumentation();
            ...
        });

ActivitySource

Die ActivitySource-Instanz ermöglicht die Erstellung eigener Aktivitäten. Sie sollte nur einmal erstellt und später bereinigt werden.
Die Angabe der Version ist optional.
public const string ActivitySourceName = "fp.monitoring.workshop.instrumentationlibrary";
using myActivitySource = new ActivitySource(ActivitySourceName);
Der Name ist wichtig für die Registrierung bei OpenTelemetry.
Services.AddOpenTelemetry()
  .WithTracing(tb => 
    {  tb.AddSource(ActivitySourceName);  });

Activity / Span

Mit der Methode StartActivity wird eine neue Aktivität erstellt. Diese ist innerhalb des Scopes (using) als Activity.Current verfügbar.
Aktivitäten können ineinander geschachtelt werden.
Der Name ist optional; der Standardwert ist der Methodenname.
using var activity = ActivitySource.StartActivity(ActivityName);
Die Methode CreateActivity erstellt eine neue Aktivität. Sie überschreibt nicht Activity.Current. In der Praxis spielt diese Funktion meist keine Rolle.

Wenn die ActivitySource nicht in OpenTelemetry registriert ist,
geben StartActivity und CreateActivity null zurück.

Tags

Tags sind Metadaten, die Aktivitäten schreiben oder von diesen gelesen werden. Die Informationen müssen serialisierbar sein.
Große Datenmengen sollten vermieden werden.
Activity.Current?.SetTag("myKey", myValue)
var value = Activity.Current?.GetTagItem("myKey");

Status

Jede Aktivität enthält zusätzlich einen der Statuswerte Ok, UnSet oder Error.
Activity.Current?.SetStatus(ActivityStatusCode.Ok);
var status = Activity.Current?.GetStatus()

Activity Events

Anders als Aktivitäten und Spans haben Activity Events keinen Zeitraum. Sie beschreiben vielmehr einen Zeitpunkt. Ein Anwendungsfall ist die Fehlerüberwachung. Die Extension-Methode RecordException sammelt Informationen zur Exception und erstellt ein Activity Event. .
var activityEvent = new ActivityEvent("Something is happen...");
Activity.Current?.AddEvent(activityEvent);
Activity.Current?.RecordException(EXCEPTION);

OpenTelemetry

Logging mit .NET

Ãœberblick

  • Logging beinhaltet das Aufzeichnen von Ereignissen, Nachrichten, Fehlern oder Statusinformationen aus Anwendungen und Systemen.
  • Jeder Eintrag ist mit einem Zeitpunkt versehen.
  • Die Korrelation mehrerer Einträge kann durch künstliche
    Schlüssel oder den Zeitpunkt erfolgen.
  • Die Protokollierung erfolgt in strukturierter oder unstrukturierter Form.
  • Logging hilft dabei, Sicherheitsverletzungen zu erkennen und
    erleichtert die Einhaltung von Vorschriften und Richtlinien.
  • Die Unterstützung für Logging wurde als letzter Bestandteil von OpenTelemetry in C# eingeführt.

Registrierung

Im Gegensatz zu Metriken und Traces wird das Logging nicht über die IServiceCollection also den DI-Container konfiguriert. Stattdessen wird ein ILoggingBuilder verwendet, der beispielsweise
vom WebApplicationBuilder bereitgestellt wird.
OpenTelemetry exportiert Traces zum Otel-Collector bzw. Otel-Agent.
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();        
builder.Logging.AddOpenTelemetry(options =>
  {
    options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(ServiceName));
    otlpOptions.Endpoint = new Uri(...);
  }
Die ASP.NET Core Umgebung gibt standardmäßig Logs auf die Konsole aus.
Dieses Verhalten wird durch ClearProviders deaktiviert.

Erstellung

Für das Logging wird eine Instanz von ILogger bzw.
ILogger<T> verwendet, die über Dependency Injection bereitgestellt wird.
public class MyClass()
{
    private ILogger<MyClass> _logger;
    public MyClass(ILogger<MyClass> logger)
    {
        _logger = logger;
    }
    public void DoSomething()
    {
        _logger.LogInformation("INFO");
    }
}

Vielen Dank für die Aufmerksamkeit




Mail: frank@pommerening-consulting.de
Xing: https://www.xing.com/profile/Frank_Pommerening3/
LinkedIn: https://www.linkedin.com/in/frank-pommerening-9050411b0/