Observability & Dağıtık Sistemler 7 dk okuma 3 okunma

Distributed Tracing: OpenTelemetry ile İzlenebilirlik

Distributed Tracing (Dağıtık İzleme), bir isteğin mikroservisler arasındaki tüm yolculuğunu izleyerek performans darboğazlarını, hataları ve servis bağımlılıklarını görselleştirmeyi sağlar. Bu yazıda, OpenTelemetry standardı, trace/span kavramları, context propagation, W3C Trace Context, otomatik ve manuel enstrümantasyon, .NET'te OpenTelemetry entegrasyonu ve Jaeger/Zipkin gibi arka uçlarla kullanımı ele alınır.

Distributed Tracing: OpenTelemetry ile İzlenebilirlik

Distributed Tracing: OpenTelemetry ile İzlenebilirlik

Modern mikroservis mimarilerinde, tek bir kullanıcı isteği onlarca servisten geçebilir. Bir istek yavaşladığında veya hata verdiğinde, sorunun hangi serviste, hangi metotta ve hangi veritabanı çağrısında oluştuğunu bulmak, log'lar arasında kaybolmak demektir. İşte Distributed Tracing (Dağıtık İzleme), bir isteğin tüm yolculuğunu tek bir görselleştirmede birleştirerek bu sorunu çözer. OpenTelemetry, bu izlemenin endüstri standardı haline gelmiş, sağlayıcıdan bağımsız açık kaynak bir çerçevedir.


1. Observability'nin Üç Sütunu ve Distributed Tracing'in Yeri

Observability (Gözlemlenebilirlik), üç temel sütundan oluşur:

Sütun Açıklama Cevapladığı Soru
Logs (Günlükler) Zaman damgalı olay kayıtları "Ne oldu?"
Metrics (Metrikler) Sayısal, toplanabilir veriler "Ne kadar sık/sıcak?"
Traces (İzler) Bir isteğin servisler arası yolculuğu "Nerede ve neden yavaş?"

Distributed Tracing, özellikle şu soruları yanıtlar:

  • Bir istek hangi servislerden geçti?

  • Her serviste ne kadar süre harcandı?

  • Hangi servis/veritabanı çağrısı darboğaz oluşturuyor?

  • Bir hata nerede başladı ve nasıl yayıldı?


2. Temel Kavramlar: Trace, Span ve Context

Trace (İz): Bir isteğin başlangıcından sonuna kadar tüm yolculuğunu temsil eder. Benzersiz bir Trace ID ile tanımlanır.

Span (Aralık): Trace içindeki her bir işlem birimidir. Bir HTTP çağrısı, bir veritabanı sorgusu veya bir metot çağrısı bir span olabilir. Her span şunları içerir:

  • Span ID: Benzersiz kimlik

  • Parent Span ID: Üst span'in kimliği (hiyerarşi)

  • Başlangıç ve Bitiş Zamanı: Süre hesaplaması

  • Attributes: Ek bilgiler (HTTP metodu, URL, durum kodu)

  • Events: Span içindeki önemli olaylar

  • Status: Başarılı, hatalı vb.

Context Propagation (Bağlam Yayılımı): Trace ID ve Span ID'nin servisler arasında taşınmasıdır. Bu sayede farklı servislerdeki span'ler aynı trace altında birleştirilir.

W3C Trace Context Standardı:

text

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             │  │                                │                │
             │  │                                │                └─ Trace Flags
             │  │                                └─ Parent Span ID
             │  └─ Trace ID (32 hex karakter)
             └─ Version

Baggage: Trace boyunca taşınan key-value çiftleri (ör. tenant-id, user-id).


3. OpenTelemetry Nedir?

OpenTelemetry (OTel), CNCF (Cloud Native Computing Foundation) tarafından yönetilen, açık kaynaklı bir observability çerçevesidir. Log, metrik ve trace verilerini toplamak, işlemek ve dışa aktarmak için sağlayıcıdan bağımsız bir standart sunar.

OpenTelemetry'nin Avantajları:

  • Vendor-Agnostic: Jaeger, Zipkin, Datadog, Application Insights gibi farklı arka uçlarla çalışır. Arka uç değiştirmek için sadece exporter değiştirilir.

  • Standart: Tüm diller için tutarlı API ve SDK.

  • Otomatik Enstrümantasyon: ASP.NET Core, HttpClient, EF Core gibi popüler kütüphaneler için hazır enstrümantasyon.

  • Bağlam Yayılımı: W3C Trace Context standardını otomatik uygular.

OpenTelemetry Mimarisi:

text

[Uygulama] → [OTel SDK] → [Exporter] → [Collector] → [Backend (Jaeger, Zipkin, vb.)]

4. .NET'te OpenTelemetry Entegrasyonu

A. Gerekli Paketler

bash

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Exporter.Jaeger

B. Temel Yapılandırma (Program.cs)

csharp

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(serviceName: "OrderService", serviceVersion: "1.0.0")
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment"] = builder.Environment.EnvironmentName
        }))
    .WithTracing(tracing => tracing
        // Otomatik enstrümantasyon
        .AddAspNetCoreInstrumentation(options =>
        {
            options.RecordException = true;
            options.Filter = httpContext => 
                !httpContext.Request.Path.StartsWithSegments("/health");
        })
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation(options =>
        {
            options.SetDbStatementForText = true;
        })
        // Manuel enstrümantasyon için kaynak
        .AddSource("OrderService.*")
        // Exporter
        .AddOtlpExporter(options =>
        {
            options.Endpoint = new Uri("http://localhost:4317");
        })
        .AddJaegerExporter(options =>
        {
            options.AgentHost = "localhost";
            options.AgentPort = 6831;
        }));

var app = builder.Build();
app.Run();

C. Manuel Enstrümantasyon (Activity API)

csharp

using System.Diagnostics;

public class OrderService
{
    // ActivitySource, manuel span oluşturmak için kullanılır
    private static readonly ActivitySource ActivitySource = new("OrderService.Orders");
    private readonly ILogger<OrderService> _logger;

    public async Task<Order> CreateOrderAsync(CreateOrderCommand command)
    {
        // Yeni bir span başlat
        using var activity = ActivitySource.StartActivity("CreateOrder", ActivityKind.Internal);
        
        // Span'e özel bilgiler ekle (attribute)
        activity?.SetTag("order.customer_id", command.CustomerId);
        activity?.SetTag("order.total_amount", command.TotalAmount);

        try
        {
            _logger.LogInformation("Sipariş oluşturuluyor: {CustomerId}", command.CustomerId);
            
            var order = new Order { ... };
            await _repository.SaveAsync(order);

            // Başarılı olarak işaretle
            activity?.SetStatus(ActivityStatusCode.Ok);
            activity?.AddEvent(new ActivityEvent("OrderCreated", 
                tags: new ActivityTagsCollection { ["order.id"] = order.Id }));

            return order;
        }
        catch (Exception ex)
        {
            // Hata durumunu kaydet
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            activity?.RecordException(ex);
            throw;
        }
    }
}

5. Context Propagation: Servisler Arası İzleme

A. Otomatik Yayılım (HttpClient ile)
AddHttpClientInstrumentation() eklendiğinde, HttpClient çağrılarında traceparent başlığı otomatik olarak eklenir. Karşı servis de aynı enstrümantasyona sahipse, span'ler otomatik olarak birleştirilir.

csharp

// Servis A: HttpClient çağrısı
var response = await _httpClient.GetAsync("http://orderservice/api/orders/123");
// traceparent başlığı otomatik eklenir

B. Manuel Yayılım (Message Queue için)
RabbitMQ, Kafka gibi mesaj kuyruklarında, trace context'i mesaj header'larına manuel eklemek gerekir.

csharp

using OpenTelemetry;
using OpenTelemetry.Context.Propagation;

private static readonly TextMapPropagator Propagator = Propagators.DefaultTextMapPropagator;

// Mesaj gönderirken context'i enjekte et
public void PublishMessage(string message)
{
    var activity = Activity.Current;
    var headers = new Dictionary<string, string>();

    Propagator.Inject(new PropagationContext(activity?.Context ?? default, Baggage.Current),
        headers, (carrier, key, value) => carrier[key] = value);

    // Mesajı header'larla gönder
    _rabbitMq.Publish(message, headers);
}

// Mesaj alırken context'i çıkar
public void ConsumeMessage(string message, Dictionary<string, string> headers)
{
    var parentContext = Propagator.Extract(default, headers,
        (carrier, key) => carrier.TryGetValue(key, out var value) ? new[] { value } : Array.Empty<string>());

    Baggage.Current = parentContext.Baggage;

    using var activity = ActivitySource.StartActivity("ProcessMessage", 
        ActivityKind.Consumer, parentContext.ActivityContext);
    
    // Mesajı işle...
}

6. OpenTelemetry Collector: Merkezi Toplama

OpenTelemetry Collector, farklı kaynaklardan gelen telemetri verilerini toplayan, işleyen ve dışa aktaran bir aracıdır (agent). Uygulamalar doğrudan arka uca göndermek yerine Collector'a gönderir; Collector, veriyi filtreleyebilir, zenginleştirebilir ve birden fazla arka uca yönlendirebilir.

Collector Yapılandırması (otel-collector-config.yaml):

yaml

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  attributes:
    actions:
      - key: environment
        value: production
        action: upsert

exporters:
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, attributes]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

7. Arka Uçlar: Jaeger, Zipkin ve Diğerleri

Arka Uç Açıklama Özellikler
Jaeger Uber tarafından geliştirilen, CNCF projesi Zengin UI, bağımlılık grafiği, distributed context propagation
Zipkin Twitter tarafından geliştirilen, olgun Basit UI, hafif, geniş dil desteği
Grafana Tempo Grafana ekosistemiyle entegre Ucuz depolama, Grafana ile görselleştirme
Azure Application Insights Microsoft'un bulut çözümü Azure entegrasyonu, otomatik enstrümantasyon
Datadog APM Ticari çözüm Kapsamlı özellikler, kolay kurulum

Docker Compose ile Jaeger:

yaml

version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"  # UI
      - "4317:4317"    # OTLP gRPC
      - "4318:4318"    # OTLP HTTP
    environment:
      - COLLECTOR_OTLP_ENABLED=true

8. En İyi Pratikler

Pratik Açıklama
Sampling (Örnekleme) Kullanın Yüksek trafikli sistemlerde tüm trace'leri toplamak pahalıdır. ParentBased(TraceIdRatioBased(0.1)) ile %10 örnekleme yapın.
Anlamlı Span İsimleri GET /api/orders/{id} gibi anlamlı isimler kullanın; HTTP GET gibi genel isimlerden kaçının.
Attribute'ları Zenginleştirin user.id, tenant.id, http.status_code gibi bağlam bilgileri ekleyin.
Hassas Veri Kaydetmeyin Trace'lere PII (kişisel veri), şifreler, token'lar eklemeyin.
Health Check'leri Filtreleyin /health, /metrics gibi endpoint'leri trace dışında bırakın.
Baggage'ı Dikkatli Kullanın Baggage, tüm servisler arasında taşınır; boyutunu sınırlayın.
Collector Kullanın Uygulamalar doğrudan arka uca bağlanmak yerine Collector'a gönderin.
Örnekleme Stratejisi Belirleyin Head-based (basit) veya tail-based (akıllı) örnekleme arasında seçim yapın.
Korelasyon ID'lerini İlişkilendirin Log'lardaki trace_id ve span_id ile trace'leri loglarla ilişkilendirin.

9. Sampling (Örnekleme) Stratejileri

Yüksek trafikli sistemlerde, tüm trace'leri toplamak hem maliyetli hem de gereksizdir.

A. Head-Based Sampling (Başlangıçta Karar)
Karar, trace'in başlangıcında verilir. Basit ve hızlıdır ancak hatalı trace'leri kaçırabilir.

csharp

.WithTracing(tracing => tracing
    .SetSampler(new TraceIdRatioBasedSampler(0.1)) // %10 örnekleme
)

B. Tail-Based Sampling (Sonunda Karar)
Karar, trace tamamlandıktan sonra verilir. Hatalı veya yavaş trace'ler her zaman örneklenir. OpenTelemetry Collector ile yapılır.

yaml

processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors-policy
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow-traces-policy
        type: latency
        latency: { threshold_ms: 1000 }
      - name: random-policy
        type: probabilistic
        probabilistic: { sampling_percentage: 5 }

Sonuç

Distributed Tracing, mikroservis mimarisinde sorunları hızlıca tespit etmenin ve performansı optimize etmenin anahtarıdır. OpenTelemetry, sağlayıcıdan bağımsız, standart ve zengin otomatik enstrümantasyon desteğiyle bu alanda endüstri standardı haline gelmiştir. .NET ekosisteminde OpenTelemetry.Extensions.Hosting ve ilgili paketlerle ASP.NET Core, HttpClient, EF Core gibi bileşenler otomatik olarak enstrümante edilir; manuel enstrümantasyon için ActivitySource ve Activity API'leri kullanılır. W3C Trace Context standardı ile servisler arası bağlam yayılımı otomatik sağlanır. Jaeger, Zipkin veya Grafana Tempo gibi arka uçlarla görselleştirme yapılır. Sampling stratejileri ile maliyet kontrol altına alınır. Unutmayın: İyi bir izleme, tahmin etmeyi ortadan kaldırır; veriye dayalı karar vermenizi sağlar

Tüm yazılar Soru ya da yorumun mu var? Yaz