đ¶ Partie 3 : Pilier 2 - APM (Traces DistribuĂ©es)
Deep dive sur l'Instrumentation, Traces, Spans, Flame Graphs, et Corrélation.
Qu'est-ce que l'APM ?
Le "OĂč" et "Pourquoi" (la cause). (MĂ©triques = "Quoi"). Connecte les piliers.
APM TracingConcepts : Trace & Span
Trace = Le voyage complet (requĂȘte). Span = L'Ă©tape (unitĂ© de travail).
Trace SpanAuto-Instrumentation
Le "comment ça marche". "Monkey-patching" (Flask, Django, requests...).
Auto-Instrumentation dd-trace-runSetup Détaillé (Agent & Libs)
Ătape 1 (Agent apm: true) + Ătape 2 (Libs dd-trace-py, javaagent).
Instrumentation Custom
@tracer.wrap(). Instrumenter la logique métier (ex: validate_cart).
Corrélation : Traces <-> Logs
Injection du trace_id. (Pivot "Trace" -> "Logs" et "Logs" -> "Trace").
Corrélation : Traces <-> Métriques
Métriques auto-générées (trace.*.hits, .errors, .latency).
Visualisation (UI)
Service Map (Dépendances), Service Page (Santé), Flame Graph (Latence).
Service Map Flame GraphCheat-sheet (APM)
Setup (Python, Java, Node.js), Span Custom (Python).
cheat SetupL'APM (ou "Tracing") est le processus qui suit une requĂȘte **Ă travers** votre application et **Ă travers** les microservices.
Alors que les MĂ©triques vous disent "Le service api-users est lent" (le Quoi), l'APM vous dit "Il est lent parce que le SELECT sur la table users_auth prend 3000ms" (le OĂč et le Pourquoi).
L'APM est le pilier central qui **connecte tous les autres piliers**.
Pour comprendre l'APM, il faut comprendre ces deux concepts.
- Trace : L'ensemble du "voyage" d'une requĂȘte. (Ex: le temps total d'un
GET /api/checkout). Une Trace est un ensemble (un arbre) de Spans. - Span : Une "unité de travail" (une opération) **à l'intérieur** d'une Trace. (Ex: l'appel à la base de données, la sérialisation JSON, l'appel à une API externe).
Diagramme : Anatomie d'une Trace Distribuée
Imaginez une requĂȘte client qui touche 3 microservices (Frontend, API-Users, DB-Auth).
Client (RequĂȘte: 1200ms)
|
+--- [Trace ID: 123] -------------------------------------------------------------+
| |
| Service: Frontend (Span A: 1100ms) |
| | |
| +---- [Span B: Appel API /api/users (1000ms)] -------------------------------+
| | | |
| | Service: API-Users (Span C: 950ms) | |
| | | | |
| | +--- [Span D: Auth Check (500ms)] -----+ |
| | | | | |
| | | Service: DB-Auth (Span E: 450ms)| | |
| | | | (db.query(...)) | | | |
| | | +---------------------------+ | | |
| | | | | |
| | +--- [Span F: Business Logic (400ms)] | |
| | | | |
| | +--------------------------------------+ |
| | |
| +-------------------------------------------------------------------------+
| | |
| +---- [Span G: Rendu React (100ms)] |
| |
+--------------------------------------------------------------------------------+
DataDog propage le trace_id: 123 (via les headers HTTP) à travers tous les services et reconstruit ce "Flame Graph". Vous voyez instantanément que Span D (Auth Check) est le goulot d'étranglement, causé par Span E (la DB).
Comment DataDog capture-t-il les Spans (ex: db.query) sans que vous n'ayez Ă modifier votre code ?
La réponse est l'Auto-Instrumentation.
Lorsque vous lancez votre application avec la bibliothÚque de tracing DataDog (ex: dd-trace-py), celle-ci "monkey-patche" (modifie à la volée) les bibliothÚques standards (Flask, Django, requests, psycopg2, Redis...) pour qu'elles "émettent" des Spans avant et aprÚs leurs opérations.
Diagramme : Auto-Instrumentation (Python/Flask)
Votre Code (myapp.py):
-------------------------
@app.route("/")
def hello():
# (requests.get est "patché" !)
r = requests.get("google.com")
return "OK"
-------------------------
|
| (Vous exécutez:)
âŒ
$ dd-trace-run python myapp.py
|
âŒ
Code Réel (Exécuté par dd-trace-run):
-------------------------
@app.route("/")
def hello():
# (Code injecté par dd-trace)
span = tracer.start_span("requests.get")
span.set_tag("http.url", "google.com")
r = requests.get("google.com")
# (Code injecté par dd-trace)
span.finish()
return "OK"
-------------------------
L'APM nécessite **deux** composants :
1. L'Agent DataDog (HÎte) : Doit avoir l'APM activé (il écoute sur le port 8126).
2. La BibliothĂšque (Client) : Doit ĂȘtre installĂ©e dans votre application (ex: dd-trace-py).
Ătape 1 : Activer l'APM sur l'Agent
L'Agent (voir Partie 1) doit avoir ces configurations (datadog.yaml ou variables d'environnement).
# (datadog.yaml)
apm_config:
enabled: true
# (Variables d'environnement Docker/K8s)
# DD_APM_ENABLED="true"
# (Pour le tracing non-local, ex: Lambda)
# DD_APM_NON_LOCAL_TRAFFIC="true"
Ătape 2 : Installer les Librairies (Application)
Ceci dépend de votre langage applicatif.
Python (dd-trace-py)
Lancement via dd-trace-run (auto-instrumentation).
# 1. Installer la librairie
pip install dd-trace
# 2. Configurer (Tags unifiés)
export DD_ENV="production"
export DD_SERVICE="api-users"
export DD_VERSION="1.2.0"
# 3. Lancer (Ex: app Flask)
# (NE PAS lancer "python app.py")
dd-trace-run gunicorn myapp:app
Java (dd-java-agent.jar)
Lancement via -javaagent (auto-instrumentation de la JVM).
# (Télécharger dd-java-agent.jar)
# Lancer l'application .jar
java -javaagent:./dd-java-agent.jar \
-Ddd.env="production" \
-Ddd.service="api-payments" \
-Ddd.version="1.2.0" \
-jar /path/to/my-app.jar
Node.js (dd-trace)
Importation (init()) au tout début du code.
# 1. Installer la librairie
npm install dd-trace
# 2. Initialiser (index.js - TOUT EN HAUT)
const tracer = require('dd-trace');
tracer.init({
env: 'production',
service: 'api-frontend',
version: '1.2.0'
});
// (Reste du code... ex: require('express'))
const express = require('express');
const app = express();
// (Express est auto-instrumenté)
L'auto-instrumentation est parfaite pour l'infrastructure (http.request, db.query), mais elle ne voit pas votre **logique métier**.
Exemple : Une requĂȘte /checkout (Span A) prend 500ms. L'auto-instrumentation vous montre un db.query (Span B) de 50ms. OĂč sont passĂ©es les 450ms restantes ?
Elles sont dans votre code (validation, calcul de TVA...). Nous devons ajouter des Spans custom.
Exemple (Python - Décorateur @tracer.wrap())
On utilise le décorateur @tracer.wrap() pour créer un nouveau Span autour d'une fonction métier.
from ddtrace import tracer
# (Auto-instrumentation de Flask)
@app.route("/checkout")
def handle_checkout():
# ...
# (Appel Ă notre fonction custom)
# (Sans le décorateur, ce temps est "perdu")
validation_result = validate_cart(cart)
# ...
return "OK"
# 1. Instrumentation Custom
@tracer.wrap(
name="ecommerce.validation", // (Nom du Span)
service="api-checkout" // (Tag 'service')
)
def validate_cart(cart):
# 2. Récupérer le Span (optionnel)
span = tracer.current_span()
# 3. Ajouter des Tags Custom (CRUCIAL)
# (Permet de rechercher/filtrer les traces)
span.set_tag("cart.id", cart.id)
span.set_tag("user.id", cart.user_id)
try:
# (Logique métier...)
if cart.value > 10000:
span.set_tag("cart.review_required", True)
# ... (Logique lente) ...
except Exception as e:
# 4. Reporter les erreurs
span.set_tag("error", True)
span.set_exc_info(type(e), e, e.__traceback__)
raise
return True
C'est la "magie" de DataDog. L'APM (Trace) est le pivot qui relie tout.
Lorsque l'APM (ex: dd-trace-py) est activé, il **injecte automatiquement** le trace_id et span_id actuels dans vos logs (surtout si vous loguez en JSON, ou via DD_LOGS_INJECTION=true).
Exemple (Log Python JSON)
{
"timestamp": "2024-10-27T10:30:05Z",
"level": "ERROR",
"message": "Payment failed for user 123",
"service": "api-payment",
"env": "production",
// (Injecté par dd-trace)
"dd": {
"trace_id": "4567890123456",
"span_id": "9876543210987"
}
}
Résultat (Pivot) :
1. (Vue "Trace" -> "Logs") : Vous regardez un Flame Graph (Trace APM) d'une requĂȘte /checkout lente. L'onglet "Logs" vous montre uniquement les logs (y compris l'erreur "Payment failed") de *cette requĂȘte prĂ©cise*.
2. (Vue "Log" -> "Trace") : Vous trouvez ce Log d'erreur. Vous cliquez sur le trace_id. DataDog vous amÚne au Flame Graph complet, vous montrant ce qui s'est passé *avant* et *aprÚs* l'erreur.
L'APM génÚre **automatiquement** des métriques (DISTRIBUTION) à partir des Traces.
Pour chaque service et chaque resource (endpoint, ex: flask.request ou db.query), DataDog génÚre :
trace.<NOM_SPAN>.request.hits(COUNT) : Nb de requĂȘtes (Hits).trace.<NOM_SPAN>.request.errors(COUNT) : Nb d'erreurs.trace.<NOM_SPAN>.request.latency(DISTRIBUTION) : La latence.
Ces métriques sont la base des "Service Pages" et des "Monitors" (Alertes).
Exemple (Alerte sur Latence DB)
Cela vous permet de créer des Monitors (Partie 6) basés sur vos Spans :
# (Alerte si la latence p95 des requĂȘtes DB (auto-instrumentĂ©es)
# du service 'db-auth' dépasse 500ms)
p95(last_5m):trace.db.query.latency{env:prod,service:db-auth} > 500
Une fois l'APM configuré, vous passez votre temps dans ces 3 écrans (APM > Services) :
| Ăcran | Description | Usage |
|---|---|---|
| Service Map | Carte auto-générée des dépendances (basée sur les traces). | "Quels services (API, DB, Kafka) mon api-users appelle-t-il ?" |
| Service Page | Le "Dashboard" d'un microservice (service:api-users). | "Quel est le Taux d'Erreur, la Latence p95, et le Nb de Hits de ce service ?" |
| Traces (Flame Graph) | La vue "Waterfall" (diagramme de Gantt) d'une requĂȘte unique. | "Pourquoi *cette* requĂȘte spĂ©cifique ( trace_id: 456...) a-t-elle pris 3 secondes ?" |
Setup (Agent)
# (datadog.yaml)
apm_config:
enabled: true
# (Docker Env)
# DD_APM_ENABLED="true"
Setup (Python)
# 1. Install
pip install dd-trace
# 2. Set Env
export DD_ENV="prod"
export DD_SERVICE="my-api"
# 3. Run
dd-trace-run gunicorn app:main
Setup (Java)
java -javaagent:./dd-java-agent.jar \
-Ddd.env="prod" \
-Ddd.service="my-api" \
-jar my-app.jar
Span Custom (Python)
from ddtrace import tracer
@tracer.wrap(name="my.custom.span")
def ma_fonction_lente(param1):
# (Optionnel: get span)
span = tracer.current_span()
# (Ajouter des tags)
span.set_tag("mon.tag", param1)
# (Marquer une erreur)
if error:
span.set_tag("error", True)
span.set_exc_info(...)
return ...
Corrélation Logs (Python JSON)
# (Activer l'injection)
export DD_LOGS_INJECTION="true"
# (Log JSON contiendra:)
# "dd": {
# "trace_id": "...",
# "span_id": "..."
# }
