docker buildなしでKong Gatewayのカスタムプラグインを追加する

通常コンテナ環境でKong Gatewayのカスタムプラグインを作る場合、Kong Gatewayのイメージをベースイメージとしてdocker buildでコンテナイメージを再作成する。
ただし、Kubernetes環境でかつ少量のLuaでプラグインを作成する場合は`ConfigMap`か`Secret`で代用することも出来る。
今回の記事は[こちら](https://docs.konghq.com/kubernetes-ingress-controller/latest/plugins/custom/)の公式手順を参考に`ConfigMap`でカスタムプラグインを実装する方法を確認した時のメモとなる。

## プラグインの作成
ここではお試しとして以下の仕様のプラグインを作成する。
- リクエストを受け取ると一定時間sleepする
- sleep時間は外部からパラメータで指定することが出来る
- なるべくどのプラグインよりも早く動く

ファイル構造は公式に従って以下の構造とする。
```sh
 /sleep
 ├── handler.lua    処理本体
 └── schema.lua     パラメータ設定（スキーマ）
```
なお、ファイル構造については公式で[指定があり](https://docs.konghq.com/gateway/latest/plugin-development/file-structure/#basic-plugin-modules)、この2つは必須となる。
中身は以下のように作った

```lua:handler.lua
local RequestSleep = {
    PRIORITY = 10000,
    VERSION = "0.0.1",
  }
  
function RequestSleep:access(conf)
    ngx.log(ngx.NOTICE, "plugin.access called")

    for k, v in pairs(conf) do
      ngx.log(ngx.NOTICE, "conf[" .. tostring(k) .. "] = " .. tostring(v))
    end
    
    local sleep_time = conf.sleep_time
    
    if sleep_time and sleep_time > 0 then
      ngx.log(ngx.NOTICE, "Sleeping for " .. sleep_time .. " seconds")
      ngx.sleep(sleep_time)
      ngx.log(ngx.NOTICE, "Woke up after sleeping for " .. sleep_time .. " seconds")
    else
      ngx.log(ngx.NOTICE, "No valid sleep_time provided, proceeding without sleep")
    end 
end
return RequestSleep
```
コードについて少し補足する。
優先度(`PRIORITY`)は[Plugin Ordering Reference](https://docs.konghq.com/konnect/reference/plugins/)よりexit-transformerより高い優先度である10000とした。これでほとんどのプラグインよりは早く動く。
`:access`については[こちら](https://docs.konghq.com/gateway/latest/plugin-development/custom-logic/#available-contexts)にあるようにリクエストをupstream（転送先サービス）に転送する前に引っ掛けて処理するためのものとなる。
`ngx`についてはlua-nginx-moduleであり、Kongはnginxで動いているので特に呼び出しとかしなくても使えるようになっている。
処理としては最初に`conf`の中身を表示し、パラメータチェックをした後にsleepするようにしている。
内部の動きを確認するために`ngx.log`を呼びまくっているが、シンプルにするならlog系は全て削除してOK。
schema.luaは以下。
```lua:schema.lua
return {
    name = "sleep",
    fields = {
      { config = {
          type = "record",
          fields = {
            { sleep_time = { type = "integer", default = 0, }, },
          },
      }, },
    }
  }
```
schema.luaの仕様は[こちら](https://docs.konghq.com/gateway/latest/plugin-development/configuration/#schemalua-specifications)を参照いただくとしてここでは詳細は特に触れない。
sleep_timeというパラメータを整数型で定義しているだけである。

## プラグインのインストール
以下の手順で行う。
1. `ConfigMap`の作成
2. Kong Gatewayにプラグイン(`ConfigMap`)を認識させる

最初に`ConfigMap`を作成する。展開先はKong Gatewayと同じNamespaceにする。
```sh
kubectl create cm kong-plugin-sleep --from-file=./sleep -n kong
```

次にKong Gatewayをデプロイし直す。
`env.plugins`に作成したプラグイン名を追加し、`plugins.configMaps`に作成した`ConfigMap`からプラグインを認識するよう設定を追加する。
```yaml
：（省略）
env:
  plugins: bundled,openid-connect,sleep
：（省略）
plugins:
  configMaps:
  - name: kong-plugin-sleep
    pluginName: sleep
```
編集したvalues.yamlを使ってupdateし設定を反映する。
```sh
helm upgrade -i kong kong/kong --namespace kong --values kong-values.yaml --wait --debug
```
設定が反映されるとKong ManagerのPlugin検索から作成したPluginが見えるようになる。
![20240528085359.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/cbf723e1-f6c9-94ed-8336-8ee7f7b83786.png)

## 動作確認
前回「[Kong Gatewayの通信の遅延箇所をJaegerで確認する](https://qiita.com/ipppppei/items/adb17cf20e13a786d283)」という記事の中でnginxをユーザサービスに見立ててJaegerでトレーシングを行ったが、同じ環境にこのプラグインを適用し、以下を確認してみたい。
- カスタムプラグインの仕様通り遅延が起こせるか
- 発生した遅延をJaegerで確認できるか

Kong Managerにアクセスし、左サイドバーの`Routes`->`<nginxのRoute>`を選択し、`Plugins`から`New Plugin`を選択する。
`sleep`で検索してカスタムプラグインを選択し、以下の設定を行って保存する。
- `Protocols`：http, https
- `Sleep Time`：5

![20240528085933.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/d3d3d813-baa1-5f44-f556-e89a213ecea9.png)

これで設定は完了となる。

コマンドを叩いて遅延が効くか確認する。時間を測るためにtimeコマンドを使って実行する。
```sh
time curl -X GET -k -i  https://nginx.hogehoge.info
```
結果は以下のようになった。
```sh
real	0m5.589s
user	0m0.010s
sys	    0m0.013s
```
プラグインによる5秒の遅延が効いているようだ。
Jaegerのトレースを見てみる。

![20240528090620.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/63cb7666-2cce-89b8-7a31-3790dda7a6fe.png)
作成したカスタムプラグインが最初に動き、かつここだけで5秒かかっていることが確認できた。
最後にログも見てみる。
```sh
$ kubectl logs -n kong kong-kong-5d667684f8-vxlzw -c proxy | grep notice
：（省略）
2024/05/28 00:07:14 [notice] 2437#0: *3169 [lua] handler.lua:10: conf[__key__] = plugins:sleep:da7eea27-3de1-552b-8f18-c038a617fc6e::::d83c6514-b5ed-462a-86e1-f3c8c577a324, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
2024/05/28 00:07:14 [notice] 2437#0: *3169 [lua] handler.lua:10: conf[__seq__] = 13, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
2024/05/28 00:07:14 [notice] 2437#0: *3169 [lua] handler.lua:10: conf[route_id] = da7eea27-3de1-552b-8f18-c038a617fc6e, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
2024/05/28 00:07:14 [notice] 2437#0: *3169 [lua] handler.lua:10: conf[__plugin_id] = 5601f1ab-939b-4810-9c36-bf9793a250f9, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
2024/05/28 00:07:14 [notice] 2437#0: *3169 [lua] handler.lua:10: conf[sleep_time] = 5, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
2024/05/28 00:07:14 [notice] 2437#0: *3169 [lua] handler.lua:16: Sleeping for 5 seconds, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
2024/05/28 00:07:19 [notice] 2437#0: *3169 [lua] handler.lua:18: Woke up after sleeping for 5 seconds, client: 192.168.59.131, server: kong, request: "GET / HTTP/1.1", host: "nginx.hogehoge.info", request_id: "6636b272c181be016d61490e1c34a106"
```
意図したログが表示されていること、デバッグ情報として入力した値（sleep_time）やRouteのIDなども拾えることなどが確認できた。

## 所感
docker buildをやってしまうとKong GatewayのVersion Up毎にイメージの作り直しが発生するが、このやり方であればイメージの作り直しが不要なのでメンテが楽である。
ただLuaのパッケージを追加したいとかもう少し複雑なことをしたい場合はあまり向いていなさそうなので、用途が限られる点は気をつけておきたい。