Kong Plugin 開発の始め方
Kongは軽量なAPIゲートウェイとして広く世界中で使われています。クラシカルなAPIマネジメントの文脈ではもちろんのこと、KubernetesネイティブなAPIゲートウェイとしても人気があります(CNCF Hubでも見つけられます)。さらに最近CP/DPセパレーションも実現して*1、ますます使いやすくなっています。
Kongの魅力は圧倒的なスループットもさることながら、プラグイン開発をすることでゲートウェイにいろんな処理を挟み込めることじゃないでしょうか。プラグインはすでに公式のものだけでも結構な数があり、特にOpenID Connectはかなりの人気がありますが、更にプラグインを自作することができます。
今回はKong Plugin開発の始め方についてご紹介します。なお、Kong OSSでもKong Enterprise Editionでも、Plugin開発のやり方は基本的に同じです。
ドキュメントなど
まず公式のガイドは以下の2つになります。それぞれOSS版、EE版のドキュメントですが、内容は同じです。
基本的にはこのガイドに従ってファイルを作っていき、APIドキュメントを見ながら開発していけば動くはずなのですが、開発にはKongの環境が必要だったり、NginxやLua/OpenRestyの知識が必要だったりと、なかなかハードルが高いです。*2
Kong社では、このあたりのハードルを下げるための開発・テストフレームワークを用意しています。
Kong-pongoというフレームワークと、 github.com
Kong-pluginというテンプレートの2つです。 github.com
この2つを使って、ヘッダの追加、書き換えを行うプラグインを書いてみたいと思います。
Kong-Pluginリポジトリのフォーク
ますはじめに、GitHub上でkong-pluginリポジトリをフォークしてください。このリポジトリはKong社が用意しているプラグイン開発のテンプレートです。開発を始めるのに必要な設定ファイルやマニフェスト、プラグインのディレクトリ構造、テストファイルなどを用意してあり、すぐに実行できる状態になっています。
フォークしたら、リポジトリの設定画面で、"Template repository"にチェックを入れてこのリポジトリをテンプレートとして使えるようにしましょう。
この設定を保存したら、GitHub上で新規リポジトリ作成をします。
リポジトリ作成時に、先程設定したテンプレートを選択してください。
こうして作ったリポジトリ上で開発を行います。ローカルPCにクローンしておいてください。
ファイルの説明を簡単に。Pluginの構成は以下のようになっています。
$ cd my-first-plugin $ tree . . ├── LICENSE ├── README.md ├── kong │ └── plugins │ └── myplugin │ ├── handler.lua │ └── schema.lua ├── kong-plugin-myplugin-0.1.0-1.rockspec └── spec └── myplugin ├── 01-schema_spec.lua └── 02-access_spec.lua
まず kong-plugin-myplugin-0.1.0-1.rockspec
というのがマニフェストファイルです。rocspecというのはLuaのパッケージマネージャであるLuaRocksの仕様です。詳しく知りたい方はこちらをどうぞ。今回の開発では一旦このまま放置しておいても問題ないです。
kong/plugins/myplugin
フォルダ以下に配置されているのがプラグインのソースファイルです。なお、mypluginというフォルダ名はrockspecで定義したプラグイン名から来ています。
handler.lua
: リクエスト・レスポンスのハンドラです。Kongが定義したイベントハンドラにここからアクセスして、処理を組み込めます。NginxやOpenRestyに詳しいなら理解は簡単なはずですschema.lua
: プラグインのコンフィグを入れておくスキーマ定義です。ここで定義したフィールドを使ってユーザーは設定値をカスタマイズできます
spec/myplugin/
フォルダ以下にあるのがSpecファイルです。Bustedという、Lua製のBDDフレームワークを使っています。Rspecになれた人なら簡単だと思います。
Kong-pongoのインストール
次に、kong-pongoをクローンして、ローカルにインストールを行います。Pongoは、プラグイン開発に必要なKong環境を用意してくれるフレームワークです。Kong起動に必要なPostgress/CassandraのDockerコンテナを用意してくれたり、該当バージョンのKong Dockerイメージをビルドして起動してくれたりします。OpenRestyやBustedといった依存モジュールも全部用意されます。開発環境構築の面倒を見てくれる便利フレームワークです。
基本的にはREADMEに書いてあるとおりに行えば大丈夫なはずですが、pongoはDockerがインストールされていることを前提していているのでDockerがない場合にはまずそれを入れてください。
あとは下記に書いてあるとおりで大丈夫です。MacOSの人はCoreutilsが必要になるのでそれも入れてください。
その他に前提条件としてLICENCEが必要とかBintrayが必要とか書いてありますが、これはKong EEに対して開発する場合の話なので、OSS版に対しての開発の場合は必要ありません。EEで開発したい場合には私に問い合わせてくれればトライアルライセンスをお渡しできます。
Pongoで開発を始める
Pongoがインストールできたら、開発対象のリポジトリ(先程テンプレから作成してクローンしておいたもの)に移動します。
$ cd ../First-Kong-Plugin # 自分でつけた名前のディレクトリに移動。Kong-pongoじゃないことに注意
プラグインのディレクトリに移動したらまずは動作確認も兼ねて、pongo run
します。このコマンド一発で、依存するPostgress/CassandraのDockerコンテナを起動した上で、配下にあるSpecファイルを探し出して実行して単体テストをしてくれます。
$ pongo run Notice: auto-starting the test environment, use the 'down' action to stop it Creating network "kong-pongo_test-network" with the default driver Creating kong-pongo_postgres_1 ... done # Postgresイメージ作成 Creating kong-pongo_cassandra_1 ... done # Cassandraイメージ作成 Waiting for postgres Waiting for cassandra Notice: image 'kong-pongo-test:2.0.1' not found, auto-building it # PongoイメージがなければBuild ...中略: dockerイメージのPull, Build... Successfully tagged kong-pongo-test:2.0.1 Stopping after installing dependencies for kong-plugin-myplugin 0.1.0-1 # Pongo run (./spec) の実行開始 Kong version: 2.0.1 [==========] Running tests from scanned files. [----------] Global test environment setup. [----------] Running tests from /kong-plugin/spec/myplugin/01-schema_spec.lua [ RUN ] /kong-plugin/spec/myplugin/01-schema_spec.lua @ 18: myplugin: (schema) accepts distinct request_header and response_header [ OK ] /kong-plugin/spec/myplugin/01-schema_spec.lua @ 18: myplugin: (schema) accepts distinct request_header and response_header (1.20 ms) [ RUN ] /kong-plugin/spec/myplugin/01-schema_spec.lua @ 28: myplugin: (schema) does not accept identical request_header and response_header [ OK ] /kong-plugin/spec/myplugin/01-schema_spec.lua @ 28: myplugin: (schema) does not accept identical request_header and response_header (2.01 ms) [----------] 2 tests from /kong-plugin/spec/myplugin/01-schema_spec.lua (289.37 ms total) [----------] Running tests from /kong-plugin/spec/myplugin/02-access_spec.lua [ RUN ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 53: myplugin: (access) [#postgres] request gets a 'hello-world' header [ OK ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 53: myplugin: (access) [#postgres] request gets a 'hello-world' header (48.05 ms) [ RUN ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 71: myplugin: (access) [#postgres] response gets a 'bye-world' header [ OK ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 71: myplugin: (access) [#postgres] response gets a 'bye-world' header (7.35 ms) [ RUN ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 53: myplugin: (access) [#cassandra] request gets a 'hello-world' header [ OK ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 53: myplugin: (access) [#cassandra] request gets a 'hello-world' header (78.16 ms) [ RUN ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 71: myplugin: (access) [#cassandra] response gets a 'bye-world' header [ OK ] /kong-plugin/spec/myplugin/02-access_spec.lua @ 71: myplugin: (access) [#cassandra] response gets a 'bye-world' header (6.05 ms) [----------] 4 tests from /kong-plugin/spec/myplugin/02-access_spec.lua (16132.22 ms total) [----------] Global test environment teardown. [==========] 6 tests from 2 test files ran. (16422.94 ms total) [ PASSED ] 6 tests.
こんなイメージです。ログを見て分かる通り、依存しているPostgress/CassandraをそれぞれDocker pullしてBuildの上起動してくれ、その後kong-pongo-test
という全部入りのKongイメージのビルドを始めます。
それが終わった後、kong-pongo-test
コンテナを起動して、その上でspecフォルダ以下のテストを実行して、結果を教えてくれる、というそういう流れになっています。
あとはファイルをいじりながらテストを繰り返していけば開発ができます。
なお、pongo run
はテスト終了後にkong-pongo-test
コンテナについては自動で終了してくれますが、Postgress/Cassandraコンテナについては自動では終了しません。こちらを終了したい場合には、明示的に手でpongo down
する必要があります。なお、逆に明示的にPostgress/Cassandraなど依存コンテナだけ起動したい場合にはpongo up
でできます。pongo run
は起動時に依存コンテナがupしてなければ自動でやってくれますが、終了時にdownはしてくれないということです。
Pongoを使ってマニュアルでKongをテストする
Specを通してではなくて実際にKongを起動してマニュアルテストをしたい場合には、下記コマンドを実行すると、pongoが立ち上げたKongコンテナにsshログインできます。
$ pongo up $ pongo shell Stopping after installing dependencies for kong-plugin-myplugin 0.1.0-1 Kong version: 2.0.1 /kong #
次にこのShell上でKongのDBマイグレーションを走らせます。ちなみに初回のみです。次回以降pongo shell
した際はこの手順は必要ありません。
/kong # kong migrations bootstrap Bootstrapping database... migrating core on database 'kong_tests'... core migrated up to: 000_base (executed) core migrated up to: 003_100_to_110 (executed) core migrated up to: 004_110_to_120 (executed) core migrated up to: 005_120_to_130 (executed) core migrated up to: 006_130_to_140 (executed) core migrated up to: 007_140_to_150 (executed) core migrated up to: 008_150_to_200 (executed) migrating hmac-auth on database 'kong_tests'... hmac-auth migrated up to: 000_base_hmac_auth (executed) hmac-auth migrated up to: 002_130_to_140 (executed) migrating oauth2 on database 'kong_tests'... oauth2 migrated up to: 000_base_oauth2 (executed) oauth2 migrated up to: 003_130_to_140 (executed) migrating jwt on database 'kong_tests'... jwt migrated up to: 000_base_jwt (executed) jwt migrated up to: 002_130_to_140 (executed) migrating basic-auth on database 'kong_tests'... basic-auth migrated up to: 000_base_basic_auth (executed) basic-auth migrated up to: 002_130_to_140 (executed) migrating key-auth on database 'kong_tests'... key-auth migrated up to: 000_base_key_auth (executed) key-auth migrated up to: 002_130_to_140 (executed) migrating acl on database 'kong_tests'... acl migrated up to: 000_base_acl (executed) acl migrated up to: 002_130_to_140 (executed) migrating session on database 'kong_tests'... session migrated up to: 000_base_session (executed) migrating response-ratelimiting on database 'kong_tests'... response-ratelimiting migrated up to: 000_base_response_rate_limiting (executed) migrating rate-limiting on database 'kong_tests'... rate-limiting migrated up to: 000_base_rate_limiting (executed) rate-limiting migrated up to: 003_10_to_112 (executed) 23 migrations processed 23 executed Database is up-to-date
Database is up-to-dateとなればOKです。ここでPostgresまたはCassandraのエラーが出る場合、DBコンテナが立ち上がっていませんので、いちどShellからExitし、改めてpongo up
して起動してみてください。
OKなら、Kongを起動します。
/kong # kong start
Kong started
起動できたら動作確認します。Pongoコンテナには予めHTTPieが入っていますので、それを使って動作確認します。HTTPieに慣れない人はcurlを使っても同じことです。
localhostの8001番ポートにリクエストを発行して、動作確認ついでにAdmin APIの設定状況を確認します。
/kong # http localhost:8001 HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 8973 Content-Type: application/json; charset=utf-8 Date: Mon, 16 Mar 2020 06:09:27 GMT Server: kong/2.0.1 X-Kong-Admin-Latency: 186 { "configuration": { "admin_acc_logs": "/kong-plugin/servroot/logs/admin_access.log", "admin_access_log": "logs/admin_access.log", "admin_error_log": "logs/error.log", "admin_listen": [ "127.0.0.1:8001 reuseport backlog=16384", "127.0.0.1:8444 http2 ssl reuseport backlog=16384" ], ...中略... "plugins": { "available_on_server": { "acl": true, "aws-lambda": true, "azure-functions": true, "basic-auth": true, "bot-detection": true, "correlation-id": true, "cors": true, "datadog": true, "file-log": true, "hmac-auth": true, "http-log": true, "ip-restriction": true, "jwt": true, "key-auth": true, "ldap-auth": true, "loggly": true, "myplugin": true, # 注目:今回開発しているプラグインがロードされています ...後略...
上記のようにレスポンスが返ってくれば正しく動作しています。開発中のプラグインがロードされていることも確認できます。
動作が確認できればあとは普通のKongです。ServiceやRouteを登録して、そこにPluginを紐付ければ手動テスト可能です。
と言いつつこれを読んでる人はKongがどういうものかしらない人も多いと思うので、ServiceとRouteをAdmin APIで登録し、そこに開発中のPluginを紐付けて、手動でテストできるところまでお見せします。
Serviceの登録
引き続きpongo shellの中で下記を実行します。Serviceオブジェクトとは、Kongがプロキシする対象のアップストリームサービスを指すものです。
http POST :8001/services/ name=mock-service url=http://mockbin.org HTTP/1.1 201 Created Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 293 Content-Type: application/json; charset=utf-8 Date: Mon, 16 Mar 2020 06:37:50 GMT Server: kong/2.0.1 X-Kong-Admin-Latency: 178 { "client_certificate": null, "connect_timeout": 60000, "created_at": 1584340670, "host": "mockbin.org", "id": "129b9b78-c493-42c6-b834-e754798ba462", "name": "mock-service", "path": null, "port": 80, "protocol": "http", "read_timeout": 60000, "retries": 5, "tags": null, "updated_at": 1584340670, "write_timeout": 60000 }
mock-serviceという名前で、http://mockbin.orgを指すサービスを作りました。 MockbinはKongが提供するモックサービスです。デフォルトではhttp://mockbin.org/mock/request
で常にリクエストを受け取って返事をしてくれます。
Routeの登録
次にRouteオブジェクトを作ります。Kongがサービスにリクエストをルーティングする際に参照するものです。
ここでは下記のようにして、ヘッダーに"Host: mockbin.org"がありかつリクエストパスが/mock
だったときに先程のmock-serviceオブジェクトにルーティングするように設定します。
/kong # http POST :8001/services/mock-service/routes hosts:='["mockbin.org"]' paths:='["/mock"]' HTTP/1.1 201 Created Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 435 Content-Type: application/json; charset=utf-8 Date: Mon, 16 Mar 2020 06:38:04 GMT Server: kong/2.0.1 X-Kong-Admin-Latency: 12 { "created_at": 1584340684, "destinations": null, "headers": null, "hosts": [ "mockbin.org" ], "https_redirect_status_code": 426, "id": "cd661405-a1a8-4465-a347-daf5ce32e030", "methods": null, "name": null, "path_handling": "v0", "paths": [ "/mock" ], "preserve_host": false, "protocols": [ "http", "https" ], "regex_priority": 0, "service": { "id": "129b9b78-c493-42c6-b834-e754798ba462" }, "snis": null, "sources": null, "strip_path": true, "tags": null, "updated_at": 1584340684 }
Pluginの紐付け
最後に、mock-serviceサービスに開発中のプラグインを紐付けます。
/kong # http POST :8001/services/mock-service/plugins name=myplugin HTTP/1.1 201 Created Access-Control-Allow-Origin: * Connection: keep-alive Content-Length: 325 Content-Type: application/json; charset=utf-8 Date: Mon, 16 Mar 2020 06:41:35 GMT Server: kong/2.0.1 X-Kong-Admin-Latency: 12 { "config": { "request_header": "Hello-World", "response_header": "Bye-World", "ttl": 600 }, "consumer": null, "created_at": 1584340895, "enabled": true, "id": "68caa29c-8985-4f75-87d7-c25bb89efd35", "name": "myplugin", "protocols": [ "grpc", "grpcs", "http", "https" ], "route": null, "service": { "id": "129b9b78-c493-42c6-b834-e754798ba462" }, "tags": null }
これで、mock-serviceにリクエストを送ると常にmypluginが動作するようになりました。
プラグインの手動テスト
下記のようにリクエストを発行してテストしてみましょう。
/kong # http :8000/mock/request Host:mockbin.org HTTP/1.1 200 OK Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: host,connection,x-forwarded-for,x-forwarded-proto,x-forwarded-host,x-forwarded-port,x-real-ip,accept-encoding,accept,user-agent,hello-world,x-request-id,via,connect-time,x-request-start,total-route-time Access-Control-Allow-Methods: GET Access-Control-Allow-Origin: * Bye-World: this is on the response # 今回のプラグインが差し込んでいるヘッダ ...後略...
上記のように、Bye-World
というヘッダが差し込まれているのが確認できます。これが今回のプラグインが動作している証拠です。ちなみに参考までにこれを行っている該当コードは下記です。
まずschema.luaで下記のようにヘッダ名を定義しています。
{ response_header = typedefs.header_name { required = true, default = "Bye-World" } },
次にhandler.luaで設定したヘッダを読み込み、そこに値をセットしています。
---[[ runs in the 'header_filter_by_lua_block' function plugin:header_filter(plugin_conf) -- your custom code here, for example; ngx.header[plugin_conf.response_header] = "this is on the response" end --]]
こんなふうにして、手動で動作確認、テストすることも可能です。
まとめ
いかがだったでしょうか。
Pongoを使えば、面倒な環境構築も素早く簡単に済ませてプラグイン開発に専念できることがわかったかと思います。BDDも手軽にできますし、CIのセットアップも簡単です。
特定バージョンのKongを指定することもできますし、Kong EEを指定することも可能です。他にも様々な機能が用意されています。そのあたりはぜひPongoのREADMEを読んで、試して見ください。
出来上がったプラグインをパッケージにしてインストール・配布する場合には基本的にはLuarocksを使って行うことになります。こちらのドキュメントを参考にしてください。
ぜひ、面白いプラグインを作ってみてください!