Distributed Tracing, Open Tracing ve Uygulamaları

Erdem OZDEMIR
hepsiburadatech
Published in
5 min readOct 15, 2018

Yazılım dünyasında mimariler değişmeye ve gelişmeye devam ediyor. Eskiden sahip olduğumuz tek parçalı mimarilerde bir isteğin baştan sona kadar takibi kütüphaneler kullanıldığından çok daha kolay olmaktaydı. Günümüzde mimariler daha fazla dağıtık yapıya ulaştıkça önümüze ilgili servis çağrılarının izlenebilmesi sorunu çıkmaktadır.

http://static-aliyun-doc.oss-cn-hangzhou.aliyuncs.com/assets/img/13157/15381935105875_en-US.png

Geleneksel izleme araçları örneğin metrik ve dağıtık loglama hala kullanılmakta fakat servisler arası çağrıları izleyebilmek hala yetersiz. Bu konuda önümüze gelen çözüm distributed tracing olarak adlandırılmaktadır. Distributed tracing, dağıtık istek takibi olarak da ifade edebileceğimiz, dağıtık uygulamaları izlememize, özellikle bir hatanın yada performans sıkıntının nerede olduğunu takip etmemize yarar.

Distributed tracing ile ilgili ilk çalışmalar 2010 yılında Google’ın Dapper ve 2012 yılında Dapper’dan etkilenerek orataya çıkan Twitter’ın Zipkin isimli uygulamalarıyla başladı. Her proje kendi standardlarında çalışmalardı. Bu noktada 2015 yılında Dapper’da çalışmış olan Ben Sigelman OpenTracing isminde bir açık kaynak distributed tracing API şartnamesi ortaya çıkardı. 2016 yılının sonlarında çalışanın ilk sürümünü çıkardı. Burada ana amaç, yazılımcıların distributed tracing için ortak bir API kullanması ve dağıtıcı bağımlığı olmadan geliştirme yapabilmelidir. Örneğin https://opentracing.io/docs/supported-tracers adresinde yer alan OpenTracing’i destekleyen uygulamalardan birini kullanırken başka bir destekleyen uygulamaya O(1) karmaşıklıkta geçebilir. OpenTracing yazının yazıldığı tarih itibariyle

dillerine destek vermekte. https://opentracing.io/docs/supported-languages ‘den bu diller hakkında detaylı bilgi alanılabilir.

OpenTracing API şartnamesini meydana getirenler öğeler:

Bir Trace, Span’ların yönlü çevrimsiz çizgelerinden (DAG — Directed Acyclic Graph) oluşur. Span’ların birbirleriyle aralarındaki kenarlar ise Reference olarak tanımlanır.

Bu 3 nesnenin birbiriyle olan ilişki aşağıdaki gibidir.

https://github.com/opentracing/specification/blob/master/specification.md

Ama çoğunlukla gösterimleri bu şekilde olmaktadır.

https://github.com/opentracing/specification/blob/master/specification.md

Span özellikleri:

  • İşlem ismi
  • Başlangıç zamanı
  • Bitiş zamanı
  • Anahtar-Değer yapısında sıfır yada daha fazla Tag.
  • String türünde sıfır yada daha fazla Log
  • SpanContext
  • Sıfır yada fazla Reference

Tag özellikleri:

Trace ararken işe yarayabilecek anahtar kelimeler. https://github.com/opentracing/specification/blob/master/semantic_conventions.md adresinde ortak kullanılabilecek tag’ler yer almaktadır.

SpanContext özellikleri:

  • Farklı process’ler arasında trace taşımaya yarar.
  • Baggage Items, anahtar-değer tipinde veriler taşır.

Reference özellikleri:

İki span arasındaki ilişkiyi gösteren ChildOf and FollowsFrom tipleri bulunmaktadır.

Örnek bir Span:

t=0            operation name: db_query               t=x 

+-----------------------------------------------------+
| · · · · · · · · · · Span · · · · · · · · · · |
+-----------------------------------------------------+

Tags:
- db.instance:"jdbc:mysql://127.0.0.1:3306/customers
- db.statement: "SELECT * FROM mytable WHERE foo='bar';"

Logs:
- message:"Can't connect to mysql server on '127.0.0.1'(10061)"

SpanContext:
- trace_id:"abc123"
- span_id:"xyz789"
- Baggage Items:
- special_id:"vsid1738"

Span oluşturan yapıya Tracer denir.

Tracer bir span oluşturken, span bir isme ihtiyaç duyar. Örneğin bir api çağrısı için span başlatılacaksa isim tercihi get_order/123 yerine get_order olmalıdır.

Process’ler arası trace yayılımı sırasında, span oluştuktan sonra Tracer span context’i Inject(serialize) eder, istek diğer process’e ulaşınca span context Extract (deserialize) edilir.

https://opentracing.io/img/overview:tracers/Extract.png

OpenTracing Uygulamaları

Jaeger

Jaeger, Dapper ve OpenZipkin’den etkilenen ve Uber tarafından geliştirilen bir Distributed Tracing çözümüdür. OpenTracing API’sini destekler ve OpenTracing’in öncül destekçilerindendir.

Jaeger Mimarisi:

https://www.jaegertracing.io/img/architecture.png

Jaeger’i kullanabilmek için kod tarafından instrumentation yapılması gereklidir. Instrumentation, bir ürünün performansını ölçebilmek yada takip edebilmek için gerek trace kodlarının yazılmasıdır. Instrumentation yapabilmek için Jaeger client kütüphaneleri kullanılır. Bunlar OpenTracing API’sinin farklı dillerde uygulanmasıdır.

https://www.jaegertracing.io/img/context-prop.png

Instrumentation yapılan servis, yeni istekleri karşılarken bir span oluşturur ve span’ın içine trace id, span id ve baggage bilgileri ekler. Burada sadece id’ler ve baggage bilgisi diğer isteklere gönderilir, diğer işlem ismi, loglar gibi bilgiler taşınmaz. Örnekleme yapılan span’ler asenkron şekilde Jaeger Agent’lara gönderilir.

Bütün trace’ler oluşturulmasına rağmen, bunların sadece bazıları örneklenir. Bir trace örneklenmesi, daha sonra işlenmek ve kaydedilmek üzere işaretlenmesidir. Varsayılan olarak Jaeger Client trace’lerin 0.1% ‘ ini örnekler ve örnekleme stratejilerini Jaeger Agent’dan alabilir.

Örneğin basit bir çağrı çizgesinde A -> B -> C. İlk olarak A servisi isteği karşılar ve her hangi bir trace bilgisi içermez, bu sebeple Jaeger yeni bir tracing başlatır, rasgele bir trace ID oluşturur ve tanımlanmış mevcut örnekleme stratejisini kullanır. Bu örnekleme stratejisi B ve C servislerine aktarılır, B ve C servisleri bu strateji bilgisini kullanır ve bu şekilde B ve C servislerinin strateji almalarına gerek kalmaz, bu şekilde trace’lerin tutarlığını sağlar. Bu işleme tutarlı baştan aşağı örnekleme (consistent upfront sampling) denir.

Örnekleme stratejisi tanımlarken iki parametre kullanılır. Bunlar sampler.type and sampler.param.

  1. Constant (sampler.type=const): sampler.param bilgisine göre ya bütün traceleri örnekler yada hiçbirini örneklemez. sampler.param=0 yada sampler.param=1
  2. Probabilistic (sampler.type=probabilistic): sampler.param bilgisine göre belli bir oranda örnekleme yapar. Örneğin; sampler.param=0.1 her 10 trace’in 1'ini örnekler.
  3. Rate Limiting (sampler.type=ratelimiting): sampler.param bilgisine göre saniyede sabit sayıda trace örneklenir. Örneğin;sampler.param=2.0saniyede 2 tane trace örneklenir.
  4. Remote (sampler.type=remote, varsayılan): Mevcut servis için agent’dan örnekleme bilgisi alınır.

Örnek bir strategies.json:

{
"service_strategies": [
{
"service": "foo",
"type": "probabilistic",
"param": 0.8,
"operation_strategies": [
{
"operation": "op1",
"type": "probabilistic",
"param": 0.2
},
{
"operation": "op2",
"type": "probabilistic",
"param": 0.4
}
]
},
{
"service": "bar",
"type": "ratelimiting",
"param": 5
}
],
"default_strategy": {
"type": "probabilistic",
"param": 0.5
}
}

Şu ana kadar sayılan örnekleme türleri statik yapıdadır. Adaptif Örnemleme ise dinamik olarak örnekleme gerçekleştirir. Örneğin; bir servis yoğun anında daha az örnekleme yapar, düşük yoğunlukta ise daha fazla örnekleme yapabilir. Bu özellik makalenin yazıldığı tarihte geliştirme aşamasında olan bir özelliktir.

Agent

Makinelere yada container’lara kurulan ve UDP üzerinden gönderilen span’leri dinleyen bir uygulamadır. Ana amacı collector’ların routing ve keşiflerini client’dan soyutlamaktır.

Collector

Agent’lardan gelen trace’leri toplar ve belli işlemleri sırasıyla çalıştırır. Mevcutdaki işlemler ise trace’lerin onaylanması, trace’leri indekslemek, trace’lere gerekli dönüşüm işlemlerini yapmak ve en sonunda bunları kaydetmek.

Desteklenen veritabanları ise Cassandra ve ElasticSearch.

Query

Trace’leri veri deposundan getiren ve bu trace verilerini bir UI göstermeye yarayan bir servistir. Ayrıca bu UI ilgili serviste barınır.

Collector, hangi veritabanının kullanıldığını bilebilmesi için SPAN_STORAGE_TYPE çevre değişkenini kullanır. Seçilebilecek değerler cassandra, elasticsearch, kafkaand memory (sadece all-in-one)’dır.

In-memory seçeneği production için uygun değildir. Deneme amaçlı kullanılması daha uygundur.

Jaeger mikroservis mimarisinde dağıtık bir uygulamadır. Bu da onu izlenmesini gerektirir. Mesela backend’in fazla trace verisiyle sature olmaması.

Varsayılan olarak Jaeger mikroservisleri metriklerini Prometheus formatında oluşturur. Diğer bir seçenek ise expvar.

Jaeger komponentleri loglarını sadece standard out’a verir. Loglama için Uber’in kendi ürünü olan go.uber.org/zap kullanmakta. Örneğin:

{"level":"info","ts":1517621222.261759,"caller":"healthcheck/handler.go:99","msg":"Health Check server started","http-port":14269,"status":"unavailable"}

Kaynaklar:

https://opentracing.io

https://www.jaegertracing.io

https://zipkin.io

https://www.alibabacloud.com/help/doc-detail/68035.htm

https://en.wikipedia.org/wiki/Instrumentation_(computer_programming)

Ek kaynaklar:

https://www.katacoda.com/courses/opentracing/opentracing-tutorial-lesson01

https://github.com/yurishkuro/opentracing-tutorial

https://github.com/jaegertracing/jaeger/tree/master/examples/hotrod

https://medium.com/opentracing/take-opentracing-for-a-hotrod-ride-f6e3141f7941

https://blog.openshift.com/openshift-commons-briefing-82-distributed-tracing-with-jaeger-prometheus-on-kubernetes

Keynote: OpenTracing and Containers: Depth, Breadth, and the Future of Tracing — Ben Sigelman

--

--

Erdem OZDEMIR
hepsiburadatech

A husband, a father of two daughters and an enthusiast software engineer