Distributed Application Runtime -Dapr’a Genel Bakış

Dapr, abstraction ve decouplingin vücut bulmuş halidir :)

Trendyol Platform ekibi olarak geliştirdiğimiz çözüm ve yaklaşımlar ile mantalite ve implementasyon açısından benzerliğinden ötürü bir süredir Dapr projesini yakından takip ediyorum. Bu yazımda da sizlerle Dapr hakkında edindiğim bilgileri paylaşacağım.

Dapr Nedir

Dapr, Microsoft tarafından Go dili ile geliştirilen ve sitesinde “distributed application runtime” olarak tanımlanan open source bir projedir.

Kubernetes ortamında çalışan microserviceler için çeşitli runtime ihtiyaçlarını sidecar yaklaşımı ile karşılayan, uygulamalarımızın cross cutting ihtiyaçlarını process seviyesine taşıyan bir çözümdür.

Dapr’ın temel amacı belirli problemleri farklı dil ve teknolojiler için tekrar tekrar çözmeden, çözümü tek bir process olarak implement edip uygulamaların hizmetine sunmaktır.

Örneğin çeşitli dillerde geliştirdiğiniz servisleriniz için message brokerlar veya key-value storelar ile haberleşme implementasyonları yapmanıza gerek kalmadan, sadece Dapr kullanarak bu işlemleri sağlayabilirsiniz.

Dapr aynı zamanda Service Invocation özelliği sayesinde farklı servislerin haberleşmesini sağlar. Çeşitli observability metrikleri ve error handling mekanizması da barındırır.

Kubernetes Sidecar

Kubernetes üzerinde çalışan uygulamalarımızı ele aldığımızda, Dapr podumuzun içerisinde ikinci bir container olarak çalışır. Ana uygulamamız Dapr ile http/grpc üzerinden veya Dapr’ın çeşitli diller için geliştirdiği sdk aracılığıyla haberleşir.

Dapr Kubernetes Installation

Dapr yükleme için kolaylık sağlayan bir cli aracı da bulunduruyor. Dapr cli’ı sisteminize kurduktan sonra dapr init -k komutu ile kubernetes clusterınıza kurulum yapabilirsiniz.

Bu işlem sonrasında dapr-system namespace’i altına dapr deploymentları gelecek.

Dapr Sidecar Injection

Dapr’ın uygulamalarımıza sidecar olarak eklenmesi için deployment tanımımıza bazı annotationlar eklememiz gerekiyor.

apiVersion: apps/v1
kind: Deployment
...
spec:
template:
metadata:
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "dapr-test"
dapr.io/app-port: "8080"
dapr.io/config: "my-config"
  • dapr.io/enabled Sidecar olarak Dapr containerın podumuza inject edilmesini sağlıyor
  • dapr.io/app-id Uygulmamızın dapr idsi
  • dapr.io/app-port Dapr sidecarın uygulamamız ile haberleşeceği portu belirtiyoruz
  • dapr.io/config Uygulamamız için aktif olmasını istediğimiz Dapr Configuration custom resource ismini veriyoruz

Dapr Components

Dapr kubernetes üzerinde çeşitli componentler ile birlikte çalışır. Dapr-sidecar-injector, dapr-operator, dapr-sentry, dapr-placement-server Dapr’ın temel componentlerindendir.

Ayrıca kubernetes üzerinde Dapr runtime konfigürasyonlarını yapabilmemiz için bizlere Component, Subscription ve Configuration tipinde üç farklı CR (Custom Resource) sunar.

Component custom resource’u ile pubsub broker veya state store bağlantılarını tanımlayabiliyoruz. Burada dikkat edilmesi gereken, Component’in type fieldında “pubsub.” veya “state.” prefixleri ile component type’ımızı belirtiyoruz.

Dapr Özellikleri

Dapr uygulamalarımız için gerekli çeşitli building blocks implementasyonlarını bizlerin kullanımına sunar.

Dapr microserviceler arasındaki haberleşmeyi kendi üzerine alarak bizlere servisler arası haberleşebilme özelliği sunuyor. Servisler birbirlerini Dapr aracılığı ile çağırdığında haberleşmeyi monitoring edebilmemizi de sağlıyor.

Ayrıca Dapr bizlere servisler arası mTLS haberleşebilme özelliği de sunuyor. Uygulamanız http üzerinden çalışmaya devam ederken, Dapr sayesinde uygulamadan habersiz bir şekilde iletişim tls üzerinden gerçekleşiyor.

//codeout, err := client.InvokeMethodWithContent(context.Background(), "httpbin", "/headers", "GET", &dapr.DataContent{})

Dapr çeşitli veritabanları kullanarak uygulamaların state tutabilmesini sağlıyor. Basit bir Component resource’u deploy ederek Dapr runtime’ın ilgili state store ile haberleşmesini sağlayabiliyoruz.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: my-redis
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis.default:6379

Aşağıda Dapr golang-sdk kullanarak microservice’imiz içerisinden Dapr sidecar ile haberleşip state işlemlerini gerçekleştirebiliyoruz.

//setState code
err := client.SaveState(context.Background(), "my-redis", key, []byte(value))
//getState code
stateItem, err := client.GetState(context.Background(), "my-redis", key)

State store’da olduğu gibi çeşitli pub-sub brokerlarını Dapr Component tanımı ile kullanabilirsiniz.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: rabbitmq-pubsub
namespace: default
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqp://my-rabbit.default:5672"

Sdk ile kullanımı:

err = client.PublishEvent(context.Background(), "rabbitmq-pubsub", "test-topic", []byte("hello dapr"))
  • Declarative Subscription

Kubernetes Custom Resource’larını kullanarak tanımlayabildiğimiz subscription tipi. Ben örnek olarak rabbitmq brokera bağlantı kuran bir custom resource hazırladım.

Topic fieldı, rabbitmq üzerinde oluşturulacak exchange’i temsil ediyor. Route fieldında ise mesajın dapr tarafından hangi endpointe gönderileceğini belirtiyoruz. Pubsubname fieldı ise pubsub component’i olarak tanımladığımız resource ismini belirtiyor.

apiVersion: dapr.io/v1alpha1
kind: Subscription
metadata:
name: my-subscription
spec:
topic: test-topic
route: /subscription
pubsubname: rabbitmq-pubsub
scopes:
- dapr-test
  • Programmatic Subscription

Yukarıda yaptığımız declarative tanımlamanın kod tarafındaki karşılığına bakalım:

http.HandleFunc("/dapr/subscribe", func(writer http.ResponseWriter, request *http.Request) {
subscriptions := []interface{}{
map[string]interface{}{
"pubsubname": "rabbitmq-pubsub",
"topic": "test-topic",
"route": "/subscription",
},
}

writer.Header().Add("content-type", "application/json")

subs,_ :=json.Marshal(subscriptions)
writer.Write(subs)
})

Dapr runtime loglarına baktığımızda şöyle bir log görüyoruz:

time="2021-04-04T14:26:18.4687115Z" level=info msg="app is subscribed to the following topics: [test-topic] through pubsub=rabbitmq-pubsub" app_id=dapr-test instance=dapr-test-5749fdf57b-pxr22 scope=dapr.runtime type=log ver=1.0.1

Duplication?

Bir topic için hem declarative hem de programmatic tanımlama yaptığımız zaman dapr duplicate subscriptionı ignore edip sadece bir kez subscribe oluyor.

Dapr ile microservice’lerimize erişimleri kısıtlamalayabiliriz. Örneğin “httpbin” servisinin “/headers” pathine “dapr-test” isimli servisten gelen requestlerin deny edilmesini söyleyen bir Configuration resource oluşturalım.

apiVersion: v1
items:
- apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: appconfig
namespace: default
spec:
accessControl:
defaultAction: allow
policies:
- appId: dapr-test
defaultAction: allow
namespace: default
operations:
- action: deny
httpVerb:
- '*'
name: /headers
trustDomain: public
trustDomain: public
metric:
enabled: true
kind: List
metadata:
resourceVersion: ""
selfLink: ""

Bu tanımı yaptıktan sonra httpbin deploymentına ilgili config için bir annotation eklemeliyiz:

spec:
template:
metadata:
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "httpbin"
dapr.io/app-port: "80"
dapr.io/config: "appconfig"

Şimdi dapr-test podumuzdan httpbin poduna istek attığımızda şöyle bir hata ile karşılaşıyoruz (curl isteği dapr-test podundan atılıyor, /invoke-service endpointi dapr üzerinden httpbin servisini çağırıyor):

Dapr bizlere actor pattern ile programlama yapabilmemizi sağlıyor. SDK aracılığıyla actorler oluşturup bunları çalıştırmamızı ve actorler arası haberleşmeyi sağlıyor.

Konunun detaylarına şuradan ulaşabilirsiniz:

Dapr üzerinden yapılan işlemleri, resource kullanımını, hataları, sidecar injectionları grafana üzerinden izleyebileceğimiz metrik dashboardları hazır olarak geliyor.

Dapr’ın hazır olarak sunduğu dashboardlara şu adresten ulaşabilirsiniz:

Dapr dashboard için gerekli kurulumları yapalım:

kubectl create namespace dapr-monitoring helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install dapr-prom prometheus-community/prometheus -n dapr-monitoring \ 
—-set alertmanager.persistentVolume.enable=false \
--set pushgateway.persistentVolume.enabled=false \
--set server.persistentVolume.enabled=false
helm repo add grafana https://grafana.github.io/helm-charts helm install grafana grafana/grafana -n dapr-monitoring \
--set persistence.enabled=false

Daha sonra port forwarding ile grafana arayüzüne erişelim:

kubectl port-forward -n dapr-monitoring svc/grafana 8082:80

Grafana giriş şifresini edinelim:

kubectl get secret — namespace dapr-monitoring grafana -o jsonpath="{.data.admin-password}" | base64 — decode ; echo
dapr sidecar dashboard
dapr system services dashboard

Son olarak Dapr bizlere oluşturduğumuz uygulamaları, komponentleri ve bunların konfigürasyonlarını, uygulama loglarını görebileceğimiz basit bir dashboard da sağlıyor.

Dashboardı açmak için dapr cli iledapr dashboard -k komutunu çalıştırıyoruz.

Yazıyı burada tamamlamak istiyorum, umarım faydalı bir içerik olmuştur.

Dapr go-sdk ile örnek kullanımları barındıran github reposunu da sizlerle paylaşıyorum:

Dapr için hazırlanmış kaynakları bir araya getirdiğim awesome-dapr projesi:

Sr. Software Engineer @Trendyol & Software Development Enthusiast | Interested in Go&Java DDD, CQRS, Event Sourcing, Scalability. Open Source Contributor.

Sr. Software Engineer @Trendyol & Software Development Enthusiast | Interested in Go&Java DDD, CQRS, Event Sourcing, Scalability. Open Source Contributor.