ElastiCache for RedisをKong Gatewayの流量制限に利用する

Kong Gatewayの3.10からRedisの設定をControl Plane全体で共有出来るようになった。
今回はこの設定方法を実際に触って確かめてみる。
あと、これを使うだけだとボリュームに欠けるので、ElastiCache for Redisを使ってAWS環境で簡単に設定できることを確認する。
なお、まだKong Gatewayは正式にValkeyを[サポートしていない](https://docs.konghq.com/gateway/latest/support/third-party/#data-stores)ので、ここではRedis OSSで進める。

## 前提
以下を前提として検証する。
- Data PlaneはEC2上にDockerで構築済み
- Konnectが利用可能

Data PlaneはDockerで起動しているが、バイナリでインストールしていてもEKSでも問題ないと思う。
Konnectについても便宜上利用しているがEnterprise版を使っても問題ない（設定方法も同じ）。

## ElastiCache for Redisの導入
### AWS側での設定
最初にElastiCacheに割り当てるセキュリティグループを作っておく。
接続元は同一VPC内のData Planeが稼働しているEC2になるので、そのEC2が使っているSGを許可するセキュリティグループを作成する。
![Pasted image 20250606120845.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/f57441ab-93b7-44a5-8199-b608aeba9f85.png)

ElastiCacheから`Redis OSSキャッシュ`をクリックし、`キャッシュを作成`でRedis OSSキャッシュを作成する。
この際、Valkeyの選択を強く勧められるが、Redis OSSで進めていく。
キャッシュの設定そのままで名前とセキュリティグループだけ設定して作成する。
![Pasted image 20250606102023.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b5bdc5d7-7dcc-4fde-a6ff-a22aa90dc4f6.png)

ただ、自分の環境ではデフォルト設定だとサブネットを3つ以上用意しているのに以下のエラーが出た。
```
Customer account VPC should have a minimum of 3 default subnets.
```
この場合、デフォルト設定をカスタマイズし、接続性のところにあるアベイラビリティゾーンを全て選択すれば作成できる。
![Pasted image 20250606102249.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/338556ee-1590-4b92-844a-f559bce8dc5e.png)

作成後、エンドポイントをメモっておく。

### 接続確認
Data PlaneからElastiCache for Redisに接続するため、Data Planeから接続できることを確認する。
[AWSで記載されている手順](https://docs.aws.amazon.com/ja_jp/AmazonElastiCache/latest/dg/nodes-connecting.html)で`valkey-cli`をビルドして接続する方法もあるが、これだとTLSをサポートしないようなので、パッケージマネージャ経由で`redis-cli`をインストールして利用する。
```bash
sudo apt install redis-tools
```
同一VPC内のEC2から以下のような感じで接続する。
```bash
redis-cli --tls -h imurata-redis-cache-xxx.serverless.use1.cache.amazonaws.com -p 6379
```
適当にコマンドを叩いて、応答があればOK。
```bash
> ping
PONG
```
## Konnect側の設定
### Shared Redis Configrationの設定

Redisの設定はControl Plane全体で共有出来るようになっており、KonnectのControl Planeの左サイドバーから`Redis Configurations`を選択することで設定できるようになっているので、`New configuration`をクリックして足してみる。
![Pasted image 20250606112022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/3916c51c-e418-45d3-a9a7-6db29f9366b8.png)

ここでは以下のように設定する。
- Redis type：`Host/Port(Enterprise)`
- Name：`aws-redis-oss`
- Host：`作成したAWSのRedisのエンドポイント`　※portは指定しない
- SSL：`true` ※GUI上ではチェックする
- SSL Verify：`true` ※GUI上ではチェックする

その他はデフォルト設定を利用する。
保存するとRedis設定時に参照できるようになる。
なお、Rate Limiting Advanced Pluginなどプラグインによっては`Host/Port(OSS)`だとプラグイン側から設定時に選べなかったりするので注意。
あとRedisに対してヘルスチェックとかはしてくれないので、本当に単に設定値をスニペット的に保存したものだと考えておくと良さそう。

### Service/Routeの作成
以下の仕様で作成した。作成方法は割愛。
- ServiceのHost：`https://httpbin.org`
- RouteのPaths: `/httpbin`

### Rate Limiting Advanced Pluginの設定
Rate Limiting Advanced Pluginはアクセスしてきた回数に応じてリクエストを許可する・拒否する、といった挙動をするが、このアクセス回数を保存するのにRedisを使用する。
ここではRate Limiting AdvancedをRouteに対して以下のように設定した。
- Rate Limit Window Type：`Sliding`
- Limit：`3 Every 60 Seconds` ※直前60秒で3回までOK
- Strategy：`redis`
- Redis Configuration：`Use shared configuration`
- Shared Redis Configuration：`aws-redis-oss`

共有のRedisの設定を使うかどうかはStrategyに`redis`を選択すると表示が変わって新たに聞かれるようになる。
![Pasted image 20250606140456.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/3ed0e663-bafa-4cf0-8991-d55441493ac9.png)

設定を保存する。

## 動作検証
### カウンタの動作確認
リクエストを何度か飛ばしてみる。
```bash
watch curl localhost:8000/httpbin/get
```
また、その裏でRedisに接続して回数がカウントされているか見てみる。
設定が上手く行っていれば、しばらくすると`curl`の方は以下の出力となり、制限されたことが分かる。
```json
{"message":"API rate limit exceeded"}
```
またRedisの方では`scan 0 count 10` みたいな感じでキーの一覧を取得してみる。
カウンタがRedisに載っていれば以下のような感じでキーが取得できる。
```bash
> scan 0 count 10
1) "0"
2) 1) "1749186420:60:AorINR0pbnfQBl6lvDm6ESmIFY6hMPfb"
```
`HGETALL`で中身を見てみる。
```bash
> HGETALL 1749186420:60:AorINR0pbnfQBl6lvDm6ESmIFY6hMPfb
1) "172.17.0.1"
2) "30"
```
カウンタの対象（Identifier、デフォルトはConsumerだがConsumerの指定がない場合はIPとなる）と回数らしきものが確認できた。
上手く動いているようだ。

なお、設定が上手く行っていない場合はData Planeのログに以下のようなメッセージが出るので、ログも確認しておくとよい。
```bash
2025/06/06 04:32:55 [error] 2624#0: *2813 [lua] init.lua:70: log(): [rate-limiting-advanced] error in fetching counters for namespace AorINR0pbnfQBl6lvDm6ESmIFY6hMPfb: failed to connect to redis: connection refused, context: ngx.timer
```

### deckの動作確認
deckによる設定のバックアップ・リストアが可能かも確認しておく。
`deck gateway dump`で設定を見ると、以下のような形でRedisの設定が確認できる。
```yaml
- config:
    cluster_max_redirections: 5
    cluster_nodes: null
    connect_timeout: 2000
    connection_is_proxied: false
    database: 0
    host: imurata-redis-cache-xxx.serverless.use1.cache.amazonaws.com
    keepalive_backlog: 0
    keepalive_pool_size: 256
    password: null
    port: 6379
    read_timeout: 2000
    send_timeout: 2000
    sentinel_master: null
    sentinel_nodes: null
    sentinel_password: null
    sentinel_role: null
    sentinel_username: null
    server_name: null
    ssl: true
    ssl_verify: true
    username: null
  id: 97e43fc4-3916-4bae-9b74-72f6658fe68e
  name: aws-redis-oss
  type: redis-ee
```
設定をバックアップし、現環境を破棄した後リストアしてみる。
なお、`~/.deck.yaml`にKonnect接続のための情報を設定済みなので、`deck`の引数にトークンや接続先は含んでいない。
```bash
deck gateway dump -o /tmp/backup.yaml
deck gateway reset
deck gateway sync /tmp/backup.yaml
```
Konnectで設定を見ると復元されていることが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/e05a21da-f8d6-42ca-81e9-2d5b3884cf37.png)

deckの利用も問題なさそうだ。

## 所感
KongのプラグインでRedisの設定をするのは毎回面倒だったが、一度設定を作成したら様々なプラグインに使い回せるのは便利だと思う。
KongのプラグインではRate Limiting Advanced Plugin以外にもCache Advanced、ACME、OIDCなど色んなプラグインでRedisが使えるので、活用シーンは多そうだ。
あとAWSのRedisの払い出しも簡単だったので、スクラッチから始める場合でもAWSであれば簡単に今回の構成が作れそうなのもありがたい。