akka Service Deployment on Kubernetes

Ausgangspunk

Es gibt viele sehr gute Tutorials zur Entwicklung von reaktiven Services mit akka, akka-cluster oder akka-persistence. Die meisten dieser Tutorials fokussieren sich allerdings rein auf die Entwicklung und sauberen Code und nicht auf das Deployment. In diesem Blogbeitrag soll es um genau diese Thematik gehen. Wir werden eine Demo Microservice Architektur mit zwei Services mithilfe des sbt-native-packagers zu Docker Containern verpacken und diese dann mithilfe von akka-management auf minikube oder kubernetes (k8s) ausrollen.

Die Demo Architektur

Unser reaktives Softwaresystem basiert auf der klassischen Greeter Demo App von akka-grpc. Mithilfe einer akka-http Schnittstelle lassen sich Personen grüßen und die Anzahl wie oft eine Person bereits gegrüßt wurde, wird von einem akka-cluster bereitgestellt.

Unsere Demo besteht dabei aus zwei services die untereinander im kubernetes Netzwerk über gRPC und das akk-grpc Modul kommunizieren. Service 1 soll dabei einen beliebigen Backend Service darstellen der ein akka-cluster auf mehreren Kubernetes Pods aufbaut und betreibt. Dabei kommt ein Deployment mit ReplicaSet zum Einsatz. Service 1 nutzt akka-management und die kubernetes-api Erweiterung für die Erstellungen und die ServiceDiscovery im Cluster. Dadurch wird das Cluster aufgebaut und neue Knoten können nach Belieben in das akka cluster hinzugefügt werden. akka-management stellt darüber hinaus auch noch sehr nützliche Schnittstellen für den health- und readiness-check für die Services auf Port 8558 bereit. Zusätzlich hat Service 1 noch eine gRPC Interface auf Port 8081, dass von Service 2 angesprochen werden kann. Service 2 simuliert einen Frontendlayer. Ein Beispiel für den Einsatz eines solchen Services wäre, wenn man die Services über graphQL oder das Backend 4 Frontend Pattern bündeln möchte. Service 2 hat eine öffentliche HTTP API, die es den Nutzern erlaubt Nutzer grüßen zu lassen. Der Service wird als NodePort Service in Minikube betrieben. Da wir minikube verwenden, brauchen wir keinen ingress. Ein Beispiel ingress befindet sich aber auch innerhalb dieses Beitrags.

akka Referenzarchitektur des Microservice Deployments

Anmerkung:
Diese Demo Architektur legt im Gegensatz zu den vielen anderen guten Tutorials nicht viel Wert auf Code Qualität und hat vieles stark vereinfacht. Die Service Discovery für den gRPC Endpunkt von Service 2 zu Service 1 ist zwar auch mit akka-management und der enthaltenen akka Service Discovery erstellt. Allerdings verwendet die demo nicht den angelegten Cluster IP Service, sondern kommuniziert direkt mit dem ersten gefundenen Pod. Für die Suche verwendet Service 2 den Selector app=service1 und blockiert solange den Thread mit Await. Für eine Produktionsumgebung sollte der Thread nicht geblockt werden, sondern auf das Future reagiert werden. Die direkte Abfrage mithilfe von „.get“ sollte vorher validiert und geprüft werden. Auch das Logging ist nicht sonderlich sauber implementiert, sondern enthält hin und wieder ein println. Ursprünglich haben wir diese Demo für einen unserer Studenten erstellt, der derzeit seine Bachelorthesis im Bereich „Advanced logging, monitoring and tracing in akka“ schreibt. Wenn er seine Arbeit umgesetzt hat, werden wir die Ergebnisse sicherlich hinzufügen und einen weiteren Blogbeitrag veröffentlichen. Die println Ausgaben sollten dann weg sein.

Vorbereitung des kubernetes clusters / minikube

Für unser Deployment verwenden wir minikube, weitgehend lassen sich diese Schritte aber auch mit einem „normalen“ kubernetes cluster (z.B. über GKE, EKS, AKS) durchführen. Auf MAC OS lässt sich minikube mit

brew cask install minikube

installieren und

minikube start

starten. Bevor wir Container für die kubernetes registry builden können, müssen wir noch unsere Umgebung für minikube konfigurieren.

eval $(minikube docker-env)

Damit die Service Discovery über akka-management funktioniert müssen wir noch RBAC RoleBindings für unseren „default“ Account im „default“ Namespace hinterlegen. Diese Script kommt aus der lightbend akka-management Dokumentation.

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "watch", "list"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: read-pods
subjects:
  - kind: User
    name: system:serviceaccount:default:default
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Erstellung von Docker Container

Das github Repository enthält ein sbt Multiprojekt mit einigen nützlichen alias Kommandos in sbt. So existiert ein Kommando „runAll“ der mit dem sbt-revolver sbt Plugin alle Services startet, wie man es auch aus Lagom kennt. Über das Kommando „stopAll“ lassen sich ebendiese Services wieder stoppen. Für das automatische builden aller Container ist ein Kommando „buildAll“ hinterlegt. Im k8s Ordner im Repository ist ein Script zum builden der Container hinterlegt. Dieses nutzt Subprozesse, um die Container parallel bauen zu lassen.

Die Konfiguration von Docker ist für jeden Service sehr simpel gehalten. Weitere Informationen zur Konfiguration gibt es in der Dokumentation des sbt-native-packager.

enablePlugins(JavaAppPackaging)
enablePlugins(DockerPlugin)
packageName in Docker := "innfactory-test/service1"
version in Docker := "0.1"
dockerExposedPorts := Seq(2552, 8558)

und

enablePlugins(JavaAppPackaging)
enablePlugins(DockerPlugin)
​
packageName in Docker := "innfactory-test/service2"
version in Docker := "0.1"
dockerExposedPorts := Seq(2552, 8558, 8090)

Up and Running – Das Deployment auf kubernetes

Wenn der Build der Docker Container erfolgreich abgeschlossen ist können die yaml files aus dem k8s Ordner mithilfe von kubectl deployed werden. Aufgrund des Awaits in Service 2 sollte der Service 1 in jedem Fall zuerst vollständig deployed werden und es sollte abgewartet werden, bis das akka Cluster vollständig online ist und alle healthchecks der pods erfolgreich sind. Service 1 kann mit

kubectl apply -f service1.yaml

und Service 2 kann mit

kubectl apply -f service2.yaml

auf kubernetes deployed werden.

Da wir akka-management für die health- und readnisse Checks in Kubernetes verwenden möchten, müssen wir die Ports entsprechend freigeben.

livenessProbe:
  httpGet:
    path: /alive
    port: 8558
readinessProbe:
  httpGet:
    path: /ready
    port: 8558
apiVersion: v1
kind: Service
metadata:
  labels:
    app: service2
  name: service2
spec:
  ports:
    - name: "http-api"
      port: 8090
      protocol: TCP
      targetPort: 8090
  selector:
    app: service2
  type: NodePort

Über minikube kann der Service 2 direkt verwendet werden. Die URL erhält man mit dem Kommando

minikube service service2

. Wenn man die ausgegebene URL in einem Browser öffnet, sollte ein HTTP-200 mit der Nachricht „Service 2 is ok“ zurückgegeben werden.

Wie bereits angesprochen erstellen beide Services ein Deployment und ein Kubernetes Service. Service 1 ist dabei eine ClusterIP und bündelt die Pods unter einen internen IP und Service 2 ist ein NodePort, der über einen Ingress auch von außen angesprochen werden könnte.

apiVersion: v1
kind: Service
metadata:
  labels:
    app: service2
  name: service2
spec:
  ports:
    - name: "http-api"
      port: 8090
      protocol: TCP
      targetPort: 8090
  selector:
    app: service2
  type: NodePort

Wenn alle Services online sind, sollte man Benutzer über die HTTP Schnittstelle von außen grüßen lassen können. Das Cluster zählt wie oft der Benutzer gegrüßt worden ist.
Der Request „http://192.168.99.100:32462/greet/innFactory“ sollte z.B. in etwa folgendes über HTTP-200 ausgeben: „Hello innFactory – The actorsystem greeted you 1 times!“

Anmerkung:
Wir haben Service 1 zwar mit einer Cluster IP in Kubernetes deployed, nutzen diesen aber nicht. Für ein produktives Szenario sollten wir als gRPC Endpunkt ebendiese Cluster IP verwenden. Außerdem benötigen wie z.B. in der Google oder AWS Cloud einen Ingress um auf unseren Service über https und gültigem Zertifikat zugreifen zu können. Nachfolgender Code erstellt einen passenden LoadBalancer in der jeweiligen Cloud.

# e.g
apiVersion: "extensions/v1beta1"
kind: Ingress
metadata:
  name: service2
  annotations:
    "ingress.kubernetes.io/rewrite-target": "/"
spec:
  rules:
    - http:
      paths:
      - backend:
        serviceName: service2
        servicePort: 8090

Links

reactive microservice example @ github
English Version @ Medium

Comments

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.