APISIX: Migration der etcd-Operationen von HTTP zu gRPC

Zexuan Luo

Zexuan Luo

February 10, 2023

Products

Einschränkungen der HTTP-basierten etcd-Operationen von Apache APISIX

Als etcd in Version 2.x war, stellte es eine HTTP 1-API-Schnittstelle bereit (wir werden sie ab jetzt als HTTP bezeichnen). Nachdem etcd auf Version 3.x aktualisiert wurde, wechselte es das Protokoll von HTTP zu gRPC. Für Benutzer, die gRPC nicht unterstützen, bietet etcd gRPC-Gateway an, um HTTP-Anfragen als gRPC zu proxen und auf die neuen gRPC-APIs zuzugreifen.

Als APISIX begann, etcd zu verwenden, nutzte es die etcd v2 API. In APISIX 2.0 (2020) haben wir die Anforderung für etcd von Version 2.x auf 3.x aktualisiert. Die Kompatibilität von etcd mit HTTP hat uns den Versionswechsel erleichtert. Wir mussten lediglich den Code für die Aufrufmethoden und die Verarbeitung der Antworten anpassen. Im Laufe der Jahre haben wir jedoch auch einige Probleme im Zusammenhang mit der HTTP-API von etcd festgestellt. Es gibt immer noch einige subtile Unterschiede. Wir haben erkannt, dass das Vorhandensein eines gRPC-Gateways nicht bedeutet, dass es den HTTP-Zugriff perfekt unterstützt.

Hier ist eine Liste der Probleme, die wir in den letzten Jahren mit etcd festgestellt haben:

  1. gRPC-Gateway standardmäßig deaktiviert. Aufgrund der Nachlässigkeit des Maintainers ist das gRPC-Gateway in einigen Projekten nicht standardmäßig aktiviert. Daher mussten wir in der Dokumentation Anweisungen hinzufügen, um zu überprüfen, ob das aktuelle etcd das gRPC-Gateway aktiviert hat. Siehe https://github.com/apache/apisix/pull/2940.
  2. Standardmäßig begrenzt gRPC Antworten auf 4 MB. etcd hebt diese Einschränkung in dem bereitgestellten SDK auf, aber nicht im gRPC-Gateway. Es stellt sich heraus, dass das offizielle etcdctl (basierend auf dem bereitgestellten SDK) einwandfrei funktioniert, APISIX jedoch nicht. Siehe https://github.com/etcd-io/etcd/issues/12576.
  3. Dasselbe Problem – diesmal mit der maximalen Anzahl von Anfragen für dieselbe Verbindung. Die HTTP2-Implementierung von Go hat eine MaxConcurrentStreams-Konfiguration, die die Anzahl der Anfragen steuert, die ein einzelner Client gleichzeitig senden kann, standardmäßig 250. Welcher Client würde normalerweise mehr als 250 Anfragen gleichzeitig senden? Daher hat etcd immer diese Konfiguration verwendet. Das gRPC-Gateway, der "Client", der alle HTTP-Anfragen an die lokale gRPC-Schnittstelle proxyt, kann jedoch dieses Limit überschreiten. Siehe https://github.com/etcd-io/etcd/issues/14185.
  4. Nachdem etcd mTLS aktiviert hat, verwendet etcd dasselbe Zertifikat sowohl als Server- als auch als Client-Zertifikat: das Server-Zertifikat für das gRPC-Gateway und das Client-Zertifikat, wenn das gRPC-Gateway auf die gRPC-Schnittstelle zugreift. Wenn die Server-Authentifizierungserweiterung auf dem Zertifikat aktiviert ist, aber die Client-Authentifizierungserweiterung nicht, führt dies zu einem Fehler bei der Zertifikatsüberprüfung. Auch hier funktioniert der direkte Zugriff mit etcdctl einwandfrei (da das Zertifikat in diesem Fall nicht als Client-Zertifikat verwendet wird), APISIX jedoch nicht. Siehe https://github.com/etcd-io/etcd/issues/9785.
  5. Nach der Aktivierung von mTLS ermöglicht etcd die Konfiguration von Sicherheitsrichtlinien für die Benutzerinformationen der Zertifikate. Wie oben erwähnt, verwendet das gRPC-Gateway ein festes Client-Zertifikat, wenn es auf die gRPC-Schnittstelle zugreift, und nicht die Zertifikatinformationen, die zu Beginn für den Zugriff auf die HTTP-Schnittstelle verwendet wurden. Daher funktioniert diese Funktion natürlich nicht, da das Client-Zertifikat fest ist und nicht geändert wird. Siehe https://github.com/apache/apisix/issues/5608.

Wir können die Probleme in zwei Punkten zusammenfassen:

  1. gRPC-Gateway (und vielleicht andere Versuche, HTTP in gRPC umzuwandeln) ist kein Allheilmittel, das alle Probleme löst.
  2. Die Entwickler von etcd legen nicht genug Wert auf die HTTP-zu-gRPC-Methode. Und ihr größter Benutzer, Kubernetes, verwendet diese Funktion nicht.

Um dieses Problem zu lösen, müssen wir etcd direkt über gRPC verwenden, damit wir nicht den für die Kompatibilität reservierten HTTP-Pfad des gRPC-Gateways durchlaufen müssen.

Überwindung der Herausforderungen bei der Migration zu gRPC

Bug in lua-protobuf

Unser erstes Problem während des Migrationsprozesses war ein unerwarteter Bug in einer Drittanbieterbibliothek. Wie die meisten OpenResty-Anwendungen verwenden wir lua-protobuf, um Protobuf zu dekodieren/encodieren.

Nach der Integration der etcd-Proto-Datei stellten wir fest, dass es gelegentlich zu Abstürzen im Lua-Code kam, die einen Fehler "table overflow" meldeten. Da dieser Absturz nicht zuverlässig reproduziert werden konnte, war unser erster Instinkt, nach einem minimalen reproduzierbaren Beispiel zu suchen. Interessanterweise kann das Problem nicht reproduziert werden, wenn man die etcd-Proto-Datei alleine verwendet. Dieser Absturz scheint nur aufzutreten, wenn APISIX läuft.

Nach einigem Debugging habe ich das Problem in lua-protobuf beim Parsen des oneof-Feldes der Proto-Datei lokalisiert. lua-protobuf versucht, die Tabellengröße beim Parsen vorab zuzuweisen, und die zugewiesene Größe wird nach einem bestimmten Wert berechnet. Es gab eine gewisse Wahrscheinlichkeit, dass dieser Wert eine negative Zahl war. Dann würde LuaJIT diese Zahl bei der Zuweisung in eine große positive Zahl umwandeln, was zu einem "table overflow"-Fehler führte. Ich habe das Problem dem Autor gemeldet und wir haben intern einen Fork mit einer Problemumgehung gepflegt.

Der lua-protobuf-Autor war sehr reaktionsschnell und lieferte am nächsten Tag eine Korrektur und veröffentlichte einige Tage später eine neue Version. Es stellte sich heraus, dass lua-protobuf beim Bereinigen von nicht mehr verwendeten Proto-Dateien einige Felder übersehen hatte, was zu einer unvernünftigen negativen Zahl führte, wenn oneof anschließend verarbeitet wurde. Das Problem trat nur gelegentlich auf, und der Grund, warum es nicht reproduziert werden konnte, wenn die etcd-Proto-Datei alleine verwendet wurde, war, dass die Schritte zum Bereinigen dieser Felder fehlten.

Anpassung an das HTTP-Verhalten

Während des Migrationsprozesses stellte ich fest, dass die bestehende API nicht genau das Ausführungsergebnis zurückgibt, sondern eine HTTP-Antwort mit Antwortstatus und -körper. Anschließend muss der Aufrufer die HTTP-Antwort selbst verarbeiten.

Wenn die Antworten in gRPC wären, müssten sie mit einer HTTP-Antwort-Hülle umschlossen werden, um sich an die Verarbeitungslogik anzupassen. Andernfalls müsste der Aufrufer den Code an mehreren Stellen anpassen, um sich an das neue (gRPC-)Antwortformat anzupassen. Besonders wenn man bedenkt, dass die alten HTTP-basierten etcd-Operationen gleichzeitig unterstützt werden müssen.

Obwohl das Hinzufügen einer zusätzlichen Ebene zur Kompatibilität mit HTTP-Antworten nicht wünschenswert ist, müssen wir dies umgehen. Zusätzlich dazu müssen wir auch einige Anpassungen an der gRPC-Antwort vornehmen. Zum Beispiel gibt HTTP keine Daten zurück, wenn es keine entsprechenden Daten gibt, aber gRPC gibt eine leere Tabelle zurück. Dies muss ebenfalls angepasst werden, um sich an das HTTP-Verhalten anzupassen.

Von Kurzverbindungen zu Langverbindungen

Bei HTTP-basierten etcd-Operationen verwendet APISIX Kurzverbindungen, sodass keine Verbindungsverwaltung erforderlich ist. Alles, was wir tun müssen, ist eine neue Verbindung zu initiieren, wann immer wir sie benötigen, und sie zu schließen, wenn wir fertig sind.

Aber gRPC kann dies nicht. Einer der Hauptzwecke der Migration zu gRPC ist die Erreichung von Multiplexing, was nicht erreicht werden kann, wenn für jede Operation eine neue gRPC-Verbindung erstellt wird. Hier müssen wir gRPC-go danken, da es über eine integrierte Verbindungsverwaltungsfunktion verfügt, die automatisch eine erneute Verbindung herstellt, sobald die Verbindung unterbrochen wird. Daher können wir gRPC-go verwenden, um die Verbindung wiederzuverwenden. Und auf der APISIX-Ebene müssen nur Geschäftsanforderungen berücksichtigt werden.

Die etcd-Operationen von APISIX können in zwei Kategorien unterteilt werden: eine ist CRUD (Hinzufügen, Löschen, Ändern, Abfragen) von etcd-Daten; die andere ist die Synchronisierung der Konfiguration von der Steuerungsebene. Obwohl theoretisch diese beiden etcd-Operationen dieselbe gRPC-Verbindung teilen könnten, haben wir uns entschieden, sie in zwei Verbindungen aufzuteilen, um die Verantwortlichkeiten zu trennen. Für die Verbindung der CRUD-Operationen, da APISIX beim Start und nach dem Start separat behandelt werden muss, wird eine if-Anweisung hinzugefügt, wenn eine neue Verbindung abgerufen wird. Wenn es eine Nichtübereinstimmung gibt (d.h. die aktuelle Verbindung wurde beim Start erstellt, während wir eine Verbindung nach dem Start benötigen), schließen wir die aktuelle Verbindung und erstellen eine neue. Ich habe eine neue Synchronisationsmethode für die Synchronisierung der Konfiguration entwickelt, sodass jede Ressource einen Stream unter der bestehenden Verbindung verwendet, um etcd zu beobachten.

Vorteile der Migration zu gRPC

Ein offensichtlicher Vorteil nach der Migration zu gRPC ist, dass die Anzahl der für etcd-Operationen erforderlichen Verbindungen stark reduziert wird. Bei der Verwendung von HTTP für etcd-Operationen konnte APISIX nur Kurzverbindungen verwenden. Und bei der Synchronisierung der Konfiguration hatte jede Ressource eine separate Verbindung.

Nach dem Wechsel zu gRPC können wir die Multiplexing-Funktion von gRPC nutzen, und jede Ressource verwendet nur einen einzelnen Stream anstelle einer vollständigen Verbindung. Auf diese Weise erhöht sich die Anzahl der Verbindungen nicht mehr mit der Anzahl der Ressourcen. Wenn man bedenkt, dass die zukünftige Entwicklung von APISIX mehr Ressourcentypen einführen wird, zum Beispiel hat die neueste Version 3.1 secrets hinzugefügt, wird die Reduzierung der Anzahl der Verbindungen durch die Verwendung von gRPC noch signifikanter sein.

Bei der Verwendung von gRPC für die Synchronisierung hat jeder Prozess nur eine (zwei, wenn das Stream-Subsystem aktiviert ist) Verbindung für die Konfigurationssynchronisierung. In der folgenden Abbildung können wir sehen, dass die beiden Prozesse vier Verbindungen haben, von denen zwei für die Konfigurationssynchronisierung verwendet werden, die Admin API eine Verbindung verwendet und die verbleibende Verbindung für den privilegierten Agenten zur Berichterstattung von Serverinformationen verwendet wird.

gRPC verwendet viel weniger Verbindungen

Zum Vergleich zeigt die folgende Abbildung die 22 Verbindungen, die bei Verwendung der ursprünglichen Konfigurationssynchronisierungsmethode erforderlich sind, während alle anderen Parameter unverändert bleiben. Darüber hinaus sind diese Verbindungen Kurzverbindungen.

zu viele Verbindungen

Der einzige Unterschied zwischen diesen beiden Konfigurationen besteht darin, ob gRPC für etcd-Operationen aktiviert ist:

etcd: use_grpc: true host: - "http://127.0.0.1:2379" prefix: "/apisix" ...

Neben der Reduzierung der Anzahl der Verbindungen kann die direkte Verwendung von gRPC für den Zugriff auf etcd anstelle von gRPC-Gateway eine Reihe von architektonisch begrenzten Problemen wie mTLS-Authentifizierung, die zu Beginn des Artikels erwähnt wurden, lösen. Es wird auch weniger Probleme geben, nachdem gRPC verwendet wird, da Kubernetes gRPC verwendet, um etcd zu betreiben. Wenn es ein Problem gibt, wird es von der Kubernetes-Community entdeckt.

Natürlich, da die gRPC-Methode noch relativ neu ist, wird APISIX unweigerlich einige neue Probleme haben, wenn es etcd über gRPC betreibt. Derzeit wird standardmäßig noch die ursprüngliche HTTP-basierte Methode verwendet, um etcd zu betreiben. Benutzer haben die Möglichkeit, use_grpc unter etcd in config.yaml selbst auf true zu setzen. Sie können ausprobieren, ob die gRPC-Methode besser ist. Wir werden auch kontinuierlich Feedback aus verschiedenen Quellen sammeln, um die gRPC-basierten etcd-Operationen zu verbessern. Wenn wir feststellen, dass der gRPC-Ansatz ausgereift genug ist, werden wir ihn zum Standardansatz machen.

Um APISIX zu maximieren, benötigen Sie API7

Sie lieben die Leistung von Apache APISIX, nicht den Aufwand, es zu verwalten. Sie können sich auf Ihr Kerngeschäft konzentrieren, ohne sich um Konfiguration, Wartung und Aktualisierung kümmern zu müssen.

Unser Team besteht aus Apache APISIX-Erstellern und -Mitwirkenden, OpenResty- und NGINX-Kernmaintainern, Kubernetes-Mitgliedern und Branchenexperten für Cloud-Infrastruktur. Sie bekommen die besten Leute hinter den Kulissen.

Möchten Sie Ihre Entwicklung mit Zuversicht beschleunigen? Um die APISIX-Unterstützung zu maximieren, benötigen Sie API7. Wir bieten umfassende Unterstützung für APISIX und API-Management-Lösungen basierend auf Ihren Anforderungen!

Kontaktieren Sie uns jetzt: https://api7.ai/contact.

Tags: