Apache APISIXプラグインをゼロから作る方法は?
過去数ヶ月間、コミュニティユーザーは多くのプラグインをApache APISIXに追加し、Apache APISIXのエコシステムを豊かにしてきました。ユーザーの視点から見ると、より多様なプラグインの出現は確かに良いことであり、Apache APISIXの高性能と低遅延を完璧にした上で、ユーザーが「ワンストップ」で「多機能」なゲートウェイに対する期待をさらに満たしています。
Apache APISIXのブログ記事では、プラグイン開発のプロセスについて詳しく説明しているものはないようです。そこで、プラグイン開発者の視点からプロセスを見て、プラグインがどのように生まれるのかを見てみましょう!
この記事では、バックエンド経験のないフロントエンドエンジニアがfile-logger
プラグインを開発するプロセスを記録しています。実装プロセスの詳細に入る前に、file-logger
の機能を簡単に紹介します。
file-loggerプラグインの紹介
file-logger
は、Apache APISIXプラグインメタデータを使用してカスタムログフォーマットを生成することをサポートしています。ユーザーはfile-logger
プラグインを介して、リクエストとレスポンスのデータをJSON形式でログファイルに追加したり、ログデータストリームを指定された場所にプッシュしたりできます。
想像してみてください:ルートのアクセスログを監視する際に、特定のリクエストとレスポンスデータの値だけでなく、ログデータを指定されたファイルに書き込みたい場合があります。このような場合にfile-logger
プラグインを使用して、これらの目標を達成することができます。
file-logger
を使用して、ログデータを特定のログファイルに書き込むことで、監視とデバッグのプロセスを簡素化できます。
プラグインを実装する方法は?
file-logger
の機能を紹介した後、このプラグインについてより理解が深まったことでしょう。以下は、サーバーサイド経験のないフロントエンド開発者である私が、Apache APISIXのプラグインを開発し、それに対応するテストを追加する方法についての詳細な説明です。
プラグインの名前と優先順位を確認する
Apache APISIXプラグイン開発ガイドを開き、優先順位に従って以下の2つのことを決定する必要があります:
- プラグインのカテゴリを決定する。
- プラグインの優先順位を決定し、
conf/config-default.yaml
ファイルを更新する。
今回のfile-logger
開発はロギングタイプのプラグインであるため、既存のApache APISIXのロギングプラグインの名前と順序を参考にし、file-logger
を配置します。
他のプラグイン作者やコミュニティの熱心なメンバーと相談した後、プラグインの名前file-logger
と優先順位399
が最終的に確認されました。
注意:プラグインの優先順位は実行順序に関連しており、優先順位の値が高いほど、実行順序が前になります。また、プラグイン名の順序は実行順序とは関係ありません。
最小限の実行可能なプラグインファイルを作成する
プラグイン名と優先順位を確認した後、apisix/plugins/
ディレクトリにプラグインコードファイルを作成できます。ここで注意すべき点が2つあります:
- プラグインコードファイルを
apisix/plugins/
ディレクトリに直接作成する場合、Makefile
ファイルを変更する必要はありません。 - プラグインに独自のコードディレクトリがある場合、
Makefile
ファイルを更新する必要があります。詳細な手順については、Apache APISIXプラグイン開発ガイドを参照してください。
- ここでは、
apisix/plugins/
ディレクトリにfile-logger.lua
ファイルを作成します。 - 次に、
example-plugin
に基づいて初期化バージョンを完成させます。
-- ヘッダーで必要なモジュールを導入 local log_util = require("apisix.utils.log-util") local core = require("apisix.core") local plugin = require("apisix.plugin") local ngx = ngx -- プラグインの名前を宣言 local plugin_name = "file-logger" -- プラグインのスキーマフォーマットを定義 local schema = { type = "object", properties = { path = { type = "string" }, }, required = {"path"} } -- プラグインメタデータスキーマ 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 } -- プラグイン設定が正しいか確認 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 -- ログフェーズ 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
最小限の使用可能なプラグインファイルが準備できたら、core.log.warn(core.json.encode(conf))
とcore.log.warn("ctx: ", core.json.encode(ctx, true))
を介して、プラグインの設定データとリクエスト関連データをerror.log
ファイルに出力できます。
プラグインを有効にしてテストする
以下はテストのためのいくつかのステップです。プラグインが設定したプラグインデータとリクエスト関連データ情報をエラーログファイルに正常に出力できるかどうかをテストするために、プラグインを有効にし、テストルートを作成する必要があります。
-
ローカルにテスト用のアップストリームを準備します(この記事で使用したテストアップストリームは
127.0.0.1:3030/api/hello
で、ローカルで作成しました)。 -
curl
コマンドを使用してルートを作成し、新しいプラグインを有効にします。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" }'
その後、ステータスコード
200
が表示され、ルートが正常に作成されたことを示します。 -
curl
コマンドを実行してルートにリクエストを送信し、file-logger
プラグインが起動したかどうかをテストします。curl -i http://127.0.0.1:9080/api/hello HTTP/1.1 200 OK ... hello, world
-
logs/error.log
ファイルに記録が表示されます:ご覧の通り、プラグインに設定した
path: logs/file.log
が正常に保存されています。この時点で、ログフェーズでconf
とctx
パラメータを出力する最小限の使用可能なプラグインを正常に作成しました。その後、
file-logger.lua
プラグインのコア機能を直接コードファイルに記述できます。ここでは、Apache APISIXを再起動せずに、apisix reload
コマンドを実行して最新のプラグインコードをリロードできます。
file-loggerプラグインのコア機能を記述する
file-logger
プラグインの主な機能は、ログデータを書き込むことです。コミュニティの他の人々に尋ねたり、情報を確認したりした後、LuaのIOライブラリについて学び、プラグインの機能のロジックがおおよそ以下のステップであることを確認しました。
-
各リクエストを受け取った後、プラグインに設定された
path
にログデータを出力します。- まず、ログフェーズで
conf
を介してfile-logger
のpath
の値を取得します。 - 次に、LuaのIOライブラリを使用して、ファイルを作成、開き、書き込み、キャッシュをリフレッシュし、ファイルを閉じます。
- まず、ログフェーズで
-
ファイルのオープン失敗、ファイルの作成失敗などのエラーを処理します。
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
-
http-logger
プラグインのソースコードを参考にして、ログデータを書き込むためのメソッドと、メタデータのいくつかの判断と処理を完了しました。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
検証とテストの追加
ログ記録を検証する
テストルートを作成した際にfile-logger
プラグインを有効にし、パスをlogs/file.log
に設定したため、テストルートにリクエストを送信してログ収集の結果を検証できます。
curl -i http://127.0.0.1:9080/api/hello
対応するlogs/file.log
では、各レコードがJSON形式で保存されていることがわかります。データの1つをフォーマットすると、以下のようになります。
{ "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" } }
これでログ記録の検証は完了です。検証結果は、プラグインが正常に起動し、適切なデータを返したことを示しています。
プラグインのためのテストを追加する
add_block_preprocessor
部分のコードについては、Perlの経験がなかったため、最初に書き始めた時は混乱しました。調査した後、正しい使用方法を理解しました:データセクションでrequest
アサーションとno_error_log
アサーションを記述しない場合、デフォルトのアサーションは以下のようになります。
--- request GET /t --- no_error_log [error]
他のロギングテストファイルを参考にした後、t/plugin/
ディレクトリにfile-logger.t
ファイルを作成しました。
各テストファイルは**DATA**
によって前文セクションとデータセクションに分かれています。公式サイトにはテスト関連のドキュメントの明確な分類がないため、詳細については記事の最後にある関連資料を参照してください。以下は、関連資料を参考にして完成したテストケースの1つです。
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
これでプラグイン追加テストセッションは終了です。
まとめ
以上が、バックエンド初心者としてApache APISIXプラグインを0から実装する全プロセスです。プラグイン開発の過程で多くの問題に遭遇しましたが、幸いにもApache APISIXコミュニティには多くの熱心な仲間がいて、問題を解決するのを手伝ってくれたため、file-logger
プラグインの開発とテストは全体的にスムーズに進みました。このプラグインに興味がある場合や、プラグインの詳細を見たい場合は、公式Apache APISIXドキュメントを参照してください。
Apache APISIXは現在も他のプラグインを開発しており、より多くの統合サービスをサポートする予定です。興味がある場合は、GitHub Discussionでディスカッションを開始するか、メーリングリストを通じてお問い合わせください。