OpenTelemetry – Teil 3: So nutzen Sie OpenTelemetry in Compute

Unsere erste OpenTelemetry Bibliothek für Compute ist ab sofort verfügbar. Sie ermöglicht es Ihrer Compute Anwendung, spezifikationskonforme Traces zu generieren, die tiefere Einblicke in ihre Performance und Ressourcen bieten. In diesem Blogpost zeige ich Ihnen, wie einfach Sie diese Funktion zu Ihrer Edge-Anwendung hinzufügen können.

Unsere Compute Plattform führt WebAssembly Module über unser globales Netzwerk von Edge-Servern aus, die aus einer Sprache Ihrer Wahl kompiliert wurden. Derzeit bieten wir offiziell Unterstützung für Rust, AssemblyScript und JavaScript an, aber einfallsreiche Kunden haben auch bereits in C++, Swift und Zig geschriebene Anwendungen ausgeliefert.

OpenTelemetry unterstützt Bibliotheken für viele dieser Sprachen und bietet Entwicklern die Möglichkeit, mit SDK-Objekten und -Aufrufen zu arbeiten, anstatt in der Rohprotokollsprache zu sprechen, wie wir es tun mussten, als wir in Teil 2 dieser Blogpost-Reihe OpenTelemetry zu einem VCL-Service hinzugefügt haben. Die von den OpenTelemetry Bibliotheken bereitgestellten Standardexports lassen sich nicht direkt nutzen, da Compute (aus Sicherheitsgründen) die Kommunikation mit der Außenwelt auf Host-Aufrufe über unsere SDKs beschränkt. Dank des modularen Ansatzes von OpenTelemetry können wir aber die offiziellen OpenTelemetry Bibliotheken um Compute-kompatible Komponenten erweitern.

So konnten wir, angefangen bei JavaScript, an der Unterstützung von OpenTelemetry auf Compute arbeiten.

Einrichten eines Collectors

Bevor wir uns mit der Übermittlung von Daten beschäftigen, brauchen wir einen Collector zum Sammeln der von Ihrer Anwendung gesendeten Traces. In einer Produktivumgebung würden Sie einen OpenTelemetry Collector mit aktiviertem OTLP-over-HTTP-Empfänger verwenden, der auf einem von Ihrer Anwendung aus erreichbaren Server läuft. Der Collector wiederum exportiert seine Daten an ein Backend. OpenTelemetry kann an viele Arten von Backends exportieren, zum Beispiel an Instanzen von Jaegar oder ZipKin, oder an Services wie Honeycomb.

Beim lokalen Experimentieren bringen Sie einen Collector am einfachsten zum Laufen, indem Sie die OpenTelemetry Collector Demo verwenden. Sie setzt einen Collector sowie eine Reihe von Backends wie Jaeger in Gang. Die Screenshots in diesem Blogpost spiegeln dieses Setup wider und stammen aus der Jaeger UI.

Um die Collector Demo mit den Beispielen in diesem Blogpost zu verwenden, brauchen Sie Docker und müssen die Anweisungen zum Ausführen der Demo befolgen. Dabei sind allerdings ein paar Änderungen an der Konfiguration notwendig, um OTLP-over-HTTP zu aktivieren. Ändern Sie die Datei otel-collector-config.yaml, um das HTTP-Protokoll zu aktivieren:

receivers:
otlp:
protocols:
grpc:
http: # Add this
exporters:
...

Die Collector Demo läuft in Docker, also stellen wir den Port zur Verfügung. Und wenn wir schon dabei sind, deaktivieren wir einige seiner Komponenten, die wir nicht brauchen. Fügen Sie docker-compose.override.yaml zu examples/demo/ hinzu:

version: "2"
services:
otel-collector:
ports:
- "4318:4318" # OTLP HTTP receiver
demo-server:
profiles:
- disabled
demo-client:
profiles:
- disabled

Starten Sie nun die Collector Demo:

$ cd examples/demo
$ docker-compose up

Sobald Sie alles zum Laufen gebracht haben, können Sie Traces an Ihren Collector senden. Der Collector exportiert Traces sowohl an Jaeger als auch an ZipKin. Die Jaeger UI finden Sie unter http://localhost:16686/. Alternativ können Sie auch die Nutzeroberfläche von ZipKin unter http://localhost:9411/ verwenden. 

Nachdem wir nun bereit sind, Daten zu sammeln, können wir uns mit OpenTelemetry in Compute austoben.

Hinzufügen von OpenTelemetry zu einer Anwendung

Typischerweise umfasst eine mit OpenTelemetry instrumentierte JavaScript Anwendung eine Initialisierungsdatei für das Tracing (mit einem Namen wie „tracing.js“), die Instanzen der folgenden Elemente erzeugt:

  • Ressource: Beschreibt dem Collector die Anwendung

  • Instrumentierungen: Module, die sich in Plattform- oder Framework-Ereignisse einklinken, um Instrumentierungsdaten zu erzeugen

  • Exporter: Modul, das OTel Daten entgegennehmen und an einen externen Collector weiterleiten kann

  • SDK: Ein Agent, der alle oben genannten Elemente miteinander verbindet

Ich habe ein Paket in JavaScript erstellt, um diese Elemente für Compute zu implementieren. Dieses finden Sie als Open-Source-Datei unter @fastly/compute-js-opentelemetry auf npm. Das Paket enthält die folgenden nutzerdefinierten Komponenten und mehr:

  • Instrumentierungen für den Request Lifecycle von Fastly sowie für Backend Fetches von Ihrem Edge-Code

  • Einen nutzerdefinierten Exporter, der OTel Daten mithilfe von Fastlys Backend-Fetch- oder Echtzeit-Logging-Mechanismen an einen Collector senden kann

  • Ein nutzerdefiniertes SDK, das die Verknüpfung von Exporter, Instrumentierungen und Ressourcen erleichtert

Wie leicht es ist, OpenTelemetry hinzuzufügen, zeige ich Ihnen anhand einer einfachen, in JavaScript geschriebenen Compute Anwendung:

/// <reference types='@fastly/js-compute' />
async function handleRequest(event) {
const backendResponse = await fetch('https://httpbin.org/json', {
backend: 'httpbin',
});
const data = await backendResponse.text();
return new Response(data.length, {
status: 200,
Headers: {
'Content-Type': 'text/plain',
},
});
}
addEventListener('fetch', (event) => event.respondWith(handleRequest(event)));

In diesem Beispiel wird ein Request verarbeitet, indem Daten aus dem Backend „httpbin“ abgerufen werden, die Größe dieser Daten ermittelt und anschließend eine Antwort an den Client erstellt wird, die das Ergebnis enthält.

Um OpenTelemetry hinzufügen zu können, fügen wir @fastly/compute-js-opentelemetry sowie einige OpenTelemetry Bibliotheken zu unserem Projekt hinzu:

$ npm install @fastly/compute-js-opentelemetry
$ npm install @opentelemetry/resources @opentelemetry/semantic-conventions

Um diese Bibliotheken auf Compute nutzen zu können, brauchen Sie einige Polyfills und Shims. Diese können Sie bequem importieren, indem Sie während des Build-Prozesses das Hilfspaket @fastly/compute-js-opentelemetry/webpack-helpers anwenden. Nehmen Sie an der Datei webpack.config.js in Ihrem Projekt folgende Anpassungen vor:

// Reference the helper module
const webpackHelpers = require("@fastly/compute-js-opentelemetry/webpack-helpers");
module.exports = {
entry: "./src/index.js",
/* ... other configuration */
};
// Add this line
module.exports = webpackHelpers.apply(module.exports);

Erstellen Sie anschließend eine Initialisierungsdatei namens tracing.js für das Tracing, die wie folgt aussieht:

import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { FastlySDK } from '@fastly/compute-js-opentelemetry/sdk-fastly';
import { OTLPTraceExporter } from '@fastly/compute-js-opentelemetry/exporter-trace-otlp-fastly-backend';
import { getComputeJsAutoInstrumentations } from '@fastly/compute-js-opentelemetry/auto-instrumentations-compute-js';
const sdk = new FastlySDK({
traceExporter: new OTLPTraceExporter({backend: 'otlp-collector'}),
instrumentations: [getComputeJsAutoInstrumentations(),],
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'my-fastly-service',
}),
});
await sdk.start();

Dieser Code sieht einer typischen Tracing-Initialisierungsdatei aus dem Node.js-Tutorial von OpenTelemetry sehr ähnlich. Wir haben aber einige Komponenten durch Compute Implementierungen ersetzt. Der aufgelistete Exporter setzt einen Collector voraus, der beim Fastly Service als festes Backend „otlp-collector“ registriert ist.

Importieren Sie tracing.js in Ihre Anwendung, um das Tracing zu aktivieren:

/// <reference types='@fastly/js-compute' />
import './tracing.js';
async function handleRequest(event) {
...

Führen Sie nun Ihre Anwendung aus und schon erhalten Sie Spans für Ihre Requests!

Otel blog pt. 3 - image 1

Dieser Trace liefert uns interessante Einblicke.

Zunächst sehen wir einen Span für den gesamten Request, der als „FetchEvent“ dargestellt wird, und untergeordnete Spans für „listener fn“, „Backend Fetch“ (zu example.com) und den Aufruf „event.respondWith“, aus denen sich das Diagramm für einen einzelnen Aufruf zusammensetzt. Man beachte dabei, dass die Ausführung von „listener fn“ und von „event.respondWith“ sehr kurz ist. Das liegt daran, dass die Handler-Funktion direkt ein Versprechen zurückgibt. Die Rückgabe eines Versprechens durch die Handler-Funktion ermöglicht es den Compute Services, die Verarbeitung fortzusetzen, nachdem „listener fn“ beendet ist. Unser Programm fährt in dieser Zeit mit dem Backend Fetch fort. Das SDK nutzt diesen Mechanismus auch, um die Lebensdauer des Ereignisses zu verlängern, bis die Daten an den Collector gesendet wurden, und macht Ihnen das Leben dadurch leichter.

Es ist wirklich großartig, dass „tracing.js“ völlig unabhängig vom Code Ihrer Anwendung ist. Der Tracing-Mechanismus und die Standard-Instrumentierungen, die in dieser Datei konfiguriert sind, reichen aus, um Telemetriedaten zu generieren, ohne Ihre Anwendung weiter anweisen zu müssen.

Nutzerdefinierte Spans

Dennoch gibt es Fälle, in denen Sie Ihren Code mit nutzerdefinierten Spans und Ereignissen instrumentieren möchten. Vielleicht haben Sie eine Funktion, deren Start- und Endzeit Sie markieren möchten, oder Sie möchten wissen, wie oft sie aufgerufen wird.

Da wir OpenTelemetry in seiner Standardversion implementieren, ist auch das möglich:

$ npm install @opentelemetry/api
/// <reference types='@fastly/js-compute' />
import './tracing.js';
import { context, trace } from "@opentelemetry/api";
async function handleRequest(event) {
const tracer = trace.getTracerProvider()
.getTracer('my-tracer');
const mySpan = tracer.startSpan('my-task');
context.with(trace.setSpan(context.active(), mySpan), () => {
doTask(); // spend some time in a task
});
mySpan.end();
return new Response('OK', {
status: 200,
headers: new Headers({"Content-Type": "text/plain"}),
});
}
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

Der generierte Trace sieht wie folgt aus:

Otel blog #3 image 2

Diesmal gibt es keinen Backend Fetch. Stattdessen haben wir einen 19 ms langen nutzerdefinierten Span namens „my-task“.

Der einzige Fastly-spezifische Code befindet sich im Tracing-Initialisierungsmodul, und alles, was im Hauptquelltext des Programms mit OpenTelemetry geschieht, wird mit den standardmäßigen OpenTelemetry APIs durchgeführt. Für Fastly ist hier nichts Besonderes erforderlich. Jeder Code, den Sie in Ihre Compute Anwendung einbinden und der bereits mit OpenTelemetry instrumentiert ist, funktioniert reibungslos, und der von uns eingerichtete Tracing-Mechanismus erfasst auch diese Daten.

Weitergabe von Trace-Kontext

Ich habe soeben erwähnt, dass der Tracing-Mechanismus OpenTelemetry Daten von anderem Code erfasst, der in Ihre Compute Anwendung integriert ist und von Ihrem eigenen Code erzeugt wird. Mit OpenTelemetry können wir aber noch einen Schritt weiter gehen und sogar Daten von anderen Prozessen und Web-APIs erfassen, die Sie von Ihrer Fastly Anwendung aus aufrufen.

Einer der Gründe, warum OpenTelemetry so leistungsstark und beliebt ist, besteht darin, dass Sie damit eine Gesamtübersicht über die Architektur Ihres Systems erstellen können, wobei Ihre Traces nahtlos zwischen den einzelnen Komponenten des Systems und sogar zwischen Prozessen und Servern übertragen werden. Dies wird durch die Weitergabe des Trace-Kontexts ermöglicht. Kurzum: Ein Trace, der in einer Komponente beginnt, gibt seinen Kontext an andere Komponenten, die er aufruft, weiter. Wie wir in Teil 2 dieser Blogpost-Reihe gesehen haben, ist dies bei Web-APIs mithilfe des Traceparent-Headers möglich. 

Die in @fastly/compute-js-opentelemetry enthaltenen Instrumentierungen unterstützen diesen Mechanismus sowohl für eingehende als auch für ausgehende Requests. Wenn Ihre Anwendung also innerhalb eines bestehenden Trace-Kontexts aufgerufen wird, extrahiert diese Bibliothek die Informationen des entsprechenden Traces aus den Headern des eingehenden Requests und verwendet diesen Trace als Parent für alle generierten Spans. Wenn Ihre Anwendung Backend Fetches durchführt, fügt diese Bibliothek den aktuellen Trace-Kontext in die Header der Anfrage ein, wenn diese Fetches durchgeführt werden. Das Beste daran ist, dass durch die Initialisierung des Tracing-Mechanismus mit getComputeJsAutoInstrumentations diese Funktionen automatisch aktiviert werden.

Sie können also per einfachem Aufruf von APIs, die OpenTelemetry übertragen, ganz ohne Zusatzaufwand verschachtelte Traces wie diesen generieren:

Otel #3 - image 3

Die Leistungsstärke von OpenTelemetry liegt darin, dass der Collector alle Komponenten, die Traces aussenden, am Ende wieder zusammenfügen kann, um solche Graphen zu erzeugen. So können Sie die gesamte Lebensdauer eines einzelnen End-to-End-Runs Ihrer kompletten Anwendung über alle ihre Bestandteile hinweg verfolgen.

Alle diese Daten lassen sich mit beliebigen Analyse- und Insight-Tools auswerten. Wir sind davon überzeugt, dass Sie mit unseren Tools den größtmöglichen Nutzen aus Ihrer Systemarchitektur ziehen können.

Aktuelle Herausforderungen

OpenTelemetry ist relativ neu. Es gibt noch einige kleine Stolpersteine, die aber mit zunehmender Reife der offiziellen Bibliotheken und der Unterstützung durch immer mehr Tools, Plattformen und Frameworks aus dem Weg geräumt werden sollten.

Insbesondere die API für Metriken und das SDK der JavaScript OpenTelemetry Bibliotheken befinden sich noch in der Entwicklung. Daher kann unsere Bibliothek die Metriken zum jetzigen Zeitpunkt nicht vollständig unterstützen. Wir planen, unsere Bibliothek mit zunehmenden Fortschritten der offiziellen Bibliotheken weiter zu aktualisieren, und hoffen, dass es schon bald möglich sein wird, Metriken zu verwenden.

Entdecken Sie unsere Bibliothek

Neugierig? Vielleicht sind Sie ja bald schon genauso begeistert von OpenTelemetry wie ich, als ich zum ersten Mal die Trace-Diagramme sah, die von meinen eigenen Demos ausgegeben wurden. Sehen Sie sich doch einfach mal den Quellcode und die Dokumentation für die in diesem Blogpost beschriebene JavaScript Bibliothek „OpenTelemetry for Compute“ an, die unter fastly/compute-js-opentelemetry auf GitHub erhältlich ist.

Wir freuen uns, dass OpenTelemetry jetzt auch für Compute verfügbar ist. Wenn Sie diese Bibliothek zur Instrumentierung Ihrer Compute Anwendung nutzen, wären wir dankbar, wenn Sie uns davon berichten. Lassen Sie uns wissen, wie es bei Ihnen gelaufen ist.

Im vierten Teil dieser Blogpost-Reihe stellen wir Ihnen eine Fallstudie vor, in der wir zeigen, wie wir OpenTelemetry für die Instrumentierung unseres eigenen Fiddle Tools einsetzen. Seien Sie also gespannt!


Alle vier Teile unserer OpenTelemetry Blogserie sind nun online:

Katsuyuki Omuro
Senior Software Engineer, Developer Relations
Veröffentlicht am

Lesedauer: 7 Min.

Sie möchten sich mit einem Experten austauschen?
Sprechen Sie mit einem Experten
Diesen Beitrag teilen
Katsuyuki Omuro
Senior Software Engineer, Developer Relations

Katsuyuki („Kats“) Omuro ist ein in Japan tätiger Developer und Echtzeit-Web-Enthusiast aus dem Developer Relations Team. Er tüftelt Dinge leidenschaftlich gerne aus und teilt sein Wissen mit anderen, um sie beim Lernen und der Weiterentwicklung zu unterstützen.

Sie möchten loslegen?

Setzen Sie sich mit uns in Verbindung oder erstellen Sie einen Account.