Kenntnisse über NGINX in OpenResty

API7.ai

September 17, 2022

OpenResty (NGINX + Lua)

Durch den vorherigen Beitrag haben Sie ein allgemeines Verständnis von OpenResty. In den folgenden Artikeln werde ich Sie durch die beiden Grundpfeiler von OpenResty führen: NGINX und LuaJIT. Indem Sie diese Grundlagen beherrschen, können Sie OpenResty besser verstehen.

Heute beginne ich mit NGINX, und hier werde ich nur einige NGINX-Grundlagen vorstellen, die in OpenResty verwendet werden könnten, was nur ein winziger Teil von NGINX ist.

In Bezug auf die Konfiguration müssen wir bei der OpenResty-Entwicklung auf folgende Punkte achten:

  • Die Konfiguration von nginx.conf sollte so minimal wie möglich gehalten werden.
  • Vermeiden Sie die Kombination mehrerer Anweisungen wie if, set, rewrite usw.
  • Verwenden Sie keine NGINX-Konfiguration, Variablen und Module, wenn Sie das Problem mit Lua-Code lösen können.

Diese Methoden maximieren die Lesbarkeit, Wartbarkeit und Erweiterbarkeit. Die folgende NGINX-Konfiguration ist ein typisches schlechtes Beispiel für die Verwendung von Konfiguration als Code.

location ~ ^/mobile/(web/app.htm) {
    set $type $1;
    set $orig_args $args;
    if ( $http_user_Agent ~ "(iPhone|iPad|Android)" ) {
        rewrite  ^/mobile/(.*) http://touch.foo.com/mobile/$1 last;
    }
    proxy_pass http://foo.com/$type?$orig_args;
}

Dies ist, was wir bei der Entwicklung mit OpenResty vermeiden müssen.

NGINX-Konfiguration

NGINX steuert sein Verhalten über Konfigurationsdateien, die als einfache DSL betrachtet werden können. NGINX liest die Konfiguration beim Start des Prozesses und lädt sie in den Speicher. Wenn Sie die Konfigurationsdatei ändern, müssen Sie NGINX neu starten oder neu laden und warten, bis NGINX die Konfigurationsdatei erneut liest, damit die neue Konfiguration wirksam wird. Nur die kommerzielle Version von NGINX bietet einige dieser dynamischen Fähigkeiten zur Laufzeit in Form von APIs.

Beginnen wir mit der folgenden Konfiguration, die sehr einfach ist.

worker_processes auto;

pid logs/nginx.pid;
error_log logs/error.log notice;

worker_rlimit_nofile 65535;

events {
    worker_connections 16384;
}

http {
    server {
        listen 80;
        listen 443 ssl;

        location / {
            proxy_pass https://foo.com;
        }
    }
}

stream {
    server {
        listen 53 udp;
    }
}

Allerdings beinhalten selbst diese einfachen Konfigurationen einige grundlegende Konzepte.

Zunächst hat jede Direktive ihren Kontext, der ihren Gültigkeitsbereich in der NGINX-Konfigurationsdatei darstellt.

Die oberste Ebene ist main, die einige Anweisungen enthält, die nichts mit dem spezifischen Geschäft zu tun haben, wie worker_processes, pid und error_log, die alle Teil des main-Kontexts sind. Darüber hinaus gibt es eine hierarchische Beziehung zwischen den Kontexten. Zum Beispiel ist der Kontext von location server, der Kontext von server ist http, und der Kontext von http ist main.

Direktiven können nicht im falschen Kontext ausgeführt werden. NGINX überprüft, ob nginx.conf beim Start legal ist. Wenn wir beispielsweise listen 80; aus dem server-Kontext in den main-Kontext verschieben und den NGINX-Dienst starten, erhalten wir einen Fehler wie diesen:

"listen" directive is not allowed here ......

Zweitens kann NGINX nicht nur HTTP-Anfragen und HTTPS-Datenverkehr verarbeiten, sondern auch UDP- und TCP-Datenverkehr. L7 befindet sich in HTTP und L4 im Stream. In OpenResty entsprechen lua-nginx-module und stream-lua-nginx-module diesen beiden.

Hier ist zu beachten, dass OpenResty nicht alle Funktionen in NGINX unterstützt, und Sie müssen die Version von OpenResty beachten. Die Version von OpenResty ist mit NGINX konsistent, was die Identifizierung erleichtert.

Die Konfigurationsdirektiven, die in nginx.conf oben beteiligt sind, befinden sich in den NGINX-Kernmodulen ngx_core_module, ngx_http_core_module und ngx_stream_core_module, auf die Sie klicken können, um die spezifische Dokumentation zu sehen.

MASTER-WORKER-Modus

Nachdem wir die Konfigurationsdatei verstanden haben, schauen wir uns den Multi-Prozess-Modus von NGINX an (wie in der folgenden Abbildung gezeigt). Wie Sie sehen können, gibt es beim Start von NGINX einen Master-Prozess und mehrere Worker-Prozesse (oder nur einen Worker-Prozess, abhängig von Ihrer Konfiguration).

NGINX Worker Mode

Zunächst spielt der Master-Prozess, wie der Name schon sagt, die Rolle des "Managers" und ist nicht für die Bearbeitung von Anfragen von Clients verantwortlich. Er verwaltet den Worker-Prozess, einschließlich des Empfangs von Signalen vom Administrator und der Überwachung des Status der Worker. Wenn ein Worker-Prozess abnormal beendet wird, startet der Master-Prozess einen neuen Worker-Prozess.

Worker-Prozesse sind die "echten arbeitenden Mitarbeiter", die Anfragen von Clients bearbeiten. Sie werden vom Master-Prozess geforkt und sind voneinander unabhängig. Dieses Multi-Prozess-Modell ist viel fortschrittlicher als das Multi-Thread-Modell von Apache, ohne Cross-Thread-Locking und einfach zu debuggen. Selbst wenn ein Prozess abstürzt und beendet wird, beeinflusst dies normalerweise nicht die Arbeit der anderen Worker-Prozesse.

OpenResty fügt dem NGINX Master-Worker-Modell einen einzigartigen privilegierten Agenten hinzu. Dieser Prozess lauscht auf keinen Ports und hat die gleichen Privilegien wie der NGINX Master-Prozess, sodass er einige Aufgaben mit hohen Privilegien ausführen kann, wie z.B. einige Schreiboperationen auf lokale Festplattendateien.

Wenn der privilegierte Prozess mit dem NGINX-Binary-Hot-Upgrade-Mechanismus zusammenarbeitet, kann OpenResty das gesamte Binary selbstständig im laufenden Betrieb aktualisieren, ohne sich auf externe Programme zu verlassen.

Die Reduzierung der Abhängigkeit von externen Programmen und der Versuch, Probleme innerhalb des OpenResty-Prozesses zu lösen, erleichtert die Bereitstellung, reduziert die Betriebs- und Wartungskosten und verringert die Wahrscheinlichkeit von Programmfehlern. Der privilegierte Prozess und ngx.pipe in OpenResty dienen diesem Zweck.

Ausführungsphasen

Ausführungsphasen sind ebenfalls ein wesentliches Merkmal von NGINX und eng mit der spezifischen Implementierung von OpenResty verbunden. NGINX hat 11 Ausführungsphasen, die wir im Quellcode von ngx_http_core_module.h sehen können:

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0,

    NGX_HTTP_SERVER_REWRITE_PHASE,

    NGX_HTTP_FIND_CONFIG_PHASE,
    NGX_HTTP_REWRITE_PHASE,
    NGX_HTTP_POST_REWRITE_PHASE,

    NGX_HTTP_PREACCESS_PHASE,

    NGX_HTTP_ACCESS_PHASE,
    NGX_HTTP_POST_ACCESS_PHASE,

    NGX_HTTP_PRECONTENT_PHASE,

    NGX_HTTP_CONTENT_PHASE,

    NGX_HTTP_LOG_PHASE
} ngx_http_phases;

Wenn Sie mehr über die Rolle dieser 11 Phasen erfahren möchten, können Sie die NGINX-Dokumentation lesen, daher werde ich hier nicht näher darauf eingehen.

Zufälligerweise hat OpenResty auch 11 *_by_lua-Direktiven, die mit der NGINX-Phase zusammenhängen, wie in der folgenden Abbildung gezeigt (aus der lua-nginx-module-Dokumentation).

Order of Lua NGINX Module Directives

init_by_lua wird nur ausgeführt, wenn der Master-Prozess erstellt wird, und init_worker_by_lua wird nur ausgeführt, wenn jeder Worker-Prozess erstellt wird. Die anderen *_by_lua-Befehle werden durch Client-Anfragen ausgelöst und wiederholt ausgeführt.

Während der init_by_lua-Phase können wir Lua-Module und öffentliche schreibgeschützte Daten vorladen, um die COW-Funktion (Copy on Write) des Betriebssystems zu nutzen und Speicher zu sparen.

Die meisten Operationen können innerhalb von content_by_lua durchgeführt werden, aber ich würde empfehlen, sie nach verschiedenen Funktionen aufzuteilen, wie folgt:

  • set_by_lua: Variablen setzen.
  • rewrite_by_lua: Weiterleitung, Umleitung usw.
  • access_by_lua: Zugriff, Berechtigungen usw.
  • content_by_lua: Rückgabeinhalt generieren.
  • header_filter_by_lua: Antwortheader-Filterverarbeitung.
  • body_filter_by_lua: Antwortkörper-Filterverarbeitung.
  • log_by_lua: Protokollierung.

Lassen Sie mich ein Beispiel geben, um die Vorteile dieser Aufteilung zu zeigen. Nehmen wir an, dass viele Klartext-APIs extern bereitgestellt werden, und jetzt müssen wir eine benutzerdefinierte Verschlüsselungs- und Entschlüsselungslogik hinzufügen. Müssen wir also den Code aller APIs ändern?

location /mixed {
    content_by_lua '...';
}

Natürlich nicht. Mit der Phasenfunktion können wir in der access-Phase entschlüsseln und in der body filter-Phase verschlüsseln, ohne Änderungen am Code in der ursprünglichen content-Phase vorzunehmen.

location /mixed {
    access_by_lua '...';
    content_by_lua '...';
    body_filter_by_lua '...';
}

NGINX-Binary im laufenden Betrieb aktualisieren

Abschließend möchte ich kurz erklären, wie man das NGINX-Binary im laufenden Betrieb aktualisiert. Wir wissen, dass Sie nach der Änderung der NGINX-Konfigurationsdatei diese neu laden müssen, damit sie wirksam wird. Aber wenn NGINX sich selbst aktualisiert, kann es dies im laufenden Betrieb tun. Dies mag wie das Pferd vor den Karren gespannt erscheinen, ist aber verständlich, wenn man bedenkt, dass NGINX mit traditionellem statischem Load Balancing, Reverse Proxying und Dateicaching begann.

Das Hot-Upgrade erfolgt durch das Senden von USR2- und WINCH-Signalen an den alten Master-Prozess. Für diese beiden Schritte startet der erstere den neuen Master-Prozess; der letztere schaltet den Worker-Prozess schrittweise ab.

Nach diesen beiden Schritten sind der neue Master und der neue Worker gestartet. Zu diesem Zeitpunkt beendet sich der alte Master nicht. Der Grund dafür ist einfach: Wenn Sie zurückfallen müssen, können Sie immer noch HUP-Signale an den alten Master senden. Natürlich, wenn Sie sicher sind, dass Sie nicht zurückfallen müssen, können Sie ein KILL-Signal an den alten Master senden, um ihn zu beenden.

Das war's, und das NGINX-Binary wurde im laufenden Betrieb aktualisiert.

Wenn Sie detailliertere Informationen dazu erfahren möchten, können Sie die offizielle Dokumentation überprüfen, um weiter zu lernen.

Zusammenfassung

Im Allgemeinen verwenden Sie in OpenResty die Grundlagen von NGINX, hauptsächlich im Zusammenhang mit Konfiguration, Master-Slave-Prozessen, Ausführungsphasen usw. Die anderen Dinge, die mit Lua-Code gelöst werden können, sollten so weit wie möglich mit Code gelöst werden, anstatt NGINX-Module und Konfigurationen zu verwenden, was eine Denkweise beim Lernen von OpenResty ist.

Abschließend habe ich Ihnen eine offene Frage hinterlassen: Nginx unterstützt offiziell NJS, was bedeutet, dass Sie JS schreiben können, um einige der NGINX-Logiken zu steuern, ähnlich wie OpenResty. Was halten Sie davon? Willkommen, diesen Artikel zu teilen.