Kong Prometheus Pluginで非Promethus対応のサービスのメトリクスを取得する

[Kong Gateway](https://github.com/Kong/kong)はAPIのGatewayとして流量制御や認証認可に使われるが、サービスのメトリクスを取得するのにも使うことが出来る。
これを利用すると、Prometheus向けのメトリクスを実装していないアプリケーションのメトリクスも取得することが出来るため、レガシーなアプリケーションの監視集約などにも利用することが出来る。
今回はこれを試してみる。

なお、ここでの検証構成は以下のような構成とする。
![1711086698341.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/c3b71f6a-a967-0792-0f7c-2c8d6d915820.png)
Prometheus/GrafanaをKong GWと同居させた方が楽だが、あまりリアリティがない感じがするのでクラスタを分けて確認する。
なお、KubernetesにはvSphere with Tanzu（Kubernetes：v1.25.7）を利用し、`type: LoadBalancer`が利用できる環境で試している。
Kong Gateway、Prometheus/Grafanaの構築方法はそれぞれ過去に書いた[ここ](https://qiita.com/ipppppei/items/44c100412d4e484f4f6a)と[ここ](https://qiita.com/ipppppei/items/c15acc5c7f7af3e7c289)の方法で構築し、Kong Gateway構築時にあわせてKong Ingress Controller（以下KIC）も構築している。
構築に不安がある人はそれぞれのリンクを参照して確認するとよい。

## Kong Promethues Plugin
Kong単体でもメトリクスを表示することは出来るが、メトリクスの表示項目を増やしたりProxyしたサービスのメトリクスを取得するには[Prometheus Plugin](https://docs.konghq.com/hub/kong-inc/prometheus/)を利用する必要がある。
Prometheus PluginはKong Gatewayの中に[内包されており](https://github.com/Kong/kong/tree/master/kong/plugins/prometheus)、Kong Gatewayの中でメンテナンスされている。
これを利用するとざっくりと以下のメトリクスを確認する事が出来る。
- KongのDBに対するリーチャビリティ
- nginx
- DataPlaneノードの状態
- ライセンス情報
- DBのエンティティ数（※Enterprise版のみ）
- サービスごとの各種情報（デフォルトでは無効）
  - HTTPステータスコード
  - レイテンシ
  - 帯域幅
  - ヘルスチェック 

今回はこの中で「サービスごとの各種情報」を確認していく。

## 準備
### Prometheus Pluginの導入
Prometheus Pluginの導入方法はいくつかあるが、ここではKICのカスタムリソースを使って導入する。
KICを導入するとPluginをKubernetesのリソースとして定義できるようになり、Manifestを適用することでPluginを追加することが出来る。
ここではクラスタ単位で導入できる[`kind: KongClusterPlugin`](https://docs.konghq.com/kubernetes-ingress-controller/latest/reference/custom-resources/#kongclusterplugin)を使ってPrometheus Pluginを導入してみる。
利用時の注意点は以下となる。
- `spec`がなく、`plugin`で使用するプラグインを定義し、`config`でプラグインの設定を定義する
- `metadata.labels`に`global: true`を書かないとKong GatewayのGlobal Pluginとして扱われない
- AnnotationでKICの`IngressClass`を明示する(`kubernetes.io/ingress.class: <IngressClass>名`を書く)必要がある

Annotationの制約については記載されている箇所が見つからなかったのだが、動作検証している際に省くとプラグインが認識されなかったので必須だと思われる。
以下を実行してPluginを適用する。
```yaml
cat <<EOF | kubectl apply -f -
apiVersion: configuration.konghq.com/v1
kind: KongClusterPlugin
metadata:
  name: prometheus
  annotations:
    kubernetes.io/ingress.class: kong
  labels:
    global: "true"
config:
  status_code_metrics: true
  bandwidth_metrics: true
  upstream_health_metrics: true
  latency_metrics: true
  per_consumer: false
plugin: prometheus
EOF
```
なお、Prometheus Pluginの設定の説明はここでは省いている。
必要に応じて[Prometheus Configuration](https://docs.konghq.com/hub/kong-inc/prometheus/configuration/)で確認するとよい。
カスタムリソースの仕様上、適用しても上手く適用されているかどうかは`kubectl`では確認できないため、`deck`やKong ManagerのUIから確認する。
ここでは`deck`で確認する。
```sh
export DECK_KONG_ADDR=https://kong-admin.10-214-154-169.nip.io
export DECK_TLS_SKIP_VERIFY=true
deck gateway dump | yq eval '.plugins'
```
出力結果は以下となった。
```yaml
- config:
    bandwidth_metrics: true
    latency_metrics: true
    per_consumer: false
    status_code_metrics: true
    upstream_health_metrics: true
  enabled: true
  name: prometheus
  protocols:
    - grpc
    - grpcs
    - http
    - https
  tags:
    - k8s-name:prometheus
    - k8s-kind:KongClusterPlugin
    - k8s-uid:bea56d0b-ab98-489d-87ab-649f9355c047
    - k8s-group:configuration.konghq.com
    - k8s-version:v1
    - managed-by-ingress-controller
```
適切に入っているようだ。

### Kong Gatewayのスクレイピング
PrometheusをPrometheus Operatorでデプロイした場合、外部のサービスのスクレイピング設定を追加で行う場合は[`Additional Scrape Configuration`](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/additional-scrape-config.md)を使って既存リソースの構成を変更せずに外部から`Secret`で設定するか、[`kind: ScrapeConfig`](https://prometheus-operator.dev/docs/user-guides/scrapeconfig/)で新しいKubernetesリソースを定義して追加する方法がある。
（※同一クラスタ内のサービスのスクレイピングなら`kind: ServiceMonitor`を使うが、今回はクラスタを分けているため対象外）
ここでは後者の`ScrapeConfig`を使った方法で追加する。

Prometheus OperatorでデプロイされたPrometheusは`kind: Prometheus`として定義されており、`Prometheus`オブジェクト内の`spec.scrapeConfigSelector`でどの`ScrapeConfig`をスクレイプするかのルールが設定されているので確認する。
```sh
$ kubectl get prometheus -n prometheus-stack prometheus-stack-kube-prom-prometheus -o yaml | yq eval .spec.scrapeConfigSelector
matchLabels:
  release: prometheus-stack
```

上記の場合は`release: prometheus-stack`をラベルに指定する必要がある。
以下が今回スクレイプのために適用する`ScrapeConfig`のManifestとなる。
```yaml
KONG_METRICS=kong-admin.10-214-154-169.nip.io/metrics
cat <<EOF > ./scrape-kong.yaml
apiVersion: monitoring.coreos.com/v1alpha1
kind: ScrapeConfig
metadata:
  name: scraping-kong
  labels:
    release: prometheus-stack
spec:
  metricsPath: /metrics
  scheme: HTTPS
  tlsConfig:
    insecureSkipVerify: true
  staticConfigs:
    - labels:
        job: kong-gateway
      targets:
        - $KONG_METRICS
EOF
```
Kong GatewayをTLS化して立てているのでHTTPSとしてスクレイピングするよう設定しているが、HTTPで立てている場合は`scheme`を`HTTP`にして`tlsConfig`を削除する。
これを適用するとPrometheus側で自動で設定変更してくれる。
```sh
kubectl apply -f ./scrape-kong.yaml
```
設定適用後、最大で1分程度待てば以下のようにPrometheusのTargetsにエンドポイントが表示されるようになる。
![1711344427212.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/559e99b9-23f7-c54c-ca9c-715a9455b434.png)
Expression browserからも確認できる。
![1711344497430.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/84b4c07a-7c4e-bfd6-6354-3d782f7c25af.png)

### GrafanaでダッシュボードでKongのメトリクスを可視化
次にメトリクスの可視化を行う。
オフィシャルのダッシュボードが[ここ](https://grafana.com/grafana/dashboards/7424-kong-official/)で配布されているのでこれを使う。
Grafanaにログインし、左サイドバーの`Dashboards`から`New`->`Import`を選択する。
ここで[ダッシュボード配布先](https://grafana.com/grafana/dashboards/7424-kong-official/)のID`7424`を入力し、Loadをクリックする。
![1711345063576.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5ff0563d-2ad8-73ea-1cf0-1a166a4e7215.png)
LoadされたらdatasourceにPrometheusを選択して`Import`をクリックする。
すると、それぞれのメトリクスのためのダッシュボードが利用できるようになる。
![1711345233800.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/d593e99b-ab6a-e44d-9a50-a7a2bb46cc96.png)
配布されているダッシュボードでは上記のように以下の6つの項目についてダッシュボードが用意される。
- リクエストレート
- レイテンシ
- 帯域幅
- キャッシュ
- Upstream
- Nginx

なお、Prometheusの仕組み上、メトリクスが発生しないとそのメトリクスは出てこないので、動作確認する場合は実際にサービスにアクセスするなどしてメトリクスが生成されるイベントを起こしてから確認する必要がある。

## 検証
KICを導入している時に`kind: Ingress`リソースを作成するとKICのWebhookが動作して自動でGateway Serviceを作成してくれる。
これを使って適当なサービスを作成して公開し、そこにアクセスした時のメトリクスがGrafanaで見れるか確認する。
動作確認に利用したものは以下となる。
```yaml
cat <<'EOF' > ./game-2048.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game-2048
  name: deployment-2048
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2048
  replicas: 3
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app-2048
    spec:
      containers:
      - image: alexwhen/docker-2048
        name: app-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: game-2048
  name: service-2048
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    konghq.com/protocols: "https"
  namespace: game-2048
  name: ingress-2048
spec:
  ingressClassName: kong
  rules:
    - host: 2048-game.10.214.154.169.nip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name:  service-2048
                port:
                  number: 80
  tls:
  - secretName: game-2048-tls
    hosts:
    - 2048-game.10.214.154.169.nip.io
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: game-2048-ca
  namespace: game-2048
spec:
  commonName: "*.10.214.154.169.nip.io"
  secretName: game-2048-tls
  issuerRef:
    name: selfsigned-issuer
    kind: ClusterIssuer
EOF
```

`Ingress`のAnnotationに`konghq.com/protocols: "https"`をつけなくてもGateway Serviceは作成されるが、これをつけないとHTTPとして作成されるので、HTTPSで作成されるように指定した。（Annotationの説明は[こちら](https://docs.konghq.com/kubernetes-ingress-controller/latest/reference/annotations/#konghqcomprotocols)）

Manifestを適用するとKong ManagerからGateway Serviceが以下のように確認できる。
![1711347689242.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5a797213-bfb7-2aae-b489-b7d98b1fab7c.png)

ブラウザでアクセスすると以下のようなゲームが表示される。
![1711347784873.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/d524ae3a-cf9e-79bd-66d8-aaa1bde4dd20.png)

このサイトに`watch`コマンドで定期的にアクセスしてみる。
```sh
watch curl -s -k https://2048-game.10.214.154.169.nip.io/
```
1,2分経ってGrafanaで見ると、アクセスが確認できる。
![1711347981001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5e67a11e-3e73-6c8d-c8c7-c76b33fff3aa.png)

以上により、サービスのメトリクスをスクレイピングしなくてもメトリクスを取得できることが確認できた。
また、サービスディスカバリについてもKICが担保することで、Prometheus側の設定変更なしで追従出来ることも確認できた。