YARP – Ein schneller und zuverlässiger Reverse Proxy

Möglichkeiten der .NET-basierten Open-Source-Lösung von Microsoft

Routing, Load Balancing, Authentifizierung, Autorisierung und Ausfallsicherheit sind wichtige Themen in vielen Webprojekten. Microsoft hat eine große Anzahl von Teams, welche einen Reverse Proxy für ihre Dienste selbst schreiben oder nach Lösungen suchen, um die genannten Aufgaben abzubilden. Dies war ein guter Anlass, die unterschiedlichen Anforderungen zusammenzubringen, um an einer gemeinsamen Lösung zu arbeiten. YARP wurde ins Leben gerufen – das Microsoft Open-Source-Projekt für einen Reverse Proxy in .NET.

Vor mehr als einem Jahr hat Microsoft die erste Preview veröffentlicht. Seitdem gab es viele Verbesserungen und YARP ist zusammen mit dem neuen .NET 6 am 9. November 2021 in der Version 1.0 erschienen.

In diesem Artikel wollen wir uns YARP etwas genauer anschauen und einen Überblick über die Konfigurationsmöglichkeiten geben, die uns Microsoft an die Hand gibt. Dafür schauen wir uns als Erstes an, was ein Reverse Proxy eigentlich ist und wie sich YARP positioniert. Danach betrachten wir die vielfältigen Konfigurationsmöglichkeiten, um am Ende einen Ausblick zu geben.

YARP ist in C# geschrieben und auf .NET aufgebaut. Es nutzt die Infrastruktur von ASP.NET und .NET. Somit werden .NET Core 3.1 und .NET 5 sowie das schon erwähnte .NET 6 unterstützt. Bei der Verwendung von .NET Core 3.1 stehen allerdings einige Funktionen nicht zur Verfügung, da YARP bei den neueren .NET Versionen auf einige neue Features und Optimierungen zurückgreift.

Tastatur mit Open-Source-Taste

Was ist ein Reverse Proxy

Ein Reverse Proxy ist eine Art Proxy-Server, der typischerweise hinter der Firewall in einem privaten Netzwerk sitzt und Client-Anfragen an Backend Services weiterleitet. Dabei liefert der Reverse Proxy eine zusätzliche Abstraktions- und Steuerungsschicht, um den reibungslosen Fluss des Netzwerkverkehrs zwischen den Clients und Servern zu gewährleisten.

Was ist YARP

Ein klassischer Reverse Proxy arbeitet meist auf der Transportschicht (4. Ebene – TCP/IP) des ISO/OSI Modells und routet die Anfragen immer weiter. Im Vergleich dazu befindet sich YARP auf der 7. Ebene – hier http-Level – und es werden die eingehenden Verbindungen gekappt und neue zum Ziel-Server erstellt. Die eingehenden und ausgehenden Verbindungen sind somit unabhängig. Dadurch ist ein Remapping des URL Space möglich, d. h. es gibt einen Unterschied zwischen den URLs, die von außen sichtbar sind und denen im Backend.

Eine Entlastung der Backend Server ist durch Aufgabenverschiebung in den Reverse Proxy möglich.

Warum YARP?

Die Nutzungsmöglichkeiten von YARP und den vielen anderen (klassischen) Reverse Proxys sind unterschiedlicher Natur. Ein Entwickler im ASP.NET-Umfeld kann leicht die Funktionalität des Reverse Proxys in seiner gewohnten Programmiersprache einrichten, konfigurieren und erweitern. Ebenso kann der Reverse Proxy mit allen Konfigurationen, wie jedes andere Projekt auch, mit der Versionskontrolle versioniert werden. Außerdem ist es Cross-Plattform-fähig, d. h. es läuft sowohl unter Windows als auch Linux und eignet sich somit gut zur Containerisierung.

Funktionen von YARP

Eine der wichtigsten Anforderungen ist die Erweiterbarkeit sowie Anpassbarkeit von YARP. Zur Konfiguration kann jede Quelle, die das IConfiguration Interface abbildet, angebunden werden. Klassischerweise sind das JSON-Konfigurationsdateien. Die Konfiguration wird bei Änderungen ohne Neustart automatisch aktualisiert. Es ist aber auch möglich, die Konfiguration dynamisch über eine API zu steuern oder sogar on demand pro Anfrage.

Anatomie einer Anfrage

Um die Funktionen besser zu verstehen, ist es sinnvoll, sich zuerst einen Überblick über die Pipeline-Architektur von YARP zu verschaffen. Als Erstes landet eine eingehende Anfrage in der Standard ASP .NET Middleware (z. B. TLS Termination, Statische Files, Routing, Authentifizierung und Autorisierung). Anschließend folgen verschiedene Phasen in YARP.

  1. Durchlauf über alle Ziel-Server inkl. Health Check
  2. Session Affinity
  3. Load Balancing
  4. Passive Health Checks
  5. Transformation der Anfrage
  6. Weiterleitung der Anfrage durch eine neue Verbindung an den Ziel-Server

Routen und Cluster

Der Reverse Proxy kann sowohl für Routen als auch für Cluster konfiguriert werden. Die Routenkonfiguration ist eine geordnete Liste von Routentreffern mit den dazugehörigen Konfigurationen. Eine Route wird typischerweise durch drei Bestandteile definiert. Das sind die Routen-ID, Cluster-ID und ein Match-Kriterium. Wenn also eine eingehende Verbindung ankommt, wird diese mit dem Match-Kriterium verglichen. Dabei wird die Liste der Routeneinträge nacheinander abgearbeitet. Wird das Match-Kriterium erfüllt, so wird das Cluster mit der angegebenen ID zur Weiterleitung verwendet. Für eine Route können aber auch CORS, Transformer, Authentifizierung und Autorisierung konfiguriert werden.

In Abbildung 1 ist eine Beispiel-Konfiguration für Routen und Cluster zu sehen.

Im Gegensatz zum „Routes Abschnitt, enthält der Cluster Abschnitt eine ungeordnete Sammlung von benannten Clustern. Ein Cluster enthält in erster Linie eine Sammlung von benannten Zielen und deren Adressen, von denen jedes als fähig angesehen wird, Anfragen für eine bestimmte Route zu bearbeiten. Der Proxy verarbeitet die Anfrage entsprechend der Routen- und Cluster-Konfiguration, um ein Ziel auszuwählen.

{
    "ReverseProxy": {
        "Routes": {
            "minimumroute": {
                "ClusterId": "minimumcluster",
                "Match": {
                    "Path": "{**catch-all}"
                }
            },
            "route2": {
                "ClusterId": "cluster2",
                "Match": {
                    "Path": "/something/{*any}"
                }
            }
        },
        "Clusters": {
            "minimumcluster": {
                "Destinations": {
                    "example.com": {
                        "Address": "http://www.example.com/"
                    }
                }
            },
            "cluster2": {
                "Destinations": {
                    "first_destination": {
                        "Address": "https://contoso.com"
                    },
                    "another_destination": {
                        "Address": "https://bing.com"
                    }
                },
                "LoadBalancingPolicy": "PowerOfTwoChoices"
            }
        }
    }
}

Abbildung 1: Beispiel-Konfiguration mit den grundlegenden Funktionen (Routen, Cluster und Load Balancing)

TLS Termination

Wie schon erwähnt werden die eingehenden Verbindungen gekappt und neue zum Ziel-Server hergestellt. Da TLS Verbindungen teuer sind, kann das für kleine Aufrufe die Geschwindigkeit verbessern. Dies kann vor allem dann sinnvoll sein, wenn der Proxy auf Server weiterleitet, die sich alle im internen Netz befinden und keine gesicherte Verbindung mehr notwendig ist. Folgende Möglichkeiten sind hier vorstellbar:

  • Routing einer eingehenden HTTPS auf HTTP Verbindung
  • Routing einer eingehenden HTTPS/1 auf HTTP/2 Verbindung
  • Routing einer eingehenden HTTP auf HTTPS Verbindung

Session Affinity

Die Session Affinity ist ein Mechanismus zur Bindung (Affinität) einer zusammenhängenden Anforderungssequenz an das Ziel, das die erste Anforderung bearbeitet hat, wenn die Last auf mehrere Ziele verteilt ist.

Es ist in Szenarien nützlich, in denen die meisten Anfragen in einer Sequenz mit denselben Daten arbeiten und die Kosten des Datenzugriffs für verschiedene Ziele, die Anfragen bearbeiten, unterschiedlich sind.

Das häufigste Beispiel ist ein transientes Caching (z. B. In-Memory). Dabei werden während der ersten Anfrage Daten aus einem langsameren persistenten Speicher in einen schnellen lokalen Cache geholt. Bei weiteren Anfragen können diese mit den Daten aus dem Cache bearbeitet werden, wodurch der Durchsatz erhöht wird.

Load Balancing

Wenn für eine Route mehrere gesunde Ziele verfügbar sind, kann einer der folgenden Load Balancing Algorithmen konfiguriert werden:

  • Random
  • Round Robin
  • Least Requests
  • Power of Two Choices
  • First

An dieser Stelle kann auch ein selbst entwickelter Algorithmus verwendet werden.

Health Checks

Der Reverse Proxy kann den Gesundheitszustand jedes Knotens analysieren und den Client-Verkehr zu ungesunden Knoten stoppen, bis sie sich erholen. YARP implementiert diesen Ansatz in Form von aktiven und passiven Prüfungen.

Passiv

YARP kann passiv auf Erfolge und Misserfolge beim Weiterleiten von Client-Anfragen achten. Die Antworten auf die Proxy-Anfragen werden von einer dedizierten Middleware zur passiven Gesundheitsprüfung abgefangen, die sie an eine auf dem Cluster konfigurierte Richtlinie weiterleitet. Die Richtlinie analysiert die Antworten, um festzustellen, ob die Ziele, die sie erzeugt haben, gesund sind oder nicht. Dann berechnet sie neue passive Gesundheitszustände, weist sie den jeweiligen Zielen zu und baut die Sammlung der gesunden Ziele des Clusters neu auf.

Aktiv

YARP kann den Zustand der Ziel-Server auch aktiv überwachen. Dafür werden regelmäßig Anfragen an vordefinierte Zustandsendpunkte gesendet. Diese Analyse wird durch eine für einen Cluster festgelegte Richtlinie zur aktiven Gesundheitsprüfung definiert. Am Ende wird anhand der Richtlinie jedes Ziel als gesund oder ungesund markiert.

Durch die aktiven und passiven Checks werden ungesunde Cluster automatisch geblockt und es können Wartungsarbeiten durchgeführt werden, ohne dass die Anwendung beeinträchtigt wird.

Transformer

Mit Hilfe von Transformern kann der Proxy Teile der Anfrage oder Antwort modifizieren. Dies kann notwendig sein, um z. B. definierte Anforderungen des Ziel-Servers zu erfüllen. Dabei wird das ursprüngliche Anfrage-Objekt nicht verändert, sondern nur die Proxy-Anfrage. Es wird keine Auswertung des Anfrage-Bodys durchgeführt und es erfolgt keine Änderung des Anfrage- und Antwort-Bodys. Allerdings kann dies über eine zusätzliche Middleware erreicht werden – falls notwendig. An dieser Stelle könnte beispielsweise das ebenfalls auf .NET basierende API-Gateway Ocelot seine Stärken ausspielen. Dieses kann Konvertierungen wie XML zu JSON vornehmen oder mehrere Antworten zusammenführen und richtet sich vor allem an .NET-Anwendungen mit einer Microservice- oder serviceorientierten Architektur.

Es gibt einige Transformer, die standardmäßig aktiviert sind. Dazu gehören unter anderem das Protokoll (X-Forwarded-Proto), der angefragte Server (X-Forwarded-Host) und die Ursprungs-IP (X-Forwarded-For).

Der Ursprung der Anfrage ist dementsprechend nach dem Routing des Reverse Proxys im Backend über die hinzugefügten Headerinfos weiterhin bekannt – dies ist nicht bei klassischen Reverse Proxys gegeben.

Authentifizierung und Autorisierung

Vor der Weiterleitung sind eine Authentifizierung und Autorisierung möglich. Dadurch können konsistente Richtlinien über mehrere Services abgebildet werden und müssen somit nicht separat gepflegt werden. Außerdem führt das zu einer Lastreduzierung bei den Zielsystemen. Die Autorisierungsrichtlinien sind ein ASP.NET Core-Konzept. Es wird eine Richtlinie pro Route festgelegt und der Rest wird von den vorhandenen ASP.NET Core-Authentifizierungs- und Autorisierungskomponenten erledigt.

Folgende Verfahren werden von YARP unterstützt:

  • Cookie, Bearer, API Keys
  • OAuth2, OpenIdConnect, WsFederation
  • Client-Zertifikate

Nicht unterstützt werden hingegen die Windows-, Negotiate-, NTLM- und Kerbereos-Authentifizierung, da diese meist an eine bestimmte Verbindung gebunden sind.

Cross-Origin Resource Sharing – CORS

YARP kann Cross-Origin Requests behandeln, bevor sie an den Ziel-Server geleitet werden. Dies reduziert die Last auf den Ziel-Servern und sorgt für einheitliche Richtlinien.

Direkte Weiterleitung mit IHttpForwarder

Wenn die Applikation nicht den vollständigen Funktionsumfang benötigt, kann anstelle der kompletten Feature-Menge auch nur der IHttpForwarder verwendet werden. Dieser dient als Proxy-Adapter zwischen eingehenden und ausgehenden Verbindungen.

Der Proxy übernimmt in diesem Fall die Erstellung einer HttpRequestMessage aus einem HttpContext, das Senden und die Weiterleitung der Antwort.

Der IHttpForwarder unterstützt die dynamische Zielauswahl, wobei man selbst das Ziel für jede Anfrage festlegt.

Es können Anpassung an Anfrage und Antwort vorgenommen werden, wobei der Body ausgeschlossen ist. Und zu guter Letzt werden die Streaming-Protokolle gRPC und WebSockets sowie Fehlerbehandlungen unterstützt.

Diese minimale Variante unterstützt kein Routing, Load Balancing, Session Affinity und Retries – bringt aber einige Performance-Vorteile mit sich.

Ausblick

Für kommende Releases arbeitet das Team hinter YARP an der Unterstützung von HTTP/3, Service Fabric, einer Integration in Kubernetes sowie weiteren Performance-Verbesserungen.

Zusätzlich versucht Microsoft mit LLHTTP (Low Level HTTP) eine Alternative zum aktuellen HttpClient zu entwickeln, um mehr Kontrolle darüber zu haben, wie Anfragen gestellt und verarbeitet werden. Es soll insbesondere in Szenarien eingesetzt werden, bei denen die Performance wichtiger ist als eine einfache Verwendung. In YARP soll es für mehr Kontrolle über ausgehende Verbindungen und eine effizientere Verarbeitung von Headern eingesetzt werden.

Fazit

In diesem Artikel wurden die Grundlagen von YARP und die umfangreichen Funktionen erläutert. Mit Hilfe des gewonnenen Wissens und der Menge an guten Code-Beispielen im YARP-Repository auf GitHub können Sie jetzt einschätzen, ob die Funktionalitäten für einen gegebenen Anwendungsfall ausreichen und Ihren eigenen Reverse Proxy erstellen.

Da das Toolkit auf dem ASP.NET Core Stack basiert, kann es auf jeder Umgebung ausgeführt werden, die Sie bisher für Ihre .NET Core-Projekte verwendet haben.

Microsoft liefert mit YARP einen schnellen und zuverlässigen Reverse Proxy der nächsten Generation und wird in vielen Projekten Verwendung finden – nicht nur in denen von Microsoft.

Dieser Beitrag wurde verfasst von: