# Lab 09: Request-Response-basierte Interaktion mit gRPC gRPC (gRPC Remote Procedure Calls) ist ein Protokoll zum Aufruf von entfernten Funktionen (Remote Procedures) in verteilten Computersystemen. Es basiert auf HTTP/2 und [Protocol Buffers](https://developers.google.com/protocol-buffers). [gRPC](https://grpc.io) wird von der [Cloud Native Computing Foundation](https://www.cncf.io) als ["incubating project"](https://www.cncf.io/projects) geführt. Dieses Lab bietet eine grundlegende Einführung in die Arbeit mit gRPC (auf Basis von Python) und umfasst: - Definieren eines Dienstes in einer `.proto`-Datei. - Server- und Client-Code mit Hilfe des Protokollpuffer-Compilers zu generieren - Die Python gRPC-API für ein einfaches Client-Server-Setting zu nutzen. Es basiert auf dem offiziellen [gRPC Tutorial](https://grpc.io/docs/languages/python). Diese Lab ist allerdings so vorbereitet, dass Sie automatisch in Kubernetes deployen können. Insbesondere werden wir uns den Performance Unterschied zu REST-basierten APIs ansehen. Beide Ansätze fallen nämlich in den Ansatz des Request-Response Kommunikationsmusters. ## Inhalt - [Lab 09: Request-Response-basierte Interaktion mit gRPC](#lab-09-request-response-basierte-interaktion-mit-grpc) - [Inhalt](#inhalt) - [Vorbereitung](#vorbereitung) - [Übung 01: Entwickeln eines gRPC-Clients](#übung-01-entwickeln-eines-grpc-clients) - [Übung 02: Entwickeln eines REST-Servers (zum Vergleich)](#übung-02-entwickeln-eines-rest-servers-zum-vergleich) - [Übung 03: Performancevergleich HTTP (REST) vs. gRPC (auf localhost)](#übung-03-performancevergleich-http-rest-vs-grpc-auf-localhost) - [Übung 04: Performancevergleich HTTP (REST) vs. gRPC (in einem Cluster)](#übung-04-performancevergleich-http-rest-vs-grpc-in-einem-cluster) - [Verständnis- und Transferfragen](#verständnis--und-transferfragen) - [Links](#links) - [Was sollten Sie mitnehmen](#was-sollten-sie-mitnehmen) ## Vorbereitung Sie benötigen auf Ihren lokalen Entwicklungsrechner: - [Python 3](https://www.python.org/downloads) - [Docker](https://www.docker.com/get-started) - [Lens](https://k8slens.dev) Führen Sie anschließend bitte folgende Schritte aus: 1. [Forken](https://git.mylab.th-luebeck.de/cloud-native/lab-rest/-/forks/new) Sie bitte dieses GitLab-Repository in Ihren GitLab-Namensraum. 2. Klonen Sie das in Schritt 1 geforkte Repository bitte anschließend auf ihrem lokalen Rechner (`git clone`). 3. Installieren Sie anschließend bitte lokal auf Ihrem Rechner die Kubernetes-IDE [Lens](https://k8slens.dev/). 4. Laden Sie sich Ihre `kubeconfig` Datei im [Moodle-Kurs](https://to-be-donex) herunter. 5. Starten Sie Lens und fügen Sie der IDE die kubeconfig Datei hinzu, um auf Ihren Cluster zugreifen zu können. Sie sollten dann Ihren Namespace in dem für Sie bereitgestellten K8S-Cluster sehen. 6. Erstellen Sie anschließend in Gitlab unter `Einstellungen -> Repository -> Bereitstellungstoken` für das in Schritt 1 geforkte Repository einen Bereitstellungstoken, um selbstgebaute Container Images deployen zu können. - **Name:** `Registry read access (deployment)` - **Username:** `image-registry` (bitte exakt so!) - **Scope:** `read-registry` (nicht mit read repository verwechseln!) - Klicken Sie anschließend auf Bereitstellungstoken erstellen und kopieren Sie sich dieses geheime Token in die Zwischenablage! 7. Hinterlegen Sie nun für Gitlab Build-Pipelines dieses geheime Token unter `Einstellungen -> CI/CD -> Variables (Aufklappen) -> ADD VARIABLE` als CI/CD-Variable. - **Key:** `CI_REGISTRY_TOKEN` (exakt so) - **Value:** Fügen Sie hier das geheime Token (Schritt 2) aus der Zwischenablage ein. - **Type:** `Variable` (nichts anderes) - **Flags:** Selektieren Sie `Mask Variable` damit das geheime Token in Log-Dateien maskiert wird. 8. Hinterlegen Sie in Ihrem geforkten GitLab-Repository nun die `kubeconfig`-Datei als CI-Environment-Variable mittels `Einstellungen -> CI/CI -> Variables (Aufklappen) -> ADD VARIABLE` (setzen Sie hierfür folgende Werte) - **Key:** `KUBECONFIG` (Exakt so eingeben) - **Value:** Inhalt der kubeconfig (z.B. mittels Copy-Paste aus Editor) - **Typ:** `File` (Auswählen, WICHTIG!!!) ## Übung 01: Entwickeln eines gRPC-Clients Zur Entwicklung einer gRPC-basierten Kommunikation zwischen Client und Server ist eine Schnittstellen-Definition erforderlich. In gRPC wird dies in `.proto` Dateien vorgenommen. Aus diesen `.proto`-Dateien werden Stubs für die Client- und die Server-Seite vorgenommen. - Installieren Sie sich bitte die notwendigen Python Pakete auf Ihrem lokalen Entwicklungssystem mittels `python3 -m pip install -r grpc/Requirements.txt` - Lesen Sie sich dann ein wenig in das offizielle [gRPC Tutorial](https://grpc.io/docs/languages/python) ein. - Schauen Sie anschließend die `grpc/helloworld.proto` Datei an und versuchen Sie diese nachzuvollziehen. Für die folgenden Schritte wechseln Sie bitte ins `grpc`-Verzeichnis (`cd grpc`). - Generieren Sie dann die Stubs für die Client- und Server-Seite. ```Bash python3 -m grpc_tools.protoc --proto_path=. --python_out=. --grpc_python_out=. helloworld.proto ``` Dies sollte im `grpc`-Verzeichnis die Stubs `helloworld_pb2_grpc.py` und `helloworld_pb2.py` für Client und Server erzeugt haben. Schauen Sie gerne einmal hinein, diese Dateien dürfen aber nicht manuell angepasst werden. Spätestens beim Container-Build würde manuelle Anpassungen an diesen Stubs wieder überschrieben werden. - Versuch Sie nun die `grpc/greeter_server.py` vor dem Hintergrund des gRPC Tutorials nachzuvollziehen. Sie sehen dort, wie die Stubs zu importieren sind, und wie die gRPC-API als von `Servicer`-Klassen abzuleitende Klasse zu implentieren ist. Diese `Servicer`-Klassen wurden aus der `.proto` im vorherigen Schritt generiert. - Versuchen Sie ferner die `grpc/greeter_client.py` nachzuvollziehen. Sie sehen hier wie der Stub zu beziehen ist. Über dieses Proxy-Objekt läuft die Kommunikation mit dem entfernten gRPC-Server. Starten Sie nun den gRPC-Server lokal bei sich auf dem Rechner. ```Bash python3 grpc/greeter_server.py ``` In einer weiteren Shell starten Sie bitte den gRPC-Client. ```Bash python3 grpc/greeter_client.py ``` Dieser Client setzt 100 entfernte Aufrufe ab. In der Konsole des Servers sollten Sie folgende Logs sehen: ``` [...] ipv6:[::1]:50554 | 2021-03-03T14:17:20.518175 | Request name: "you" ipv6:[::1]:50554 | 2021-03-03T14:17:20.520777 | Request name: "you" ipv6:[::1]:50554 | 2021-03-03T14:17:20.529476 | Request name: "you" ipv6:[::1]:50554 | 2021-03-03T14:17:20.531520 | Request name: "you" ipv6:[::1]:50554 | 2021-03-03T14:17:20.537797 | Request name: "you" [...] ``` Gratulation! Sie haben erfolgreich Ihren (ersten?) gRPC-Server entwickelt! Prüfen Sie bitte noch, ob Sie erfolgreich einen entsprechenden Container bauen können. ``` docker build -t greeter_grpc grpc ``` ## Übung 02: Entwickeln eines REST-Servers (zum Vergleich) Für einen Performance Vergleich bauen wir uns einen REST-Server auf Basis von Flask (analog zum vorherigen Lab). Dieser Server soll auf Port 5000 arbeiten und - unter der Route `/` die Zeichenkette `"Hello!"` liefern. - Unter der Route `/hello/` soll die Zeichenkette `"Hello, !"` geliefert werden. `` ist als Platzhalter für Routen wie `/hello/Max` oder `hello/Anna` zu verstehen. Entwickeln Sie diesen REST-Server in der Datei `rest/greeter_rest.py` und lassen diesen wie folgt laufen. ```Bash > python3 rest/greeter_rest.py * Serving Flask app "greeter_rest" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) [...] ``` Prüfen Sie z.B. mit diesen URLs, ob dies funktioniert: - [http://localhost:5000](http://localhost:5000) => `Hello!` - [http://localhost:5000/hello/Otto](http://localhost:5000/hello/Otto) => `Hello, Otto!` Prüfen Sie bitte auch, ob Sie den vorbereiteten Container bauen können. ```Bash docker build -t greeter_rest rest ``` Gratulation! Sie haben schon wieder einen REST-API gebaut. > __Hinweis:__ > Wenn Sie gar nicht weiter kommen, hilft vielleicht folgende Abkürzung. > > `> cp cheat/greeter_rest-ue2.py rest/greeter_rest.py` ## Übung 03: Performancevergleich HTTP (REST) vs. gRPC (auf localhost) Entwickeln Sie nun auf Basis des Clients aus Übung 01 einen Client, der einmal den gRPC-Server und dann den REST-Server abfragt und dies 100-mal wiederholt. Dabei sollen die Antwortzeiten für gRPC-Calls und REST-Calls gesammelt und hinsichtlich ihres Medianwertes ausgewertet werden. Folgende Pakete sind ggf. hilfreich! ```Python from statistic import median # median() bestimmt den Median einer List von Werten import time # time.time() liefert aktuelle Timestamp in ms import requests # HTTP Get request in Python # response = requests.get("http://localhost:5000/hello/you") ``` Entwickeln Sie diesen Client bitte in der Datei `grpc/compare.py`. 1. Bauen Sie den REST- und den gRPC Container ```Bash > docker build -t greeter_rest rest > docker build -t greeter_grpc grpc ``` 2. Starten Sie beide Container in zwei unterschiedlichen Shells ```Bash Shell 1:> docker run -p 5000:5000 greeter_rest Shell 2:> docker run -p 5555:5555 greeter_grpc ``` 3. Starten Sie dann in einer dritten Shell Ihr Benchmarking ```Bash > python3 grpc/compare.py gRPC: 11.01ms REST: 25.45ms gRPC: 11.16ms REST: 26.45ms gRPC: 11.81ms REST: 27.41ms [...] ``` 4. Beenden Sie die Messung mittels `CTRL-C` (Linux/Windows) oder `CMD-C` (Mac OS). Führen Sie diese Messungen gerne ein paar mal hintereinander auf Ihrem System aus. > __Hinweis:__ > Wenn Sie gar nicht weiter kommen, hilft vielleicht folgende Abkürzung. > > `> cp cheat/compare-ue3.py grpc/compare.py` ## Übung 04: Performancevergleich HTTP (REST) vs. gRPC (in einem Cluster) Nachdem wir in Übung 03, die REST und gRPC lokal auf einem Host verglichen haben, wollen wir dies nun in der eher üblichen "Lebensumgebung", d.h. in einem Kubernetes Cluster machen. Hierzu sind im Verzeichnis `deploy` entsprechende Deployments und Services sowie in der `.gitlab-ci.yml` eine entsprechende Deployment Pipeline vorbereitet. - `deploy/grpc-dep.yaml` und `deploy/grpc-svc.yaml` deployed insgesamt 5 Pods mit gRPC-API (in einem Cluster von fünf Knoten) und fasst diese zu einem gRPC-Service zusammen. - `deploy/rest-dep.yaml` und `deploy/rest-svc.yaml` deployed insgesamt 5 Pods mit REST-API (in einem Cluster von fünf Knoten) und fasst diese zu einem REST-Service zusammen. - `deploy/monitor-dep.yaml` deployed einen Pod mit der `compare.py`-Funktionalität, der den REST- und gRPC-Service analog zu Übung 3 in Kubernetes abfragt und die Latenzen ermittelt und loggt. Durch dieses Setting ist sichergestellt, dass der Monitor Pod sowohl Pods auf seinem eigenen Node als auch auf anderen Nodes abfragt, um sowohl Intra- als auch Inter-Node Calls abzudecken. Stoßen Sie also bitte jetzt die Deployment Pipeline an und verfolgen Sie in Lens wie die REST- und gRPC-Pods deployed werden. Wenn dies erfolgt ist triggern Sie dann bitte in der Gitlab Pipeline den manuellen `monitor`-Job der `deploy`-Stage. Verfolgen Sie in Lens das Log (`Pods -> Monitor Pod (auswählen) -> Log`) des `monitor`-Pods. Sie sollten dann ein Log wie dieses hier in etwa sehen: ```Bash gRPC: 00.66ms REST: 04.70ms gRPC: 00.63ms REST: 04.59ms gRPC: 00.61ms REST: 04.41ms gRPC: 00.60ms REST: 04.39ms gRPC: 00.59ms REST: 04.40ms gRPC: 00.60ms REST: 04.42ms gRPC: 00.61ms REST: 04.47ms [...] ``` Vermutlich sind die Latenzen anders als auf ihrem lokalen System (je nachdem was für ein System Sie haben). Allerdings sollten auch im Cluster die gRPC-Latenzen gegenüber den REST-Latenzen deutlich niedriger sein. Vermutlich ist dies sogar deutlich ausgeprägter. ## Verständnis- und Transferfragen - Welche Art der API-Entwicklung erscheint Ihnen "zugänglicher" (REST, gRPC)? - Wie schätzen Sie den Grad der Kopplung bei REST und gRPC ein und warum? - Erklären Sie die gemessenen Performance-Unterschiede zwischen Übung 03 und Übung 04. Benennen Sie ein paar Komponenten, die dafür verantwortlich sein könnten. - Wie schätzen Sie vor dem Hintergrund der Zahlen die Leistungsfähigkeit des Ihnen zur Verfügung gestellten Clusters im Vergleich zu Ihrem eigenen System ein? Zur Beantwortung der folgenden Fragen, füllen Sie bitte die folgende Tabelle aus. - Sie haben einen Use Case mit wenigen langlaufenden Requests. Für welchen Ansatz (REST, gRPC) entscheiden Sie sich? - Sie haben einen Use Case mit vielen langlaufenden Requests. Für welchen Ansatz (REST, gRPC) entscheiden Sie sich? - Sie haben einen Use Case mit wenigen kurzen Requests. Für welchen Ansatz (REST, gRPC) entscheiden Sie sich? - Sie haben einen Use Case mit vielen kurzen Requests. Für welchen Ansatz (REST, gRPC) entscheiden Sie sich? . | schneller Req. | langsamer Req. -------------- | -------------- | --------------- **viele Req.** | gRPC o. REST? | gRPC o. REST? **wenig Req.** | gRPC o. REST? | gRPC o. REST? Beantworten Sie nun die obigen Fragen erneut. Berücksichtigen Sie aber ergänzend die folgenden Randbedingungen des Cloud-native Computings. - Gewichten Sie in Fällen wo sowohl gRPC als auch REST in Frage kommen könnten, das Kriterium der losen Kopplung höher als das der Performance. - Beachten Sie, dass Sie höhere Requestzahlen auch durch horizontale Skalierung in den Griff bekommen können. . | schneller Req. | langsamer Req. -------------- | -------------- | --------------- **viele Req.** | gRPC o. REST? | gRPC o. REST? **wenig Req.** | gRPC o. REST? | gRPC o. REST? ## Links - [gRPC](https://www.grpc.io) - [gRPC + Python](https://www.grpc.io/docs/languages/python/quickstart) - [Case study Salesforce](https://www.cncf.io/case-studies/salesforce) (Streaming services und Push notification services) - [Case study Mux](https://www.cncf.io/case-studies/mux) (Video streaming) - [Weitere gRPC Fallstudien](https://www.cncf.io/case-studies/?_sft_lf-project=grpc) (von CNCF.io) ## Was sollten Sie mitnehmen - gRPC basiert auf HTTP/2 und fügt sich daher nahtlos in das Service Computing ein, da alle mit HTTP kompatiblen Komponenten (Proxies, etc.) auf Applikationsschicht des ISO-Netzwerk-Stacks dann auch mit gRPC funktionieren. Z.B. können gRPC-Dienste über Nginx-Ingress-Controller in Kubernetes exponiert werden. - gRPC ist ein auf HTTP/2-basiertes Binärprotokoll im Gegensatz zum HTTP/1-basierten Textprotokoll und ist daher bis zu einer Größenordnung schneller als HTTP-basierte Request-Response-Verfahren. - gRPC erhöht allerdings die Kopplung zwischen Diensten (durch Stubs und Service Definition Files `.proto`) - Muss man `.proto` Dateien in gRPC Dateien anfassen, muss man sowohl Consuming wie auch Providing Service updaten. Mit automatisierten Deployment Pipelines kann man diesen Aufwand zwar "verstecken", allerdings erhöht dies die Abhängigkeiten zwischen Diensten. - Vor dem Hintergrund vieler/weniger und schneller/langsamer Requests ist in Cloud-nativen Systemen der Einsatz von gRPC daher fast nur noch in 25% dieses Problemraums ratsam (bei vielen schnellen Requests). In allen anderen Fällen, verliert der Performance Vorteil von gRPC erheblich an Wert bzw. kann durch horizontale Skalierung kompensiert werden. - gRPC wird daher in Cloud-nativen Systemen meist im "inneren" von Systemen eingesetzt, die durch ein hohes Volumen von schnell zu berechnenden und zu verteilenden Events charakterisiert sind (z.B. Messaging). - Für Messaging gibt es aber noch Spezial-Lösungen. Mit diesen werden wir uns im kommenden Lab beschäftigen.