README.md 15.5 KB
Newer Older
Nane Kratzke's avatar
Nane Kratzke committed
1
# Lab 10: Messaging-basierte Interaktion
Nane Kratzke's avatar
Nane Kratzke committed
2

Nane Kratzke's avatar
Nane Kratzke committed
3
Wir werden in diesem Lab Messaging überaschenderweise am Beispiel einer Datenbank -- [Redis](https://redis.io) -- demonstrieren. Wir nutzen Redis vor allem deswegen, weil es sehr einfach zu deployen ist und bereits an anderen Stellen in Labs verwendet wurde und wir so den Technologiestack übersichtlich und die Komplexität überschaubar halten können. Redis wurde zwar primär als In-Memory Key-Value Datenbank entwickelt, hat jedoch mittlerweile sowohl Publish-Subscribe als auch Queueing Features erhalten, die für einfache Anwendungsfälle durchaus ausreichend sind. Als Messaging-Komponenten werden in Cloud-nativen Systemen häufig "klassische" Lösungen wie bspw. [Kafka](https://kafka.apache.org), [Nats](https://nats.io), [RabbitMQ](https://www.rabbitmq.com), [ActiveMQ](https://activemq.apache.org) und weitere [AMQP](https://www.amqp.org)- oder [MQTT](https://mqtt.org)-konforme Lösungen verwendet.
Nane Kratzke's avatar
Nane Kratzke committed
4
5

Die in diesem Lab am Beispiel von Redis vermittelten Prinzipien sind aber durchaus auf diese Systeme übertragbar, da die zu Grunde liegenden Kommunikationsmuster Publish-Subscribe und Queueing letztlich von all diesen Messaging-Systemen ermöglicht werden.
Nane Kratzke's avatar
Nane Kratzke committed
6
7
8
9
10
11
12

## Inhalt

- [Lab 10: Messaging-basierte Interaktion](#lab-10-messaging-basierte-interaktion)
  - [Inhalt](#inhalt)
  - [Vorbereitung](#vorbereitung)
  - [Übung 01: Pub/Sub Messaging (Fan Out)](#übung-01-pubsub-messaging-fan-out)
Nane Kratzke's avatar
Nane Kratzke committed
13
14
15
  - [Übung 02: Persistentes Pub/Sub Messaging](#übung-02-persistentes-pubsub-messaging)
  - [Übung 03: Event Sourcing](#übung-03-event-sourcing)
  - [Übung 04: Queueing](#übung-04-queueing)
Nane Kratzke's avatar
Nane Kratzke committed
16
17
18
19
20
21
  - [Verständnis- und Transferfragen](#verständnis--und-transferfragen)
  - [Links](#links)
  - [Was sollten Sie mitnehmen](#was-sollten-sie-mitnehmen)

## Vorbereitung

Nane Kratzke's avatar
Nane Kratzke committed
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Sie benötigen auf Ihren lokalen Entwicklungsrechner:

- [Python 3](https://www.python.org/downloads)
- [Docker](https://www.docker.com/get-started)
- [Lens](https://k8slens.io)

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!!!)
9. Öffnen Sie Lens und hinterlegen Sie mittels `+` die `kubeconfig`-Datei, die Sie auch im vorherigen Schritt in der Deployment Pipeline hinterlegt haben.

Nane Kratzke's avatar
Nane Kratzke committed
51
52
## Übung 01: Pub/Sub Messaging (Fan Out)

Nane Kratzke's avatar
Nane Kratzke committed
53
54
In dieser Übung werden wir uns folgenden Publish-Subscribe Kommunikationsmuster widmen.

Nane Kratzke's avatar
Nane Kratzke committed
55
56
57
58
59
60
61
62
```
              +--M3,M2,M1-> C
              |
P --M3,M2,M1--+--M3,M2,M1-> C
              |
              +--M3,M2,M1-> C
```

Nane Kratzke's avatar
Nane Kratzke committed
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
Ein Producer *P* erzeugt Messages, die in einen Channel einer Message Queue gegeben werden (Publish). Diese Queue
verteilt dann alle Messages an Consumer *C*, die sich für diesen Channel angemeldet haben (Subscribe). Auf diese Weise erhalten alle Consumer alle Messages, ohne dass Producer die Empfänger kennen muss. Die Message Queue entkoppelt so Producer von Consumer und umgekehrt.

- Studieren Sie bitte sowie den [Publish/Subscribe Abschnitt](https://github.com/andymccurdy/redis-py#publish--subscribe) der Redis Python API.
- Versuchen Sie nun die beiden Dateien `messaging/pubsub-producer.py` und `messaging/pubsub-consumer.py` nachzuvollziehen.
- Versuchen Sie nun die `.gitlab-ci.yaml` sowie die Kubernetes Manifests im Ordner `deploy` nachzuvollziehen. Die Deployment Pipeline dieses Labs erzeugt eine Redis Datenbank in Ihrem Kubernetes Namespace und sieht diverse manuell triggerbare Jobs für unterschiedliche Consumer und Producer vor, um Teile dieses Labs zu starten.
- Triggern Sie nun diese Pipeline (bspw. durch einen Commit dieses Repos).
- Wenn die Redis Datenbank erfolgreich in Kubernetes läuft (checken Sie dies in Lens), wechseln Sie in Gitlab in die Pipelines Ansicht (`CI/CD -> Pipelines` -> auf letzte bestandene Pipeline klicken) und starten Sie manuell die beiden Jobs `pubsub-consumer` und `pubsub-producer` in der Web-Oberfläche (Play Buttons der Jobs).
- Im Lens Terminal (oder der Oberfläche sollten Sie nun drei Pods sehen)
  ```Bash
  > kubectl get pods
  NAME                               READY   STATUS    RESTARTS   AGE
  redis-599f4cc796-6pltl             1/1     Running   0          25m
  pubsub-consumer-b9756d886-62z8l    1/1     Running   0          19m
  pubsub-producer-79bcdbf9d8-bdxrn   1/1     Running   0          19m
  ```
- Sehen Sie sich in Lens nun die Logs des Producers an:
  ```
  ...
  Publishing message 'Hi, this is message #4317 from pubsub-producer-79bcdbf9d8-bdxrn.' to channel 'xpubsub'
  Publishing message 'Hi, this is message #4318 from pubsub-producer-79bcdbf9d8-bdxrn.' to channel 'xpubsub'
  Publishing message 'Hi, this is message #4319 from pubsub-producer-79bcdbf9d8-bdxrn.' to channel 'xpubsub'
  Publishing message 'Hi, this is message #4320 from pubsub-producer-79bcdbf9d8-bdxrn.' to channel 'xpubsub'
  Publishing message 'Hi, this is message #4321 from pubsub-producer-79bcdbf9d8-bdxrn.' to channel 'xpubsub'
  ```
- Die Logs des Consumers sollten in etwa so aussehen:
  ```
  ...
  0.92ms | msg: {"timestamp": 1614764396330193114, "message": "Hi, this is message #4689 from pubsub-producer-79bcdbf9d8-bdxrn."}
  0.64ms | msg: {"timestamp": 1614764396731672144, "message": "Hi, this is message #4690 from pubsub-producer-79bcdbf9d8-bdxrn."}
  0.79ms | msg: {"timestamp": 1614764397232874681, "message": "Hi, this is message #4691 from pubsub-producer-79bcdbf9d8-bdxrn."}
  0.79ms | msg: {"timestamp": 1614764397734203948, "message": "Hi, this is message #4692 from pubsub-producer-79bcdbf9d8-bdxrn."}
  0.81ms | msg: {"timestamp": 1614764398035370260, "message": "Hi, this is message #4693 from pubsub-producer-79bcdbf9d8-bdxrn."}
  0.52ms | msg: {"timestamp": 1614764398236610874, "message": "Hi, this is message #4694 from pubsub-producer-79bcdbf9d8-bdxrn."}
  0.70ms | msg: {"timestamp": 1614764398737878344, "message": "Hi, this is message #4695 from pubsub-producer-79bcdbf9d8-bdxrn."}
  ```
  Die Zeitangaben in ms geben an, wie lange eine Message vom Producer zum Consumer benötigt hat (Latenz). Vergleichen Sie diese Werte mit den Werten, die Sie im letzten Lab zu REST und gRPC basierten Interaktionsverfahren erhoben haben.
- Merken Sie sich in etwa die letzte Messagenummer, die der Consumer bis dahin erhalten hat. Löschen Sie dann in Lens  den Consumer und warten Sie ab, bis der Pod wieder durch Kubernetes automatisch neu gestartet wurde. Prüfen Sie im neuen Container Log des regenerierten Pods ab welcher Message Nummer dieser das Streaming wieder auf nimmt?
- Löschen Sie nun das Producer Deployment in Lens, so dass kein neuer Producer Pod gestartet wird? Laufen noch weitere Messages im Consumer auf? Stürzt der Consumer ab?
- Deployen Sie in Gitlab mittels des manuellen Pipeline Jobs erneut den Producer (Play Button des Jobs).
- Der Producer sollte wieder ab 1 nummerierte Messages erzeugen, die der Consumer im Log anzeigt.
- Skalieren Sie nun in Lens den Producer auf 3 Instanzen hoch (`Producer Deployment -> Scale`). Achten Sie nun im Consumer Log auf die Ursprünge der Messages. Sie erhalten nun Messages von mehreren Producern.
  ```
  0.89ms | msg: {"timestamp": 1614765108327074527, "message": "Hi, this is message #95 from pubsub-producer-79bcdbf9d8-ch4w9."}
  1.10ms | msg: {"timestamp": 1614765108467772402, "message": "Hi, this is message #610 from pubsub-producer-79bcdbf9d8-lc2rq."}
  0.78ms | msg: {"timestamp": 1614765108746772325, "message": "Hi, this is message #100 from pubsub-producer-79bcdbf9d8-nzr2v."}
  0.86ms | msg: {"timestamp": 1614765108828138961, "message": "Hi, this is message #96 from pubsub-producer-79bcdbf9d8-ch4w9."}
  0.81ms | msg: {"timestamp": 1614765108869005443, "message": "Hi, this is message #611 from pubsub-producer-79bcdbf9d8-lc2rq."}
  ```
- Skalieren Sie auch die Consumer auf drei Instanzen hoch. Vergleichen Sie die Logs der drei Pods in Lens. Laufen unterschiedliche Messages auf?
- Löschen Sie nun das Producer Deployment und das Consumer Deployment (entweder in Lens oder mit dem Gitlab Pipeline `pubsub`-Job in der `terminate`-Stage).

Diese Übung hat nicht persistentes PubSub-Messaging mittels Redis demonstriert. Sie haben gesehen, dass Sie sowohl Consumer wie auch Producer unabhängig von einander hoch- und herunterskalieren können. Sie haben allerdings auch gesehen, dass diese Form des Messaging stateless ist. Messages, die einmal verschickt wurden, werden von Redis vergessen. Wenn Sie Anwendungsfälle haben, in denen die Historie von Events wichtig ist, weil Sie ggf. wieder abgespielt werden müssen (z.B. um eine Datenbank zu rekonstruieren), benötigen wir persistente Messaging Systeme.

Als Datenbank, kann Redis dies natürlich auch.
  
Nane Kratzke's avatar
Nane Kratzke committed
119
120
## Übung 02: Persistentes Pub/Sub Messaging

Nane Kratzke's avatar
Nane Kratzke committed
121
122
123
Diese Übung zeigt Ihnen wie man Redis als persistentes Messaging System einsetzen kann. Wir nutzen hierfür das `xadd`-[Command](https://redis.io/commands/XADD) (Producer) von Redis mit dem Redis Event Streams aufbauen kann. Mittels des `xread`-[Commands](https://redis.io/commands/XREAD) lässt sich aus solchen Streams lesen (Consumer).

- Lesen Sie sich hierzu als erstes ein wenig in [Redis Streams](https://redis.io/topics/streams-intro) ein.
124
- Studieren Sie anschließend die Klasse `messaging/MQueue.py`. Hier ist Ihnen eine Wrapper-Klasse um Redis gegeben, die die Commands `xadd`, `xread` und `xgroupread` so kapselt, dass diese komfortabel mittels einer `listen()`-Methode für Consumer und mittels einer `publish()`-Methode für Producer genutzt werden kann. *Auf `listen_as_group()` gehen wir in Übung 04 noch genauer ein.*
125
126
- Versuchen Sie nun die Dateien `messaging/queueing-producer.py` und `messaging/queueing-consumer.py` nachzuvollziehen, um zu verstehen, wie man die Klasse `MQueue` für ein Publish-Subscribe Pattern einsetzen kann.
- Starten Sie nun in Gitlab manuell die Jobs `queueing-consumer` und `queueing-producer` in der `deploy`-Stage um einen Consumer und einen Producer zu erzeugen.
Nane Kratzke's avatar
Nane Kratzke committed
127

Nane Kratzke's avatar
Nane Kratzke committed
128
129
130
## Übung 03: Event Sourcing

## Übung 04: Queueing
Nane Kratzke's avatar
Nane Kratzke committed
131
132
133
134
135
136
137
138
139
140
141

```
                 +--M4,M1-> C2
                 |
P --M4,M3,M2,M1--+-----M2-> C3
                 |
                 +-----M3-> C4
```

## Verständnis- und Transferfragen

Nane Kratzke's avatar
Nane Kratzke committed
142
143
- Für welche Anwendungsfälle würden Sie das PubSub-Kommunikationsmuster einsetzen?
- Für welche Anwendungsfälle würden Sie das Queueing-Kommunikationsmuster einsetzen?
Nane Kratzke's avatar
Nane Kratzke committed
144
145
- Welche Vor- und Nachteile hat die Persistierung von Message Queues?
- Erklären Sie Event Sourcing in eigenen Worten.
Nane Kratzke's avatar
Nane Kratzke committed
146
147
148
149
150
151
152
153
154
- Vergleichen Sie die Latenzzeiten bei PubSub und Queue? Gibt es nennenswerte Unterschiede?
- Vergleichen Sie die Latenzzeiten dieses Labs mit dem gRPC-Lab? Welche Technologie ist grundsätzlich performanter? Woher kommt das?
- Wie stufen Sie den Grad der Kopplung bei Messaging/Streaming im Vergleich zu gRPC ein?
- Wie stufen Sie den Grad der Kopplung bei Messaging/Streaming im Vergleich zu REST ein?
- Blicken wir nun auf die Labs zu REST, gRPC und Streaming zurück?
  - Vor dem Hintergrund loser Kopplung und Performanz:
  - In welchen Fällen würden Sie REST einsetzen?
  - In welchen Fällen würden Sie gRPC einsetzen?
  - In welchen Fällen würden Sie Messaging einsetzen?
Nane Kratzke's avatar
Nane Kratzke committed
155
156
157
158
159
160
161

## Links

- [Redis](https://redis.io)
- [Introduction to Redis Streams](https://redis.io/topics/streams-intro)
- Weitere Messaging Lösungen wie [Kafka](https://kafka.apache.org), [Nats](https://nats.io), [RabbitMQ](https://www.rabbitmq.com), [ActiveMQ](https://activemq.apache.org)
- Messaging Standards wie [AMQP](https://www.amqp.org) und [MQTT](https://mqtt.org)
Nane Kratzke's avatar
Nane Kratzke committed
162
163
- [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html) (Martin Fowler)
- [Event Sourcing](https://de.wikipedia.org/wiki/Event_Sourcing) (Wikipedia)
Nane Kratzke's avatar
Nane Kratzke committed
164
165
166

## Was sollten Sie mitnehmen

Nane Kratzke's avatar
Nane Kratzke committed
167
- Obwohl Redis eigentlich eine In-Memory Key-Value Datenbank ist, kann Sie (für viele überraschend) auch für (einfaches) Messaging eingesetzt werden. Gegenüber komplexeren Lösungen wie bspw. Kafka kann dies durchaus von Vorteil sein.
Nane Kratzke's avatar
Nane Kratzke committed
168
- Das Kommunikationsmuster Publish-Subscribe kann für vor allem für Fan-Out Anwendungsfälle eingesetzt werden, d.h. bei der Nachrichten an ALLE Consumer gehen. Wenn Publish-Subscribe Messaging persistiert wird, kann es zum [Event Sourcing](https://de.wikipedia.org/wiki/Event_Sourcing) verwendet werden.
Nane Kratzke's avatar
Nane Kratzke committed
169
- Das Kommunikationsmuster Queueing wird vor allem für Load Balancing eingesetzt werden, bei der Nachrichten über mehrere Consumer zur Verarbeitung verteilt und dort verarbeitet werden müssen. Sowohl Producer als auch Consumer können dabei unabhängig von einander skaliert werden.
Nane Kratzke's avatar
Nane Kratzke committed
170
- Persistente Messaging Systeme können als Event Stores im Rahmen des Event Sourcings eingesetzt werden.
Nane Kratzke's avatar
Nane Kratzke committed
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
- Messaging ist hinsichtlich Übertragungslatenzen meist performanter als gRPC, gRPC ist üblicherweise performanter als REST.
- Sowohl REST als auch Messaging ermöglichen eine lose Kopplung von Services.
- Die bislang betrachteten Technologien kann man aufsteigend anhand des Grads Ihrer Kopplung ordnen:
  1. **Messaging** (Consumer und Producer gehen eine Kopplung zu einem Use Case agnostischen Message Broker ein)
  2. **REST** (Consuming Services gehen eine Kopplung zu einem Providing Service ein)
  3. **gRPC** (Consuming und Providing Service gehen eine gegenseitige Kopplung ein)

Eine abschließende Platzierung der betrachteten Ansätze könnte daher wie folgt aussehen:

| Technologie | Kopplung | Performanz | Broker  | Summe der Platzierungen | Kommunikationsmuster
|-------------|:--------:|:----------:|:------: |:------:|--------------------------
| REST        | 2        | 3          | nein (1)| **6** | Request-Response
| gRPC        | 3        | 2          | nein (1)| **6** | Request-Response
| Messaging   | 1        | 1          | ja (3)  | **5** | Publish-Subscribe + Queueing

Einen klaren Gewinner gibt es also nicht (ansonsten hätte sich dieser vermutlich auch schon durchgesetzt). Es kommt also (wie so häufig) auf den Use Case an.