Talk notes
This commit is contained in:
177
notes/NOTES.md
Normal file
177
notes/NOTES.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# Millionen langlebiger TCP-Verbindungen – Herausforderungen und Lösungen bei Update-Prozessen
|
||||
|
||||
[0] Ihr wollt in euren Urlaub starten, seid mit gepackten Koffern am Bahnhof. Beim Warten auf den verspäteten Zug habt ihr jede
|
||||
Menge Zeit zum überlegen. Ihr fragt euch: "Ist der Herd wirklich abgeschaltet?". Anders als in früheren Urlauben habt ihr aber
|
||||
einen smarten Herd daheim. Ihr öffnet eure App, stellt fest, dass alles in Ordnung ist, und könnt beruhigt in Urlaub starten.
|
||||
|
||||
IoT (also "Internet of Things") beschreibt genau diese Art von Geräten. Mein Name ist Bene. Ich bin Softwareentwickler mit Fokus
|
||||
auf Softwarearchitektur bei Scandio. Wir bei Scandio entwickeln für unsere Kunden maßgeschneiderte intelligente und kreative
|
||||
Softwarelösungen. Wir haben dabei ausgeprägten Stärken unter anderem im Bereich "Internet of Things".
|
||||
|
||||
Ein Projekt aus diesem Bereich will ich heute vorstellen. Dabei geht es darum, wie die IoT-Geräte unseres Kunden mit
|
||||
verschiedenen Backends kommunizieren und um die Herausforderungen bei der Aktualisierung des Systems, das zwischen den Geräten
|
||||
und den Backends liegt.
|
||||
|
||||
Wenn ihr während des Vortrags Fragen habt, stellt sie gerne direkt.
|
||||
|
||||
## Inhalt
|
||||
|
||||
Ein kurzer Überblick über den Vortrag: Nach dem Projektsetup (also Hintergrund, Motivation und Ziel des Projekts) beschreibe ich
|
||||
euch die Problemstellung etwas genauer und dann natürlich auch die Lösungen, also: wie sieht die Architektur des Systems jetzt
|
||||
ab, was genau passiert, wenn das System Updates bekommt, und zum Schluss noch: was wären unsere Wunschträume für die
|
||||
Architektur, wenn Zeit und Geld keine Rolle spielen würde oder wir ganz von vorne anfangen würden.
|
||||
|
||||
## Projektsetup
|
||||
|
||||
Das Projekt, von dem ich heute rede, hat den Namen "Heimdall".
|
||||
|
||||
[5] Heimdall ist in der nordischen Mythologie der Wächter der Götterbrücke Bifröst (das ist der Regenbogen im Bild) und Heimdall
|
||||
hat außergewöhnliche Wahrnehmungsfähigkeiten, hier durch das Hören symbolisiert. Genauso wie Heimdall die Verbindung zwischen
|
||||
den Welten überwacht, fungiert unser Heimdall-System als ein Gateway, das die Brücke zwischen einer Vielzahl von IoT-Geräten und
|
||||
verschiedenen Backend-Systemen bildet. Ähnlich der mythischen Figur hat unser Heimdall die Aufgabe, die Sicherheit, Stabilität
|
||||
und Effizienz dieser Verbindungen zu gewährleisten.
|
||||
|
||||
Heimdall wird seit 2018 entwickelt und hat ein vollständig extern entwickeltes und verwaltetes System abgelöst. Bei diesem alten
|
||||
System hatte unser Kunde keinen Zugriff auf den Source Code oder Entwicklungsdetails. Daher wurde die Entscheidung für eine
|
||||
Neuentwicklung getroffen. Die fehlenden Informationen über das alte System gingen sogar so weit, dass wir bei der Migration zu
|
||||
unserem neuen System regelmäßig das alte System überlastet haben, da wir einfach keine Informationen bekommen konnten, welche
|
||||
Last das alte System aushält.
|
||||
|
||||
Ich will hier einmal schematisch zeigen, welche Aufgabe Heimdall hat. Eure smarte Herdplatte von vorhin muss regelmäßig an das
|
||||
Herdplattenstatusbackend die Information senden, welche Herdplatten auf welcher Stufe angeschaltet sind. Gleichzeitig tauscht
|
||||
das smarte Heizungsthermostat eures Nachbarn Nachrichten mit einem Backend aus, um herauszufinden, ob die Heizung nach oben oder
|
||||
unter geregelt werden sollte, und die smarte Dunstabzugshaube eurer Tante meldet an das entsprechende Backend, dass die Filter
|
||||
mal wieder eine Reinigung bräuchten. Heimdall sitzt zwischen den Geräten und den Backends und sorgt dafür, dass die richtigen
|
||||
Nachrichten das richtige Backend erreichen, und dass umgekehrt auch Nachrichten in die Gegenrichtung versendet werden können:
|
||||
beispielsweise kann das Herdplattenbackend die Herdplatte anweisen, sich abzuschalten. Die Geräte halten dazu eine
|
||||
Websocket-Verbindung zu Heimdall aufrecht. Über diese Verbindung werden die Nachrichten dann ausgetauscht.
|
||||
|
||||
## Problemstellung
|
||||
|
||||
Das wichtigste Qualitätsziel bei Heimdall ist, dass diese Verbindungen zwischen Gerät und Heimdall so selten wie möglich
|
||||
unterbrochen werden. Das hat zwei Gründe: Zum einen kann ich als Nutzer nicht sehen, auf welcher Stufe mein Herd gerade kocht,
|
||||
wenn der Herd nicht mit Heimdall verbunden ist. Zum anderen finden einige Prozesse nach jeder Neuverbindung statt. Das kann
|
||||
potenziell zu Überlastung von Heimdall oder den Backends führen, wenn die Neuverbindungen zu häufig stattfinden.
|
||||
|
||||
Ein Szenario gibt es aber, in dem Verbindungen auf keinen Fall gehalten werden können: nämlich wenn der Server, mit dem die
|
||||
Hausgeräte verbunden sind, ein Update bekommt und daher ersetzt werden muss. Deswegen ist eins der größten Ziele bei der
|
||||
Entwicklung der Architektur von Heimdall, dass solche Neustarts bei den meisten Konfigurationsänderungen des Systems nicht
|
||||
notwendig sind, sondern die Konfigurationsänderung am Live-System durchgeführt werden können.
|
||||
|
||||
## Lösungen
|
||||
|
||||
[10] Wir schauen uns jetzt mal an, wie Heimdall aufgebaut ist.
|
||||
|
||||
Das Kernstück von Heimdall ist der sogenannte "Web Socket Manager", kurz WSM. Im Wesentlichen öffnet ein Gerät eine
|
||||
Websocket-Verbindung zum WSM. Der WSM leitet die Nachrichten des Geräts an das Backend weiter und umgekehrt werden Nachrichten
|
||||
vom Backend an das Gerät zurückgeleitet.
|
||||
|
||||
In Wirklichkeit gibt es allerdings mehrere WSM-Pods, also mehrere Instanzen des WSM. Zum einen wird dadurch die Skalierbarkeit
|
||||
des Systems verbessert, und zum anderen verlieren im Falle eines Crashs nicht alle Geräte die Verbindung. Vor diesen Instanzen
|
||||
liegt ein Load Balancer. Der routet die Verbindungen zum WSM im Round-Robin-Algorithmus, also: eine Verbindung wird an die erste
|
||||
Instanz geroutet, die nächste an die zweite Instanz, und nach der letzten Instanz beginnt das Spiel von vorne. Bei synchronen
|
||||
Aufrufen an das Backend kommt die Antwort an die richtige WSM-Instanz zurück. Allerdings sollte man synchrone Aufrufe wegen der
|
||||
stärkeren Kopplung besser vermeiden und durch asynchrone Aufrufe ersetzen. Außerdem könnten Backends auch direkt Nachrichten an
|
||||
Geräte senden wollen.
|
||||
|
||||
Hier kommen der Forwarding-Service (kurz FORS) und eine Adress-Datenbank ins Spiel. Jeder WSM-Pod schreibt nach einer geöffneten
|
||||
Verbindung mit einem Gerät die Geräte-ID zusammen mit der eigenen Adresse in die Adress-Datenbank. Der FORS fragt jetzt für ein
|
||||
Gerät die richtige Adresse ab und kann Nachrichten vom Backend an die richtige WSM-Instanz weiterleiten. Konkret schaut der
|
||||
Ablauf also so aus: Gerät 1 baut eine Websocket-Verbindung zu WSM-Pod 1 auf. In der Folge teilt WSM-Pod 1 der Adress-Datenbank
|
||||
mit: Gerät 1 ist mit dem Pod verbunden, der Adresse 1 besitzt. An der Kommunikation vom Gerät zum Backend hat sich nichts
|
||||
geändert. Wenn jetzt aber das Backend eine Nachricht an das Gerät senden möchte, dann wird die Nachricht an unseren
|
||||
Forwarding-Service geschickt. Der Forwarding-Service fragt die konkrete Adresse des richtigen WSM-Pods bei der Adress-Datenbank
|
||||
an und leitet die Nachricht entsprechend weiter. Dann ist die Nachricht beim richtigen WSM gelandet und kann an das Gerät über
|
||||
die Websocket-Verbindung geschickt werden.
|
||||
|
||||
[15] Dazu kommt, dass es in Wirklichkeit nicht nur ein Backend gibt. Ein Message Mapping kann vom WSM genutzt werden, um zu
|
||||
entscheiden, an welches Backend ein konkreter Nachrichtentyp mit einer konkreten Version gesendet werden soll. Der Ablauf hier
|
||||
ist also: Das Gerät (der Einfachheit halber habe ich in diesem Bild nur noch ein Gerät) schickt die Nachricht mit dem Typ
|
||||
"/foo/config" und der Version 1 an den WSM. Der WSM findet mit Hilfe des Message Mappings heraus, dass Backend 1 für die
|
||||
Verarbeitung der entsprechenden Nachrichten verantwortlich ist und leitet die Nachricht entsprechend weiter. Wir sind also wie
|
||||
ein Postunternehmen dafür zuständig, dass die richtigen Nachrichten an den richtigen Empfänger gelangen, aber nicht dafür, dass
|
||||
die Nachrichten inhaltlich auch sinnvoll sind. Es findet also bei Heimdall keinerlei Validierung der Nachrichten statt. Das
|
||||
Message Mapping ändert sich regelmäßig. Deshalb ist es wichtig, dass eine Rekonfiguration ohne Neustart der WSM-Pods möglich
|
||||
ist, da sonst ja Verbindungen gekappt werden müssten.
|
||||
|
||||
Der WSM hat noch diverse andere Aufgaben: Er führt den TLS-Handshake durch. Dabei wird auch validiert, dass das
|
||||
Client-Zertifikat von einer bekannten Issuer-CA signiert wurde. Bei manchen dieser Issuer soll der Expiry-Timestamp des
|
||||
Client-Zertifikats ignoriert werden. Das hat den Hintergrund, dass auch die Kommunikation zu Firmware-Updates über Heimdall
|
||||
stattfindet. Insbesondere könnten alte Geräte keine Firmware-Updates bekommen, wenn ihre Client-Zertifikate abgelehnt würden.
|
||||
Auf der anderen Seite kann aber das Client-Zertifikat nicht ausgetauscht werden, ohne ein Firmware-Update durchzuführen. Um
|
||||
dieses Problem zu lösen, werden eben in manchen Fällen auch abgelaufene Zertifikate akzeptiert. Manche Client-Zertifikate sollen
|
||||
außerdem sofort abgelehnt werden, weil die entsprechenden Geräte defekt sind. Das wird über eine Datenbank mit blockierten
|
||||
Zertifikaten geregelt. Für das Blockieren eines Zertifikats ist wie beim Message Mapping kein Neustart des WSM notwendig, sodass
|
||||
auch hier Neuverbindungen vermieden werden. Andere Geräte sind in einem Quarantäne-Status und für diese Geräte dürfen nur
|
||||
bestimmte Nachrichten versendet und empfangen werden, beispielsweise wieder Nachrichten im Zusammenhang mit Firmware-Updates.
|
||||
Wie die blockierten Zertifikate wird das über eine eigene Datenbank konfiguriert und benötigt keinen WSM-Neustart. Nach einem
|
||||
erfolgreichen TLS-Handshake muss der WSM das Websocket-Upgrade (also den Protokollwechsel von HTTP zu Websocket) durchführen.
|
||||
Schließlich gibt es noch einen Corporate Handshake, der vom Protokoll unseres Kunden vorgesehen ist. Bevor dieser Handshake
|
||||
abgeschlossen ist, dürfen keine anderen Nachrichten in beide Richtungen gesendet oder weitergeleitet werden.
|
||||
|
||||
Hier ist noch einmal eine Zusammenfassung des gesamten Heimdall-Systems, soweit es für den Vortrag relevant ist. Die Grundidee
|
||||
ist also, dass der WSM so selten wie möglich neugestartet werden muss. Das erreichen wir, indem Konfiguration, die sich
|
||||
regelmäßig ändern könnte, externalisiert wird und ohne Neustarts geändert werden kann.
|
||||
|
||||
[20] Was passiert jetzt aber, wenn der WSM doch einmal neugestartet werden muss oder abstürzt? Wenn ein WSM-Pod kontrolliert
|
||||
beendet wird, werden die Verbindungen zu den entsprechenden Geräten nacheinander beendet. Dann verbinden sich die Geräte mit
|
||||
anderen Instanzen neu. Aktuell dauert dieser Prozess in unserer größten Stage mit etwa 2,5 Millionen Geräten knapp 3 Stunden. Da
|
||||
wir ja Round-Robin für das Load Balancing nutzen, hat der Ersatz-Pod, der hier gestartet wurde, danach deutlich weniger offene
|
||||
Verbindungen als die alten Pods. In der Praxis führt das nicht zu Problemen, denn die Last ist bei neuen Verbindungen mit
|
||||
Abstand am höchsten.
|
||||
|
||||
Falls ein Pod (beispielsweise aufgrund eines Bugs) unkontrolliert beendet wird, versuchen sich alle Geräte auf einmal neu zu
|
||||
verbinden. Das würde zur Überlastung des Systems führen, daher gibt es einen Rate Limiter, der dafür sorgt, dass sich Geräte
|
||||
nicht zu schnell wieder verbinden können. Natürlich ist das ein ungünstiger Zustand, da die Funktionalität der noch nicht wieder
|
||||
verbundenen Geräte eine ganze Weile eingeschränkt sein kann.
|
||||
|
||||
Jetzt kommen wir mal zum geplanten Fall: eine neue WSM-Version soll deployed werden. Das Deployment läuft dann so ab: In einem
|
||||
ersten Schritt werden 1/3 der Instanzen als Canary-Deployment durch Instanzen der neuen Version ersetzt. Diese neue Version
|
||||
läuft parallel zu der alten für eine Woche, bevor auch die übrigen Instanzen ersetzt werden. So können Fehler frühzeitig erkannt
|
||||
werden.
|
||||
|
||||
Das bedeutet übrigens nicht, dass nach dem Canary-Deployment 1/3 der Verbindungen auf der neuen Version stattfinden. Die
|
||||
Wahrheit ist hier etwas komplizierter: die Geräte, die sich nach dem Abschalten mancher der alten Instanzen neu verbinden,
|
||||
verbinden sich ja genauso häufig mit jeder der Canary-Instanzen wie mit jeder der alten Stable-Instanzen. In dem 7-Tage-Zeitraum
|
||||
gleichen sich die Anzahl der verbundenen Geräte dann etwas aus, weil Geräte regelmäßig auch aus anderen Gründen die Verbindung
|
||||
verlieren und sich neu verbinden. Nach einer Woche werden die übrigen 2/3 der Instanzen ersetzt und wie vorhin haben dann die
|
||||
zuerst deployeten Canary-Instanzen mehr offene Verbindungen als die zuletzt deployeten. Wie gesagt führt das allerdings in der
|
||||
Praxis zu keinen Problemen und gleicht sich auf Dauer von selbst aus.
|
||||
|
||||
Jetzt habt ihr gesehen, wie das System tatsächlich aussieht. Natürlich haben wir in den gut 5 Jahren, in denen das Projekt
|
||||
existiert, auch dazugelernt und würden einige Dinge anders gestalten. Am Anfang des Vortrags habe ich ja schon gesagt, dass das
|
||||
Ziel wäre, den WSM so selten wie möglich updaten zu müssen. Dafür ist es hilfreich, wenn der WSM so wenige Aufgaben wie möglich
|
||||
bekommt. In einer idealen Welt sollte daher der WSM der kleinste Service im gesamten System sein. Die Realität ist: der WSM ist
|
||||
der mit Abstand größte Service des Heimdall-Systems.
|
||||
|
||||
[25] Woran liegt das? Aktuell hat der WSM einige Aufgaben:
|
||||
|
||||
* TLS und alles, was dazugehört: die Terminierung der TLS-Verbindung, die Validierung der Clientzertifikate, die Ausnahmen zur
|
||||
Validierung der Zertifikate und das Blocklisting von Zertifikaten.
|
||||
* Der Websocket-Upgrade, also die Entgegennahme der initialen HTTP-Verbindung und der Wechsel zum Websocket-Protokoll
|
||||
* Der Corporate Handshake, der zu Beginn jeder Verbindung stattfinden muss
|
||||
* Das Message Routing an die Backends inklusive Offenhalten der Verbindungen und Warten auf Antworten
|
||||
* Die Quarantäne-Logik
|
||||
* Einige andere Workarounds, die im WSM diverse Spezialfälle abhandeln
|
||||
|
||||
Um also den WSM möiglichst klein zu bekommen, müssen möglichst viele dieser Aufgaben ausgelagert werden. Oder umgekehrt: die
|
||||
Aufgaben, die unbedingt notwendig zum Halten der Verbindung sind, sollten aus dem WSM extrahiert werden. Hier wäre daher unser
|
||||
Vorschlag, um die Verbindungen noch länger halten zu können. Vor die übrigen Systeme wird ein Service gesetzt, den ich hier
|
||||
"Connection Holder" genannt habe und der nur dafür zuständig ist, die TCP-Verbindung zu den Geräten zu halten, die
|
||||
TLS-Verschlüsselung zu terminieren und zu Validieren, dass die Client-Zertifikate von einem bekannten Issuer ausgestellt wurden.
|
||||
Nach der TLS-Terminierung wird die TCP-Verbindung einfach an das Äquivalent des jetzigen WSMs weitergetunnelt. Wenn die
|
||||
WSM-Instanz beendet wird, kann der Tunnel an eine andere WSM-Instanz weitergeroutet werden, ohne die Verbindung zum Gerät
|
||||
abzubrechen. Damit der WSM Zertifikatslogik wie Blocklisting durchführen kann, muss zu Beginn des Verbindungsaufbaus zwischen
|
||||
Connection Holder und WSM das validierte Client-Zertifikat mitgeschickt werden. Außerdem müssen die Verbindungen eine eindeutige
|
||||
ID bekommen, sodass die WSM-Instanzen beispielsweise persistieren können, ob für eine gegebene Verbindung bereits der Corporate
|
||||
Handshake durchgeführt wurde. Natürlich könnten auch andere Aufgaben aus dem WSM ausgelagert werden, hier mal exemplarisch das
|
||||
Message Routing inklusive Warten auf die Antwort des Backends.
|
||||
|
||||
Zusammenfassend können wir schon im aktuellen System zuverlässig dafür sorgen, dass Konfiguration schnell und unkompliziert ohne
|
||||
Neuverbindungen geändert werden kann. Neuverbindungen komplett zu vermeiden dürfte nicht möglich sein, aber wir haben Ideen, wie
|
||||
wir uns in diese Richtung verbessern können. Ich hoffe, ich konnte euch einen guten Einblick in die Herausforderungen geben, vor
|
||||
die uns die langlaufenden Verbindungen in Zusammenhang mit Systemupdates stellen.
|
||||
|
||||
[30] ...
|
||||
|
||||
Hier seht ihr noch einmal, wie ihr mich erreichen könnt. Danke fürs Zuhören!
|
||||
Reference in New Issue
Block a user