JAEGER

Merve Naz Mumcu
Turk Telekom Bulut Teknolojileri
8 min readJun 5, 2023

Merhaba! Bu yazıda, sizlere Jaeger’in ne olduğunu ve ne için kullanıldığını anlatacağım. Ayrıca, bir Go projesinde Jaeger’in nasıl kullanılabileceğine dair bir örnek sunacağım.

Öncelikle Jaeger’ı daha iyi anlayabilmek için distributed tracing ne demek olduğuna biraz değinelim

DISTRIBUTED TRACING

Distributed tracing, karmaşık ve dağıtık sistemlerde bir uygulamanın izlenmesi ve analiz edilmesi sürecidir. Özellikle monolitik uygulamalardan microservis uygulamalarına geçiş yapılmaya başlandıkça bu durum daha çok önem kazanmıştır. Microservis uygulamalar bağımsız dağıtım ve geliştirme, ölçeklenebilirliği artırırken bu uygulamalar büyüdükçe sistemin karmaşıklığı, yönetimi, hata ayıklama ve izlemeyi de zorlaştırır. Distributed tracing , bu karmaşıklıkla başa çıkmak için önemli bir araç haline gelmiştir. Distributed tracing, bir işlemi izleyerek, hangi mikro hizmetlerin etkileşimde olduğunu ve performans sorunlarının nerede olduğunu belirlemeye yardımcı olur.

  • Distributed tracing nasıl çalışır ?

Distributed tracing, bir isteğin izleme sürecini takip ederek isteğe dayalı bir görünüm oluşturur.

trace_hu62b752b265dd3243af891d3544498d1c_493083_4144x0_resize_box_3.png (4144×1589) (reflectoring.io)

Bunu üsteki görsel de yer alan örnek senaryo ile açıklayalım. Tarayıcı, kullanıcıya bir müşterinin detaylı görünümünü almak için API servisine bir istekte bulunur ve bu bilgiyi kullanıcıya gösterir. API servisi bu isteği tek başına yanıtlayamaz ve müşteri servisine iki ayrı çağrı yaparak müşterilerin isimlerini ve adreslerini almak zorundadır.

Bir servisten diğerine yapılan her geçişe “span” denir. Bir isteği yanıtlamak için bir araya gelen tüm spanlar bir “trace” oluşturur.

Her span ve trace benzersiz bir kimliğe sahiptir. Bir trace’in ilk span’ı, genellikle span ID olarak trace ID’sini kullanır. Yani, bir izleme başladığında, o izlemenin ilk adımını temsil eden span, aynı zamanda trace ID’sini kullanarak bir kimlik alır. Daha sonra, her hizmet, çağırdığı sonraki hizmete trace ID’sini iletmelidir. Böylece, sonraki hizmet, loglarında aynı trace ID’sini bir ilişkilendirme kimliği olarak kullanabilir. Bu sayede loglardaki farklı hizmetler arasında bir bağlantı kurulabilir ve izleme süreci boyunca oluşan olaylar takip edilebilir.

Distributed tracing aslında uzun süredir kullanılıyor ama bakımı ve uygulanması çok zor. Uygulamanın en kolay yolu bir tracing framework kullanmaktır. Genel olarak kullanılan iki önemli framework vardır. Bunlar OpenCensus ve OpenTracing. OpenTelemetry ise mayıs 2019'da OpenCensus ve OpenTracing projelerinin birleşmesiyle oluşturulmuştur

Şuana kadar distributed tracing ne olduğunu ve nasıl çalıştığını, kullanılan frameworkleri öğrenmiş olduk.Şimdi Jaeger’a gecelim

JAEGER

Jaeger, dağıtık sistemlerdeki performans izleme ve hata ayıklama için kullanılan bir açık kaynaklı Distributed Tracing çözümüdür.Jaeger, Uber tarafından geliştirilmiştir . Başlangıçta Jaeger, OpenTracing API’sini destekleyen bir çözüm olarak geliştirildi ve OpenTracing topluluğu tarafından aktif olarak desteklendi. Ancak sonraki gelişmelerle birlikte, Jaeger, OpenTelemetry’in dağıtık izleme standartlarına uyumlu hale gelerek OpenTelemetry tarafından da desteklenmeye başlandı.

  • Jaeger’ın Mimarisi
architecture-v1.png (960×540) (jaegertracing.io)

Jaeger’da agent’lar istemcilerden gelen istekleri dinleyen ve bu istekleri kaydederek ve doğrulayarak ilgili verileri daha sonra analiz için “collector” bileşenine iletecek bileşendir. Agent’lar, gelen istekleri UDP bağlantısı üzerinden dinler. Span’lari (istemciden gelen isteğin farklı adımlarını temsil eden veriler) alır ve bunları toplar. Bu span’ler, bir dahili kuyruğa yerleştirilir ve işlenmek üzere collector bileşenine iletilir.

Collector, gelen span’leri genellikle dahili bir kuyruk sistemi kullanarak işler. Bu işlemler arasında span’leri doğrulama, dönüştürme, filtreleme ve önbelleğe alma gibi adımlar bulunabilir. Bu sayede, agentlar span’ları depolama işlemi sırasında beklemek zorunda kalmadan yeni istekleri dinlemeye devam edebilirler. Collector, span’ları işledikten sonra, bu spanları uygun bir veritabanına kaydederek saklar. Cassandra veya Elasticsearch gibi ölçeklenebilir depolama sistemleri veritabanı olarak kullanılabilir.

Collector’ün bir diğer görevi de depolanan span verilerini sorgulama hizmeti sağlamaktır. Query service react tabanlı arayüz kullanarak REST API aracılığıyla analiz etmeyi mümkün kılar. Bu sayede, geliştiriciler ve sistem operatörleri, Jaeger arayüzünü kullanarak trace verilerini sorgulayabilir, analiz edebilir ve performans sorunlarını tespit edebilir.

context-prop.png (960×540) (jaegertracing.io)

Burada gelen her bir isteği kaydetmek yerine sadece bazılarını kaydedebiliriz. İşte burada “örnekleme” devreye girer. Örnekleme, tüm isteklerin sadece bir kısmını seçerek kaydetmeyi sağlar. Örnekleme yüzdesi, kaydedilecek isteklerin oranını belirler Varsayılan olarak Jaeger Client trace’lerin 0.1% ‘ ini örnekler ve örnekleme stratejilerini Jaeger Agent’dan alabilir.

Bir örnek olarak, A -> B -> C şeklinde basit bir çağrı çizgesi düşünelim. A servisi isteği karşılar ve herhangi bir izleme bilgisi içermez. Bu durumda Jaeger, yeni bir izlemenin başlatılmasını sağlar, rasgele bir izleme kimliği oluşturur ve belirlenmiş bir örnekleme stratejisini kullanır. Bu örnekleme stratejisi B ve C servislerine iletilir. B ve C servisleri, bu strateji bilgisini kullanarak izlemeyi gerçekleştirir ve bu sayede B ve C servislerinin ayrıca bir strateji almalarına gerek kalmaz. Bu yöntemle izlemelerin tutarlılığı sağlanır. Bu işleme “tutarlı baştan aşağı örnekleme” (consistent upfront sampling) denir.

KOD UYGULAMASI

Go programlama dilinde Jaeger’ı entegre etmek için kod tarafında bir “instrumentation” işlemi yapmamız gerekmektedir. Instrumentation, bir ürünün performansını ölçmek veya izlemek amacıyla trace (iz) kodlarının yazılması anlamına gelir. Jaeger’ı kullanabilmek için, Go dilinde Jaeger client kütüphanelerini kullanmanız gerekmektedir. Bu kütüphaneler, OpenTracing API’sini Go dilinde uygulamaktadır.

Burada https://github.com/albertteoh/jaeger-go-example adresinde bulunan bir örneği kullanabiliriz. Bu örnekte, service-a ve service-b olmak üzere iki adet servisimiz bulunmaktadır. Service-a, service-b’yi çağıracak ve biz de Jaeger üzerinde oluşan spanları inceleyeceğiz. Bu sayede, gerçek bir senaryo da iki servis arasındaki izleme ve izleme verilerinin nasıl görüntülenebileceği konusunu daha iyi bir şekilde anlayacağız.

servis-a kısmındaki kodları inceleyerek başlayalım.

İlk olarak Tracing işlemi için aşağıdaki “ping/lib/tracing” ve “github.com/opentracing/opentracing-go” kütüphaneleri kullanılmıştır.

tracing.Init(thisServiceName) ifadesi, izleme işlemleri için bir tracer oluşturur. Bu tracer, bu hizmetin adıyla ilişkilendirilir. Tracer, iz süreçlerini (span) oluşturmak ve izlemek için kullanılır.

Tracer’ın erişilebilir olması için SetGlobalTracer çağrısı yapılır. Bu yapılmazsa, SDK varsayılan olarak “NoopTracer” kullanır ve span gönderilmez.

outboundHostPort, ok := os.LookupEnv(“OUTBOUND_HOST_PORT”) ifadesi aslında docker-compose.yaml dosyasına bakarsak oradaki çevre değişkenini okuyor.Böylece hedef servise(burada service-b) isteklerin yönlendirilmesi için gereken bilgileri elde ediliyor.

Şimdi de endpoint kısmına bakarsak:

http.HandlerFunc içinde, gelen isteğe dayanarak yeni bir iz süreci (span) oluşturulur ve başlatılır. Bu sayede neden-sonuç ilişkileri (ebeveyn-çocuk ilişkisi) doğru bir şekilde ayarlanır. Yani, bu HTTP istek işleyici span , HTTP isteğini yapan üst düzey çağırıcının çocuğu olarak kabul edilir. StartSpanFromRequest işlevinin ne yaptığına daha sonra daha detaylı olarak değineceğiz. Geleneksel olarak, bu span “server span” olarak adlandırılır.

Bu işlemin ilk spanı olduğunu ve dolayısıyla bir üst span olmadığı için ayrıca “root span” da denir.

Defer span.Finish()” çağrısı son derece önemlidir, çünkü span’i kapatır ve agent/collector’e ileterek kaydetme işlemini gerçekleştirir. Bu çağrı olmadan, Jaeger kullanıcısı trace/spanları görüntüleyemez.

Bu endpoint de service-b’ye yapılan akış çağrısında, server span ayrıntılarını içeren yeni bir bağlam örneği(context) ContextWithSpan işlevi kullanılarak oluşturulur. Bu da çok önemlidir, çünkü bu şekilde server span ile service-b/ping’e yapılan HTTP istekleri arasındaki üst-alt(parent-child) ilişkisinin devam etmesi sağlanır. Akış çağrısı için yeni span , geleneksel olarak client span olarak adlandırılır.

Service b’ye bakalım.

Burada sadece bir endpoint var ve bu endpoint de HTTP yanıtına thisServiceName değişkeninin değerini yazmaktadır.Bu fonksiyonun içerisinde bir tane span üretilmiştir ve bu fonksıyon bitene kadar izleme devam eder.

Yapılan işlem kısaca a servisinin ping uç noktasına istek atarsak a servisi b servisini çağırıyor .B servisi yanıt olarak kendi service name değerini iletiyor.

Bir tabloda bu kod kısımlarını özetlemek istersek:

span-lifecycle.png (942×642) (dytvr9ot2sszz.cloudfront.net)

Kütüphaneler ile gelen hazır fonksiyonların içlerine bakarsak:

  • tracing/init.go

“tracing/init.go” dosyasında, “tracing.Init(…)” çağrısıyla OpenTracing API’sini uygulayan yeni bir izleyici (tracer) başlatılır. Bu durumda, Jaeger Tracer’ının bir örneği oluşturulur ve başlatılır.

  • ping/ping.go

Bu, servislerimiz tarafından diğer servislere çağrı yapmak için kullanılan “Ping” işlevini sağlar. Spesifik olarak, “Ping” işlevi, daha önce gördüğümüz gibi hizmet-a’nın hizmet-b’nin /ping uç noktasını çağırmak için kullandığı işlevdir.

  • tracing/span.go

tracing.StartSpanFromRequest(…), hizmetlerimiz tarafından HTTP isteği işlenir işlenmez yeni bir span başlatmak için kullanılır.

Sizde bu kodu kendi localinize çektikten sonra “make start” diyerek projeyi ayağa kaldırabilirsiniz. Eğer make komutu tanımlı değilse o zaman makefile dosyasında hem service a hem de service-b için yazılan bu komutları “docker build -t service-a -f service-a/Dockerfile .

docker build -t service-b -f service-b/Dockerfile .

tekrer teker çalıştırarak servislerin imagelarını oluşturup daha sonra bu servislerin bir arada çalışmasını sağlamak içinde “ docker-compose up -d” diyerek hem jaeger’ı hem a hem de b servislerini ayağa kaldırmış olacaksınız.

Şimdi de a servisinden ping uç noktasına istek atmak için “localhost:8081/ping” dersek bu şekilde bir sayfa karsımıza çıkacak.

Ardından jaeger UI’yı açarak service-a’dan yolladığımız bu isteği inceleyelim

Burada görüldüğü gibi a servisinde ping endpoint kısmında ilk olarak oluşturduğumuz ping-receive adındaki root span ilk olarak oluşuyor.Bu root span’ın altında b servisine istek atmadan önce oluşturduğumuz (ping.Ping() fonksiyonunda ) ping-send adında bir span geliyor.En sonda b servisine gidip /ping endpoint kısmı çalıştırıldığı için orada oluşturduğumuz ping-receive adında en son span oluşmaktadır.

Gördüğünüz gibi Jaeger uygulama içindeki farklı bileşenlerde (servisler) oluşan izleme verilerini toplar ve bir hiyerarşi şeklinde gösterir. Bu izleme verileri, kullanıcılara bir isteğin hangi servislerden geçtiğini göstererek hataları tespit etme, performans analizi yapma ve sistem davranışını anlama gibi bir dizi izleme ve analiz işlevi sunar.

Burada jaeger’a basit bir giriş yaptık. Yakın zamanda bulutbilişimciler.com sitesinde daha fazla jaeger kullanımı ile ilgili örnekler gösterilecek ve ileri düzey jaeger eğitimleri sunulacaktır.bulutbilişimciler.com sitesine üye olmayı unutmayın😊

Görüşmek üzere

Kaynaklar

What is Distributed Tracing? — DeepSource

Understand distributed tracing | Lightstep Observability Learning Portal

What is Jaeger?. Jaeger is a distributed tracing system… | by Ravi Prakash | Medium

Architecture — Jaeger documentation (jaegertracing.io)

JAEGER — Open Tracing | by Çiğdem Kadakoğlu | Medium | cidokimi | Medium

Distributed Tracing, Open Tracing ve Uygulamaları | by Erdem OZDEMIR | hepsiburadatech | Medium

Distributed Tracing. Mikroservis tabanlı uygulamaların ve… | by Cem Doğan | Medium

Guide to Go Instrumentation for Tracing & Jaeger | Logz.io

Introduction to Tracing : OpenTelemetry & Opentracing — YouTube

Jaeger Tracing Quick Example Implementation using Go & Docker — YouTube

https://logz.io/blog/go-instrumentation-distributed-tracing-jaeger/

--

--