Java Uygulamalarında OpenTelemetry Kullanımı

Süleyman Fazıl Yeşil
6 min readJan 13, 2025

--

Bu yazıda, uygulama gözlemlenebilirliği (observability) ekosisteminde bir endüstri standardı haline gelen OpenTelemetry aracının Java uygulamalarında kullanımına ve konfigürasyonlarına bakmayı planlıyorum. Günümüzde artık ChatGPT vb araçların sunduğu yetkinlikler sebebiyle “nasıl” sorusuna (how-to) cevap vermek için yazı yazmaya pek gerek olmasa da, implementasyon tecrübesini kullandığımız bağlam çerçevesinde kayda geçirmek istedim. Malum, zaman geçtikçe ve başka konularla uğraşmaya başladıkça detaylar unutuluyor ve sınırlı hafızamızda geriye bulanık izler kalıyor.

Not olarak, tüm implementasyon detayları yerine ana hatlarıyla konuyu üst seviyeden önemli noktalar çerçevesinde aktarmayı planlıyorum. Kubernetes konfigürasyon detayları ve bileşenlerin tüm kurulum detay bilgilerini paylaşmayacağım. Yazı kapsamı çok genişlemesin, soru soruyu açmasın diye. Bu açıdan tam bir adım adım kurulum kılavuzu gibi değil de bir bağlamdaki bir tecrübenin aktarımı gibi düşünün. Malum kendim için yazıyorum :)

OpenTelemetry’nin ekosistemdeki yeri ve genel konseptlerin anlatımı için bir önceki yazıya bakabilirsiniz.

Implementasyon Bağlamı

Java tabanlı dağıtık mimariye sahip bir uygulamamız var. Dağıtık mimarideki bileşenleri iki tipte sınıflandırıyoruz: web tabanlı modüller, standalone modüller.

Web tabanlı modüller, Spring Framework kullanıyorlar ve konteynır içindeki Tomcat üzerinde koşuyorlar.

Standalone modüller ise arkaplanda çalışan worker niteliğine sahip ve konteynır içinde düz java uygulaması olarak çalışıyorlar. Bunlar da yine Spring Framework kullanıyorlar.

Dağıtık mimarideki bileşenler Kafka üzerinden haberleşiyorlar. Biz de bu dağıtık mimarideki sistemde akan verinin uçtan uca iz takibini yapmayı hedefliyoruz.

Sistemdeki veri akışını dağıtık iz takibiyle (distributed tracing), Kubernetes üzerinde çalıştırıldığı kurguda izlemek istiyoruz. Performans metriklerini ise bu aşamada takip etmek istemiyoruz.

Implementasyon Mimarisi

Intrumente edilen sistem bileşenleri bilgileri OpenTelemetry Collector bileşenine aktarıyorlar. O da bilgileri Jaeger servisine (distibuted tracing observability backend) aktarıyor. Jaeger üzerinden de görsel iz takibini yapıyoruz.

Otel Collector: Veri toplama, işleme ve Jaeger vb araçlara aktarım

Open Telemetry Collector bileşenini iki farklı mimaride çalıştırmak mümkün.

İlki Collector’ü merkezi olarak ayrı bir bileşen gibi konumlandırmak. Bu durumda ölçeklenebilirlik ve servis sürekliliği için birden fazla kopya halinde çalıştırmak gerekiyor.

Merkezi Otel Collector bileşeni kullanımı

Bununla uğraşmak yerine şimdilik daha kolay olan sidecar desenini tercih ediyoruz. Bunu, eskilerde ortalıkta gezinen, içine insanların bindiği sepetli motosikletler gibi düşünün. Her bir uygulama podunun içine ayrı bir Collector konteynırı koyuyoruz, yaklaşık bir 200MB bellek ve yine minimal bir CPU maliyeti eşliğinde.

Sidecar olarak Otel Collector kullanımı

Uygulama Bileşenlerinin Instrumentasyonu ve Konfigürasyonu

OpenTelemetry SDK üzerinden manuel instrumentasyon imkanı sunsa da, uygulamaya temas etmeden sağlanan OpenTelemetry Java agent jar’ı üzerinden otomatik olarak instrumente etmek istiyoruz. Instrumentasyonun opsiyonel olmasını ve istediğimizde açıp kapatabilmek istiyoruz.

Öncelikle tüm bileşenlerin Docker imajlarına opentelemetry-javaagent.jar dosyasını ekliyoruz. Yaklaşık 20MB büyüklüğünde.

Web tabanlı uygulamaları uygulamaları instrumente etmek için Tomcat’in CATALINA_OPTS konteynır ortam değişkenine (env var) aşağıdaki şekilde agent dosyasının yolunu vermek yeterli.

CATALINA_OPTS="-javaagent:path/to/opentelemetry-javaagent.jar"

Standalone uygulamaları instrumente etmek içinse düz java komutuna javaagent parametresini geçmek yeterli.

java -javaagent:path/to/opentelemetry-javaagent.jar -jar myapp.jar

Eğer Spring Boot kullanıyorsanız, gömülü web sunucu üzerinden çalıştığı için standalone uygulama konfigürasyonunu yapmak yeterli olacaktır.

Agent konfigürasyonları ise ortam değişkenleri üzerinden yönetiliyor, dolayısıyla ayarlamak istediğimiz ortam değişkenlerini izlenecek uygulama konteynırlarına geçirmemiz gerekiyor. Bu amaçla aşağıdaki değerlerle ortak kullanılacak merkezi bir ConfigMap oluşturup uygulama konteynırlarına enjekte ediyoruz.

OTEL_RESOURCE_ATTRIBUTES="service.name=app1"
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED="true"
OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_VIEW_TELEMETRY_ENABLED="true"
OTEL_INSTRUMENTATION_KAFKA_EXPERIMENTAL_SPAN_ATTRIBUTES="true"
OTEL_INSTRUMENTATION_HTTP_SERVER_EMIT_EXPERIMENTAL_TELEMETRY="true"
OTEL_INSTRUMENTATION_SPRING_INTEGRATION_PRODUCER_ENABLED="true"
OTEL_METRICS_EXPORTER="none"
OTEL_LOGS_EXPORTER="none"
  • OTEL_RESOURCE_ATTRIBUTES değişkeni ile uygulama bazında anahtar değer çiftleri halinde birtakım bilgileri ekleyebiliyoruz. Yukarıdaki konfigürasyonda service.name=app1 ile servis/uygulama ismini app1 olarak belirtmiş çoluyoruz.
  • OTEL_EXPORTER_OTLP_ENDPOINT ile OpenTelemetry Collector servisinin adresini veriyoruz. Sidecar deseni ile aynı pod üzerinde collector konteynırını da çalıştıracağımız için localhost:4318 olarak ayarlıyoruz.
  • OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_CONTROLLER_TELEMETRY_ENABLED ve OTEL_INSTRUMENTATION_COMMON_EXPERIMENTAL_VIEW_TELEMETRY_ENABLED ile Spring MVC’nin controller ve view katmanında telemetri verisi üretmesini istiyoruz.
  • OTEL_INSTRUMENTATION_KAFKA_EXPERIMENTAL_SPAN_ATTRIBUTES ile Kafka entegrasyonunun telemetri verisi üretmesini istiyoruz.
  • OTEL_INSTRUMENTATION_HTTP_SERVER_EMIT_EXPERIMENTAL_TELEMETRY ile Tomcat uygulama sunucusunun telemetri verisi üretmesini istiyoruz.
  • OTEL_INSTRUMENTATION_SPRING_INTEGRATION_PRODUCER_ENABLED ile Spring Integration kütüphanesinin otomatik telemetri verisi üretmesini istiyoruz.
  • OTEL_METRICS_EXPORTER üzerinden metrik telemetri veri toplama ve aktarımını kapatıyoruz, gündemimizde yer almadığı için.
  • OTEL_LOGS_EXPORTER üzerinden de uygulama log aktarımını kapatıyoruz.

Otel Collector Konfigürasyonu

Collector konfigürasyonu yaml üzerinden yapılıyor. Yaml dosyasını ConfigMap üzerinden konteynır içindeki bir dizine bağlamak (mount) ve bu dizini parametre olarak geçmek yeterli.

Konfigürasyon temelde verilerin alınmasını, işlenmesini ve hedef gözlemlenebilirlik sistemlerine (observability backend) aktarımını tarif diyor. Bu veri işleme hattı (pipeline) tarifi deklaratif olarak yapılıyor. Bir sistemden gelen girdiler işlenerek birden fazla sisteme aktarılabiliyor. bu aktarım toplu biçimde de yapılabiliyor. Tüm verileri aktarmak yerine örnekleme (sampling) yapmak da mümkün. 1000 TPS’lerin üzerine filan çıkıldığında örnekleme yapmak tavsiye ediliyor, toplam veri hacmini sınırlamak için.

Sidecar Olarak Otel Collector’ün Eklenmesi

containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.112.0
imagePullPolicy: IfNotPresent
env:
- name: OTEL_COLLECTOR_HOST
value: "localhost"
- name: OTEL_COLLECTOR_PORT_HTTP
value: "4318"
- name: OTEL_BACKEND_HOST
value: "jaeger-collector.observability.svc.cluster.local"
- name: OTEL_BACKEND_PORT
value: "4317"
ports:
- containerPort: 4318
volumeMounts:
- mountPath: /run/opentelemetry-collector
name: opentelemetry-collector-volume
args: ["--config", "/run/opentelemetry-collector/otel-collector-config.yml"]
- name: app-container
...

Otel Collector konteynırına otel-collector-config.yaml dosyasında kullanılmak üzere eklenen ortam değişkenleri şöyle:

  • OTEL_COLLECTOR_HOST ile uygulamanın collector’a gelirken kullanacağı DNS ismini veriyoruz. Sidecar olarak aynı pod içinde yer alacakları için değer localhost oluyor.
  • OTEL_COLLECTOR_PORT_HTTP ile uygulamanın collector’a gelirken kullanacağı port bilgisini veriyoruz. GRPC vb protokolleri de destekliyor. Biz HTTP ile gelmeyi planladığımız için HTTP portunu veriyoruz 4318 olarak.
  • OTEL_BACKEND_HOST ile verilerin aktarılağı Jaeger adresini veriyoruz. Örnekte bu değer jaeger-collector.observability.svc.cluster.local oluyor.
  • OTEL_BACKEND_PORT ile Jaeger port bilgisini veriyoruz. Birden fazla protokol destekliyor Jaeger. Burada Jaeger GRPC port değerini 4317 olarak ayarlıyoruz.

Aşağıdaki bölümde içeriğini anlattığımız otel-collector-config.yml dosyasını ise ConfigMap olarak tanımlıyoruz, ardından bunun üzerinden opentelemetry-collector-volume isimli bir Volume tanımlaması yapıp, konteynıra içindeki /run/opentelemetry-collector/ dizinine koyuyoruz.

otel-collector-config.yml Konfigürasyon Dosyası

Otel Collector konfigürasyonu için aşağıdaki otel-collector-config.yml dosyasını ConfigMap ile Collector konteynırına geçiyoruz, ve

receivers:
otlp:
protocols:
http:
endpoint: ${env:OTEL_COLLECTOR_HOST}:${env:OTEL_COLLECTOR_PORT_HTTP}

exporters:
otlp:
endpoint: "${env:OTEL_BACKEND_HOST}:${env:OTEL_BACKEND_PORT}"
tls:
insecure: true

processors:
batch:
probabilistic_sampler:
sampling_percentage: 10
mode: "proportional"

service:
pipelines:
traces:
receivers: [otlp]
processors: [probabilistic_sampler,batch]
exporters: [otlp]
  • receivers bölümü verilerin geleceği adresi belirtiyor.
  • processors bölümü Collector’e gelen veriler üzerinde yapılacak işlemleri belirtiyor. batch processor, gelen verilerin kümeler halinde gönderilmesi için kullanılıyor. probabilistic_sampler tüm veri yerine örneklem almayı (sampling) sağlıyor. yukarıdaki örnekte gelen verilerin %10'unun yayınlanmasını ayarlıyoruz.
  • service/pipelines bölümü verinin alınması, işlenmesi ve gönderimi tarif ediyor.

Ola Olala

app1 Rest API’sine gelen bir isteğin Kafka’ya yayınlanması, app2 standalone worker uygulamasının isteği işlemesi ve response topic üzerinden app1'e cevabın geri gönderimi ve app1'in senkron olarak istemciye geriye cevabı iletmesi. Hepsi 17ms.

Dağıtık iz takibi

Bonus: Loglara Context Eklemek

Binlerce log arasından isteğimiz log kümesine erişmek için korelasyon kurmamız şart. Bu amaçla işlem no, oturum no vb bilgiler ekleyebildiğimiz gibi her bir log satırına telemetri bilgilerini (trace-id, span-id) ekleyebiliyoruz basitçe.

<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<param name="Target" value="System.out"/>
<PatternLayout pattern="... | %X{trace_id} | %X{span_id} | %m%n"/>
</Console>
</Appenders>

Bunun çalışması için OpenTelemetry log konfigürasyon kütüphanesini uygulamaya eklemek yeterli

<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-log4j-context-data-2.17-autoconfigure</artifactId>
<version>2.9.0-alpha</version>
</dependency>

Sonuç olarak aşağıdaki gibi her bir log’a isteğe ait trace-id ve span-id bilgileri eklenmiş oluyor.

Bitirirken

Tüm detayları içermese de belli bir seviyede konuyu aktarabilmişimdir umarım. Faydalı olması dileğiyle.

Kaynakça

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Süleyman Fazıl Yeşil
Süleyman Fazıl Yeşil

No responses yet

Write a response