Kubernetes Ortamında Python Flask ile Distributed Tracing (Jaeger)

Standard

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;

Sorularınızı bu makale altından sorabilirsiniz. Kodların son haline ve çalıştırılabilir versiyonuna aşağıdaki repodan ulaşabilirsiniz;

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

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site istenmeyenleri azaltmak için Akismet kullanır. Yorum verilerinizin nasıl işlendiğini öğrenin.