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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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.
Pourquoi Zipkin ?
Section intitulée « Pourquoi Zipkin ? »Zipkin est reconnu pour sa simplicité de déploiement et son écosystème mature, particulièrement dans les environnements Java/Spring.
| Caractéristique | Description |
|---|---|
| Licence | Apache 2.0 |
| Origine | Twitter (2012) |
| Langage | Java |
| Protocoles | Zipkin B3, OpenTelemetry |
| Force | Simplicité, Spring Cloud Sleuth |
Concepts clés
Section intitulée « Concepts clés »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.
Le format B3
Section intitulée « Le format B3 »Zipkin utilise le format de propagation B3 via les headers HTTP :
X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7X-B3-SpanId: e457b5a2e4d86bd1X-B3-ParentSpanId: 05e3ac9a4f6e3b90X-B3-Sampled: 1Ou au format condensé :
b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1Annotations
Section intitulée « Annotations »Les spans Zipkin utilisent des annotations pour marquer les événements :
| Annotation | Signification |
|---|---|
cs | Client Send - début de la requête |
cr | Client Receive - fin de la requête |
sr | Server Receive - réception de la requête |
ss | Server Send - réponse envoyée |
Architecture
Section intitulée « Architecture »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) │ │ │ └───────────────┘ │ └─────────────────────┘Installation
Section intitulée « Installation »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.
docker run -d -p 9411:9411 openzipkin/zipkin:3.6.1
# UI disponible sur http://localhost:9411version: '3'services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0 environment: - discovery.type=single-node - xpack.security.enabled=false - ES_JAVA_OPTS=-Xms256m -Xmx256m ports: - "9200:9200"
zipkin: image: openzipkin/zipkin:3.6.1@sha256:d17e856dcbba7ffeefbbfc252f89ab78a4ab6faed47e646d46daad78f91b5ee2 environment: - STORAGE_TYPE=elasticsearch - ES_HOSTS=elasticsearch:9200 ports: - "9411:9411" depends_on: - elasticsearchLe script quickstart.sh s'exécute en curl | bash. Préférez le téléchargement direct du JAR exécutable depuis Maven Central, avec une version épinglée :
curl -sSL -o zipkin.jar \ 'https://repo1.maven.org/maven2/io/zipkin/zipkin-server/3.6.1/zipkin-server-3.6.1-exec.jar'
java -jar zipkin.jar
# Avec ElasticsearchSTORAGE_TYPE=elasticsearch ES_HOSTS=localhost:9200 java -jar zipkin.jarapiVersion: apps/v1kind: Deploymentmetadata: name: zipkin namespace: observabilityspec: replicas: 1 selector: matchLabels: app: zipkin template: metadata: labels: app: zipkin spec: containers: - name: zipkin image: openzipkin/zipkin:3.6.1@sha256:d17e856dcbba7ffeefbbfc252f89ab78a4ab6faed47e646d46daad78f91b5ee2 ports: - containerPort: 9411 env: - name: STORAGE_TYPE value: elasticsearch - name: ES_HOSTS value: elasticsearch:9200 resources: limits: memory: 512Mi cpu: 500m---apiVersion: v1kind: Servicemetadata: name: zipkin namespace: observabilityspec: ports: - port: 9411 targetPort: 9411 selector: app: zipkinConfiguration
Section intitulée « Configuration »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.
Variables d'environnement
Section intitulée « Variables d'environnement »STORAGE_TYPE vaut mem par défaut ; les blocs Elasticsearch, Cassandra et
MySQL ne sont lus que si le stockage correspondant est sélectionné.
# Type de stockageSTORAGE_TYPE=elasticsearch # mem, elasticsearch, cassandra, mysql
# ElasticsearchES_HOSTS=http://elasticsearch:9200ES_INDEX=zipkinES_DATE_SEPARATOR=-ES_INDEX_REPLICAS=1ES_INDEX_SHARDS=5
# CassandraCASSANDRA_CONTACT_POINTS=cassandra1,cassandra2CASSANDRA_KEYSPACE=zipkin
# MySQLMYSQL_HOST=mysqlMYSQL_USER=zipkinMYSQL_PASS=zipkin
# SamplingCOLLECTOR_SAMPLE_RATE=1.0 # 0.0 à 1.0Rétention
Section intitulée « Rétention »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.
# Elasticsearch - suppression des anciens indexES_INDEX_MAX_AGE=7 # jours
# Pour cassandra, utiliser TTLCASSANDRA_SPAN_TTL=604800 # 7 jours en secondesInstrumentation
Section intitulée « Instrumentation »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.
Spring Cloud Sleuth (Spring Boot 2.x)
Section intitulée « Spring Cloud Sleuth (Spring Boot 2.x) »Sleuth ne concerne que Spring Boot 2.x : le projet n'est plus maintenu au-delà, remplacé par Micrometer Tracing.
<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>spring: zipkin: base-url: http://zipkin:9411 sleuth: sampler: probability: 1.0 # 100% en dev, réduire en prodMicrometer Tracing (Spring Boot 3.x)
Section intitulée « Micrometer Tracing (Spring Boot 3.x) »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.
<dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId></dependency><dependency> <groupId>io.zipkin.reporter2</groupId> <artifactId>zipkin-reporter-brave</artifactId></dependency>management: tracing: sampling: probability: 1.0 zipkin: tracing: endpoint: http://zipkin:9411/api/v2/spansLe 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_spanfrom 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 passOpenTelemetry (universel)
Section intitulée « OpenTelemetry (universel) »Zipkin supporte le format OTLP, permettant d'utiliser les SDKs OpenTelemetry.
from opentelemetry.exporter.zipkin.json import ZipkinExporterfrom 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))API Zipkin
Section intitulée « API Zipkin »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.
Envoyer des spans
Section intitulée « Envoyer des spans »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é.
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" } } ]'Rechercher des traces
Section intitulée « Rechercher des traces »minDuration s'exprime en microsecondes : la valeur 100000 retient donc les
traces de plus de 100 ms.
# Par servicecurl "http://zipkin:9411/api/v2/traces?serviceName=api"
# Avec filtrescurl "http://zipkin:9411/api/v2/traces?serviceName=api&spanName=get&minDuration=100000"
# Par trace IDcurl "http://zipkin:9411/api/v2/trace/5982fe77008310cc80f1da5e10147517"Lister les services
Section intitulée « Lister les services »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.
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"]Interface utilisateur
Section intitulée « Interface utilisateur »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é.
Fonctionnalités
Section intitulée « Fonctionnalités »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
Graphe de dépendances
Section intitulée « Graphe de dépendances »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
Déploiement production
Section intitulée « Déploiement production »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.
Haute disponibilité
Section intitulée « Haute disponibilité »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.
apiVersion: apps/v1kind: Deploymentmetadata: name: zipkinspec: 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: 9411Scaling Elasticsearch
Section intitulée « Scaling Elasticsearch »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/v1kind: Elasticsearchmetadata: name: zipkin-esspec: 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: 100GiSampling
Section intitulée « Sampling »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.
Côté collector
Section intitulée « Côté collector »COLLECTOR_SAMPLE_RATE s'applique une fois les spans arrivés : le trafic
réseau est déjà consommé, seul le stockage est économisé.
# Rejeter X% des spans au niveau du collectorCOLLECTOR_SAMPLE_RATE=0.1 # Garder 10%Côté client (recommandé)
Section intitulée « Côté client (recommandé) »Le sampling côté client réduit le trafic réseau.
spring: sleuth: sampler: probability: 0.1 # 10% des tracesSampling adaptatif
Section intitulée « Sampling adaptatif »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@Beanpublic 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; } };}Monitoring
Section intitulée « Monitoring »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.
Métriques Prometheus
Section intitulée « Métriques Prometheus »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.
docker run -d -p 9411:9411 \ -e SELF_TRACING_ENABLED=true \ openzipkin/zipkin:3.6.1Mé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.
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"Bonnes pratiques
Section intitulée « Bonnes pratiques »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.
-
Utilisez Elasticsearch en production
Le stockage mémoire perd les données au redémarrage.
-
Configurez le sampling côté client
Évitez de surcharger le collector avec 100% des traces.
-
Ajoutez des tags métier
span.tag("user.id", userId);span.tag("order.amount", amount); -
Définissez une rétention
Les traces accumulent beaucoup de données. 7-14 jours suffisent généralement.
Migration vers OpenTelemetry
Section intitulée « Migration vers OpenTelemetry »Si vous envisagez de migrer vers OpenTelemetry tout en conservant Zipkin :
# OpenTelemetry SDK → Zipkin exporterfrom opentelemetry.exporter.zipkin.json import ZipkinExporter
# Fonctionne comme avant, mais avec les SDKs OTel modernesexporter = ZipkinExporter(endpoint="http://zipkin:9411/api/v2/spans")Dépannage
Section intitulée « Dépannage »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ôme | Cause probable | Solution |
|---|---|---|
| Traces incomplètes | Headers B3 non propagés | Vérifier middleware |
| UI lente | Trop de traces | Activer le sampling |
| Elasticsearch full | Pas de rétention | Configurer ILM |
| 503 au collector | Mémoire insuffisante | Augmenter heap |
# Healthcheckcurl http://zipkin:9411/health
# Vérifier les spans reçuscurl http://zipkin:9411/api/v2/services
# Logsdocker logs zipkinFAQ : questions fréquentes
Section intitulée « FAQ : questions fréquentes »Les questions qui reviennent le plus souvent au moment de choisir Zipkin et de le faire cohabiter avec OpenTelemetry.
X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId, X-B3-Sampled, ou en version condensée dans l'en-tête b3. Il permet de relier les spans d'une même requête d'un service à l'autre. C'est un standard supporté au-delà de Zipkin./api/v2/spans du serveur Zipkin (qui répond 202). On le fait depuis un SDK (Spring, Go, Python) ou directement en curl. On récupère ensuite les traces via /api/v2/services, /api/v2/spans?serviceName=... et /api/v2/trace/<id>. Zipkin accepte aussi les traces via un exporter OpenTelemetry.À retenir
Section intitulée « À retenir »- 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
Prochaines étapes
Section intitulée « Prochaines étapes »Ressources
Section intitulée « Ressources »Les références officielles à garder sous la main pour aller plus loin que ce guide.