Kong Ingress Controllerに入門する

前回、[Kong Gatewayに入門した](https://qiita.com/ipppppei/items/9b8d0cca52597a124bb1)が、KongのIngress Controller(KIC)でも似たような事が出来るようなので、それを検証した時のメモ。
以下の機能をKICの[Get Started](https://docs.konghq.com/kubernetes-ingress-controller/latest/get-started/)を元に一通り触ってみる。

- ロードバランシング
- レート制限
- Proxy Cache
- 鍵認証

## Kong Ingress Controllerとは
Kongが提供するIngress Controllerで、特徴としてKubernetesリソースの設定を読み取り、Kong Gateway側に設定を送って反映してくれる。
![1703736396271.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/0efcf814-de64-119d-54a1-9cce4b926c4f.png)
([Kong Ingress Controller
　- Architecture](https://docs.konghq.com/kubernetes-ingress-controller/3.0.x/)より引用)

要はKong Gatewayの全ての設定をManifestで実現できることになり、**GitOpsでKong Gateway（＝API）を設定・管理できるようになる**ことを意味する。
Tanzu Mission Controlと組み合わせれば、クラスタのデプロイ完了と同時にKong Gatewayの設定も完了、みたいなのも実現できそうだ。
あと、前回検証時は使っていなかったが、実はKIC自体がKong Gatewayを持っているため、Kong GatewayをデプロイしなくてもGatewayを利用することが出来る。（ただしUIはない）

## 検証時の前提

ここでは以下の環境があるものとして進める

- Kubernetesクラスタ（ここではTanzu Kubernetes Gridを利用）
- Kubernetesクラスタはtype: LoadBalancerが利用可能
- 作業端末にkubectl, helmコマンドがインストール済み

また、今回検証で使ったKICのバージョンは3.4である。

## 事前準備
KICをインストールする。
```
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/experimental-install.yaml
helm install kong-ingress --namespace kong --create-namespace --repo https://charts.konghq.com ingress
```


[こちらの手順](https://docs.konghq.com/kubernetes-ingress-controller/3.0.x/get-started/#install-the-gateway-apis)に従ってGatewayとGatewayClassを作成する。
```yaml
echo "
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: kong
  annotations:
    konghq.com/gatewayclass-unmanaged: 'true'

spec:
  controllerName: konghq.com/kic-gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: kong
spec:
  gatewayClassName: kong
  listeners:
  - name: proxy
    port: 80
    protocol: HTTP
" | kubectl apply -n dev -f -
```
また、Ingress ControllerがデプロイしたKong Gatewayを経由してエンドサービスにアクセスするため、`External-IP`で公開しているIPを環境変数に設定しておく。
```sh
export PROXY_IP=$(kubectl get svc -n kong kong-ingress-gateway-proxy -o jsonpath={.status.loadBalancer.ingress[0].ip})
```
## ロードバランシング
最初にロードバランシングする先を用意する。
アクセス先を用意するのが面倒だったので、`externalName`を使って適当な外部サービスを参照させる。`httpbin.org`と`httpbun.com`を使おうと思ったが、`httpbun.com`がHostヘッダを適切に設定しないと値を返してくれなかったので、こちらは代わりに`ipaddr.show`に変更した。
※ipaddr.showは聞き慣れないが、[こちらの方](https://fujish.hateblo.jp/entry/2015/10/26/001813)が作成したサービス
以下の`kind: Service`を作成する。
```yaml
cat << EOF > ./ipcheck-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: ipaddr
  namespace: dev
spec:
  type: ExternalName
  externalName: ipaddr.show
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  namespace: dev
spec:
  type: ExternalName
  externalName: httpbin.org
EOF
kubectl apply -f ./ipcheck-svc.yaml -n dev
```

次に`HTTPRoute`リソースを作成する。[こちら](https://gateway-api.sigs.k8s.io/api-types/httproute/#backendrefs-optional)によると`spec.rules[0].backendRefs`に宛先を複数書けるので、以下のような感じで`/new-mock`にアクセスするとhttpbinかhttpbunにアクセスする`HTTPRoute`を作成する。
```yaml
cat << EOF > ./httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: ipcheck
  annotations:
    konghq.com/strip-path: 'true'
spec:
  # Gatewayリソース
  parentRefs:
  - name: kong
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /new-mock
    backendRefs:
    - name: ipaddr
      kind: Service
      port: 80
    - name: httpbin
      kind: Service
      port: 80
EOF
kubectl apply -n dev -f ./httproute.yaml
```
`HTTPRoute`が作成されると、Kong Gateway側でもServiceやRouteが作成される。
```json
$ kubectl exec -it centos -- curl https://kong-ingress-gateway-admin.kong.svc:8444/routes -k | jq .
{
  "data": [
    {
      "id": "f61597cf-65e3-5da0-b4fb-60193a8102ba",
：（省略）
      "name": "httproute.dev.ipcheck.0.0",
      "snis": null,
      "methods": null,
      "sources": null,
      "paths": [
        "~/new-mock$",
        "/new-mock/"
      ],
：（省略）
```
分散先を確認したい場合はUpstreamのTargetを見るとよい。
```json
$ kubectl exec -it centos -- curl https://kong-ingress-gateway-admin.kong.svc:8444/upstreams/httproute.dev.ipcheck.0/targets -k | jq .
  "data": [
：（省略）
      "target": "ipaddr.show:80",
      "weight": 1,
：（省略）
      "target": "httpbin.org:80",
      "weight": 1,
：（省略）
```

アクセスしてみる。httpbin.org/ipとipaddr.show/ipで見え方が異なるため、何度かアクセスして見え方が変われば分散していることが確認できる。
```sh
$ curl $PROXY_IP/new-mock/ip
{
  "origin": "192.168.3.1, xxx.xxx.xxx.xxx"
}
$ curl $PROXY_IP/new-mock/ip
xxx.xxx.xxx.xxx
```
問題ないようだ。

### レート制限
レート制限などPluginを利用する場合、適用範囲に応じて2パターンの利用方法がある。
- 特定の対象にPluginを適用する場合
   1. [`KongPlugin`](https://docs.konghq.com/kubernetes-ingress-controller/latest/reference/custom-resources/#kongplugin)リソースを作成する
   2. 適用対象（`Service`、`HTTPRoute`など）の[`annotation`](https://docs.konghq.com/kubernetes-ingress-controller/latest/reference/annotations/)に`konghq.com/plugins=<作成したKongPluginの名前>`を設定する
- 全体（グローバル）にPluginを適用する場合
  1. [`KongClusterPlugin`](https://docs.konghq.com/kubernetes-ingress-controller/latest/reference/custom-resources/#kongclusterplugin)リソースを作成する

ここではレート制限をServiceに適用してみる。
以下のPluginを定義したManifestをapplyする。
```yaml
echo "
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: rate-limit-5-min
  annotations:
    kubernetes.io/ingress.class: kong
config:
  minute: 5
  policy: local
plugin: rate-limiting
" | kubectl apply -n dev -f -
```
`KongPlugin`リソースでは`spec`がなく、`config`でPlugin固有の設定を書いていく。
また、`plugin`でPluginを指定する。
まだこの段階ではPluginを定義しただけで、実際のサービスとは紐づいていな異状態である。
このPluginを使うようにKubernetesリソースを修正する。
最初`Service`の`annotation`を変更して適用しようとしたのだが、何故か適用するとroutesが見れなくなってルーティングが出来なくなってしまったので、`HTTPRoute`の方を変更する。
```sh
kubectl annotate httproute ipcheck konghq.com/plugins=rate-limit-5-min  -n dev
```
設定適用後、6回アクセスすると6回目に失敗することが確認できる。
```sh
$ curl $PROXY_IP/new-mock/ip
xxx.xxx.xxx.xxx
$ curl $PROXY_IP/new-mock/ip
{
  "message":"API rate limit exceeded",
  "request_id":"4d6f884b9495c702d9fc690df40e2323"
```

### Proxy Cache
Proxy CacheもManifestで設定できる。
ここではGet Startedに従い、`KongClusterPlugin`リソースで定義してグローバルにキャッシュを効かせるようにする。
早速Manifestを適用する。TTLのところだけ、300秒だと検証しづらくなるので30秒に変更した。
```yaml
echo '
apiVersion: configuration.konghq.com/v1
kind: KongClusterPlugin
metadata:
 name: proxy-cache-all-endpoints
 annotations:
   kubernetes.io/ingress.class: kong
 labels:
   global: "true"
plugin: proxy-cache
config:
 response_code:
 - 200
 request_method:
 - GET
 - HEAD
 content_type:
 - text/plain; charset=utf-8
 cache_ttl: 30
 strategy: memory
' | kubectl apply -f -
```
なお、httpbinにアクセスする場合、どうもBypassされてしまうようなので、ipaddrにのみアクセスするようServiceを削除する。
```sh
kubectl delete svc httpbin -n dev
```
確認する。
```sh
$ curl $PROXY_IP/new-mock/ip -i 2>&1 | grep -i cache-status
X-Cache-Status: Miss
$ curl $PROXY_IP/new-mock/ip -i 2>&1 | grep -i cache-status
X-Cache-Status: Hit
```
初回はMissとなり、2回目はHitとなった。
また、30秒程度放置し再実行すると`Miss`となりTTLが効いていることも確認できる。

### 鍵認証
最後に鍵認証も試してみる。
コンシューマがカスタムリソース[`KongConsumer`](https://docs.konghq.com/kubernetes-ingress-controller/latest/reference/custom-resources/#kongconsumer)として用意されているので、以下のような流れで試していく。
1. `KongPlugin`で鍵認証用のPluginを定義
2. `KongConsumer`でユーザと鍵を定義
3. 鍵を使ってアクセス

最初に鍵認証のPluginを作成する。ManifestはGet Startから引用した以下となる。
```yaml
echo "
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: key-auth
plugin: key-auth
config:
  key_names:
  - apikey
" | kubectl apply -n dev -f -
```
これを`HTTPRoute`に適用する。`annotation`にはカンマ区切りで複数のPluginを指定することが出来る。
```sh
kubectl annotate httproute ipcheck konghq.com/plugins=rate-limit-5-min,key-auth --overwrite
```
`annotation`を変更するとすぐにアクセスできなくなる。
```sh
$ curl $PROXY_IP/new-mock/ip
{
  "message":"No API key found in request",
  "request_id":"49e03858394ffd7f98f1690bb8354d7f"
}
```
次に鍵を作成する。`Secret`で鍵の種類(`kongCredType`)と鍵の内容(`key`)を指定する。
```sh
kubectl create secret generic alex-key-auth \
  --from-literal=kongCredType=key-auth \
  --from-literal=key=hello_world
```
`Secret`というのがイケていないが、Kongは`Secret`のバックエンドに[AWS SecretManagerやVaultをサポートしている](https://docs.konghq.com/gateway/latest/kong-enterprise/secrets-management/backends/)ので、多分置き換えることも出来ると思われる。
鍵を作成したら、`KongConsumer`を作成する。
```yaml
echo "apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
  name: alex
  annotations:
    kubernetes.io/ingress.class: kong
username: alex
credentials:
- alex-key-auth
" | kubectl apply -f -
```
`credentials`に先ほど作成した`Secret`を渡している。
接続してみる。
```sh
$ curl $PROXY_IP/new-mock/ip -H 'apikey: hello_world'
xxx.xxx.xxx.xxx
```
問題なく接続できた。
キーを変更するとエラーになる。
```sh
$ curl $PROXY_IP/new-mock/ip -H 'apikey: hello'
{
  "message":"Invalid authentication credentials",
  "request_id":"7ca9a84eed124b100182f440694a2154"
}
```
鍵認証も正常に動作した。

## まとめ
KICがあればKong Gatewayの構築から管理までManifestで簡単に実現できることが確認できた。
また、よくあるKubernetesリソースになると機能制約が発生する、といったものも見当たらなかったので、普通にKong Gatewayとして使えそうだった。
Manifestで宣言的に用意できるということで、GitOpsでの利用も容易そうで、KubernetesのCloudNativeな作りと上手くマッチしそうなツールだと思う。