Zum Inhalt

combined-upstreams: NGINX Combined Upstreams-Modul

Installation

Sie können dieses Modul in jeder RHEL-basierten Distribution installieren, einschließlich, aber nicht beschränkt auf:

  • RedHat Enterprise Linux 7, 8, 9 und 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 und Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-combined-upstreams
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-combined-upstreams

Aktivieren Sie das Modul, indem Sie Folgendes an den Anfang von /etc/nginx/nginx.conf hinzufügen:

load_module modules/ngx_http_combined_upstreams_module.so;

Dieses Dokument beschreibt nginx-module-combined-upstreams v2.3.1 veröffentlicht am 15. April 2025.


Das Modul führt drei Direktiven add_upstream, combine_server_singlets und extend_single_peers ein, die innerhalb von Upstream-Konfigurationsblöcken verfügbar sind, sowie einen neuen Konfigurationsblock upstrand zum Erstellen von Super-Schichten von Upstreams. Zusätzlich wird die Direktive dynamic_upstrand eingeführt, um Upstrands zur Laufzeit auszuwählen.

Direktive add_upstream

Füllt den Host-Upstream mit Servern, die in einem bereits definierten Upstream aufgeführt sind, der durch den obligatorischen 1. Parameter der Direktive angegeben ist. Die Serverattribute wie Gewichte, max_fails und andere werden im Host-Upstream beibehalten. Optionale Parameter können Werte backup enthalten, um alle Server des Quell-Upstreams als Backup-Server zu kennzeichnen, und weight=N, um die Gewichte der Server des Quell-Upstreams durch Multiplikation mit dem Faktor N zu kalibrieren.

Ein Beispiel

upstream  combined {
    add_upstream    upstream1;            # src upstream 1
    add_upstream    upstream2 weight=2;   # src upstream 2
    server          some_another_server;  # falls benötigt
    add_upstream    upstream3 backup;     # src upstream 3
}

Direktive combine_server_singlets

Erzeugt mehrere singlet upstreams aus Servern, die bisher im Host-Upstream definiert sind. Ein Singlet-Upstream enthält nur einen aktiven Server, während andere Server als Backup oder down markiert sind. Wenn keine Parameter übergeben wurden, haben die Singlet-Upstreams die Namen des Host-Upstreams, gefolgt von der Ordnungsnummer des aktiven Servers im Host-Upstream. Zwei optionale Parameter können verwendet werden, um ihre Namen anzupassen. Der 1. Parameter ist ein Suffix, das nach dem Namen des Host-Upstreams und vor der Ordnungsnummer hinzugefügt wird. Der 2. Parameter muss ein ganzzahliger Wert sein, der die Null-Ausrichtung der Ordnungsnummer definiert. Wenn er beispielsweise den Wert 2 hat, könnten die Ordnungsnummern '01', '02', ..., '10', ... '100' ....

Um sekundäre Server als down und nicht als Backup zu kennzeichnen, verwenden Sie einen weiteren optionalen Parameter nobackup. Dieser Parameter muss am Ende, nach allen anderen Parametern, platziert werden.

Ein Beispiel

upstream  uhost {
    server                   s1;
    server                   s2;
    server                   s3 backup;
    server                   s4;
    # erstellen Sie Singlet-Upstreams uhost_single_01,
    # uhost_single_02, uhost_single_03 und uhost_single_04
    combine_server_singlets  _single_ 2;
    server                   s5;
}

Warum Zahlen, nicht Namen?

Im obigen Beispiel haben Singlet-Upstreams Namen wie uhost_single_01, aber Namen, die Servernamen wie uhost_single_s1 enthalten, würden besser und bequemer aussehen. Warum nicht diese anstelle von Ordnungszahlen verwenden? Leider erinnert sich NGINX nicht an Servernamen, nachdem ein Server in einen Upstream hinzugefügt wurde, daher können wir sie nicht einfach abrufen.

Update. Es gibt gute Nachrichten! Seit Version 1.7.2 erinnert sich NGINX an Servernamen in Upstream-Daten, und jetzt können wir sie verwenden, wenn wir auf ein spezielles Schlüsselwort byname verweisen. Zum Beispiel,

    combine_server_singlets  byname;
    # oder
    combine_server_singlets  _single_ byname;

Alle Doppelpunkte (:) in den Servernamen werden durch Unterstriche (_) ersetzt.

Wo kann das nützlich sein

Ein Singlet-Upstream fungiert wie ein einzelner Server im Fallback-Modus. Dies kann verwendet werden, um sticky HTTP-Sitzungen zu verwalten, wenn Backend-Server sich mit einem geeigneten Mechanismus wie HTTP-Cookies identifizieren.

upstream  uhost {
    server  s1;
    server  s2;
    combine_server_singlets;
}

server {
    listen       8010;
    server_name  main;
    location / {
        proxy_pass http://uhost$cookie_rt;
    }
}
server {
    listen       8020;
    server_name  server1;
    location / {
        add_header Set-Cookie "rt=1";
        echo "Passed to $server_name";
    }
}
server {
    listen       8030;
    server_name  server2;
    location / {
        add_header Set-Cookie "rt=2";
        echo "Passed to $server_name";
    }
}

In dieser Konfiguration wird die erste Client-Anfrage zufällig einen Backend-Server auswählen, der gewählte Server wird das Cookie rt auf einen vordefinierten Wert (1 oder 2) setzen, und alle weiteren Anfragen von diesem Client werden automatisch an den gewählten Server weitergeleitet, bis er ausfällt. Angenommen, es war server1, dann wird, wenn er ausfällt, das Cookie rt auf der Client-Seite weiterhin 1 sein. Die Direktive proxy_pass wird die nächste Client-Anfrage an einen Singlet-Upstream uhost1 weiterleiten, wo server1 als aktiv erklärt ist und server2 als Backup. Sobald server1 nicht mehr erreichbar ist, wird NGINX die Anfrage an server2 weiterleiten, der das Cookie rt umschreibt und alle weiteren Client-Anfragen an server2 weitergeleitet werden, bis er ausfällt.

Direktive extend_single_peers

Peers in Upstreams fallen gemäß den in der Direktive proxy_next_upstream aufgeführten Regeln aus. Wenn ein Upstream nur einen Peer in seinem Haupt- oder Backup-Teil hat, wird dieser Peer niemals ausfallen. Dies kann ein ernstes Problem darstellen, wenn man einen benutzerdefinierten Algorithmus für aktive Gesundheitsprüfungen von Upstream-Peers schreibt. Die Direktive extend_single_peers, die in einem Upstream-Block deklariert wird, fügt einen gefälschten Peer hinzu, der als down im Haupt- oder Backup-Teil des Upstreams markiert ist, wenn der Teil ursprünglich nur einen Peer enthält. Dies bewirkt, dass NGINX den ursprünglichen einzelnen Peer als ausgefallen markiert, wenn er die Regeln von proxy_next_upstream nicht besteht, genau wie im allgemeinen Fall mehrerer Peers.

Ein Beispiel

upstream  upstream1 {
    server  s1;
    extend_single_peers;
}

upstream  upstream2 {
    server  s1;
    server  s2;
    server  s3 backup;
    extend_single_peers;
}

Beachten Sie, dass, wenn ein Teil (der Haupt- oder der Backup-Teil) eines Upstreams mehr als einen Peer enthält (wie der Hauptteil in upstream2 aus dem Beispiel), die Direktive keine Wirkung hat: Insbesondere betrifft sie in upstream2 nur den Backup-Teil des Upstreams.

Block upstrand

Zielt darauf ab, eine Super-Schicht von Upstreams zu konfigurieren, die ihre Identitäten nicht verlieren. Akzeptiert eine Reihe von Direktiven, einschließlich upstream, order, next_upstream_statuses und anderen. Upstreams mit Namen, die mit einem Tilde (~) beginnen, entsprechen einem regulären Ausdruck. Nur Upstreams, die bereits vor der Definition des Upstrand-Blocks deklariert wurden, werden als Kandidaten betrachtet.

Ein Beispiel

upstrand us1 {
    upstream ~^u0 blacklist_interval=60s;
    upstream b01 backup;
    order start_random;
    next_upstream_statuses error timeout non_idempotent 204 5xx;
    next_upstream_timeout 60s;
    intercept_statuses 5xx /Internal/failover;
}

Der Upstrand us1 wird alle Upstreams kombinieren, deren Namen mit u0 beginnen, und den Upstream b01 als Backup. Backup-Upstreams werden überprüft, wenn alle normalen Upstreams ausfallen. Der Ausfall bedeutet, dass alle Upstreams in normalen oder Backup-Zyklen mit Status geantwortet haben, die in der Direktive next_upstream_statuses aufgeführt sind, oder blacklisted wurden. Hier bedeutet die Antwort des Upstreams den Status, der vom letzten Server des Upstreams zurückgegeben wird, der stark von dem Wert der Direktive proxy_next_upstream beeinflusst wird. Ein Upstream wird als blacklisted festgelegt, wenn er den Parameter blacklist_interval hat und mit einem Status antwortet, der in den next_upstream_statuses aufgeführt ist. Der Blacklisting-Zustand wird nicht zwischen NGINX-Worker-Prozessen geteilt.

Die nächsten vier Upstrand-Direktiven sind ähnlich wie die aus dem NGINX-Proxy-Modul.

Die Direktive next_upstream_statuses akzeptiert die Notation für 4xx und 5xx Status sowie die Werte error und timeout, um zwischen Fällen zu unterscheiden, in denen Fehler bei den Verbindungen zu den Upstream-Peers auftreten, und solchen, in denen Backends mit den Status 502 oder 504 antworten (einfache Werte 502 und 504 sowie 5xx beziehen sich auf beide Fälle). Sie akzeptiert auch den Wert non_idempotent, um eine weitere Verarbeitung von non-idempotent Anfragen zu ermöglichen, wenn sie vom letzten Server eines Upstreams beantwortet wurden, aber gemäß anderen in der Direktive aufgeführten Status fehlgeschlagen sind. Anfragen gelten als non-idempotent, wenn ihre Methoden POST, LOCK oder PATCH sind, genau wie in der Direktive proxy_next_upstream.

Die Direktive next_upstream_timeout begrenzt die Gesamtdauer, die der Upstrand benötigt, um alle seine Upstreams zu durchlaufen. Wenn die Zeit abläuft, während der Upstrand bereit ist, zu einem nächsten Upstream überzugehen, wird das Ergebnis des letzten Upstream-Zyklus zurückgegeben.

Die Direktive intercept_statuses ermöglicht upstrand failover, indem sie die endgültige Antwort an einem Standort abfängt, der mit der angegebenen URI übereinstimmt. Abfangungen müssen auch dann erfolgen, wenn der Upstrand zeitlich abläuft. Beachten Sie auch, dass das Durchlaufen von Upstreams in einem Upstrand und die URI für das Upstrand-Failover nicht abfangbar sind. Allgemeiner gesagt, wird jede interne Umleitung (durch error_page, proxy_intercept_errors, X-Accel-Redirect usw.) die geschachtelten Subanfragen brechen, auf denen die Implementierung des Upstrands basiert, was zu leeren Antworten führt. Dies sind äußerst schlechte Fälle, und genau deshalb wurde das Durchlaufen von Upstreams gegen Abfangungen geschützt. Die URI für das Upstrand-Failover ist davon stärker betroffen, da die Implementierung weniger Kontrolle über ihren Standort hat. Insbesondere hat das Upstrand-Failover nur Schutz gegen Abfangungen durch error_page und proxy_intercept_errors. Das bedeutet, dass der Standort der URI für das Upstrand-Failover so einfach wie möglich sein muss (z. B. durch Verwendung einfacher Direktiven wie return oder echo).

Das gesagt, gibt es eine anständige Lösung für das Problem mit Upstrand-Failover-Standorten und internen Umleitungen darin. Wie genau brechen interne Umleitungen Subanfragen? Nun, sie löschen die Subanfrage-Kontexte, die in den Antwortfiltern des Moduls benötigt werden. Wenn wir also den Subanfrage-Kontext persistent machen könnten, würden wir das Problem lösen? Die Antwort ist ja! Das NGINX-Modul nginx-easy-context ermöglicht den Aufbau persistenter Anfragekontexte. Upstrands können davon profitieren, indem sie einen Schalter in der Datei config aktivieren und beide Module bauen. Siehe Details im Abschnitt Build and test.

Die Direktive order akzeptiert derzeit nur einen Wert start_random, was bedeutet, dass die Start-Upstreams in normalen und Backup-Zyklen nach dem Start des Workers zufällig ausgewählt werden. Die Start-Upstreams in weiteren Anfragen werden im Round-Robin-Verfahren durchlaufen. Zusätzlich wird ein Modifikator per_request auch in der Direktive order akzeptiert: Er schaltet den globalen Round-Robin-Zyklus pro Worker aus. Die Kombination von per_request und start_random bewirkt, dass der Start-Upstream in jeder neuen Anfrage zufällig ausgewählt wird.

Ein solches Failover zwischen Ausfall-Status kann während einer einzelnen Anfrage erreicht werden, indem eine spezielle Variable, die mit upstrand_ beginnt, an die Direktive proxy_pass übergeben wird:

location /us1 {
    proxy_pass http://$upstrand_us1;
}

Seien Sie vorsichtig, wenn Sie auf diese Variable aus anderen Direktiven zugreifen! Sie startet die Subanfrage-Mechanik, was in vielen Fällen möglicherweise nicht wünschenswert ist.

Upstrand-Statusvariablen

Es gibt eine Reihe von Upstrand-Statusvariablen: upstrand_addr, upstrand_cache_status, upstrand_connect_time, upstrand_header_time, upstrand_response_length, upstrand_response_time und upstrand_status. Sie sind alle Gegenstücke zu den entsprechenden upstream-Variablen und enthalten die Werte letzterer für alle Upstreams, die während einer Anfrage und aller Subanfragen chronologisch durchlaufen wurden. Die Variable upstrand_path enthält den Pfad aller Upstreams, die während der Anfrage besucht wurden.

Wo kann das nützlich sein

Der upstrand sieht sehr ähnlich aus wie ein einfacher kombinierter Upstream, hat jedoch einen entscheidenden Unterschied: Die Upstreams innerhalb eines Upstrands werden nicht abgeflacht und behalten ihre Identitäten. Dies ermöglicht es, einen Failover-Status für eine Gruppe von Servern zu konfigurieren, die mit einem einzelnen Upstream verbunden sind, ohne sie alle nacheinander überprüfen zu müssen. Im obigen Beispiel kann der Upstrand us1 eine Liste von Upstreams wie u01, u02 usw. enthalten. Stellen Sie sich vor, dass der Upstream u01 10 Server enthält und einen Teil eines geografisch verteilten Backend-Systems darstellt. Lassen Sie den Upstrand us1 all diese Teile zu einem Ganzen kombinieren, und lassen Sie uns eine Client-Anwendung ausführen, die die Teile abfragt, um einige Aufgaben zu erledigen. Lassen Sie die Backends den HTTP-Status 204 senden, wenn sie keine neuen Aufgaben haben. In einem flachen kombinierten Upstream hätten alle 10 Server abgefragt werden müssen, bevor die Anwendung schließlich eine neue Aufgabe von einem anderen Upstream erhält. Der Upstrand us1 ermöglicht es, zum nächsten Upstream überzugehen, nachdem der erste Server in einem Upstream überprüft wurde, der keine Aufgaben hat. Diese Mechanik eignet sich offensichtlich für Upstream-Broadcasting, wenn Nachrichten an alle Upstreams in einem Upstrand gesendet werden.

Die obigen Beispiele zeigen, dass ein Upstrand als 2-dimensionale Upstream betrachtet werden kann, die eine Anzahl von Clustern umfasst, die natürliche Upstreams repräsentieren und ein kurzes Durchlaufen über sie ermöglichen.

Um dies zu veranschaulichen, lassen Sie uns einen Upstream ohne Round-Robin-Balancing emulieren. Jede neue Client-Anfrage beginnt mit der Proxyierung zum ersten Server in der Upstream-Liste und wechselt dann zum nächsten Server.

    upstream u1 {
        server localhost:8020;
        server localhost:8030;
        combine_server_singlets _single_ nobackup;
    }

    upstrand us1 {
        upstream ~^u1_single_ blacklist_interval=60s;
        order per_request;
        next_upstream_statuses error timeout non_idempotent 5xx;
        intercept_statuses 5xx /Internal/failover;
    }

Die Direktive combine_server_singlets im Upstream u1 erzeugt zwei Singlet- Upstreams u1_single_1 und u1_single_2, die im Upstrand us1 wohnen. Aufgrund der per_request-Reihenfolge innerhalb des Upstrands werden die beiden Upstreams in der Reihenfolge u1_single_1 → u1_single_2 in jeder Client-Anfrage durchlaufen.

Direktive dynamic_upstrand

Erlaubt die Auswahl eines Upstrands aus übergebenen Variablen zur Laufzeit. Die Direktive kann in Server-, Standort- und location-if-Klauseln gesetzt werden.

In der folgenden Konfiguration

    upstrand us1 {
        upstream ~^u0;
        upstream b01 backup;
        order start_random;
        next_upstream_statuses 5xx;
    }
    upstrand us2 {
        upstream ~^u0;
        upstream b02 backup;
        order start_random;
        next_upstream_statuses 5xx;
    }

    server {
        listen       8010;
        server_name  main;

        dynamic_upstrand $dus1 $arg_a us2;

        location / {
            dynamic_upstrand $dus2 $arg_b;
            if ($arg_b) {
                proxy_pass http://$dus2;
                break;
            }
            proxy_pass http://$dus1;
        }
    }

Die Upstrands, die in den Variablen dus1 und dus2 zurückgegeben werden, sollen aus den Werten der Variablen arg_a und arg_b ausgewählt werden. Wenn arg_b gesetzt ist, wird die Client-Anfrage an einen Upstrand mit dem Namen gesendet, der dem Wert von arg_b entspricht. Wenn es keinen Upstrand mit diesem Namen gibt, wird dus2 leer sein und proxy_pass wird den HTTP-Status 500 zurückgeben. Um die Initialisierung einer dynamischen Upstrand-Variablen mit einem leeren Wert zu verhindern, muss ihre Deklaration mit einem literalen Namen beendet werden, der einem vorhandenen Upstrand entspricht. In diesem Beispiel wird die dynamische Upstrand-Variable dus1 durch den Upstrand us2 initialisiert, wenn arg_a leer oder nicht gesetzt ist. Insgesamt, wenn arg_b nicht gesetzt oder leer ist und arg_a gesetzt ist und einen Wert hat, der einem vorhandenen Upstrand entspricht, wird die Anfrage an diesen Upstrand gesendet, andernfalls (wenn arg_b nicht gesetzt oder leer ist und arg_a gesetzt ist, aber nicht auf einen vorhandenen Upstrand verweist) wird proxy_pass höchstwahrscheinlich den HTTP-Status 500 zurückgeben (es sei denn, es gibt eine Variable, die aus dem literalen String upstrand_ und dem Wert von arg_a zusammengesetzt ist, die auf ein gültiges Ziel verweist), andernfalls (wenn sowohl arg_b als auch arg_a nicht gesetzt oder leer sind) wird die Anfrage an den Upstrand us2 gesendet.

Siehe auch

Es gibt mehrere Artikel über das Modul in meinem Blog, in chronologischer Reihenfolge:

  1. Простой модуль nginx для создания комбинированных апстримов (auf Russisch). Ein umfassender Artikel, der Details zur Implementierung der Direktive add_upstream entdeckt, die auch als kleines Tutorial für die Entwicklung von NGINX-Modulen angesehen werden kann.
  2. nginx upstrand to configure super-layers of upstreams. Ein Überblick über die Nutzung des Blocks upstrand und einige Details zu seiner Implementierung.
  3. Не такой уж простой модуль nginx для создания комбинированных апстримов (auf Russisch). Ein Überblick über alle Funktionen des Moduls mit Konfigurationsbeispielen und Test-Sessions.

GitHub

Sie finden zusätzliche Konfigurationstipps und Dokumentation für dieses Modul im GitHub Repository für nginx-module-combined-upstreams.