Herkese merhabalar,
Aslında anlatmaya distributed tracing nedir ile başlasam sanırım daha açıklayıcı olacak. Birbirleriyle haberleşen servislerden oluşan mikroservis mimarisi gibi yapılarda olan projelerde, bir paketin birden fazla servisten oluştuğu ortamda, sisteme girdiği andan, üzerinden geçtiği tüm servislerinizde ne kadar vakit geçirdiğini, eğer bir serviste takıldıysa hangi paketin neden takıldığını, olası gecikme durumlarında hata tespiti yapabilmenize imkan veren yapıya distributed tracing denmekte. Konu hakkında daha detaylı bir açıklama için şu makaleyi tavsiye ederim.
Gelelim bu işi K8s ortamında ayağa kaldırdığımız birbiriyle haberleşen Python Flask servisleriyle nasıl yapabileceğimize…
Şu an ilerleyeceğimiz yapı örneğinde, diyelim ki 2 servisimiz mevcut. Bunlardan birincisi frontend tarafını yönettiğimiz, önyüz için oluşturduğumuz proje, diğeriyse backend tarafında DB’ye bağlanan ve rest üzerinden haberleştirdiğimiz bir servis.
Frontend Servisi
Projelerimizden frontend ile başlayalım, projemizi geliştireceğimiz dizinde “frontend” diye bir dizin oluşturuyoruz. frontend.py dosyamızın içerisine;
# Created by Sezer BOZKIR<admin@sezerbozkir.com> at 13.04.2022
import os
import requests
from flask import Flask
from jaeger_client import Config
from flask_opentracing import FlaskTracing
app = Flask(__name__)
config = Config(
config={
'sampler':
{'type': 'const',
'param': 1},
'logging': True,
'reporter_batch_size': 1, },
service_name="service")
jaeger_tracer = config.initialize_tracer()
tracing = FlaskTracing(jaeger_tracer, True, app)
def get_counter(counter_endpoint):
counter_response = requests.get(counter_endpoint)
return counter_response.text
def increase_counter(counter_endpoint):
counter_response = requests.post(counter_endpoint)
return counter_response.text
@app.route('/')
def hello_world():
counter_service = os.environ.get('COUNTER_ENDPOINT', default="https://localhost:5000")
counter_endpoint = f'{counter_service}/api/counter'
counter = get_counter(counter_endpoint)
increase_counter(counter_endpoint)
return f"""Hello, World! You're visitor number {counter} in here!\n\n"""
Anadizine her istek geldiğinde bu dizin içerisinde backend olarak global environment’tan gelen endpoint’e giderek buraya post atarak counter’ı bir artıran örnek bir proje oluşturduk.
2 tane metodumuz mevcut, 1 metodumuz güncel counter değerini alırken (get_counter), “increase_counter” metodumuz da backend servisimize gidip değerin 1 artırılmasını sağlamakta.
Bu yapıyı sürdürürken, arka tarafta da sidecar yöntemiyle inject ettiğimiz jaeger agent’a da her istek için bilgi vermesi için “FlaskTracing” kütüphanesini entegre ettik.
Şimdi frontend yapımızı dockerize edelim;
FROM alpine:latest
RUN apk add --no-cache py3-pip python3 && \
pip3 install flask requests Flask-Opentracing jaeger-client
COPY . /usr/src/frontend
ENV FLASK_APP frontend.py
WORKDIR /usr/src/frontend
CMD flask run --host=0.0.0.0 --port=8000
Dockerfile dosyamızda alpine’den bir imaj almasını, python, flask, request ve gerekli alt kütüphaneleri yüklemesini söyleyip, 8000 portundan ayağa kalkmasını komutlarını verdik.
Backend Servisi
Şimdi de frontend servisimizden gelen isteği alıp, DB’ye güncelleme yapacak (burada basit bir örnek olması açısından direkt global bir değişkeni güncelliyoruz) servisimizi oluşturarak başlıyoruz. Bu projemizide “backend” isimli bir dosyanın altında oluşturuyoruz;
# Created by Sezer BOZKIR<admin@sezerbozkir.com> at 13.04.2022
from random import randint
from time import sleep
from flask import request
from flask import Flask
from jaeger_client import Config
from flask_opentracing import FlaskTracing
app = Flask(__name__)
config = Config(
config={
'sampler':
{'type': 'const',
'param': 1},
'logging': True,
'reporter_batch_size': 1, },
service_name="service")
jaeger_tracer = config.initialize_tracer()
tracing = FlaskTracing(jaeger_tracer, True, app)
counter_value = 1
def get_counter():
return str(counter_value)
def increase_counter():
global counter_value
int(counter_value)
sleep(randint(1, 10))
counter_value += 1
return str(counter_value)
@app.route('/api/counter', methods=['GET', 'POST'])
def counter():
if request.method == 'GET':
return get_counter()
elif request.method == 'POST':
return increase_counter()
Backend servisimizse tek bir endpoint’ten oluşan, gelen istek get ise “counter_value” değerimizi dönen, post gelirse de bu değişkenin değerini 1 artıran bir servis.
Bu servisimizi de dockerize ederek k8s için hazır hale getiriyoruz;
FROM alpine:latest
RUN apk add --no-cache py3-pip python3 && \
pip3 install flask Flask-Opentracing jaeger-client
COPY . /usr/src/backend
ENV FLASK_APP backend.py
WORKDIR /usr/src/backend
CMD flask run --host=0.0.0.0 --port=5000
Tıpkı frontend servisimizde olduğu gibi, bu servisimiz için de python ve flask ile alt bileşenlerini yüklemesini söyleyip bu sefer 5000 portundan yayın yapmasını söylüyoruz.
Artık elimizde birbiriyle haberleşen 2 servisimiz ve bu servislerin dockerize edilmiş halleri mevcut.
Docker imajlarının oluşturulması ve DockerHub’a yüklenmesi (Dockerizing)
Bu servislerimizin docker imajlarını oluşturup, bu imajları k8s ortamında çekebilmek için dockerhub ortamına yükleyeceğiz. (konu hakkında detaylı bilgi için şu makaleye bakabilirsiniz.)
(eğer giriş yaptıysanız tekrar etmenize gerek yok) dockerhub’a imajlarımızı yükleyebilmemiz için giriş yapıyoruz:
$ docker login --username=your_username --password=your_password
Şimdi de yükleyeceğimiz frontend ve backend docker imajlarımızı önce built sonrasında da push komutları ile dockerhub’a aktarıyoruz;
$ docker build -t your_username/do-visit-counter-frontend:v1 ./frontend $ docker push your_username/do-visit-counter-frontend:v1 $ docker build -t your_username/do-visit-counter-backend:v1 ./backend $ docker push your_username/do-visit-counter-backend:v1
Kubernetes Ortamına Hazırlık
Frontend Servisinin k8s konfigürasyonu
Artık 2 servisimiz, bu servislerimizin dockerhub’da birer imajları elimizde mevcut. Şimdi de bu servisleri k8s üzerinde hem birbirleri ile hem de global ortam ile haberleşecek şekilde konfigüre etmeye başlayabiliriz. Frontent projemizin deployment konfigurasyonu ile başlıyoruz:
apiVersion: apps/v1
kind: Deployment
metadata:
name: do-visit-counter-frontend
labels:
name: do-visit-counter-frontend
spec:
replicas: 1
selector:
matchLabels:
app: do-visit-counter-frontend
template:
metadata:
labels:
app: do-visit-counter-frontend
spec:
containers:
- name: do-visit-counter-frontend
image: your_dockerhub_username/do-visit-counter-frontend:v1
imagePullPolicy: Always
env:
- name: COUNTER_ENDPOINT
value: "http://do-visit-counter-backend.default.svc.cluster.local:5000"
ports:
- name: frontend-port
containerPort: 8000
protocol: TCP
Yaml dosyamızda 1 replikadan oluşan, global env. olarak endpoint değerimizi verdiğimiz port olarakta 8000’den erişilebilir olacak şekilde konfigürasyon yaptık. Burada dikkat etmeniz gereken, image kısmını kendi kullanıcı adınızla değiştirmeniz, versiyon numarasına dikkat etmeniz, counter_endpoint değişkenindeki domain bilgisini birazdan oluşturacağımız backend endpoint adresi ile vermeniz. Eğer ortamı openshift’te ayağa kaldırıyorsanız domain bilgisi farklı olacaktır. Bu nedenle öncelikle backend deployment’ını ayağa kaldırıp, sonrasında frontend konfigürasyonlarına geçebilirsiniz.
Backend Servisinin k8s konfigurasyonu
Neredeyse frontend ile aynı yapıya sahip backend için de (5000 portunun olduğuna dikkat) deployment yaml’ımızı oluşturuyoruz:
apiVersion: apps/v1
kind: Deployment
metadata:
name: do-visit-counter-backend
labels:
name: do-visit-counter-backend
spec:
replicas: 1
selector:
matchLabels:
app: do-visit-counter-backend
template:
metadata:
labels:
app: do-visit-counter-backend
spec:
containers:
- name: do-visit-counter-backend
image: your_dockerhub_username/do-visit-counter-backend:v1
imagePullPolicy: Always
ports:
- name: backend-port
containerPort: 5000
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: do-visit-counter-backend
spec:
selector:
app: do-visit-counter-backend
ports:
- protocol: TCP
port: 5000
targetPort: 5000
Yaml dosyasında backend portu olarak 5000’i açtığımızı ve bu 5000’i servis katmanında 5000 ile eşleştirdiğimizi k8s’e söyleyerek ayağa kaldırmasını istediğimizi söylüyoruz.
Servislerin k8s Ortamında Yayınlanması
Öncelikle backend, sonrasında da frontend servislerimizi ayağa kaldırıp, pod larımızı listeleyerek status kontrolü yapıyoruz;
$ kubectl apply -f ./backend/deploy_backend.yaml $ kubectl apply -f ./frontend/deploy_frontend.yaml $ kubectl get pods
Eğer herşey yolundaysa aşağıdaki gibi bir liste görüyor olmalıyız;
Output NAME READY STATUS RESTARTS AGE do-visit-counter-backend-79f6964-prqpb 1/1 Running 0 3m do-visit-counter-frontend-6985bdc8fd-92clz 1/1 Running 0 3m
Jaeger Servisi
Örneğimiz gereği, jaeger servisini de kendimiz oluşturuyoruz. Jaeger’ın resmi kütüphanesinden ihtiyaç duyduğu Role, Role Binding ve operator yaml larını ortamımızda çalıştırıyoruz;
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
Yukarıdaki yaml dosyaları göz korkutucu geldiyse alternatif olarak jaeger helm chart üzerinden de jaeger’ı ayağa kaldırmak mümkün;
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts helm install jaeger jaegertracing/jaeger
Şimdi tekrar pod larımızı kontrol edelim;
$ kubectl get pods Output NAME READY STATUS RESTARTS AGE do-visit-counter-backend-79f6964-prqpb 1/1 Running 0 3m do-visit-counter-frontend-6985bdc8fd-92clz 1/1 Running 0 3m jaeger-operator-547567dddb-rxsd2 1/1 Running 0 73s simplest-759cb7d586-q6x28 1/1 Running 0 42s
Arayüze ulaşabilmek için port yönlendirmemizi yapıyoruz;
kubectl port-forward $(kubectl get pods -l=app="jaeger" -o name) 16686:16686
Artık Jaeger UI’a ulaşmak http://localhost:16686 adresi üzerindwn mümkün;

Jaeger’da tracing işlemini görebilmek için için ister konsoldan ister arayüzden 8000 portuna HTTP istek atarak tracing denemesi yapabiliriz;
for i in 0 1 2 3 4 5 6 7 8 9; do curl localhost:8000; done
Eğer herşey yolundaysa görmemiz gereken çıkıt;
Output Hello, World! You're visitor number 1 in here! Hello, World! You're visitor number 2 in here! . . . Hello, World! You're visitor number 10 in here!
İsteklerimiz başarıyla ulaştığına göre Jaeger üzerinden de görüntüleyebiliriz demektir;

https://github.com/Natgho/Python-Distributed-Tracing-with-Jaeger-in-K8s
Bir sonraki yazımda görüşmek üzere 🙂
Kaynaklar
https://artifacthub.io/packages/helm/jaegertracing/jaeger
https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/
https://www.magalix.com/blog/deploying-an-application-on-kubernetes-from-a-to-z
https://www.digitalocean.com/community/tutorials/how-to-implement-distributed-tracing-with-jaeger-on-kubernetes