Kong Gatewayの通信の遅延箇所をJaegerで確認する

Kong Gatewayには[OpenTelemetry Plugin](https://docs.konghq.com/hub/kong-inc/opentelemetry/)が用意されていて、これを使うとアプリの改変やMeshへの追加なしで以下のようなトレースを取得することが出来る。
![20240525132205.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/3c521fce-2dc6-2b99-e42f-e6b6602f77e9.png)

これを使うとレイテンシが大きい場合に以下のどこに問題があるかが確認できるようになる。
- クライアント->Kong Gateway
- Kong Gateway内
- Kong Gateway->アプリケーション

この記事はOpenTelemetry Pluginの設定方法を確認した時のメモ。

## 検証環境
以下の環境で検証した。
- KubernetesはEKS
- Kong Ingress Controller(KIC)は導入済み
- Kong Gatewayは構築済み
- cert-managerは導入済み

KIC、Kong Gateway、cert-managerについては[こちら](https://qiita.com/ipppppei/items/44c100412d4e484f4f6a)の記事の内容で構築したものを利用した。

## Kong Gatewayの設定変更
Kong Gatewayに設定を加えないとトレースがエンドポイントに転送されないため、最初に設定変更を行う。
OpenTelemetry Pluginの[こちら](https://docs.konghq.com/hub/kong-inc/opentelemetry/#set-up-kong-gateway)の説明に従い、`tracing_instrumentations`と`tracing_sampling_rate`をKong Gatewayに設定する。
Helmのvalues.yamlに以下を追加してupgradeする。
```yaml
env:
  tracing_instrumentations: all
  tracing_sampling_rate: 1.0
```
upgradeのコマンドは以下。
```sh
helm upgrade -i -f kong-values.yaml kong kong/kong -n kong
```
設定が反映されているかはKong ManagerにログインしてInfo（右上の`i`）をクリックし、`Config Details`の`View Full Configuration`から確認できる。
![20240525134034.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9f180f0e-25fa-0c26-f54f-0533d724665f.png)

## Jaegerのインストール
Jaegerの構築方法はいくつかあるが、ここではJaeger Operatorで構築する。
まずJaeger Operatorをインストールする。
```sh
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm upgrade -i jaeger-operator jaegertracing/jaeger-operator -n observability --create-namespace --set rbac.clusterRole=true
```
なお、私の環境ではOperatorが以下のエラーを吐いて`Jaeger`リソースを作成しようとしても上手く行かなかった。
```sh
failed to list *v1.IngressClass: ingressclasses.networking.k8s.io is forbidden: User "system:serviceaccount:observability:jaeger-operator" cannot list resource "ingressclasses" in API group "networking.k8s.io" at the cluster scope
```
そのため以下のClusterRoleを作成し、Podを再起動した。
```yaml
cat <<EOF > ./rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: jaeger-operator-ingress-class
rules:
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingressclasses"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jaeger-operator-ingress-class-binding
subjects:
  - kind: ServiceAccount
    name: jaeger-operator
    namespace: observability
roleRef:
  kind: ClusterRole
  name: jaeger-operator-ingress-class
  apiGroup: rbac.authorization.k8s.io
EOF
kubectl apply -f ./rbac.yaml
kubectl delete pod --all -n observability
```

`Ingress`を使ったJaegerを作成するために、`Issuer`で証明書を作成できるようにする。
```yaml
MY_EMAIL=hogehoge@hogehoge.com
cat <<EOF > ./issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  labels:
    app: certmanager-issuer
    chart: certmanager-issuer-0.1.0
  name: jaeger-issuer
  namespace: observability
spec:
  acme:
    email: $MY_EMAIL
    privateKeySecretRef:
      name: jaeger-acme-key
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: kong
EOF
kubectl apply -f ./issuer.yaml
```
`Jaeger`リソースを作成する。
```yaml
MYHOST=xxx.hogehoge.com
cat <<EOF > ./jaeger.yaml
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: jaeger
  namespace: observability
spec:
  ingress:
    hosts:
    - jaeger.$MYHOST
    ingressClassName: kong
    enabled: true
    tls:
    - hosts:
      - jaeger.$MYHOST
      secretName: jaeger-cert
    annotations:
      kubernetes.io/tls-acme: "true"
      cert-manager.io/issuer: "jaeger-issuer"
EOF
kubectl apply -f ./jaeger.yaml
```
Ingressで設定したホスト名にアクセスするとJaegerのUIが確認できる。
![20240524181058.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/e97e88eb-2e8d-f038-c755-9d4473ef9f92.png)

## サンプルアプリケーションのデプロイとService/Routeの作成
次にテストに使うサンプルアプリケーション(nginx)をデプロイし、サンプルアプリに到達するためのKong GatewayのService/Routeを作成する。
なお、KICを使って`Ingress`を作れば自動的にService/Routeは生成されるため、今回は`Ingress`の作成＝Service/Routeの作成となる。
以下のManifest(`Deployment`, `Service`, `Issuer`, `Ingress`)を作成する。
<details><summary>Manifest（クリックして表示）</summary><div>

```yaml:nginx.yaml
cat <<EOF > ./nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  selector:
    app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/issuer: nginx-issuer
    kubernetes.io/tls-acme: "true"
  name: nginx
spec:
  ingressClassName: kong
  rules:
    - host: nginx.eks.$MYHOST
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name:  nginx
                port:
                  number: 80
  tls:
  - secretName: nginx-general-tls
    hosts:
    - nginx.eks.$MYHOST
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: nginx-issuer
spec:
  acme:
    email: $MY_EMAIL
    privateKeySecretRef:
      name: nginx-acme-key
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: kong
EOF
```
</div></details>

デプロイする。
```yaml
kubectl create ns nginx-sample
kubectl apply -f ./nginx.yaml -n nginx-sample
```
`Ingress`のホスト名にブラウザでアクセスすれば、以下のようにnginxの初期画面が確認できる。
![20240525141547.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/36b11064-f775-3909-5182-ac295fd36099.png)
また、Kong ManagerからService/Routeが作成されていることも確認できる。
![20240525141936.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/4fff6feb-da34-c93d-6ca4-ff358d3ae0d0.png)

## OpenTelemetry Pluginの導入
Kong GatewayにOpenTelemetry Pluginを導入してトレースを取得・転送する。
ここではKong Managerから設定し、Pluginの対象は先程作ったRouteに限定するものとする。
先程作成されたnginxのRouteを選択し、`Plugins`から`New Plugin`を選択する。
Plugin選択の画面に遷移するので、OpenTelemetry Pluginを選択する。

<img alt="picture 5" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/b2bd628d-38e1-5b8e-b27e-3b1e2643ecd9.png" width=200 />  

選択すると設定画面に遷移するので、以下の設定を入力して`Save`する。
- `Endpoint`: `http://jaeger-collector.observability.svc:4318/v1/traces`
- `Resource Attributes`: `{ "service.name": "nginx" }`

`Endpoint`については同一クラスタ内で展開しているので直接`Service`を参照した。`Ingress`のホスト名を使って参照したい場合、今回Jaeger OperatorでデプロイしたJaegerの`Ingress`ではOTEL用のPort(4318)向けのパスが存在していないので注意。
`Resource Attributes`については後ほどJaegerでトレースを絞る際のタグみたいなものとなる。ここでは`Add Config.Resource Attributes`をクリックして以下のように入力する。
![20240525144018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/08519489-9bcf-dc68-3437-21e69bb07413.png)

以上で設定は完了となる。

## 動作確認
nginxに公開している`Ingress`のホスト名でアクセスする。
```sh
curl -X GET https://$(kubectl get ing nginx -n nginx-sample -o jsonpath={.spec.rules[].host})
```
Jaegerを見てみる。
ログインすると`Search`の`Service`のところにOpenTelemetry Pluginの`service.name`で設定した名前のServiceが確認できる。

<img alt="picture 7" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/f1d5f345-ebd9-8677-678b-3989c10db0b5.png" width=300 />  

選択して`Find Traces`を選択し、表示された検索結果をクリックするとトレースが確認できる。
![20240525145239.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/1bec3f08-fd43-42f6-835f-a093a91ae18a.png)