Kong GatewayのカスタムエンティティをKubernetes上で利用する

前回「[Kong Gatewayのカスタムエンティティを作成する](https://qiita.com/ipppppei/items/9d04d4dabe972d69cdf7)」という記事でPongo（Docker）を使ってカスタムエンティティの作成から動作確認まで行った。
これをKubernetes上で動かすためのメモ。
既存のドキュメントだとカスタムプラグインをKICで動かす方法については[記載があるが](https://docs.jp.konghq.com/kubernetes-ingress-controller/latest/plugins/custom/)、DAOを含む場合については記載がないので参考になれば幸いである。

なお、「[Kong Gatewayのカスタムエンティティを作成する](https://qiita.com/ipppppei/items/9d04d4dabe972d69cdf7)」の延長で書いているため、前の記事を読んでいることが前提となる。

## カスタムプラグインのおさらい

DAOを含むカスタムプラグインの構造は以下となる。
```sh
complete-plugin
├── daos.lua
├── handler.lua
├── migrations
│   ├── init.lua
│   └── 000_base_complete_plugin.lua
└── schema.lua
```
`schema.lua`と`hadnler.lua`がカスタムプラグインの実装にあたり、カスタムエンティティの実装は`daos.lua`で行い、DBの初期化は`migrations`以下のコードで行う。
`migrations`以下のファイルは以下のようになる。

`init.lua`：DBの初期化で利用するファイルの定義
`000_xxx.lua`：実際に初期化で使うコード

ポイントとしては`migrations`のディレクトリはどうも必須っぽく、ディレクトリなしでフラットにファイルを配置してもinit.luaや000_xxx.luaは読み込まれずにテーブルの初期化が出来ない。
Kubernetesにおいて少し面倒なのが、ConfigMapではディレクトリ構造は表現できない点である。
そのため、カスタムプラグインをConfigMapで登録する場合はConfigMapとは別にディレクトリ構造を用意してあげる必要がある。

## カスタムプラグインの構築
ここでは前回記事で作成したファイルを以下のように配置したところからスタートする。
```sh
$ tree ./dao-test
./dao-test
├── daos.lua
├── handler.lua
├── migrations
│   ├── 000_base_my_plugin.lua
│   ├── 001_100_to_110.lua
│   └── init.lua
└── schema.lua
```
前回記事で作成した際にプラグイン名はmypluginとしていたが、ここではdao-testに変更している（`schema.lua`内も変更済み）。

最初にConfigMapを作成してカスタムプラグインをKubernetesクラスタ上にロードする。`migrations`ディレクトリ以下も登録するよう`--from-file`を各ディレクトリに対して指定する。
```sh
kubectl create cm kong-plugin-dao-test --from-file=./dao-test/ --from-file=./dao-test/migrations  -n kong --dry-run=client -o yaml | kubectl apply -f -
```
後述するが、`migrations`以下はカスタムプラグイン本体とは別でマウントするため、ConfigMapをルートディレクトリと`migrations`で分けてもいいと思う（ここでは面倒だったので1つにまとめた）。

次にカスタムプラグインのためにHelmのvalues.yamlを編集する。
ここではvalues.yamlの詳細は触れず、変更が必要な箇所のみ記載する。
また、**大前提としてTraditionalモードでデプロイするものとし、Hybridモードではデプロイしない**。
Hybridモードの場合、Data PlaneからDAO経由でのDB更新処理が出来ないためである。

カスタムプラグインの追加箇所は以下となる。
```yaml
env:
  plugins: bundled,dao-test
plugins:
  configMaps:
  - name: kong-plugin-dao-test
    pluginName: dao-test
```
上記の部分はDAOの利用がなくても同じである。
次にDBのテーブルの初期化のための`migrations`を利用するために以下を追加する。
```yaml
deployment:
  enabled: true
  userDefinedVolumes:
  - name: kong-plugin-dao-test-migration
    configMap:
      name: kong-plugin-dao-test
  userDefinedVolumeMounts:
  - name: kong-plugin-dao-test-migration
    mountPath: /opt/kong/plugins/dao-test/migrations
```

[`UserDefinedVolumes`](https://github.com/Kong/charts/blob/main/charts/kong/README.md#user-defined-volumes)と[`UserDefinedVolumeMounts`](https://github.com/Kong/charts/blob/main/charts/kong/README.md#user-defined-volume-mounts)はPod内にディレクトリを作ってConfigMapをマウントするためのKongのHelmChartのパラメータである。

上記の内容を含むHelmのvalues.yamlを使ってKong Gatewayをデプロイする。
デプロイ後、Podの中を見てみると先ほどの設定によって以下のような感じでPod内からプラグインが見えるようになる。
```sh
$ kubectl exec -it kong-aws-cp-kong-bd856c4d8-m64l8 -c proxy -- ls -R /opt/kong/plugins
/opt/kong/plugins:
dao-test

/opt/kong/plugins/dao-test:
000_base_my_plugin.lua	daos.lua     init.lua	 schema.lua
001_100_to_110.lua	handler.lua  migrations

/opt/kong/plugins/dao-test/migrations:
000_base_my_plugin.lua	daos.lua     init.lua
001_100_to_110.lua	handler.lua  schema.lua
```
## 動作確認
追加したカスタムプラグインはリクエストのヘッダに`"mykey-add:<文字列>"`を含んでいると文字列部分をDB上のテーブルに格納する。
そのためプラグイン設定後に通信を行い、その前後のテーブルの状態を確認することで上手く設定できているかどうかを確認する。

最初にAdmin APIとProxyのアドレスを環境変数で設定する。
筆者は今回は`type: LoadBalancer`などを使わずクラスタ内からアクセスしたため、以下のようにServiceを指定した。
```sh
ADMINAPI=http://kong-aws-cp-kong-admin:8001
PROXY=http://kong-aws-cp-kong-proxy:80
```
Service、Route、Pluginを設定する。
```sh
curl -s -X POST ${ADMINAPI}/services \
  -H "Content-Type: application/json" \
  -d '{
    "name":"httpbin-svc",
    "url":"https://httpbin.org"
  }'
```
```sh
curl -s -X POST ${ADMINAPI}/services/httpbin-svc/routes \
  -H "Content-Type: application/json" \
  -d '{
    "name":"httpbin-rt",
    "paths":["/httpbin"]
  }'
```
```sh
curl -s -X POST ${ADMINAPI}/routes/httpbin-rt/plugins \
  -d name=dao-test
```

以上で検証の準備は整った。

続いて検証を行う。
テーブルを参照するためにまずPostgresのパスワードを環境変数に設定する。
```sh
PGPASSWORD=$(kubectl get secret -n kong kong-aws-cp-postgresql -o jsonpath={.data.password} | base64 -d)
```
設定したパスワードを使ってテーブルを参照する。
```
kubectl exec -it kong-aws-cp-postgresql-0 -- env PGPASSWORD=$PGPASSWORD psql -U kong -d kong -c "SELECT * FROM my_plugin_table;"
```
まだDBを操作するようなリクエストを投げていないため、テーブル内は空である。
```
 id | created_at | mykey
----+------------+-------
(0 rows)
```
次にDBにInsertするようなリクエストを発行する。
```sh
curl $PROXY/httpbin/ip -H mykey-add:test
```
再度テーブルを確認すると以下のように反映された。
```
                  id                  |     created_at      | mykey
--------------------------------------+---------------------+-------
 8c6d489b-b3cb-447d-a4ed-90f655e3ada0 | 2025-01-07 04:02:09 | test
```

Kubernetes上でも問題なくカスタムエンティティを利用することが出来た。
なお、途中でも触れたようにHybridモードではカスタムエンティティは更新できない。
KubernetesだとDBlessやHybridモードで展開する事が多いと思うので、そもそも要件やアーキテクチャ的に使えるのかどうかを確認した上で利用した方がよい。