Go Distributed Tracing with OpenTelemetry in 2026: Complete Observability for Microservices
Effectuez le traçage distribué avec OpenTelemetry en 2026 : une observabilité complète pour les microservices
Si vous continuez à déboguer les problèmes de microservices en "ajoutant des logs → redémarrant → lisant les logs", votre efficacité opérationnelle reste bloquée en 2018. Lorsqu'une requête traverse 5 services, 3 bases de données et 2 files d'attente de messages, sans traçage distribué, vous ne pouvez tout simplement pas identifier le goulot d'étranglement de latence. Le traçage distribué n'est pas optionnel - c'est l'un des trois piliers de l'observabilité des microservices (Métriques, Logs, Traçage).
En 2026, OpenTelemetry est devenu la norme de facto, avec Jaeger et Grafana Tempo prenant pleinement en charge le protocole OTLP. Cet article commence par l'architecture d'OpenTelemetry, fournit un code complet d'implémentation en Go, et couvre l'instrumentation automatique, l'instrumentation manuelle, la propagation du contexte et l'intégration backend.
Pourquoi le traçage distribué est essentiel pour les microservices
| Pilier de l'observabilité | Problème résolu | Outils typiques | Conséquence sans lui |
|---|---|---|---|
| Métriques | "Qu'est-ce qui ne va pas ?" | Prometheus | Impossible de quantifier l'ampleur du problème |
| Logs | "Où est l'erreur ?" | Loki/ELK | Impossible de localiser les erreurs spécifiques |
| Traces | "Pourquoi est-ce lent ? Où se situe le goulot d'étranglement ?" | Jaeger/Tempo | Ne peut pas localiser les goulets d'étranglement de latence |
| Tout combiné | "Image complète du problème" | Grafana | Ne voir que des fragments du problème |
Insight clé : Pour une requête lente traversant 5 services, les logs ne peuvent que vous dire "chaque service est lent", tandis que les traces peuvent vous indiquer "la requête de base de données du service 3 représente 80 % du temps."
1.
Architecture OpenTelemetry
L'architecture centrale d'OpenTelemetry : API → SDK → Exporter → Collector → Backend
[App] → [OTel API] → [OTel SDK] → [OTLP Exporter] → [OTel Collector] → [Jaeger/Tempo]
| Composant | Responsabilité | Obligatoire |
|---|---|---|
| OTel API | Interface d'instrumentation | Oui |
| OTel SDK | Échantillonnage, regroupement, export | Oui |
| OTLP Exporter | Envoyer au Collecteur | Oui |
| OTel Collector | Recevoir, traiter, acheminer | Recommandé (production) |
| Backend | Stockage, requête, affichage | Oui |
1.1 Initialiser le fournisseur OpenTelemetry
package tracing
import (
"context"
"fmt"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
func InitProvider(serviceName, collectorURL string) (func(context.Context) error, error) {
exporter, err := otlptracegrpc.New(context.Background(),
otlptracegrpc.WithEndpoint(collectorURL),
otlptracegrpc.WithInsecure(),
)
if err != nil {
return nil, fmt.Errorf("creating exporter: %w", err)
}
res, err := resource.New(context.Background(),
resource.WithAttributes(
semconv.ServiceNameKey.String(serviceName),
semconv.ServiceVersionKey.String("1.0.0"),
semconv.DeploymentEnvironmentKey.String("production"),
),
)
if err != nil {
return nil, fmt.Errorf("creating resource: %w", err)
}
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter,
sdktrace.WithBatchTimeout(5*time.Second),
sdktrace.WithMaxExportBatchSize(512),
),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)),
)
otel.SetTracerProvider(provider)
return provider.Shutdown, nil
}
2.
Auto-instrumentation vs Instrumentation manuelle
2.1 Instrumentation automatique HTTP
import (
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
shutdown, err := tracing.InitProvider("user-service", "otel-collector:4317")
if err != nil {
log.Fatal(err)
}
defer shutdown(context.Background())
mux := http.NewServeMux()
mux.HandleFunc("/users", handleGetUsers)
mux.HandleFunc("/orders", handleGetOrders)
handler := otelhttp.NewHandler(mux, "user-service",
otelhttp.WithMessageEvents(otelhttp.Read, otelhttp.Write),
)
http.ListenAndServe(":8080", handler)
}
2.2 Auto-instrumentation gRPC
CBLK3
2.3 Instrumentation automatique des bases de données
import (
"database/sql"
"go.opentelemetry.io/contrib/instrumentation/database/sql/otsql"
)
func initDB() *sql.DB {
db, err := otsql.Open("postgres", "postgres://localhost/mydb",
otsql.WithAttributes(semconv.DBSystemPostgreSQL),
)
if err != nil {
log.Fatal(err)
}
return db
}
2.4 Instrumentation manuelle
func ProcessOrder(ctx context.Context, order *Order) error {
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "ProcessOrder",
trace.WithAttributes(
attribute.String("order.id", order.ID),
attribute.Float64("order.amount", order.Amount),
),
)
defer span.End()
ctx, validateSpan := tracer.Start(ctx, "ValidateOrder")
if err := validate(order); err != nil {
validateSpan.RecordError(err)
validateSpan.SetStatus(codes.Error, err.Error())
validateSpan.End()
return err
}
validateSpan.End()
ctx, paySpan := tracer.Start(ctx, "ProcessPayment")
if err := processPayment(ctx, order); err != nil {
paySpan.RecordError(err)
paySpan.SetStatus(codes.Error, err.Error())
paySpan.End()
return err
}
paySpan.End()
return nil
}
Comparaison Auto vs Manuel :
| Dimension | Auto-instrumentation | Instrumentation manuelle |
|---|---|---|
| Invasivité | Nulle | Exige des modifications de code |
| Granularité | Niveau du framework (HTTP/gRPC/DB) | Niveau métier (toute fonction) |
| Richesse des attributs | Attributs standard | Attributs personnalisés |
| Surcharge de performance | Faible (optimisé par le framework) | Dépend du nombre d'instrumentations |
| Stratégie recommandée | Utiliser auto pour la couche de framework | Utiliser manuel pour les chemins critiques métier |
3.
Propagation du contexte de traçage
La propagation du contexte de traçage inter-services est au cœur du traçage distribué. OpenTelemetry utilise la norme W3C Trace Context.
3.1 Propagation HTTP
import (
"go.opentelemetry.io/otel/propagation"
)
func callDownstream(ctx context.Context, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
return http.DefaultClient.Do(req)
}
3.2 Propagation de la file de messages
func publishMessage(ctx context.Context, topic string, msg []byte) error {
carrier := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, carrier)
kafkaMsg := &kafka.Message{
TopicPartition: kafka.TopicPartition{Topic: &topic},
Value: msg,
Headers: make([]kafka.Header, 0, len(carrier)),
}
for k, v := range carrier {
kafkaMsg.Headers = append(kafkaMsg.Headers, kafka.Header{
Key: k, Value: []byte(v),
})
}
return producer.Produce(kafkaMsg, nil)
}
4.
Intégration de Jaeger et Tempo
4.1 Jaeger Tout-en-un (Développement)
apiVersion: apps/v1
kind: Deployment
metadata:
name: jaeger
spec:
selector:
matchLabels:
app: jaeger
template:
spec:
containers:
- name: jaeger
image: jaegertracing/all-in-one:1.60
ports:
- containerPort: 16686
name: ui
- containerPort: 4317
name: otlp-grpc
env:
- name: COLLECTOR_OTLP_ENABLED
value: "true"
4.2 Grafana Tempo (Production)
CBLK9
4.3 Configuration du Collecteur OTel
CBLK10
Comparaison des backends :
| Dimension | Jaeger | Grafana Tempo |
|---|---|---|
| Stockage | Elasticsearch/Cassandra | Stockage d'objets (S3/GCS) |
| Coût | Élevé (cluster ES) | Faible (stockage d'objets) |
| Latence de requête | Faible (indexée) | Moyenne (les requêtes de Trace ID sont très rapides) |
| Intégration Grafana | Exige un plugin | Intégration native |
| Cas d'utilisation | Développement/petite échelle | Production/grande échelle |
5 Pièges courants
| # | Piège | Conséquence | Solution |
|---|---|---|---|
| 1 | Taux d'échantillonnage fixé à 100% | Coût de stockage explosif, dégradation des performances | Utilisez 0,1%-10% en production, 100% pour les traces d'erreur |
| 2 | Ne propage pas le contexte de suivi | Suivi inter-service interrompu | Utilisez TextMapPropagator.Inject/Extract |
| 3 | Forgetting span.End() | Spans incomplets, fuites de mémoire | Utilisez defer span.End() |
| 4 | Créer trop de spans sur les chemins chauds | Surcharge excessive de performance | Instrumentation manuelle pour les chemins critiques, automatique pour le reste |
| 5 | Déploiement avec un seul Collecteur | Panne du Collecteur entraînant une perte de données | Déployer plusieurs instances de Collecteur + équilibrage de charge |
10 éléments de dépannage des erreurs
| # | Symptôme d'erreur | Cause possible | Méthode de dépannage |
|---|---|---|---|
| 1 | Aucune trace visible dans Jaeger | L'exportateur n'est pas connecté au collecteur | Vérifiez l'URL et le port du collecteur |
| 2 | Trace inter-service interrompue | Contexte non propagé | Vérifiez si Propagator.Inject est appelé |
| 3 | Attributs de span manquants | Attributs de ressource non définis | Vérifier la ressource. Nouvelle avec Attributs |
| 4 | Les traces critiques sont perdues après l'échantillonnage | Le taux d'échantillonnage est trop faible | Utilisez l'échantillonnage en queue pour prioriser les traces d'erreur |
| 5 | Collecteur OOM | La file de lot est trop grande | Réduisez la taille du lot et le délai d'attente |
| 6 | Timeout de requête Tempo | Aucun index pour les requêtes sans Trace-ID | Assurez-vous d'utiliser des requêtes avec Trace ID |
| 7 | Trajets gRPC incomplets | L'intercepteur otelgrpc n'a pas été ajouté | Ajoutez le StatsHandler à la fois côté client et serveur |
| 8 | DB spans manquants | Utilisation d'otsql sans remplacer le pilote | Confirmer l'utilisation de otsql.Open au lieu de sql.Open |
| 9 | Traçage des messages Kafka interrompu | La trace n'est pas injectée dans les en-têtes du message | Injection lors de la production, extraction lors de la consommation |
| 10 | Trop de spans | Superposition d'instrumentation automatique et manuelle | Éviter les spans manuels lorsque l'instrumentation automatique couvre |
Recommandations d'outils
Lors de la mise en œuvre du traçage distribué, ces outils facilitent les tâches de format et d'encodage des données :
- JSON Formatter — Formater la configuration de l'OTel Collector et les données JSON de Span pour le débogage
- Encodeur Base64 — Encodage des Trace IDs et Span IDs pour la transmission inter-systèmes
- Hash Calculator — Générer des hachages pour les décisions d'échantillonnage, garantissant un échantillonnage cohérent pour la même Trace
Résumé : Le traçage distribué est la « machine à rayons X » de l'observabilité des microservices — sans lui, on ne peut voir que les symptômes, pas les causes. OpenTelemetry unifie les API et les SDK, l'instrumentation automatique couvre HTTP/gRPC/DB, l'instrumentation manuelle complète les chemins critiques métier, l'échantillonnage en queue garantit que les traces d'erreur ne sont pas perdues, et le Collecteur gère le regroupement et la transmission. En 2026, Jaeger pour le débogage de développement et Tempo pour le stockage en production constituent la combinaison optimale. Rappelez-vous : un système de microservices sans traçage distribué est comme une boîte noire non surveillée — quand les choses tombent en panne, on ne peut que deviner.
Essayez ces outils exécutés localement dans le navigateur — aucune inscription requise →