Istioでカスタムメトリクスの取得をやってみた


この記事は[ニフクラ](https://www.nifcloud.com/) 等を提供している、[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct) の 8 日目の記事です。

前日は@yusayoshi さんの [playwrightとGitlab CI/CDでE2Eテストを自動化してみた](https://qiita.com/yusayoshi/items/6b48bca5b5c0a17cd40e) でした。 
時間が掛かる作業であるテストを自動化して工数を削減しつつ、テストが失敗したときはログだけでなく動画付きで教えてくて、至れり尽くせりで素晴らしいと感じました。

本日はIstioのカスタムメトリクスの取得の仕方について説明したいと思います。
実は私は10月にHatobaチームに配属されたばかりで、k8sについて絶賛勉強中です。
その勉強した内容の一部を共有したいと思います。

> k8s環境があれば、Istio初心者でも手を動かしながら試せる内容を心がけています。

## Istioとは

https://istio.io/latest/about/service-mesh/

### 概要

Istioとはマイクロサービスのサービスメッシュを構築・管理するためのOSSです。
最近のサービスはマイクロ化が進み、一つのサービスに対して複数のアプリケーションが複雑に連携して動作しています。
そのアプリケーション間の通信を管理し、またアップデートを行っていくことはとても労力のかかる行為となります。
その網目のように張り巡らされたアプリケーションの通信網(サービスメッシュ)の管理をIstioが行ってくれます。

### Istioでできること

公式サイトによると次のような機能が紹介されています。

* サービス間の通信の暗号化

* ロードバランシング機能(HTTP, gRPC, WebSocket, TCP)

* サービス間のトラフィック管理(ルーティング, リトライ, フェイルオーバー, フォールトインジェクション)

* メトリクスやログ、トレースを用いたサービス内のトラフィックの自動監視

### Istioを使う利点

Istioの大きな特徴の一つとしては既存のコードを変更しなくても導入することができるという点が挙げられます。
各アプリケーションのサイドカーとしてプロキシを注入し、アプリケーションの双方向の通信をproxy経由で行います。
アプリケーション側からはproxyを意識せずに通信を行うことができ、Istioは各proxyによって通信の制御や監視を実現します。

## 監視について

システムを運用、開発していく上で監視は重要です。
システムが正しく動いているかを確認する手段として必要なものです。
それ以上にシステムの状態を把握し、システムの正しい状態とは何か、必要なものは何かを知ることができます。
監視の要素としてログ、メトリクス、トレースの3つがあります。

* ログ
  * 文字でシステムの状態や動作を記録する
  * そのため、データ量は巨大で解析にも時間が掛かる
* メトリクス 
  * 数値でシステムの状態や動作を記録する
  * 状態の時間変化を確認できる
* トレース
  * アプリケーション間の通信とその経路を記録する
  * アプリケーションの連携を確認できる

本記事はこの中のうち、メトリクスのみを対象としています。

## カスタムメトリクスの取得

### Istioのインストール

本記事では下記のような環境で検証を行っています。

|   | version |
|------------------|---------|
| Kubernetes  | v1.24.3 |
| Istio  | v1.15.2 |

[Istio公式ドキュメント](https://istio.io/latest/docs/setup/getting-started/)を参考にIstioの環境構築を行っていきます。

https://istio.io/latest/docs/setup/getting-started/

まず、Istioctlのインストールを行います。

```sh
$ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.15.2 TARGET_ARCH=x86_64 sh -
$ cp -p istio-1.15.2/bin/istioctl /usr/local/bin
$ istioctl version
no running Istio pods in "istio-system"
1.15.2
```

次にプロファイルを利用してIstioのインストールを行います。

```sh
$ istioctl install --set profile=demo -y
✔ Istio core installed                                                                                                                                                                                       
✔ Istiod installed                                                                                                                                                                                           
✔ Egress gateways installed                                                                                                                                                                                  
✔ Ingress gateways installed                                                                                                                                                                                 
✔ Installation complete                                                                                                                                                                                      Making this installation the default for injection and validation.
```

デプロイが成功しているかを確認します。

```sh
$ kubectl get all -n istio-system
NAME                                        READY   STATUS    RESTARTS   AGE
pod/istio-egressgateway-789bbc8b8b-zpr5s    1/1     Running   0          17m
pod/istio-ingressgateway-5474bb49b8-6q8nj   1/1     Running   0          2m8s
pod/istiod-6dcd646fc9-fs48q                 1/1     Running   0          17m

NAME                           TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                                      AGE
service/istio-egressgateway    ClusterIP      10.99.96.227     <none>          80/TCP,443/TCP                               17m
service/istio-ingressgateway   LoadBalancer   10.107.127.207   111.64.92.157   15021:30817/TCP,80:32633/TCP,443:30004/TCP   17m
service/istiod                 ClusterIP      10.108.217.159   <none>          15010/TCP,15012/TCP,443/TCP,15014/TCP        17m

NAME                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/istio-egressgateway    1/1     1            1           17m
deployment.apps/istio-ingressgateway   1/1     1            1           17m
deployment.apps/istiod                 1/1     1            1           17m

NAME                                              DESIRED   CURRENT   READY   AGE
replicaset.apps/istio-egressgateway-789bbc8b8b    1         1         1       17m
replicaset.apps/istio-ingressgateway-5474bb49b8   1         1         1       17m
replicaset.apps/istiod-6dcd646fc9                 1         1         1       17m
```

サイドカーを自動的にデプロイするように設定します。

```
$ kubectl label namespace default istio-injection=enabled
namespace/default labeled
```

サンプルのサービスとしてhttpbinをデプロイします。

```sh
$ kubectl apply -f istio-1.15.2/samples/httpbin/httpbin.yaml
serviceaccount/httpbin created
service/httpbin created
deployment.apps/httpbin created
$ kubectl apply -f istio-1.15.2/samples/httpbin/httpbin-gateway.yaml
gateway.networking.istio.io/httpbin-gateway created
virtualservice.networking.istio.io/httpbin created
```

デプロイが成功しているか確認します

```sh
$ kubectl get all
NAME                           READY   STATUS    RESTARTS   AGE
pod/httpbin-847f64cc8d-ft5cj   2/2     Running   0          4s

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/httpbin      ClusterIP   10.105.123.149   <none>        8000/TCP   3m3s
service/kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    132m

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/httpbin   1/1     1            1           3m3s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/httpbin-847f64cc8d   1         1         1       3m3s
```

curlコマンドでアクセスをしてみます。

IPアドレスとPortは環境によって異なるため、[公式ドキュメント](https://istio.io/latest/docs/setup/getting-started/#determining-the-ingress-ip-and-ports)を参考に取得してください。

https://istio.io/latest/docs/setup/getting-started/#determining-the-ingress-ip-and-ports

自分の環境の場合は次のようにして取得しています。
```
$ export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
$ export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')
```

```sh
$ curl http://$INGRESS_HOST:$INGRESS_PORT/get
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "xxx.xxx.xxx.xxx", 
    "User-Agent": "curl/7.68.0", 
    "X-B3-Parentspanid": "fe58d5ff194cc300", 
    "X-B3-Sampled": "1", 
    "X-B3-Spanid": "85a24cab8430ae49", 
    "X-B3-Traceid": "afcfbec3b49a33e1fe58d5ff194cc300", 
    "X-Envoy-Attempt-Count": "1", 
    "X-Envoy-External-Address": "xxx.xxx.xxx.xxx", 
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Has......."
  }, 
  "origin": "xxx.xxx.xxx.xxx,xxx.xxx.xxx.xxx", 
  "url": "http://xxx.xxx.xxx.xxx/get"
}
```

httpbinからの応答が返ってくれば成功です。

### メトリクス取得

次にIstioでデフォルトで公開されているメトリクスを確認していきます。
今回はprometheusを用いてメトリクスを確認します。
prometheusは次のようにインストールすることができます。

```sh
$ kubectl apply -f istio-1.15.2/samples/addons/prometheus.yaml
erviceaccount/prometheus created
configmap/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created
service/prometheus created
deployment.apps/prometheus created
```

prometheusのダッシュボードを確認してみます。

```sh
$ kubectl -n istio-system port-forward service/prometheus 9090:9090
```

ブラウザで`http://localhost:9090`にアクセスするとダッシュボードが確認できます。

expressionに`istio_request_total`と入力し、Executeするとメトリクスが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985538/d5d90876-307f-e1a9-5dbb-ac98d1c755ed.png)

先ほどのcurlコマンドを繰り返し実行し、prometheusのグラフ表示をすると次のようにリクエスト数が増えていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985538/a97e28ae-5148-2c13-23e8-61ac3e140977.png)

`istio_request_total`以外にも次のようなメトリクスがIstioではデフォルトで公開されます。

https://istio.io/latest/docs/reference/config/metrics/

### ラベル追加

では、本題のカスタムメトリクスに移っていきます。本記事ではメトリクスへのラベルの追加を行います。
以下の[公式ドキュメント](https://istio.io/latest/docs/tasks/observability/metrics/customize-metrics/)を参考にしました。

https://istio.io/latest/docs/tasks/observability/metrics/customize-metrics/

例えば、istio_request_totalメトリクスには、appラベルやresponse_codeラベルなどが既に存在しています。
Istioではこれらのラベル以外に追加可能な値が存在しています。
追加可能な変数の一覧は次のサイトに掲載されています。

https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes

この中の、destination.portとrequest.methodをラベルとして追加してみます。
IstioOperatorにラベルの設定を追加します。
次のような`metrics.yaml`ファイルを作成します。

```yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  meshConfig:
    defaultConfig:
      extraStatTags:
       - destination_port
       - request_method
  values:
    telemetry:
      v2:
        prometheus:
          configOverride:
            inboundSidecar:
              metrics:
                - name: requests_total
                  dimensions:
                    destination_port: string(destination.port)
                    request_method: request.method
```

設定を適用します。

```sh
$ istioctl install -f metrics.yaml -y
```

次にhttpbinのpodのサイドカーにも設定を追加します。
`httpbin.yaml`をコピーして設定を追加します。

```sh
$ cp istio-1.15.2/samples/httpbin/httpbin.yaml httpbin_metrics.yaml
```

`httpbin_metrics.yaml`のannotationsに`sidecar.istio.io/extraStatTags`の設定を追加します。

```yaml
...省略...
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
  annotations:
    sidecar.istio.io/extraStatTags: destination_port, request_method
...省略...
```

設定変更を適用し、podを再起動します。

```sh
$ kubectl apply -f httpbin_metrics.yaml
$ kubectl delete pods --all
```

prometheusの起動コマンドを再度実行して確認すると、ラベルが追加されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985538/dd2b33ec-f62a-bca6-5355-3f33f45d7380.png)

## まとめ

今回はIstioのカスタムメトリクスの取得する方法を紹介させていただきました。
Istioを使えば簡単にメトリクスを取得することができるので、ぜび皆さんも試してみてください！
この記事は[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct) の 8 日目の記事でした。

明日は @nori3636 さんの「[WSLでminikubeの環境構築をしてみる](https://qiita.com/nori3636/items/f4287be438d98b62152a)」です。
偶然ですが連続してk8sに関連した内容となりました。
自分はまだminikubeで色々試したことが無いため、どんなことをするのか楽しみです。


### 参考文献

https://istio.io/