Go で Kong Gateway カスタムプラグインを作成する

## この記事の目的

以下の記事で Kong Gateway の公式ドキュメントを読んで動作確認を行いました．

https://qiita.com/caunu-s/items/636aff1d04778fbbd779

こちらの記事では Kong Gateway のドキュメントから特にカスタムプラグイン作成について確認と検証をしてみます．

:::note info
こちらの記事を参考にして Kong Gateway (OSS) および Kong Gateway (Free) 用のカスタムプラグインを Go 言語を使って作成することができます．
:::

:::note warn
こちらの記事の動作確認は上記の記事による Kong Gateway 環境構築を前提としています．
:::

## 動作確認環境

動作確認を行った環境情報を以下に示します．

| Name | Version |
|----|----|
| Windows 10 | 22H2 |
| Ubuntu (WSL2) | 20.04 LTS |
| Docker | 20.10.17 |
| Docker Compose | v2.10.2 |
| Kong Gateway (OSS) | 3.5.0 |
| Go | 1.21.3 |
| go-pdk | v0.10.0 |

## Kong Gateway のカスタムプラグインと言語選択

https://docs.konghq.com/gateway/latest/plugin-development/

> lua-nginx-module enables Lua scripting capabilities in Nginx. Instead of compiling Nginx with this module, Kong is distributed along with OpenResty, which already includes lua-nginx-module. OpenResty is not a fork of Nginx, but a bundle of modules extending its capabilities.
> Hence, Kong is a Lua application designed to load and execute Lua modules (which we more commonly refer to as plugins) and provides an entire development environment for them, including an SDK, database abstractions, migrations, and more.

公式ドキュメントにある通り Kong Gateway は Lua モジュールを利用する Lua アプリケーションです．

Kong からは Lua の関数セットであるプラグイン開発キット (PDK) が提供されています．
PDK は複数の言語でサポートされています．

| Language | GitHub Link |
|----|----|
| Lua | https://github.com/Kong/kong/tree/master/kong/pdk |
| Go | https://github.com/Kong/go-pdk |
| JavaScript | https://github.com/Kong/kong-js-pdk |
| Python | https://github.com/Kong/kong-python-pdk |

言語選択と実装方法は「何の言語で書きたいか」と「パフォーマンス」を考慮して選択することになります (Go/Python ではマルチコアが動作可能だが，JavaScript では不可など)．

https://docs.konghq.com/gateway/latest/plugin-development/pluginserver/performance/

本記事では Go による Kong Gateway カスタムプラグインを作成してみます．
Go で作成されたプラグインはプロセスとして稼働し，Kong Gateway には Socket を登録することになります．

Lua によるカスタムプラグイン作成は，公式ドキュメントと以下の記事を参考にして進めてみるとよさげです．(筆者もそのうちやってみることとします...)

https://qiita.com/Naoto-raimi/items/5a67b4ca28bea5a8a694

参考 : **[Lua とは](https://www.lua.org/about.html)**

> Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description.

## Go による Kong Gateway のカスタムプラグインを利用する

こちらでは [go-pdk](https://pkg.go.dev/github.com/Kong/go-pdk) を使ってカスタムプラグインを作成し，Kong Gateway で利用できるようにします．

(go-pdk はドキュメントもまだ作成中で，最新版が v0.10.0 という状態ではありますが...)

### プラグインの作成

Kong Gateway の[公式ドキュメント](https://docs.konghq.com/gateway/3.5.x/plugin-development/pluginserver/go/)によると，以下の実装が必要になります．

- 取り扱うデータを含める ```structure``` を作成
- ```New()``` で ```structure``` を利用するインスタンスを作成
- ```structure``` を利用するメソッドを作成
- ```go-pdk/server``` ライブラリをインポート
- ```server.StartServer(New, Version, Priority)``` を呼ぶ ```main()``` 関数を実装
- ```go build``` でビルド

手順に従って Go ファイルを記述していきます．
ここでは HTTP ヘッダに ```foo: bar``` を含めるプラグインを作成します．

```shell: go-pdk を使うリポジトリを作成 & Go ファイル作成
mkdir -p $HOME/codes/GitHub/kong-go-sample-plugin && cd $_ && \
go mod init kong-go-sample-plugin && \
go get github.com/Kong/go-pdk
vim add_header.go
```

```golang: add_header.go
package main

import (
	"github.com/Kong/go-pdk"
	"github.com/Kong/go-pdk/server"
)

func main() {
	server.StartServer(New, Version, Priority)
}

const Version = "1.0.0"
const Priority = 1

type MyConfig struct {
	FieldValue string
 }

func New() interface{} {
	return &MyConfig{}
}

func (conf MyConfig) Access(kong *pdk.PDK) {
	value := conf.FieldValue
	if value == "" {
		value = "bar"
	}
	kong.Response.AddHeader("foo", value)
}
```

go-pdk が提供している関数は go-pdk のファイル群から探します． 
例えば ```AddHeader``` は [go-pdk/response/response.go](https://github.com/Kong/go-pdk/blob/master/response/response.go) に記載されています．

参考にリクエストとレスポンスに関する使いそうな関数を以下の表に書き出しておきます．

| File | Function | Argument | 
|----|----|----|
| [go-pdk/request/request.go](https://github.com/Kong/go-pdk/blob/master/request/request.go) | GetScheme | () |
|  | GetHost | () |
|  | GetPort | () |
|  | GetForwardedScheme | () |
|  | GetForwardedPort | () |
|  | GetHttpVersion | () |
|  | GetMethod | () |
|  | GetPath | () |
|  | GetQuery | (max_args int) |
|  | GetHeader | (k string) |
| [go-pdk/response/response.go](https://github.com/Kong/go-pdk/blob/master/response/response.go) | GetStatus | () |
|  | GetHeader | (name string) |
|  | GetSource | () |
|  | SetHeader | (k string, v string) |
|  | AddHeader | (k string, v string) |
|  | ClearHeader | (k string) |

``` Access(kong *pdk.PDK)``` では実行するフェーズを指定しています．
利用できるものは以下です．[^KONG/CONTEXTS]

[^KONG/CONTEXTS]: https://docs.konghq.com/gateway/latest/plugin-development/custom-logic/#available-contexts

| Handler | Phase | 
|----|----|
| Certificate | SSL ハンドシェイク時 |
| Rewrite | リクエストが届いてからプロキシする前 (Global設定時のみ) |
| Access | リクエストが届いてからプロキシする前 |
| Response | 上流 API からのレスポンスが届いた時 |
| Preread | リクエストが届く前 (不正確かもしれません...) |
| Log | 最後の応答がクライアントに送信された時 |

続いてビルドします．

```shell: Go プラグインビルド
go mod tidy
go build
ls
> add_header.go  go.mod  go.sum  kong-go-sample-plugin
```

### プラグインを有効化

ビルドしたプラグインを Kong Gateway コンテナに渡す方法については，

- プラグインを含めた Kong Gateway コンテナイメージを作成する
- プラグインをマウントボリュームに配置する

が一般的かと思いますが，ここでは簡単のため直接コンテナにコピーします．
また Kong Gateway 設定ファイル ```kong.conf``` にプラグイン情報を記載し，コンテナにコピーします．

```shell: Kong Gateway コンテナにプラグインと設定ファイルをコピー
docker cp ./kong-go-sample-plugin kong-kong-1:/usr/local/bin/kong-go-sample-plugin
vim kong.conf
docker cp ./kong.conf kong-kong-1:/etc/kong/kong.conf
```

```conf: kong.conf
plugins = bundled,kong-go-sample-plugin
pluginserver_names = kong-go-sample-plugin

pluginserver_kong_go_sample_plugin_socket = /usr/local/kong/kong-go-sample-plugin.socket
pluginserver_kong_go_sample_plugin_start_cmd = /usr/local/bin/kong-go-sample-plugin
pluginserver_kong_go_sample_plugin_query_cmd = /usr/local/bin/kong-go-sample-plugin  -dump
```

※ [Write plugins in Go](https://docs.konghq.com/gateway/latest/plugin-development/pluginserver/go/) には記載ありませんが ```plugins = bundled,kong-go-sample-plugin``` の追加が必要です．特に ```bundled``` を除くと[前回の記事](https://qiita.com/caunu-s/items/636aff1d04778fbbd779)で利用したものが使えなくなります．

Kong Gateway 再起動後にカスタムプラグインを有効化して動作確認をします．

```shell: カスタムプラグインの有効化
docker compose --profile gateway restart

curl -i -X POST http://localhost:8001/plugins \
 --data name=kong-go-sample-plugin
> HTTP/1.1 201 Created
> ...

curl --head -X GET http://localhost:8000/mock/api/timezone/Asia/Tokyo
> HTTP/1.1 200 OK
> Content-Type: application/json; charset=utf-8
> Content-Length: 346
> Connection: keep-alive
> foo: bar
> ...
```

以上で Go で作成した Kong Gateway カスタムプラグインが正常に起動していることが確認できました．
なお，プラグインを有効化すると管理 GUI でも ```kong-go-sample-plugin``` プラグインの管理ができるようになります．

![Kong-Go-Custom-Plugin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/b6cd77a2-5d07-8dca-8a96-1f42b278de87.png)

また，Kong Gateway コンテナの中に入ると ```kong-go-sample-plugin``` がプロセスとして動いていることも観察できます．

```shell
docker exec -it kong-kong-1 /bin/bash
> ps -aux
>> USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
>> kong         1  0.0  0.6 399560 53040 ?        Ss   07:49   0:01 nginx: master process /usr/local/openresty/nginx/sbin/n
>> kong      1288  0.6  1.3 458132 107504 ?       S    07:49   0:09 nginx: worker process
>> kong      1289  0.2  1.1 446892 95352 ?        S    07:49   0:04 nginx: worker process
>> kong      1290  0.2  1.1 440324 89900 ?        S    07:49   0:03 nginx: worker process
>> kong      1291  0.2  1.1 440516 90128 ?        S    07:49   0:04 nginx: worker process
>> kong      1292  0.0  0.1 1603596 12468 ?       Sl   07:49   0:00 /usr/local/bin/kong-go-sample-plugin
>> kong      3840  0.1  0.0   4492  3852 pts/0    Ss   08:13   0:00 /bin/bash
>> kong      3906  0.0  0.0   7060  1608 pts/0    R+   08:13   0:00 ps -aux
```

## まとめ

今回は Go による Kong Gateway のカスタムプラグインを作成しました．

go-pdk が整ってくるとより開発の幅が広がったり，わかりやすいドキュメントが公開されたりしてくるのかなと思います．

カスタムプラグインを使って Kong Gateway をより高機能に使っていきたいと思います！