Mit unserem Rust-Framework Pingora haben wir die Dienste entwickelt, auf denen ein großer Teil des Traffics bei Cloudflare beruht. Wir sind stolz darauf, dass Pingora ab sofort quelloffen ist. Veröffentlicht wurde Pingora unter der Apache-Lizenz 2.0.
Wie in einem früheren Blogbeitrag bereits erwähnt, ist Pingora ein asynchrones Multithread-Rust-Framework, das uns bei der Entwicklung von HTTP-Proxy-Diensten unterstützt. Seit Veröffentlichung unseres letzten Blogbeitrags hat Pingora fast eine Billiarde Internetanfragen in unserem globalen Netzwerk verarbeitet.
Wir bieten Pingora quelloffen an, um über unsere eigene Infrastruktur hinaus ein besseres und sichereres Internet zu schaffen. Auf diesem Weg möchten wir unter anderem unseren Kunden und Nutzern Werkzeuge, Ideen und Anregungen bieten, mit denen sie ihre eigene Internetinfrastruktur mit einem speichersicheren Framework aufbauen können. Angesichts des wachsenden Bewusstseins für die Bedeutung der Speichersicherheit in der gesamten Branche und bei der US-Regierung ist ein solcher Rahmen besonders wichtig. Um dieses gemeinsame Ziel zu erreichen und die Verwendung von Pingora in der kritischsten Infrastruktur des Internets zu unterstützen, beteiligen wir uns an dem von der Internet Security Research Group (ISRG) ins Leben gerufenen Prossimo-Projekt.
In unserem vorherigen Blogbeitrag wurde erörtert, weshalb und wie wir Pingora entwickelt haben. Diesmal wird es darum gehen, warum Sie Pingora verwenden sollten und auf welche Weise.
Pingora stellt Bausteine nicht nur für Proxys, sondern auch für Clients und Server bereit. Zusammen mit diesen Komponenten bieten wir auch einige Dienstprogrammbibliotheken an, die gängige Logik wie Ereigniszählung, Fehlerbehandlung und Zwischenspeicherung implementieren.
Was verbirgt sich alles dahinter?
Pingora stellt Bibliotheken und API zur Erstellung von Diensten auf Grundlage von HTTP/1 und HTTP/2, TLS oder einfach nur TCP/UDP bereit. Als Proxy bietet die Lösung die Ende-zu-Ende-Unterstützung von HTTP/1 und HTTP/2 sowie die Unterstützung von gRPC und Websocket-Weiterleitung. (Die Unterstützung von HTTP/3 ist in Planung.) Hinzu kommen individuell anpassbare Lastverteilungs- und Failover-Strategien. Aus Gründen der Compliance und Sicherheit werden die häufig verwendeten OpenSSL- und BoringSSL-Bibliotheken unterstützt, die FIPS-Konformität und Post-Quanten-Verschlüsselung vorweisen können.
Neben diesen Funktionen stehen bei Pingora Filter und Callbacks zur Verfügung, mit denen die Nutzer vollständig anpassen können, wie der Dienst die Anfragen verarbeiten, umwandeln und weiterleiten soll. Diese API werden besonders OpenResty- und NGINX-Nutzern vertraut sein, da viele intuitiv an die „*_by_lua“-Callbacks von OpenResty erinnern.
Im Betrieb bietet Pingora elegante Neustarts ohne Ausfallzeiten, um Selbstaktualisierungen zu ermöglichen, bei denen keine einzige eingehende Anfrage verworfen wird. Syslog, Prometheus, Sentry, OpenTelemetry und andere unverzichtbare Beobachtungs-Tools lassen sich ebenfalls problemlos in Pingora integrieren.
Für wen ist Pingora nützlich?
Unter diesen Umständen sollten Sie Pingora in Betracht ziehen:
Sicherheit steht für Sie an erster Stelle: Pingora ist eine speicherfreundlichere Alternative für in C/C++ geschriebene Dienste. Manche mögen zwar über die Speichersicherheit bei verschiedenen Programmiersprachen streiten, aber in der Praxis haben wir beobachtet, dass wir beim Programmieren viel seltener Fehler machen, die die Speichersicherheit beeinträchtigen. Außerdem verbringen wir weniger Zeit mit den entsprechenden Problemen, was höhere Produktivität bei der Implementierung neuer Funktionen ermöglicht.
Ihr Dienst ist Performance-abhängig: Pingora ist schnell und effizient. Wie in unserem vorherigen Blogbeitrag bereits erläutert, haben wir dank der Multi-Thread-Architektur von Pingora eine Menge CPU- und Speicher-Ressourcen eingespart. Diese Zeit- und Ressourcenersparnis könnte für Workloads, die von den Kosten und/oder der Geschwindigkeit des Systems abhängen, ein überzeugendes Argument sein.
Ihr Dienst erfordert umfangreiche Anpassungen: Die vom Pingora-Proxy-Framework bereitstellten API sind hochgradig programmierbar. Für Nutzer, die individuell gestaltete und anspruchsvolle Gateways oder Load Balancer erstellen möchten, bietet Pingora leistungsstarke und zugleich einfache Implementierungsmöglichkeiten. Schauen wir uns dazu ein paar Beispiele an.
Erstellung eines Load Balancers
Lassen Sie uns die programmierbare API von Pingora anhand eines einfachen Load Balancers erkunden. Der Load Balancer wählt im Rundlaufverfahren zwischen https://1.1.1.1/ und https://1.0.0.1/ als Upstream aus.
Zunächst erstellen wir einen leeren HTTP-Proxy.
Jedes Objekt, das den Trait ProxyHttp
implementiert (ähnlich dem Konzept eines Interface in C++ oder Java), ist ein HTTP-Proxy. Die einzige erforderliche Methode ist upstream_peer()
, die für jede Anfrage aufgerufen wird. Diese Funktion sollte mit einem HttpPeer
antworten. Dieser enthält die IP-Adresse des Ursprungsservers, zu dem eine Verbindung hergestellt werden soll, und die Art und Weise, in der die Verbindung hergestellt werden kann.
pub struct LB();
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
todo!()
}
}
Als Nächstes implementieren wir die Rundlaufverfahrensauswahl. Das Pingora-Framework stellt für den LoadBalancer
bereits gängige Auswahlalgorithmen wie das Rundlaufverfahren und Hashing zur Verfügung, also werden wir dieses Angebot auch nutzen. Wenn der Anwendungsfall eine ausgefeiltere oder individuell angepasste Serverauswahllogik erfordert, können Sie diese einfach selbst in dieser Funktion implementieren.
Da wir uns mit einem HTTPS-Server verbinden, muss auch die SNI festgelegt werden. Zertifikate, Timeouts und andere Verbindungsoptionen können bei Bedarf auch hier im HttpPeer-Objekt festgelegt werden.
pub struct LB(Arc<LoadBalancer<RoundRobin>>);
#[async_trait]
impl ProxyHttp for LB {
async fn upstream_peer(...) -> Result<Box<HttpPeer>> {
let upstream = self.0
.select(b"", 256) // hash doesn't matter for round robin
.unwrap();
// Set SNI to one.one.one.one
let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string()));
Ok(peer)
}
}
Schließlich wollen wir den Dienst in Aktion sehen. In diesem Beispiel werden die IP-Adressen der Ursprungsserver fest programmiert. Bei Workloads im echten Leben können die IP-Adressen von Ursprungsservern auch im Hintergrund oder dynamisch ermittelt werden, wenn upstream_peer()
aufgerufen wird. Nachdem der Dienst erstellt ist, weisen wir den LB-Dienst einfach an, nach 127.0.0.1:6188 Ausschau zu halten. Letztendlich haben wir einen Pingora-Server erstellt, und der Server wird die Lastverteilung ausführen.
Probieren wir es einmal aus:
fn main() {
let mut upstreams = LoadBalancer::try_from_iter(["1.1.1.1:443", "1.0.0.1:443"]).unwrap();
let mut lb = pingora_proxy::http_proxy_service(&my_server.configuration, LB(upstreams));
lb.add_tcp("127.0.0.1:6188");
let mut my_server = Server::new(None).unwrap();
my_server.add_service(lb);
my_server.run_forever();
}
Wir können sehen, dass der Proxy funktioniert, aber der Ursprungsserver liefert uns eine 403-Fehlermeldung. Das liegt daran, dass unser Dienst einfach den von curl gesetzten Host-Header, 127.0.0.1:6188, als Proxy verwendet, was für den Ursprungsserver ein Problem darstellt. Wie bringen wir den Proxy dazu, das zu korrigieren? Ganz einfach: durch Hinzufügen eines weiteren Filters namens upstream_request_filter
. Dieser Filter wird bei jeder Anfrage ausgeführt, nachdem die Verbindung zum Ursprungsserver hergestellt wurde und bevor eine HTTP-Anfrage gesendet wird. In diesem Filter können HTTP-Anfrage-Header hinzugefügt, entfernt oder geändert werden.
curl 127.0.0.1:6188 -svo /dev/null
> GET / HTTP/1.1
> Host: 127.0.0.1:6188
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 403 Forbidden
Versuchen wir es noch einmal:
async fn upstream_request_filter(…, upstream_request: &mut RequestHeader, …) -> Result<()> {
upstream_request.insert_header("Host", "one.one.one.one")
}
Diesmal funktioniert es! Das vollständige Beispiel finden Sie hier.
curl 127.0.0.1:6188 -svo /dev/null
< HTTP/1.1 200 OK
Unten sehen Sie ein sehr einfaches Diagramm dazu, wie diese Anfrage den Callback und den Filter durchläuft, den wir in diesem Beispiel verwendet haben. Das Proxy-Framework von Pingora bietet derzeit mehr Filter und Callbacks für verschiedene Phasen einer Anfrage, damit Nutzer die Anfrage (und die Antwort) ändern, ablehnen, weiterleiten und/oder protokollieren können.
Hinter den Kulissen kümmert sich das Pingora-Proxy-Framework um das Verbindungspooling, den TLS-Handshake, das Lesen, das Schreiben und das Parsen von Anfragen sowie um alle anderen üblichen Proxy-Aufgaben. Damit können sich die Nutzer voll und ganz auf die Logik konzentrieren, die für sie wichtig ist.
Quelloffen, heute und in Zukunft
Bei Pingora handelt es sich um eine Bibliothek und ein Toolset, nicht um eine ausführbare Binärdatei. Mit anderen Worten: Pingora ist der Motor eines Autos, nicht das Auto selbst. Pingora ist für den Produktiveinsatz in der Industrie bereit. Uns ist aber bewusst, dass sich viele einen direkt einsetzbaren Webdienst mit wenig bis gar keinem Programmieraufwand wünschen. Diese Anwendung auf Pingora aufzubauen und die Reichweite der Lösung zu vergrößern, wird der Schwerpunkt unserer Zusammenarbeit mit der ISRG sein. Schauen Sie doch ab und zu auf unserem Blog vorbei, um sich über den Fortgang dieses Projekts auf dem Laufenden zu halten.
Was Sie sonst noch bedenken sollten:
Aktuell kann die API-Stabilität nicht garantiert werden. Wir werden versuchen, so wenig Änderungen wie möglich vorzunehmen, die Störungen verursachen. Doch wir behalten uns weiterhin vor, im Zuge der Weiterentwicklung der Bibliothek Komponenten wie Anfrage- und Antwortfilter hinzuzufügen, zu entfernen oder zu ändern, insbesondere vor Einführung der Version 1.0.
Die Unterstützung Nicht-Unix-basierter Betriebssysteme ist zurzeit nicht in Planung. Wir haben keine unmittelbaren Pläne, die Unterstützung dieser Systeme zu ermöglichen, auch wenn sich dies in Zukunft ändern könnte.
Ihr persönlicher Beitrag
Unser Issue-Tracker bei GitHub steht Ihnen zur Einreichung von Fehlerberichten, zur Meldung von Problemen mit der Dokumentation oder für Funktionsanfragen zur Verfügung. Bevor Sie einen Pull-Request stellen, möchten wir Ihnen aber nahelegen, einen Blick in unseren Beitragsleitfaden zu werfen.
Fazit
In diesem Blogbeitrag haben wir die Quelloffenheit unseres Pingora-Frameworks bekannt gegeben. Wir haben gezeigt, dass Internetelemente und -infrastrukturen von der Sicherheit, Performance und Anpassbarkeit von Pingora profitieren können. Außerdem haben wir unter Beweis gestellt, wie leicht Pingora zu verwenden ist und wie gut sich die Lösung individuell anpassen lässt.
Ganz gleich, ob Sie Web-Produktivdienste entwickeln oder mit Netzwerktechnologien experimentieren: Wir hoffen, dass Pingora Ihnen einen Mehrwert bieten kann. Der Weg war lang, aber es war von Anfang an unser Ziel, die Open-Source-Community an diesem Projekt teilhaben zu lassen. Wir möchten der Rust-Community danken, da Pingora mit vielen großartigen quelloffenen Rust-Crates entwickelt wurde. Der Übergang zu einem speichersicheren Internet mag wie ein unmögliches Unterfangen erscheinen, aber wir hoffen, dass Sie uns dabei begleiten.