Während der Geburtstagswoche 2020 hat Cloudflare gRPC®-Unterstützung eingeführt. Wir haben uns über das immense Interesse an der Beta-Version sehr gefreut und möchten allen danken, die gRPC ausprobiert haben! In diesem Beitrag möchten wir genauer erklären, wie wir die Unterstützung implementiert haben.

Was ist gRPC?

gRPC ist ein Open-Source-RPC-Framework, das über HTTP/2 ausgeführt wird. Ein RPC (Remote Procedure Call) ist eine Möglichkeit für einen Computer, einen anderen Computer zu einer Aktion aufzufordern, ohne eine lokale Funktion in einer Bibliothek aufzurufen. RPC gibt es in der verteilten Datenverarbeitung schon lange, wobei unterschiedliche Implementierungen unterschiedliche Schwerpunkte haben. Die folgenden Eigenschaften machen gRPC einzigartig:

  • Es erfordert das jetzt weit verbreitete moderne HTTP/2-Protokoll für den Transport.
  • Eine vollständige Client/Server-Referenzimplementierung, Demo- und Testsuiten sind als Open Source verfügbar.
  • Es gibt kein bestimmtes Nachrichtenformat vor, allerding sind Protokoll-Puffer der bevorzugte Serialisierungsmechanismus.
  • Sowohl Clients als auch Server können Daten streamen, wodurch vermieden wird, dass neue Daten abgefragt oder neue Verbindungen hergestellt werden müssen.

Im Hinblick auf das Protokoll verwendet gRPC ausgiebig HTTP/2-Frames: Anfragen und Antworten sehen ähnlich aus wie eine normale HTTP/2-Anfrage.

Ungewöhnlich ist jedoch die Verwendung des HTTP-Trailers durch gRPC. Auch wenn sie in der freien Wildbahn nicht weit verbreitet sind, sind HTTP-Trailer schon seit 1999 im Umlauf, wie im ursprünglichen HTTP/1.1 RFC2616 definiert. HTTP-Nachrichten-Header müssen definitionsgemäß vor dem HTTP-Nachrichtentext stehen, aber ein HTTP-Trailer ist ein Satz von HTTP-Headern, die hinter dem Nachrichtentext angehängt werden können. Da es jedoch nicht viele Anwendungsfälle für Trailer gibt, werden sie von vielen Server- und Client-Implementierungen nicht vollständig unterstützt. Während HTTP/1.1 für seinen Hauptteil eine Chunked-Transfer-Codierung verwenden muss, um einen HTTP-Trailer zu senden, befindet sich im Fall von HTTP/2 der Trailer im HEADER-Frame hinter dem DATA-Frame des Hauptteils.

Der HTTP-Trailer ist in einigen Fällen nützlich. Beispielsweise verwenden wir einen HTTP-Antwortcode, um den Status der Anfrage anzuzeigen, aber der Antwortcode ist die erste Zeile der HTTP-Antwort, daher müssen wir uns sehr früh für den Antwortcode entscheiden. Ein Trailer ermöglicht es, einige Metadaten nach dem Body zu senden. Nehmen wir zum Beispiel an, Ihr Webserver sendet einen Stream mit großen Datenmengen (ein Stream hat keine feste Größe), und am Ende möchten Sie eine SHA-2-Prüfsumme der von Ihnen gesendeten Daten verschicken, damit der Client den Inhalt überprüfen kann. Mit einem HTTP-Statuscode oder dem Antwort-Header ist das normalerweise nicht möglich, da diese zu Beginn der Antwort gesendet werden müssen. Mit einem HTTP-Trailer-Header können Sie nach dem Senden aller Daten einen weiteren Header (z. B. Digest) senden.

Bei gRPC werden HTTP-Trailer für zwei Zwecke verwendet. Zunächst einmal sendet es seinen endgültigen Status (grpc-Status) als Trailer-Header, nachdem der Inhalt gesendet wurde. Der zweite Zweck ist die Unterstützung von Streaming-Anwendungsfällen. Diese Anwendungsfälle dauern viel länger als normale HTTP-Anfragen. Der HTTP-Trailer wird verwendet, um das Ergebnis der Nachverarbeitung der Anfrage oder der Antwort anzuzeigen. Wenn z. B. bei der Verarbeitung von Streaming-Daten ein Fehler auftritt, können Sie über den Trailer einen Fehlercode senden, was mit dem Header vor dem Nachrichtentext nicht möglich ist.

Hier ein einfaches Beispiel einer gRPC-Anfrage und -Antwort in HTTP/2-Frames:

gRPC-Unterstützung für die Cloudflare-Edge

Da gRPC HTTP/2 verwendet, hätte es eigentlich einfach sein müssen, gRPC nativ zu unterstützen, denn Cloudflare unterstützt bereits HTTP/2. Wir hatten jedoch ein paar Probleme:

  • Die Trailer-Header für HTTP-Anfragen/Antworten wurden von unserem Edge-Proxy nicht vollständig unterstützt. Cloudflare verwendet NGINX, um den Traffic von Eyeballs anzunehmen, und das unterstützt Trailer nur begrenzt. Erschwerend kommt hinzu, dass Anfragen und Antworten, die durch Cloudflare fließen, über eine Reihe anderer Proxys laufen.
  • HTTP/2 zum Ursprung: Unser Edge-Proxy verwendet HTTP/1.1, um Objekte (ob dynamisch oder statisch) vom Ursprung zu holen. Um gRPC-Traffic über Proxys zu leiten, müssen wir HTTP/2-Verbindungen zum gRPC-Ursprung des Kunden unterstützen.
  • gRPC-Streaming muss einen bidirektionalen Anfrage-/Antwortfluss ermöglichen: Bei gRPC gibt es zwei Arten von Protokollfluss: eine ist unär, d. h. eine einfache Anfrage und Antwort; und eine andere ist Streaming, d. h. ein ununterbrochener Datenfluss in jede Richtung. Um das Streaming vollständig zu unterstützen, muss der HTTP-Nachrichtenbody nach dem Empfang des Response-Headers am anderen Ende gesendet werden. Zum Beispiel wird beim Client-Streaming nach Erhalt eines Response-Headers weiterhin ein Anfragebody gesendet.

Aus diesen Gründen würden gRPC-Anfragen scheitern, wenn sie unser Netzwerk als Proxy verwenden. Um diese Einschränkungen zu überwinden, haben wir verschiedene Lösungen ausprobiert. Bei NGINX gibt es zum Beispiel ein vorgeschaltetes gRPC-Modul, um einen Ursprung mit HTTP/2 gRPC zu unterstützen, aber das ist ein separates Modul und erfordert außerdem HTTP/2 Downstream, was für unseren Dienst nicht verwendet werden kann, da die Anfragen in einigen Fällen über mehrere HTTP-Proxys kaskadiert werden. HTTP/2 auf der gesamten Pipeline zu verwenden, ist wegen der Eigenschaften unserer internen Load-Balancing-Architektur nicht realistisch. Außerdem wäre es zu aufwendig gewesen, den gesamten internen Traffic ausschließlich über HTTP/2 abzuwickeln.

Konvertierung zu HTTP/1.1?

Letztendlich fanden wir einen besseren Weg: Wir konvertieren gRPC-Nachrichten innerhalb unseres Netzwerks in HTTP/1.1-Nachrichten ohne einen Trailer und konvertieren sie dann wieder in HTTP/2, bevor die Anfrage an den Ursprung gesendet wird. Dies sollte mit den meisten HTTP-Proxys innerhalb von Cloudflare funktionieren, die keine HTTP-Trailer unterstützen, und wir bräuchten nur minimale Änderungen.

Wir brauchten kein eigenes Format zu erfinden, denn die gRPC-Community hat bereits eine HTTP/1.1-kompatible Version entwickelt: gRPC-web. gRPC-web ist eine Modifikation der ursprünglichen HTTP/2-basierten gRPC-Spezifikation. Ursprünglich sollte das mit den Web-Browsern eingesetzt werden, die keinen direkten Zugriff auf HTTP/2-Frames haben. Mit gRPC-web wird der HTTP-Trailer in den Body verschoben, sodass wir uns um die Unterstützung von HTTP-Trailern innerhalb des Proxys keine Sorgen machen müssen. Außerdem wird Streaming dabei unterstützt. Die resultierende HTTP/1.1-Nachricht kann immer noch von unseren Sicherheitsprodukten wie WAF und Bot Management überprüft werden, um das gleiche Maß an Sicherheit zu bieten, das Cloudflare auch für anderen HTTP-Traffic gewährleistet.

Wenn eine HTTP/2-gRPC-Nachricht auf Cloudflares Edge-Proxy empfangen wird, wird die Nachricht in das Format HTTP/1.1 gRPC-web „konvertiert“. Sobald die gRPC-Nachricht konvertiert ist, durchläuft sie unsere Pipeline. Dabei werden Dienste wie WAF, Cache und Argo auf dieselbe Weise angewendet wie bei jeder normalen HTTP-Anfrage.

Kurz bevor eine gRPC-web-Nachricht das Cloudflare-Netzwerk verlässt, muss sie erneut in HTTP/2 gRPC „zurückkonvertiert“ werden. Anfragen, die von unserem System konvertiert werden, werden markiert, damit unser System nicht versehentlich von Kunden stammenden gRPC-web-Traffic konvertiert.

HTTP/2-Ursprungsunterstützung

Eine der technischen Herausforderungen bestand darin, auch HTTP/2 für Verbindungen zu Ursprungsservern zu unterstützen. Vor diesem Projekt konnte Cloudflare über HTTP/2 keine Verbindung zu Ursprüngen herstellen.

Daher haben wir beschlossen, die HTTP/2-Ursprungsunterstützung intern zu entwickeln. Wir haben einen eigenständigen Ursprungs-Proxy gebaut, der in der Lage ist, über HTTP/2 eine Verbindung zu den Ursprüngen herzustellen. Auf dieser neuen Plattform haben wir die Konvertierungslogik für gRPC implementiert. Die gRPC-Unterstützung ist das erste Feature, das die Vorteile dieser neuen Plattform nutzt. Wir planen eine breitere Unterstützung für HTTP/2-Verbindungen zu Ursprungsservern.

Unterstützung für gRPC-Streaming

Wie oben erläutert, verfügt gRPC über einen Streaming-Modus, bei dem Anfrage- oder Antworttext im Stream gesendet werden können. In der gesamten Lebensdauer von gRPC-Anfragen können jederzeit gRPC-Nachrichtenblöcke gesendet werden. Am Ende des Streams gibt es einen HEADER-Rahmen, der das Ende des Streams anzeigt. Wenn er in gRPC-web konvertiert wird, senden wir den Body mit Chunked Encoding und halten die Verbindung offen, wobei wir beide Seiten des Bodys annehmen, bis wir einen gRPC-Nachrichtenblock erhalten, der das Ende des Streams anzeigt. Dazu muss unser Proxy bidirektionale Übertragung unterstützen.

Ein interessanter Modus ist beispielweise Client-Streaming. Dabei antwortet der Server bereits mit einem Antwortcode und seinem Header, aber der Client kann den Anfragetext weiterhin senden.

Interoperabilitätsprüfung

Jedes neue Cloudflare-Feature muss vor der Freigabe ordnungsgemäß getestet werden. Während der anfänglichen Entwicklung verwendeten wir den envoy-Proxy mit seiner gRPC-web-Filterfunktion und offizielle Beispiele für gRPC. Wir haben eine Testumgebung mit envoy und einem gRPC-Test-Ursprung aufgebaut, um sicherzustellen, dass der Edge-Proxy ordnungsgemäß mit gRPC-Anfragen funktioniert. Anfragen vom gRPC-Testclient werden an den Edge-Proxy gesendet, in gRPC-web konvertiert und an den envoy-Proxy weitergeleitet. Danach konvertiert envoy die Anfrage wieder in eine gRPC-Anfrage und sendet sie an den gRPC-Test-Ursprung. Auf diese Weise konnten wir das grundlegende Verhalten verifizieren.

Sobald wir die Grundfunktionalität hergestellt hatten, mussten wir auch dafür sorgen, dass die Konvertierungsfunktionalität auf beiden Seiten ordnungsgemäß funktionierte. Dafür haben wir tiefgehendere Interoperabilitätstests entwickelt.

Für unsere Testsuite nutzten wir die vorhandenen gRPC-Interoperabilitäts-Testfälle als Referenz und führten die erste Iteration von Tests zwischen dem Edge-Proxy und dem neuen Ursprungs-Proxy lokal durch.

Für die zweite Iteration der Tests verwendeten wir verschiedene gRPC-Implementierungen. Zum Beispiel sendeten einige Server ihren endgültigen Status (grpc-Status) in einer Antwort, die nur aus Trailern bestand, wenn sofort ein Fehler auftrat. Diese Antwort enthielt dann HTTP/2-Antwort-Header und -Trailer in einem einzigen HEADERS-Frame-Block, in dem sowohl das END_STREAM- als auch das END_HEADERS-Flag gesetzt waren. Andere Implementierungen sendeten den endgültigen Status als Trailer in einem separaten HEADERS-Frame.

Nachdem wir die Interoperabilität vor Ort verifiziert hatten, haben wir den Testaufbau mit einer Entwicklungsumgebung verglichen, die alle Dienste unterstützt, die wir in der Produktion haben. Wir konnten dann sicherstellen, dass es keine unbeabsichtigten Seiteneffekte gab, die sich auf gRPC-Anfragen auswirken.

Wir lieben Dogfooding! Einer der ersten Dienste, bei dem wir erfolgreich Edge-gRPC-Unterstützung eingesetzt haben, ist der Cloudflare Drand Randomness Beacon. Der Einstieg war einfach, und wir haben den Beacon in den letzten Wochen reibungslos in die Produktion übernommen.

Fazit

Ein neues Protokoll zu unterstützen, ist spannend! Unterstützung für neue Technologien in vorhandenen Systemen zu implementieren, ist spannend und kompliziert, oft mit Kompromissen zwischen der Geschwindigkeit der Implementierung und der Komplexität des Gesamtsystems. Im Fall von gRPC konnten wir die Unterstützung zügig und in einer Weise entwickeln, die keine wesentlichen Änderungen an der Cloudflare-Edge erforderte. Dazu mussten wir die Implementierungsoptionen sorgfältig abwägen, bevor wir uns auf die Konvertierung zwischen den Formaten HTTP/2 gRPC und HTTP/1.1 gRPC-web einigten. Durch diese Designentscheidung gelang die Dienstintegration schneller und einfacher, während die Erwartungen und Nebenbedingungen unserer Nutzer weiterhin berücksichtigt wurden.

Wenn Sie Ihren gRPC-Dienst mit Cloudflare sichern und beschleunigen möchten, können Sie hier mehr erfahren. Und wenn Sie selbst an spannenden technischen Herausforderungen wie dieser mitarbeiten möchten, bewerben Sie sich bei uns!

gRPC® ist ein eingetragenes Warenzeichen der Linux Foundation.