Terraformを使ったKonnect環境の構築

Kong GatewayのSaaS版である[Konnect](https://cloud.konghq.com/)に対応したTerraform ProviderがひっそりとGitHubに[公開されている](https://github.com/Kong/terraform-provider-konnect)。
'24/6/13現在まだベータ版であり[Terraformのページ](https://registry.terraform.io/providers/Kong/konnect/latest)ではドキュメントも公開されていない状態だが、使おうと思えば使える状態なので試しに使ってみる。

※'24/10/8追記：'24/9/12に1.0が[リリースされました](https://konghq.com/blog/product-releases/managing-kong-with-terraform)。ドキュメントも今は揃ってます。

ここでは以下をTerraformでやってみる
1. ControlPlaneの構築
2. ServiceとRouteの作成
3. レート制限の設定

なお、DataPlaneに関してはKonnect APIの管轄外なのでKonnectのProviderからデプロイせず、別で行う必要がある（Cloud Gatewayの場合はデプロイ可能）。
別のProviderを使ってTerraform化は出来そうだがここでは対象外とした。
あと今回使うtfファイル・ディレクトリの配置に関しては適当なので、好みに合わせて適宜修正して欲しい。

## terraform-provider-konnectとは
Kong KonnectのためのTerraform Providerで、以下のようなものの自動構築が可能となっている（らしい）。
- Control Planeの構築
- Cloud Gatewayの構築
- Kong Gatewayの基本コンポーネント（Service、Route、Upstream、Consumer等）の設定
- 一部のPluginの設定（AI関連や認証、レート制限、キャッシュ等範囲は広い）
- その他もろもろ（Dev Portal、API Product、Organization、etc）

Konnectを自動構築する場合、これまでは[`deck`](https://docs.konghq.com/deck/latest/reference/entities/)で行うことが一般的だったが新しい選択肢が増えたといえる。

なお、`deck`と比較するとこんな感じじゃないかと思われる。
||deck|Terraform|
|---|---|---|
|基本機能のカバー範囲|Keyなどは含まれない|Keyなど含んでやや広い|
|ControlPlane/DataPlaneのデプロイ|出来ない|出来る(DPはCloudGatewayのみ)|
|Plugin|完全サポート|特定のもののみ|
|サポートプラットフォーム|Kong Gateway/Konnect|Konnect＋その他色々なプラットフォーム|
|実装の容易さ|慣れれば簡単|ドキュメントなく難|
|移植(Konnect↔KongGateway)の容易さ|コード修正不要|別でコードが必要|
|CI/CDとの相性|バッチリ|バッチリ|

Terraform Providerだとドキュメントがないのとサポートプラグインが限られるのが非常に痛いが、ここが改善されると`deck`を置き換えても良さそう。


## 前提

以下が用意されているものとする。

- Konnectのアカウントがある
- Konnect上にOrganizationを作成済み

Konnectは無料枠でも問題ない。メールアドレスさえあれば利用できる。

## 構築・設定
KonnectのTerraform Providerを使って構築・設定を行っていく。
最終的には以下のファイルを作成する。
```
.
├── main.tf
├── modules
│   ├── control-planes
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── provider.tf
│   └── gateway-configs
│       ├── httpbin-ratelimit.tf
│       ├── httpbin-rt.tf
│       ├── httpbin-svc.tf
│       ├── main.tf
│       ├── provider.tf
│       └── variables.tf
├── provider.tf
└── variables.tf
```
なお、上記のファイルは以下に置いたので、実際に試したい人で作成するのが面倒であればそちらから入手して欲しい。
[konnect-provider-sample](https://github.com/imurata/konnect-provider-sample)

### Terraform CLIの導入
[この辺](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli#install-terraform)を参考に自PCにCLIをインストールする。
ここではMacの手順で進める。
```sh
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
```
### 基本となるファイルの作成

KonnectへのAPIの発行はKonnectのPAT(Personal Access Token)を使う。
持っていない人は[KonnectのPersonal Access Tokenのページ](https://cloud.konghq.com/global/account/tokens)に飛んで、`Generate Token`でTokenを生成する。
次にトークンを環境変数に設定する。
`TF_VAR_`がついた環境変数は`terraform`コマンド実行時に変数として扱えるので、以下のような感じでトークンを設定する。
```sh
export TF_VAR_konnect_token=kpat_Qbzexxxxx
```

変数を定義する`variables.yaml`を作成する
```tf:variables.yaml
variable "konnect_token" {
  description = "Konnect PAT"
  type = string
  sensitive = true
}
```
Terraform Providerを定義する。
```tf:provider.tf
terraform {
  required_providers {
    konnect = {
      source  = "kong/konnect"
    }
  }
}

provider "konnect" {
  personal_access_token = var.konnect_token
  server_url            = "https://us.api.konghq.com"
}
```

一旦ここで`terraform init`を実行してProviderが使えるか確認する。
```sh
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of kong/konnect...
- Installing kong/konnect v0.2.5...
：（省略）
Terraform has been successfully initialized!
：（省略）
```
上記のように出力されればOK。


### ControlPlaneの構築
Control Planeのtfファイルの格納用ディレクトリを作成する。
```sh
mkdir -p modules/control-planes
```
またこちらの下にもprovider.tfを配置する。
```tf:modules/control-planes/provider.tf
terraform {
  required_providers {
    konnect = {
      source  = "kong/konnect"
    }
  }
}
```

ControlPlaneは`konnect_gateway_control_plane`で定義できる。
サンプルを参考に以下のように作成した。
```tf:modules/control-planes/main.tf
resource "konnect_gateway_control_plane" "tfdemo" {
  name         = "Control Plane by Terraform"
  description  = "Created by Terraform"
  cluster_type = "CLUSTER_TYPE_HYBRID"
  auth_type    = "pinned_client_certs"
}
```
少し補足する。
`cluster_type`はControlPlaneをデプロイする時のタイプを指定するもので、以下の4️つから選択できる。
- `CLUSTER_TYPE_CONTROL_PLANE`：Cloud Gateway用？
- `CLUSTER_TYPE_CONTROL_PLANE_GROUP`：Control Plane Groupを選択したのと同じ
- `CLUSTER_TYPE_HYBRID`：Kong Gateway(	Self-Managed Gateway)を選択したのと同じ
- `CLUSTER_TYPE_K8S_INGRESS_CONTROLLER`：Kong Ingress Controllerを選択したのと同じ

`auth_type`は[ControlPlaneとDataPlaneの通信方法](https://docs.konghq.com/konnect/gateway-manager/data-plane-nodes/secure-communications/)を設定する箇所で、以下の2つから選択できる。
- `pinned_client_certs`：Pinned mode（鍵認証）で動作
- `pki_client_certs`：PKI mode（証明書による認証）で動作

Control Planeモジュールを読み込むための`main.tf`を作成する

```tf:main.tf
module "control-planes" {
  source = "./modules/control-planes"
}
```
モジュールを読み込ませる。
```
terraform init
```
設定を適用する。
```
terraform apply
```
問題なければ以下のようなメッセージが表示される。
```
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```
Konnectにアクセスすると、以下のように設定した内容でControl Planeが作成されていることが分かる。
![20240613141836.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/f9431b46-f071-f1c1-c05e-b8bd5b9e8412.png)



### ServiceとRouteの作成
次にServiceとRouteを設定する。
Service・Route側でControl Planeを参照できるよう、outputでControlPlaneを出力する。
```tf:output.tf
output "cp" {
    value = konnect_gateway_control_plane.tfdemo
}
```

Service・Route作成に使うtfファイルの格納用ディレクトリを作成する。
```sh
mkdir -p modules/gateway-configs
```
またこちらの下にもprovider.tfを配置する。
```tf:modules/gateway-configs/provider.tf
terraform {
  required_providers {
    konnect = {
      source  = "kong/konnect"
    }
  }
}
```
ControlPlaneを参照するための変数を用意する。
```tf:modules/gateway-configs/variables.tf
variable "control_plane" {
  type = any
}
```

次にServiceを作成する。Serviceはここでは[httpbin](https://httpbin.org/)を利用する。
作成するファイルは以下となる。
```tf:modules/gateway-configs/httpbin-svc.tf
resource "konnect_gateway_service" "httpbin" {
  name             = "HTTPBin"
  protocol         = "https"
  host             = "httpbin.org"
  port             = 443
  path             = "/"
  control_plane_id = var.control_plane.id
}
```
`control_plane_id`はどのControlPlaneと紐づけるかを指定するためにControlPlaneのIDを指定する。
ControlPlaneのリソースが`var.control_plane`に格納されるように後ほど設定する。
なお、おそらくIDのベタ書きでも行けるとは思う。

次にRouteを作成する。
```tf:modules/gateway-configs/httpbin-rt.tf
resource "konnect_gateway_route" "httpbin" {
  name    = "httpbin"
  paths   = ["/httpbin"]
  strip_path = true
  control_plane_id = var.control_plane.id
  service = {
    id = konnect_gateway_service.httpbin.id
  }
}
```
Serviceと同じ要領で`control_plane_id`はControlPlane作成時に設定される変数の情報を使って引っ張る。
`service.id`については同一モジュール内からリソース(`konnect_gateway_service.httpbin`)を直接参照してRouteを紐づける。
また、モジュールには`main.tf`が必要なので空ファイルを作成する。
```sh
touch modules/gateway-configs/main.tf
```
最後にroot直下のmain.tfに以下を追記する。
```tf:main.tf
module "gateway-configs" {
  source = "./modules/gateway-configs"
  control_plane = module.control-planes.cp
}
```
gateway-configsの中の`variables.tf`で定義した`control_plane`はここで設定した。

動かしてみる。
```sh
terraform apply
```
以下のような出力が得られるはずだ。
```sh
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```

Konnect側でもService、Routeが作成されたことが確認できる。
![20240613161625.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/26100faa-51c5-ebfb-ebde-aab9ea2baff6.png)

ここで動作確認用にDataPlaneもデプロイしてhttpbinにアクセスできるかを確認する。
DataPlaneのデプロイ方法についてはTerraformを使っていないため割愛する。
ここではMac上にDockerでデプロイした。

Data Planeが提供するProxy経由でアクセスしてみる。
```sh
$ curl -i localhost:8000/httpbin/ip
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 47
Connection: keep-alive
Date: Thu, 13 Jun 2024 07:24:01 GMT
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
X-Kong-Upstream-Latency: 731
X-Kong-Proxy-Latency: 1
Via: kong/3.7.0.0-enterprise-edition
X-Kong-Request-Id: 0c066f075523c4032279af617e1cb80c

{
  "origin": "192.168.127.1, xxx.xxx.xxx.xxx"
}
```
無事httpbinにアクセスできることが確認できた。

### レート制限の設定
最後にRate Limit Pluginを使ってレート制限を設定する。
Rate Limit Pluginはリソース`konnect_gateway_plugin_rate_limiting`で用意されているので以下のようにtfファイルを作成する。
```tf:modules/gateway-configs/httpbin-ratelimit.tf
resource "konnect_gateway_plugin_rate_limiting" "httpbin_rate_limiting_plugin" {
  enabled = true
  config = {
    minute = 5
    policy = "local"
  }
  protocols        = ["http", "https"]
  control_plane_id = var.control_plane.id
  route = {
    id = konnect_gateway_route.httpbin.id
  }
}
```
1分間のアクセス可能な上限を5回とした。
ServiceやRouteと書き方は変わらないので、ここでは書き方については補足説明は特にしない。
ファイル作成後、適用する。
```
terraform apply
```
Konnect上で適用されていることが分かる。
![20240613174732.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/79a15cc8-abb8-d025-cfb3-bfb0fe8a3edc.png)

動作確認する。6回アクセスしてみる。
```sh
for((i=0;i<6;i++)); do curl -I localhost:8000/httpbin/ip | grep "HTTP\|Limit"; sleep 1 ; done
```
6回目で以下が表示されて上限に達したことが分かる。
```sh
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit-Minute: 5
X-RateLimit-Remaining-Minute: 0
RateLimit-Remaining: 0
RateLimit-Limit: 5
RateLimit-Reset: 8
```
Pluginの適用も問題なさそうだ。

## 所感
Konnectの自動構築は`deck`でも出来るが、他の環境も含めてまとめて一元管理・まとめて構築したい場合はTerraformが使えるのは嬉しい。
まだドキュメントがなくて使うにはかなりキツイが、今後の発展を見守りたい。