Telemetrie

Grundlagen, Micrometer und abgrenzung zu Tracing

Einleitung

Welches Problem gibt es?

  • Aufgabenstellung

    • Fehlerquellen identifizieren

    • Performance optimieren

  • Herausforderungen

    • Analyse von komplexen Anwendungen (z.B.: k8s).

    • Mangelnde Übersicht und fehlende Kennzahlen.

Was ist Telemetrie?

  • Definition: Automatische Sammlung von Metriken, Logs und Events.

  • Aufgabe: Systemzustand analysieren und optimieren.

  • Bestandteile: Metriken (Zähler, Timer), Logs, Tracing.

Wie Telemetrie dieses Problem löst

  • Einblicke in Systemzustände.

  • Erleichtert Fehlerdiagnose und Performance-Monitoring.

    • Option einer grafischen Darstellung

    • Automatisierte Alarme und Benachrichtigungen.

Beispiel: Kubernetes-Cluster

  • Metriken: CPU- und Speicherauslastung.

  • Logs: Fehlermeldungen und Debug-Informationen.

  • Tracing

    • Nachverfolgung von Request durch verschiedene Services. (request-flows)

    • Bottlenecks identifizieren

Micrometer

Was ist Micrometer?

Micrometer in der Praxis

  • Integration mit Quarkus:

    • Einfaches Hinzufügen durch Erweiterungen (micrometer-registry-prometheus).

    • Erfassung von JVM-Metriken (Heap, Garbage Collection, Threads).

  • Visualisierung: Daten an Prometheus senden, mit Grafana anzeigen.

Live Demo: Micrometer in Quarkus

Schritt 1: Micrometer in Quarkus einbinden

quarkus-micrometer-prometheus-Dependency hinzufügen.

Schritt 2: Metriken sammeln

@Path("/palindrome")
@Produces("application/json")
public class PalindromeResource {
    private final MeterRegistry registry;
    private final LinkedList<String> list = new LinkedList<>();

    public PalindromeResource(MeterRegistry registry) {
        this.registry = registry;
        registry.gaugeCollectionSize("palindrome.list.size", Tags.empty(), list);
    }

    @GET
    @Path("counter/check/{input}")
    public boolean checkPalindromeCounter(@PathParam("input") String input) {
        list.add(input);

        registry.counter("palindrome.counter").increment();
        boolean result = internalCheckPalindrome(input);
        return result;
    }

    @GET
    @Path("timer/check/{input}")
    public boolean checkPalindromeAndTimer(@PathParam("input") String input) {
        list.add(input);

        Timer.Sample sample = Timer.start(registry);
        boolean result = internalCheckPalindrome(input);
        sample.stop(registry.timer("palindrome.timer"));
        return result;
    }

    private boolean internalCheckPalindrome(String input) {
        int left = 0;
        int right = input.length() - 1;

        while (left < right) {
            if (input.charAt(left) != input.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

    @DELETE
    @Path("empty-list")
    public void emptyList() {
        list.clear();
    }
}

Schritt 3: Package erstellen

mvn package

Schritt 4: Container mit Quarkus, Prometheus und Grafana starten

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
    restart: always
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    restart: always
    networks:
      - monitoring

  quarkus:
    build:
      context: ../../../
      dockerfile: ./src/main/docker/Dockerfile.jvm
    container_name: quarkus
    ports:
      - "8080:8080" # Optional, for host access
    restart: always
    networks:
      - monitoring

networks:
  monitoring:
    driver: bridge

Schritt 5: Prometheus

Schritt 6: Grafana

Alternativen zu Micrometer

  • Dropwizard Metrics:

    • Älter, weniger flexibel.

  • Spring Boot Actuator (eingebaut, aber weniger universell).

  • OpenTelemetry (vollständige Lösung für Telemetrie inkl. Tracing).

Tracing

Welches Problem gibt es?

  • Identifizierung von Performance-Problemen

  • Bottleneck identifizieren

  • Warum dauert ein Request so lange?

Was ist Tracing?

  • Nachverfolgung von Requests auf der Seite des Servers

  • Analyse von vielen Requests

  • Zeit zwischen einzelnen Schritten messen

Was ist ein Trace?

spans-traces

Daten Sammeln mit OpenTelemetry

OpenTelemetry ist ein Open-Source-Framework zur Sammlung und Verarbeitung von Metriken, Logs und Traces für Observability in modernen Anwendungen.

LiveDemo: LeoVote - Tracing mit Jaeger

jaeger-logo

Tracing in Quarkus

OpenTelemetry

OpenTelemetry ist ein Open-Source-Framework zur Sammlung und Verarbeitung von:

  • Metriken

  • Logs

  • Traces

OpenTelemetry Tracing in Quarkus

Dependencies

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-opentelemetry</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-jdbc</artifactId>
</dependency>

Application.properties

# Enable OpenTelemetry tracing

quarkus.otel.exporter.otlp.endpoint=http://localhost:4317

quarkus.otel.traces.sampler=always_on

quarkus.otel.service.name=quarkus-backend

# For JDBC telemetry
quarkus.datasource.jdbc.telemetry=true

Jaeger Tracing

apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: simplest

Live Demo

LeoVote

Custom Span

public Uni<Response> sendInvite(@PathParam("electionId") Long electionId) {
Span span = tracer.spanBuilder("sendEmails").startSpan();

        Optional<Election> election = Election.findByIdOptional(electionId);

        if (election.isEmpty()) {
            return Uni.createFrom().item(Response.status(Response.Status.NOT_FOUND).build());
        }

        try (Scope scope = span.makeCurrent()) {

            // Perform email sending logic in a background task
            emailService.sendInvitations(election.get()).subscribe().with(
                    success -> System.out.println("Emails sent successfully"),
                    failure -> System.out.println("Emails could not be sent\n" + failure.toString())
            );
        } finally {
            span.end();
        }

        return Uni.createFrom().item(Response.ok().entity("{\"message\": \"Emails are being sent asynchronously.\"}").build());
    }

Jaeger in Docker

docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.62.0

Kubernetes

minikube start --cpus 15 --memory=8g --driver=docker
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml
kubectl create namespace observability (1)
export WORKING_DIR=/tmp/jaeger
export NAMESPACE=observability  # Change if needed
./generate_certs.sh
kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.62.0/jaeger-operator.yaml
kubectl delete secret jaeger-operator-service-cert -n observability
kubectl create secret tls jaeger-operator-service-cert \
  --cert=${WORKING_DIR}/user.jaeger.crt \
  --key=${WORKING_DIR}/user.jaeger.key \
  -n observability

Im Leovote projekt in den k8s Ordner wechseln und folgendes ausführen:

kubectl apply -f .

Es kommt ein Error deshalb einfach das ausführen:

kubectl apply -f ./jaeger.yaml

Danke für eure Aufmerksamkeit!

Quellen