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

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

Dapr Kubernetes Installation

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

Dapr Sidecar Injection

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

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

Service Invocation

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{})

State Store

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)

Pub-Sub Publish

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"))

Pub-Sub 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.

Access Control

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):

Actor Pattern

Konunun detaylarına şuradan ulaşabilirsiniz:

Monitoring

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

Dashboard

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.