Aller au contenu
English
Outils medium

Zipkin : Tracing distribué léger

21 min de lecture

Zipkin est un système de tracing distribué open source, créé par Twitter, qui collecte les temps d'exécution et visualise le parcours d'une requête à travers les microservices. Simple à déployer (un seul conteneur) et mature dans l'écosystème Java/Spring, il reste une valeur sûre aux côtés de Jaeger et Tempo.

Ce guide s'adresse aux profils débutants et intermédiaires. Vous allez déployer Zipkin (version 3.6.1), comprendre le format de propagation B3, instrumenter vos applications, et utiliser l'API pour envoyer et rechercher des traces.

  • Déployer Zipkin avec Docker, en mémoire ou sur Elasticsearch.
  • Comprendre le format de propagation B3 et les annotations.
  • Instrumenter une application (Spring, Go, Python, OpenTelemetry).
  • Envoyer et rechercher des traces via l'API /api/v2/.
  • Configurer le sampling et la rétention.

Zipkin est reconnu pour sa simplicité de déploiement et son écosystème mature, particulièrement dans les environnements Java/Spring.

CaractéristiqueDescription
LicenceApache 2.0
OrigineTwitter (2012)
LangageJava
ProtocolesZipkin B3, OpenTelemetry
ForceSimplicité, Spring Cloud Sleuth

Deux notions suffisent pour lire une trace Zipkin : la façon dont le contexte circule entre les services, et la façon dont chaque étape est horodatée. Une trace représente le parcours complet d'une requête ; elle se découpe en spans, un span étant une opération unitaire avec un début, une fin et un service d'origine. Sans propagation de ce contexte d'un service à l'autre, chaque application produirait des spans isolés, impossibles à recoller en une trace unique.

Zipkin utilise le format de propagation B3 via les headers HTTP :

X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7
X-B3-SpanId: e457b5a2e4d86bd1
X-B3-ParentSpanId: 05e3ac9a4f6e3b90
X-B3-Sampled: 1

Ou au format condensé :

b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1

Les spans Zipkin utilisent des annotations pour marquer les événements :

AnnotationSignification
csClient Send - début de la requête
crClient Receive - fin de la requête
srServer Receive - réception de la requête
ssServer Send - réponse envoyée

Zipkin tient dans un seul processus qui assume trois rôles : le collector reçoit les spans envoyés par les applications, le storage les persiste, et l'UI les restitue. Les trois partagent le port 9411, qui sert donc à la fois l'interface web et l'API d'ingestion : c'est ce qui rend le déploiement aussi court. Le stockage est le seul élément réellement externalisable, vers Elasticsearch ou Cassandra.

┌─────────────────────────────────────────────────────────────────┐
│ Applications instrumentées │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Spring │ │ Go │ │ Node │ │
│ │ Service │ │ Service │ │ Service │ │
│ │ (Sleuth) │ │ (zipkin-go) │ │ (zipkin-js) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────────────┘
│ │ │
└──────────────────┴──────────────────┘
▼ HTTP POST /api/v2/spans
┌─────────────────────┐
│ Zipkin Server │
│ │
│ ┌───────────────┐ │
│ │ Collector │ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ Storage │ │
│ │ (ES/Cassandra)│ │
│ └───────┬───────┘ │
│ │ │
│ ┌───────▼───────┐ │
│ │ UI │ │
│ │ (port 9411) │ │
│ └───────────────┘ │
└─────────────────────┘

Quatre modes de déploiement, du plus rapide au plus durable. Le mode Docker minimal démarre avec le stockage mem, qui garde tout en mémoire vive : parfait pour découvrir, mais toutes les traces disparaissent au redémarrage. Dès que vous voulez conserver un historique, fixez STORAGE_TYPE=elasticsearch. Dans les quatre cas, l'interface répond sur le port 9411.

Fenêtre de terminal
docker run -d -p 9411:9411 openzipkin/zipkin:3.6.1
# UI disponible sur http://localhost:9411

Zipkin se configure entièrement par variables d'environnement, sans fichier à monter dans le conteneur. C'est ce qui permet d'utiliser la même image en local et en cluster, en ne changeant que l'environnement. Deux réglages pèsent plus que les autres : le type de stockage et la rétention.

STORAGE_TYPE vaut mem par défaut ; les blocs Elasticsearch, Cassandra et MySQL ne sont lus que si le stockage correspondant est sélectionné.

Fenêtre de terminal
# Type de stockage
STORAGE_TYPE=elasticsearch # mem, elasticsearch, cassandra, mysql
# Elasticsearch
ES_HOSTS=http://elasticsearch:9200
ES_INDEX=zipkin
ES_DATE_SEPARATOR=-
ES_INDEX_REPLICAS=1
ES_INDEX_SHARDS=5
# Cassandra
CASSANDRA_CONTACT_POINTS=cassandra1,cassandra2
CASSANDRA_KEYSPACE=zipkin
# MySQL
MYSQL_HOST=mysql
MYSQL_USER=zipkin
MYSQL_PASS=zipkin
# Sampling
COLLECTOR_SAMPLE_RATE=1.0 # 0.0 à 1.0

Zipkin ne purge rien de lui-même : la rétention se règle dans le moteur de stockage, par âge d'index côté Elasticsearch et par TTL côté Cassandra.

Fenêtre de terminal
# Elasticsearch - suppression des anciens index
ES_INDEX_MAX_AGE=7 # jours
# Pour cassandra, utiliser TTL
CASSANDRA_SPAN_TTL=604800 # 7 jours en secondes

Instrumenter une application, c'est lui faire produire des spans et les envoyer au collector. Zipkin fournit des bibliothèques natives pour les principaux langages et accepte aussi les spans émis par les SDK OpenTelemetry. Tous les exemples partagent deux constantes : l'endpoint d'ingestion /api/v2/spans et un nom de service stable, car ce nom sert de filtre dans l'interface et de nœud dans le graphe de dépendances.

Sleuth ne concerne que Spring Boot 2.x : le projet n'est plus maintenu au-delà, remplacé par Micrometer Tracing.

pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
application.yml
spring:
zipkin:
base-url: http://zipkin:9411
sleuth:
sampler:
probability: 1.0 # 100% en dev, réduire en prod

Sur Spring Boot 3.x, le pont micrometer-tracing-bridge-brave prend la relève, et l'endpoint se déclare sous la clé management.zipkin et non plus spring.zipkin.

pom.xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
application.yml
management:
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans

Le client officiel zipkin-go réclame trois éléments : un reporter qui assure le transport vers le collector, un endpoint local portant le nom du service, et un sampler qui décide des traces à conserver.

import (
"github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
)
func initTracer() (*zipkin.Tracer, error) {
reporter := zipkinhttp.NewReporter("http://zipkin:9411/api/v2/spans")
endpoint, _ := zipkin.NewEndpoint("my-service", "localhost:8080")
tracer, err := zipkin.NewTracer(
reporter,
zipkin.WithLocalEndpoint(endpoint),
zipkin.WithSampler(zipkin.AlwaysSample),
)
return tracer, err
}

Le BatchRecorder regroupe les spans avant de les transmettre au HttpLogger, ce qui évite une requête HTTP par span.

const { Tracer, ExplicitContext, BatchRecorder } = require('zipkin');
const { HttpLogger } = require('zipkin-transport-http');
const tracer = new Tracer({
ctxImpl: new ExplicitContext(),
recorder: new BatchRecorder({
logger: new HttpLogger({
endpoint: 'http://zipkin:9411/api/v2/spans',
}),
}),
localServiceName: 'my-service',
});

La bibliothèque py_zipkin s'emploie en décorateur : chaque appel de la fonction décorée produit un span portant le span_name déclaré.

from py_zipkin.zipkin import zipkin_span
from py_zipkin.transport import SimpleHTTPTransport
transport = SimpleHTTPTransport('http://zipkin:9411/api/v2/spans')
@zipkin_span(service_name='my-service', span_name='my_operation')
def my_function():
# ... code
pass

Zipkin supporte le format OTLP, permettant d'utiliser les SDKs OpenTelemetry.

from opentelemetry.exporter.zipkin.json import ZipkinExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
zipkin_exporter = ZipkinExporter(endpoint="http://zipkin:9411/api/v2/spans")
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(zipkin_exporter)
)

Tout ce que fait l'interface web passe par l'API REST exposée sous /api/v2/, et rien n'empêche de l'appeler directement. C'est le moyen le plus court de vérifier qu'un collector reçoit bien des données sans instrumenter quoi que ce soit, et d'automatiser un contrôle depuis un pipeline. Attention à l'unité : les champs timestamp et duration s'expriment en microsecondes.

Le corps de la requête est un tableau JSON de spans, même pour un span unique ; le collector répond 202 Accepted avec un corps vide quand l'envoi est accepté.

Fenêtre de terminal
curl -X POST http://zipkin:9411/api/v2/spans \
-H 'Content-Type: application/json' \
-d '[
{
"traceId": "5982fe77008310cc80f1da5e10147517",
"id": "5982fe77008310cc",
"name": "get /api/users",
"timestamp": 1472470996199000,
"duration": 207000,
"localEndpoint": {
"serviceName": "api"
},
"tags": {
"http.method": "GET",
"http.path": "/api/users"
}
}
]'

minDuration s'exprime en microsecondes : la valeur 100000 retient donc les traces de plus de 100 ms.

Fenêtre de terminal
# Par service
curl "http://zipkin:9411/api/v2/traces?serviceName=api"
# Avec filtres
curl "http://zipkin:9411/api/v2/traces?serviceName=api&spanName=get&minDuration=100000"
# Par trace ID
curl "http://zipkin:9411/api/v2/trace/5982fe77008310cc80f1da5e10147517"

Ces deux appels alimentent les listes déroulantes de l'interface ; un service n'y apparaît qu'après avoir envoyé au moins un span.

Fenêtre de terminal
curl http://zipkin:9411/api/v2/services
# ["api", "frontend", "database"]
curl http://zipkin:9411/api/v2/spans?serviceName=api
# ["get /api/users", "post /api/orders"]

L'interface web est servie par le même processus que le collector, à l'adresse http://<hote>:9411/zipkin/. Elle répond à deux besoins distincts : retrouver une requête précise à partir de critères de recherche, et observer la forme globale des appels entre services. Aucune authentification n'est fournie en standard, placez-la derrière un reverse proxy si le réseau n'est pas isolé.

L'UI Zipkin (port 9411) permet de :

  • Rechercher des traces par service, span, durée, annotation
  • Visualiser les traces en timeline
  • Analyser les dépendances entre services
  • Télécharger les traces en JSON

Zipkin génère automatiquement un graphe des dépendances inter-services basé sur les traces collectées.

Accédez à : http://zipkin:9411/zipkin/dependency

Passer en production suppose trois décisions : un stockage externe (le mode mem est disqualifié), plusieurs répliques du serveur derrière un Service, et un cluster Elasticsearch dimensionné pour le volume de spans attendu. Le serveur Zipkin est sans état dès lors que le stockage est externalisé, donc les répliques n'ont besoin d'aucune coordination entre elles.

Les sondes interrogent l'endpoint /health : sans readinessProbe, le Service enverrait du trafic à un pod dont la connexion au stockage n'est pas encore établie.

zipkin-ha.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: zipkin
spec:
replicas: 3 # Multiple replicas
template:
spec:
containers:
- name: zipkin
image: openzipkin/zipkin:3.6.1@sha256:d17e856dcbba7ffeefbbfc252f89ab78a4ab6faed47e646d46daad78f91b5ee2
env:
- name: STORAGE_TYPE
value: elasticsearch
- name: ES_HOSTS
value: elasticsearch:9200
resources:
limits:
memory: 1Gi
cpu: 1000m
requests:
memory: 512Mi
cpu: 500m
livenessProbe:
httpGet:
path: /health
port: 9411
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /health
port: 9411

Ce manifeste s'appuie sur ECK, l'operator Elasticsearch officiel ; node.store.allow_mmap: false évite d'avoir à relever vm.max_map_count sur chaque nœud du cluster.

# Pour la production, utilisez un cluster ES dédié
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: zipkin-es
spec:
version: 8.11.0
nodeSets:
- name: default
count: 3
config:
node.store.allow_mmap: false
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi

Le sampling consiste à ne conserver qu'une fraction des traces. Tracer 100 % du trafic sature le collector et le stockage pour un gain faible, puisque les traces d'un même appel se ressemblent. La décision se prend à deux endroits : côté client avant l'envoi, ou côté collector à la réception. Les deux taux se multiplient si vous activez les deux, ce qui fait vite disparaître les traces attendues.

COLLECTOR_SAMPLE_RATE s'applique une fois les spans arrivés : le trafic réseau est déjà consommé, seul le stockage est économisé.

Fenêtre de terminal
# Rejeter X% des spans au niveau du collector
COLLECTOR_SAMPLE_RATE=0.1 # Garder 10%

Le sampling côté client réduit le trafic réseau.

application.yml (Spring)
spring:
sleuth:
sampler:
probability: 0.1 # 10% des traces

Un sampler personnalisé garde 100 % des traces en erreur tout en échantillonnant le reste, ce qui est le meilleur compromis pour enquêter sur des incidents.

// Sampler personnalisé basé sur le contexte
@Bean
public Sampler customSampler() {
return new Sampler() {
@Override
public SamplingDecision sample(String operation, long traceId) {
// Toujours tracer les erreurs
if (operation.contains("error")) {
return SamplingDecision.SAMPLE;
}
// 10% pour le reste
return Math.random() < 0.1
? SamplingDecision.SAMPLE
: SamplingDecision.NOT_SAMPLE;
}
};
}

Zipkin s'observe lui-même : il expose ses propres métriques au format Prometheus et sait tracer son activité interne. Surveiller le collector n'est pas un luxe, car des spans rejetés silencieusement produisent des traces incomplètes que l'on met longtemps à imputer à Zipkin plutôt qu'aux applications.

SELF_TRACING_ENABLED=true fait de Zipkin son propre client de tracing ; les métriques restent exposées sur /prometheus même sans cette option.

Fenêtre de terminal
docker run -d -p 9411:9411 \
-e SELF_TRACING_ENABLED=true \
openzipkin/zipkin:3.6.1

Métriques disponibles sur /prometheus.

Cette règle surveille la taille des spans reçus : des spans anormalement volumineux trahissent en général des tags métier trop verbeux.

prometheus-rules.yaml
groups:
- name: zipkin
rules:
- alert: ZipkinHighLatency
expr: histogram_quantile(0.99, zipkin_collector_span_bytes_bucket) > 1000000
for: 5m
labels:
severity: warning
annotations:
summary: "Zipkin spans trop volumineux"

Quatre réglages séparent un Zipkin de démonstration d'un Zipkin exploitable au quotidien. Ils tournent tous autour de la même tension : conserver assez de données pour diagnostiquer un incident, sans saturer le stockage.

  1. Utilisez Elasticsearch en production

    Le stockage mémoire perd les données au redémarrage.

  2. Configurez le sampling côté client

    Évitez de surcharger le collector avec 100% des traces.

  3. Ajoutez des tags métier

    span.tag("user.id", userId);
    span.tag("order.amount", amount);
  4. Définissez une rétention

    Les traces accumulent beaucoup de données. 7-14 jours suffisent généralement.

Si vous envisagez de migrer vers OpenTelemetry tout en conservant Zipkin :

# OpenTelemetry SDK → Zipkin exporter
from opentelemetry.exporter.zipkin.json import ZipkinExporter
# Fonctionne comme avant, mais avec les SDKs OTel modernes
exporter = ZipkinExporter(endpoint="http://zipkin:9411/api/v2/spans")

Les problèmes de Zipkin se signalent rarement par une erreur franche : ce sont des traces incomplètes ou une interface qui rame. Commencez toujours par vérifier que le collector reçoit quelque chose avec /api/v2/services : une liste vide innocente Zipkin et déplace l'enquête vers l'instrumentation des applications.

SymptômeCause probableSolution
Traces incomplètesHeaders B3 non propagésVérifier middleware
UI lenteTrop de tracesActiver le sampling
Elasticsearch fullPas de rétentionConfigurer ILM
503 au collectorMémoire insuffisanteAugmenter heap
Fenêtre de terminal
# Healthcheck
curl http://zipkin:9411/health
# Vérifier les spans reçus
curl http://zipkin:9411/api/v2/services
# Logs
docker logs zipkin

Les questions qui reviennent le plus souvent au moment de choisir Zipkin et de le faire cohabiter avec OpenTelemetry.

  • Simple à déployer : un seul JAR ou container
  • Écosystème Spring mature : Sleuth / Micrometer
  • Format B3 : standard de propagation supporté partout
  • OpenTelemetry compatible : migration progressive possible
  • UI intuitive : recherche et graphe de dépendances

Les références officielles à garder sous la main pour aller plus loin que ce guide.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn