Zum Inhalt

exec: Externe Programme im nginx-module-lua ausführen, ohne eine Shell zu starten oder zu blockieren

Installation

Wenn Sie noch kein RPM-Repository-Abonnement eingerichtet haben, melden Sie sich an. Dann können Sie mit den folgenden Schritten fortfahren.

CentOS/RHEL 7 oder Amazon Linux 2

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 lua-resty-exec

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-exec

Um diese Lua-Bibliothek mit NGINX zu verwenden, stellen Sie sicher, dass nginx-module-lua installiert ist.

Dieses Dokument beschreibt lua-resty-exec v3.0.3, das am 22. August 2017 veröffentlicht wurde.


Ein kleines Lua-Modul zum Ausführen von Prozessen. Es ist hauptsächlich für die Verwendung mit OpenResty gedacht, funktioniert aber auch in regulären Lua-Anwendungen. Bei der Verwendung mit OpenResty ist es vollständig nicht blockierend (ansonsten fällt es auf die Verwendung von LuaSocket zurück und blockiert).

Es ist ähnlich wie (und inspiriert von) lua-resty-shell, der Hauptunterschied besteht darin, dass dieses Modul sockexec verwendet, das keine Shell startet - stattdessen geben Sie ein Array von Argumentzeichenfolgen an, was bedeutet, dass Sie sich keine Gedanken über Shell-Escaping/Anführungszeichen/Parsing-Regeln machen müssen.

Darüber hinaus können Sie ab Version 2.0.0 resty.exec.socket verwenden, um auf eine niedrigere Schnittstelle zuzugreifen, die eine bidirektionale Kommunikation mit Programmen ermöglicht. Sie können mit laufenden Anwendungen lesen und schreiben!

Dies erfordert, dass Ihr Webserver eine aktive Instanz von sockexec ausführt.

Changelog

  • 3.0.0
  • neues zurückgegebenes Feld: unknown - wenn dies passiert, senden Sie mir bitte einen Fehlerbericht!
  • 2.0.0
  • Neues resty.exec.socket-Modul zur Verwendung einer Duplexverbindung
  • resty.exec verwendet nicht mehr das Argument bufsize
  • resty.exec akzeptiert jetzt ein timeout-Argument, das in Millisekunden angegeben wird, standardmäßig 60s
  • Dies ist eine größere Überarbeitung, bitte testen Sie gründlich, bevor Sie ein Upgrade durchführen!
  • Kein Changelog vor 2.0.0

resty.exec Verwendung

local exec = require'resty.exec'
local prog = exec.new('/tmp/exec.sock')

Erstellt ein neues prog-Objekt, das /tmp/exec.sock für seine Verbindung zu sockexec verwendet.

Von dort aus können Sie prog auf verschiedene Arten verwenden:

ez-mode

local res, err = prog('uname')

-- res = { stdout = "Linux\n", stderr = nil, exitcode = 0, termsig = nil }
-- err = nil

ngx.print(res.stdout)

Dies führt uname aus, ohne Daten auf stdin.

Gibt eine Tabelle mit Ausgabe-/Fehlercodes zurück, wobei err auf alle aufgetretenen Fehler gesetzt wird.

argv vorher einrichten

prog.argv = { 'uname', '-a' }
local res, err = prog()

-- res = { stdout = "Linux localhost 3.10.18 #1 SMP Tue Aug 2 21:08:34 PDT 2016 x86_64 GNU/Linux\n", stderr = nil, exitcode = 0, termsig = nil }
-- err = nil

ngx.print(res.stdout)

stdin vorher einrichten

prog.stdin = 'das ist toll!'
local res, err = prog('cat')

-- res = { stdout = "das ist toll!", stderr = nil, exitcode = 0, termsig = nil }
-- err = nil

ngx.print(res.stdout)

Aufruf mit expliziten argv, stdin-Daten, stdout/stderr-Callbacks

local res, err = prog( {
    argv = 'cat',
    stdin = 'Spaß!',
    stdout = function(data) print(data) end,
    stderr = function(data) print("Fehler:", data) end
} )

-- res = { stdout = nil, stderr = nil, exitcode = 0, termsig = nil }
-- err = nil
-- 'Spaß!' wird ausgegeben

Hinweis: Hier ist argv eine Zeichenfolge, was in Ordnung ist, wenn Ihr Programm keine Argumente benötigt.

Einrichten von stdout/stderr-Callbacks

Wenn Sie prog.stdout oder prog.stderr auf eine Funktion setzen, wird diese für jeden Chunk von stdout/stderr-Daten aufgerufen, die empfangen werden.

Bitte beachten Sie, dass es keine Garantie dafür gibt, dass stdout/stderr eine vollständige Zeichenfolge oder etwas besonders Sinnvolles ist!

prog.stdout = function(data)
    ngx.print(data)
    ngx.flush(true)
end

local res, err = prog('some-program')

Zeitüberschreitungen als keine Fehler behandeln

Standardmäßig behandelt sockexec eine Zeitüberschreitung als Fehler. Sie können dies deaktivieren, indem Sie den Schlüssel timeout_fatal des Objekts auf false setzen. Beispiele:

-- setze timeout_fatal = false auf den prog-Objekten
prog.timeout_fatal = false

-- oder setze es zur Aufrufzeit:
local res, err = prog({argv = {'cat'}, timeout_fatal = false})

Aber ich möchte tatsächlich eine Shell!

Kein Problem! Sie können einfach so etwas tun:

local res, err = prog('bash','-c','echo $PATH')

Oder wenn Sie ein ganzes Skript ausführen möchten:

prog.stdin = script_data
local res, err = prog('bash')

-- dies ist ungefähr äquivalent zu `bash < script` im CLI

Prozesse daemonisieren

Ich empfehle im Allgemeinen, Prozesse nicht zu daemonisieren - ich denke, es ist viel besser, eine Art von Nachrichtenwarteschlange und/oder Überwachungssystem zu verwenden, damit Sie Prozesse überwachen, Maßnahmen bei Fehlern ergreifen usw.

Das gesagt, wenn Sie einen Prozess starten möchten, könnten Sie start-stop-daemon verwenden, z. B.:

local res, err = prog('start-stop-daemon','--pidfile','/dev/null','--background','--exec','/usr/bin/sleep', '--start','--','10')

wird sleep 10 als abgetrennter Hintergrundprozess starten.

Wenn Sie sich nicht mit start-stop-daemon auseinandersetzen möchten, habe ich ein kleines Dienstprogramm zum Starten eines Hintergrundprogramms namens idgaf, z. B.:

local res, err = prog('idgaf','sleep','10')

Dies wird im Grunde das gleiche erreichen, was start-stop-daemon tut, ohne eine Million Flags zu benötigen.

resty.exec.socket Verwendung

local exec_socket = require'resty.exec.socket'

-- Sie können die Zeitüberschreitung in Millisekunden angeben, optional
local client = exec_socket:new({ timeout = 60000 })

-- jede neue Programminstanz erfordert einen neuen
-- Aufruf zur Verbindung
local ok, err = client:connect('/tmp/exec.sock')

-- Programmargumente senden, akzeptiert nur eine Tabelle von
-- Argumenten
client:send_args({'cat'})

-- Daten für stdin senden
client:send('Hallo dort')

-- Daten empfangen
local data, typ, err = client:receive()

-- `typ` kann einer der folgenden sein:
--    `stdout`   - Daten von der stdout des Programms
--    `stderr`   - Daten von der stderr des Programms
--    `exitcode` - der Exit-Code des Programms
--    `termsig`  - falls über ein Signal beendet, welches Signal verwendet wurde

-- wenn `err` gesetzt ist, werden data und typ nil sein
-- häufige `err`-Werte sind `closed` und `timeout`
print(string.format('Empfangene %s-Daten: %s',typ,data))
-- wird 'Empfangene stdout-Daten: Hallo dort' ausgeben

client:send('hey dieser cat-Prozess läuft noch')
data, typ, err = client:receive()
print(string.format('Empfangene %s-Daten: %s',typ,data))
-- wird 'Empfangene stdout-Daten: hey dieser cat-Prozess läuft noch' ausgeben

client:send_close() -- schließt stdin
data, typ, err = client:receive()
print(string.format('Empfangene %s-Daten: %s',typ,data))
-- wird 'Empfangene exitcode-Daten: 0' ausgeben

data, typ, err = client:receive()
print(err) -- wird 'closed' ausgeben

client Objektmethoden:

  • ok, err = client:connect(path)

Stellt über einen Unix-Socket eine Verbindung zu dem angegebenen Pfad her. Wenn dies in nginx ausgeführt wird, wird der unix:-String automatisch vorangestellt.

  • bytes, err = client:send_args(args)

Sendet eine Tabelle von Argumenten an sockexec und startet das Programm.

  • bytes, err = client:send_data(data)

Sendet data an die Standard-Eingabe des Programms.

  • bytes, err = client:send(data)

Nur eine Abkürzung für client:send_data(data)

  • bytes, err = client:send_close()

Schließt die Standard-Eingabe des Programms. Sie können auch eine leere Zeichenfolge senden, wie client:send_data('')

  • data, typ, err = client:receive()

Empfängt Daten vom laufenden Prozess. typ gibt den Typ der Daten an, der stdout, stderr, termsig, exitcode sein kann.

err ist typischerweise entweder closed oder timeout.

  • client:close()

Schließt die Clientverbindung gewaltsam.

  • client:getfd()

Eine getfd-Methode, nützlich, wenn Sie die zugrunde liegende Socketverbindung in einer Select-Schleife überwachen möchten.

Einige Beispiel-nginx-Konfigurationen

Angenommen, Sie führen sockexec unter /tmp/exec.sock aus

$ sockexec /tmp/exec.sock

Dann in Ihrer nginx-Konfiguration:

location /uname-1 {
    content_by_lua_block {
        local prog = require'resty.exec'.new('/tmp/exec.sock')
        local data,err = prog('uname')
        if(err) then
            ngx.say(err)
        else
            ngx.say(data.stdout)
        end
    }
}
location /uname-2 {
    content_by_lua_block {
        local prog = require'resty.exec'.new('/tmp/exec.sock')
        prog.argv = { 'uname', '-a' }
        local data,err = prog()
        if(err) then
            ngx.say(err)
        else
            ngx.say(data.stdout)
        end
    }
}
location /cat-1 {
    content_by_lua_block {
        local prog = require'resty.exec'.new('/tmp/exec.sock')
        prog.stdin = 'das ist toll!'
        local data,err = prog('cat')
        if(err) then
            ngx.say(err)
        else
            ngx.say(data.stdout)
        end
    }
}
location /cat-2 {
    content_by_lua_block {
        local prog = require'resty.exec'.new('/tmp/exec.sock')
        local data,err = prog({argv = 'cat', stdin = 'toll'})
        if(err) then
            ngx.say(err)
        else
            ngx.say(data.stdout)
        end
    }
}
location /slow-print {
    content_by_lua_block {
        local prog = require'resty.exec'.new('/tmp/exec.sock')
        prog.stdout = function(v)
            ngx.print(v)
            ngx.flush(true)
        end
        prog('/usr/local/bin/slow-print')
    }
    # schauen Sie im `/misc` dieses Repos nach `slow-print`
}
location /shell {
    content_by_lua_block {
        local prog = require'resty.exec'.new('/tmp/exec.sock')
        local data, err = prog('bash','-c','echo $PATH')
        if(err) then
            ngx.say(err)
        else
            ngx.say(data.stdout)
        end
    }
}

GitHub

Sie finden möglicherweise zusätzliche Konfigurationstipps und Dokumentationen für dieses Modul im GitHub-Repository für nginx-module-exec.