Wie man ein Apache APISIX Plugin von 0 auf 1 entwickelt?

Qi Guo

Qi Guo

February 16, 2022

Ecosystem

In den letzten Monaten haben Community-Nutzer viele Plugins zu Apache APISIX hinzugefügt, wodurch das Apache APISIX-Ökosystem bereichert wurde. Aus Sicht der Nutzer ist die Entstehung vielfältigerer Plugins sicherlich eine gute Sache, da sie mehr Erwartungen an ein Gateway erfüllen, das als „All-in-One“- und „Multifunktions“-Prozessor fungiert, zusätzlich zur Perfektionierung der hohen Leistung und niedrigen Latenz von Apache APISIX.

Keiner der Artikel im Apache APISIX-Blog scheint den Prozess der Plugin-Entwicklung im Detail zu behandeln. Schauen wir uns also den Prozess aus der Perspektive eines Plugin-Entwicklers an und sehen wir, wie ein Plugin entsteht!

Dieser Artikel dokumentiert den Prozess der Entwicklung des file-logger-Plugins durch einen Frontend-Entwickler ohne Backend-Erfahrung. Bevor wir uns mit den Details des Implementierungsprozesses befassen, werden wir kurz die Funktionalität von file-logger vorstellen.

Einführung des file-logger-Plugins

file-logger unterstützt die Generierung von benutzerdefinierten Log-Formaten mithilfe von Apache APISIX-Plugin-Metadaten. Benutzer können über das file-logger-Plugin Anfrage- und Antwortdaten im JSON-Format an Log-Dateien anhängen oder den Log-Datenstrom an einen bestimmten Ort senden.

Stellen Sie sich vor: Wenn wir das Zugriffslog einer Route überwachen, interessieren wir uns nicht nur für den Wert bestimmter Anfrage- und Antwortdaten, sondern möchten die Log-Daten auch in eine bestimmte Datei schreiben. Hier kommt das file-logger-Plugin ins Spiel, um diese Ziele zu erreichen.

how it works

Wir können file-logger verwenden, um Log-Daten in eine spezifische Log-Datei zu schreiben und so den Prozess der Überwachung und Fehlerbehebung zu vereinfachen.

Wie implementiert man ein Plugin?

Nach der Einführung der Funktionen von file-logger haben Sie ein besseres Verständnis für dieses Plugin. Im Folgenden wird detailliert erklärt, wie ich, ein Frontend-Entwickler ohne Server-seitige Erfahrung, das Plugin für Apache APISIX entwickle und die entsprechenden Tests dafür hinzufüge.

Bestätigung des Namens und der Priorität des Plugins

Öffnen Sie den Apache APISIX Plugin Development Guide und bestimmen Sie der Reihenfolge nach die folgenden zwei Dinge:

  1. Bestimmen Sie die Plugin-Kategorie.
  2. Priorisieren Sie die Plugins und aktualisieren Sie die Datei conf/config-default.yaml.

Da es sich bei der Entwicklung von file-logger um ein Logging-Plugin handelt, beziehe ich mich auf den Namen und die Reihenfolge der vorhandenen Logging-Plugins für Apache APISIX und platziere file-logger hier.

file-logger's position

Nach Rücksprache mit anderen Plugin-Autoren und engagierten Mitgliedern der Community wurden schließlich der Name file-logger und die Priorität 399 des Plugins bestätigt.

Beachten Sie, dass die Priorität des Plugins mit der Ausführungsreihenfolge zusammenhängt; je höher der Wert der Priorität, desto weiter vorne wird es ausgeführt. Die Reihenfolge der Plugin-Namen steht nicht in Zusammenhang mit der Ausführungsreihenfolge.

Erstellen einer minimal ausführbaren Plugin-Datei

Nach der Bestätigung des Plugin-Namens und der Priorität können wir unsere Plugin-Code-Datei im Verzeichnis apisix/plugins/ erstellen. Hier sind zwei Punkte zu beachten:

  • Wenn die Plugin-Code-Datei direkt im Verzeichnis apisix/plugins/ erstellt wird, muss die Datei Makefile nicht geändert werden.
  • Wenn Ihr Plugin ein eigenes Code-Verzeichnis hat, müssen Sie die Datei Makefile aktualisieren. Bitte lesen Sie den Apache APISIX Plugin Development Guide für detaillierte Schritte.
  1. Hier erstellen wir die Datei file-logger.lua im Verzeichnis apisix/plugins/.
  2. Dann werden wir eine initialisierte Version basierend auf dem example-plugin erstellen.
-- Einführung der benötigten Module im Header
local log_util     =   require("apisix.utils.log-util")
local core         =   require("apisix.core")
local plugin       =   require("apisix.plugin")
local ngx          =   ngx

-- Deklaration des Plugin-Namens
local plugin_name = "file-logger"

-- Definition des Plugin-Schema-Formats
local schema = {
    type = "object",
    properties = {
        path = {
            type = "string"
        },
    },
    required = {"path"}
}

-- Plugin-Metadaten-Schema
local metadata_schema = {
    type = "object",
    properties = {
        log_format = log_util.metadata_schema_log_format
    }
}

local _M = {
    version = 0.1,
    priority = 399,
    name = plugin_name,
    schema = schema,
    metadata_schema = metadata_schema
}

-- Überprüfen, ob die Plugin-Konfiguration korrekt ist
function _M.check_schema(conf, schema_type)
    if schema_type == core.schema.TYPE_METADATA then
        return core.schema.check(metadata_schema, conf)
    end
    return core.schema.check(schema, conf)
end

-- Log-Phase
function _M.log(conf, ctx)
    core.log.warn("conf: ", core.json.encode(conf))
    core.log.warn("ctx: ", core.json.encode(ctx, true))
end

return _M

Sobald die minimal verfügbare Plugin-Datei fertig ist, können die Konfigurationsdaten des Plugins und die anfragebezogenen Daten über core.log.warn(core.json.encode(conf)) und core.log.warn("ctx: ", core.json.encode(ctx, true)) in die Datei error.log ausgegeben werden.

Aktivieren und Testen des Plugins

Die folgenden Schritte dienen dem Testen. Um zu testen, ob das Plugin die Plugin-Daten und die anfragebezogenen Daten, die wir dafür konfiguriert haben, erfolgreich in die Fehlerprotokolldatei ausgeben kann, müssen wir das Plugin aktivieren und eine Testroute erstellen.

  1. Bereiten Sie lokal einen Test-Upstream vor (der in diesem Artikel verwendete Test-Upstream ist 127.0.0.1:3030/api/hello, den ich lokal erstellt habe).

  2. Erstellen Sie eine Route über den curl-Befehl und aktivieren Sie unser neues Plugin.

    curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
    {
    "plugins": {
        "file-logger": {
        "path": "logs/file.log"
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
        "127.0.0.1:3030": 1
        }
    },
    "uri": "/api/hello"
    }'
    

    Sie werden dann einen Statuscode 200 sehen, was bedeutet, dass die Route erfolgreich erstellt wurde.

  3. Führen Sie den curl-Befehl aus, um eine Anfrage an die Route zu senden und zu testen, ob das file-logger-Plugin gestartet wurde.

    curl -i http://127.0.0.1:9080/api/hello
    HTTP/1.1 200 OK
    ...
    hello, world
    
  4. In der Datei logs/error.log wird ein Eintrag vorhanden sein:

    record in logs/error.log

    Wie Sie sehen können, wurde der path: logs/file.log, den wir für das Plugin im conf-Parameter konfiguriert haben, erfolgreich gespeichert. An diesem Punkt haben wir erfolgreich ein minimal verwendbares Plugin erstellt, das die conf- und ctx-Parameter in der Log-Phase ausgibt.

    Danach können wir die Kernfunktionalität für das file-logger.lua-Plugin direkt in seiner Code-Datei schreiben. Hier können wir den Befehl apisix reload ausführen, um den neuesten Plugin-Code neu zu laden, ohne Apache APISIX neu zu starten.

Schreiben der Kernfunktion für das file-logger-Plugin

Die Hauptfunktion des file-logger-Plugins besteht darin, Log-Daten zu schreiben. Nachdem ich andere Mitglieder der Community gefragt und Informationen recherchiert habe, habe ich die Lua-IO-Bibliothek kennengelernt und bestätigt, dass die Logik der Plugin-Funktion in etwa die folgenden Schritte umfasst.

  1. Nach jeder akzeptierten Anfrage werden die Log-Daten in den durch das Plugin konfigurierten path ausgegeben.

    1. Zuerst wird der Wert von path in file-logger über conf in der Log-Phase abgerufen.
    2. Dann wird die Lua-IO-Bibliothek verwendet, um die Datei zu erstellen, zu öffnen, zu schreiben, den Cache zu aktualisieren und die Datei zu schließen.
  2. Fehler wie das Öffnen oder Erstellen der Datei werden behandelt.

    local function write_file_data(conf, log_message)
        local msg, err = core.json.encode(log_message)
        if err then
            return core.log.error("message json serialization failed, error info : ", err)
        end
    
        local file, err = io_open(conf.path, 'a+')
    
        if not file then
            core.log.error("failed to open file: ", conf.path, ", error info: ", err)
        else
            local ok, err = file:write(msg, '\n')
            if not ok then
                core.log.error("failed to write file: ", conf.path, ", error info: ", err)
            else
                file:flush()
            end
            file:close()
        end
    end
    
  3. Unter Bezugnahme auf den Quellcode des http-logger-Plugins habe ich die Methode zum Übergeben der Log-Daten an die Schreiblog-Daten und einige Überprüfungen und Verarbeitungen der Metadaten abgeschlossen.

    function _M.log(conf, ctx)
        local metadata = plugin.plugin_metadata(plugin_name)
        local entry
    
        if metadata and metadata.value.log_format
            and core.table.nkeys(metadata.value.log_format) > 0
        then
            entry = log_util.get_custom_format_log(ctx, metadata.value.log_format)
        else
            entry = log_util.get_full_log(ngx, conf)
        end
    
        write_file_data(conf, entry)
    end
    

Validierung und Hinzufügen von Tests

Validierung der Log-Einträge

Da das file-logger-Plugin beim Erstellen der Testroute aktiviert und der Pfad als logs/file.log konfiguriert wurde, können wir einfach eine Anfrage an die Testroute senden, um die Ergebnisse der Log-Erfassung zu überprüfen.

curl -i http://127.0.0.1:9080/api/hello

In der entsprechenden Datei logs/file.log können wir sehen, dass jeder Eintrag im JSON-Format gespeichert ist. Nach der Formatierung eines der Daten sieht es so aus.

{
  "server": {
    "hostname": "....",
    "version": "2.11.0"
  },
  "client_ip": "127.0.0.1",
  "upstream": "127.0.0.1:3030",
  "route_id": "1",
  "start_time": 1641285122961,
  "latency": 13.999938964844,
  "response": {
    "status": 200,
    "size": 252,
    "headers": {
      "server": "APISIX/2.11.0",
      "content-type": "application/json; charset=utf-8",
      "date": "Tue, 04 Jan 2022 08:32:02 GMT",
      "vary": "Accept-Encoding",
      "content-length": "19",
      "connection": "close",
      "etag": "\"13-5j0ZZR0tI549fSRsYxl8c9vAU78\""
    }
  },
  "service_id": "",
  "request": {
    "querystring": {},
    "size": 87,
    "method": "GET",
    "headers": {
      "host": "127.0.0.1:9080",
      "accept": "*/*",
      "user-agent": "curl/7.77.0"
    },
    "url": "http://127.0.0.1:9080/api/hello",
    "uri": "/api/hello"
  }
}

Damit ist die Validierung der Log-Erfassung abgeschlossen. Die Validierungsergebnisse zeigen, dass das Plugin erfolgreich gestartet wurde und die entsprechenden Daten zurückgegeben hat.

Hinzufügen weiterer Tests für das Plugin

Für den Teil des Codes add_block_preprocessor war ich zunächst verwirrt, da ich keine vorherige Erfahrung mit Perl hatte. Nach Recherchen habe ich die korrekte Verwendung verstanden: Wenn wir keine request-Assertions und no_error_log-Assertions im Datenabschnitt schreiben, dann ist die Standard-Assertion wie folgt.

--- request
GET /t
--- no_error_log
[error]

Nachdem ich einige andere Logging-Testdateien berücksichtigt habe, habe ich die Datei file-logger.t im Verzeichnis t/plugin/ erstellt.

Jede Testdatei ist durch **DATA** in einen Präambelabschnitt und einen Datenabschnitt unterteilt. Da es auf der offiziellen Website keine klare Klassifizierung von testbezogenen Dokumenten gibt, können Sie sich auf die am Ende des Artikels aufgeführten Materialien beziehen. Hier ist einer der Testfälle, den ich nach der Berücksichtigung der relevanten Materialien abgeschlossen habe.

use t::APISIX 'no_plan';

no_long_string();
no_root_location();

add_block_preprocessor(sub {
    my ($block) = @_;

    if (! $block->request) {
        $block->set_value("request", "GET /t");
    }

    if (! $block->no_error_log && ! $block->error_log) {
        $block->set_value("no_error_log", "[error]");
    }
});

run_tests;

__DATA__

=== TEST 1: sanity
--- config
    location /t {
        content_by_lua_block {
            local configs = {
                -- full configuration
                {
                    path = "file.log"
                },
                -- property "path" is required
                {
                    path = nil
                }
            }

            local plugin = require("apisix.plugins.file-logger")

            for i = 1, #configs do
                ok, err = plugin.check_schema(configs[i])
                if err then
                    ngx.say(err)
                else
                    ngx.say("done")
                end
            end
        }
    }
--- response_body_like
done
property "path" is required

Damit ist die Sitzung zum Hinzufügen von Plugin-Tests abgeschlossen.

Zusammenfassung

Das oben Genannte ist der gesamte Prozess der Implementierung eines Apache APISIX-Plugins von 0 als Neuling im Backend. Ich bin in der Tat auf viele Fallstricke bei der Entwicklung des Plugins gestoßen, aber glücklicherweise gibt es viele engagierte Kollegen in der Apache APISIX-Community, die mir geholfen haben, die Probleme zu lösen, was die Entwicklung und das Testen des file-logger-Plugins insgesamt relativ reibungslos gemacht hat. Wenn Sie an diesem Plugin interessiert sind oder die Details des Plugins sehen möchten, können Sie sich auf die offizielle Apache APISIX-Dokumentation beziehen.

Apache APISIX arbeitet derzeit auch an anderen Plugins, um mehr Integrationsdienste zu unterstützen. Wenn Sie interessiert sind, können Sie gerne eine Diskussion in den GitHub Discussions starten oder über die Mailingliste Kontakt aufnehmen.

Referenzen

Tags: