IstioのTraffic Managementの動作イメージを掴もう

# IstioのTraffic Managementの動作イメージを掴もう

## 本記事について
Istioを使う上で、Traffic Managementを司るVirtual Service, DestinationRuleについて、
どう作用しているのかという点がわかりにくかったため、本記事にて整理し実際に動かしながら検証する。
前提として、サービスメッシュやEnvoyの基本的な設定についてはざっくりと理解している読者を想定している。

## Istioとは？
Istioは、サービスメッシュの機能を統括的に導入、管理を行うためのOSSである。
内部ではEnvoyを使用していて、IstioとしてはとしてはこのEnvoyのコントロールプレーンとしての機能も担っている。
[What is Istio?](https://istio.io/docs/concepts/what-is-istio/)

主に、

- Traffic Management
  - 割合やヘッダーによるルーティングのコントロールやロードバランシングなどを行うための機能
  - Envoyの機能であるような、サーキットブレーカー、Fault Injectionなどの機能も基本的に使える

- Observability
  - Promheusなどメトリクスを集計するためのソフトウェアにデータを転送する機能

- Security
  - mTLS通信による認証された通信や、JWTを活用した認可処理をプロキシとして挟んだりすることができる
  - mTLSに用いられる証明書はIstioが独自証明書を発行してくれ、特に意識せずともmTLSをサービス間通信に導入することができる

といった機能を提供しており、Kubernetsなどのプラットフォーム連携が充実している。

## Traffic Managementについて
Istioは、k8sにデプロイされたPodに自動的にサイドカーとしてEnvoyベースのProxyをInjectする機能が備わっている。
基本的には、リクエスト元サービス -> Envoy -> Envoy -> リクエスト先サービスという流れで通信することになる。
そして、このEnvoyが実際にルーティングの制御を行っている。
このEnvoyに設定情報を提供するのがk8sのCRDである下記3種類のリソースである。

- Virtual Service
- DestinationRule

各リソースについての概念説明などはなどは、
[公式ドキュメント](https://istio.io/docs/concepts/traffic-management/) に記載されている通りである。
このさきは、minikubeにIstioと、Istioのデモアプリである、[Bookinfo](https://istio.io/docs/examples/bookinfo/) をデプロイして挙動を確認していく。

## 事前準備
- minikube v1.9.2
- Istio v1.5.2
- Kubernets v1.18.0

## Ingress Gatewayの実態を見てみよう

### 初期状態
Ingress Gatewayを有効にしてIstioをk8sのクラスタにインストールすると、

- Service(Load Balancer Type)
- Deployment

のリソースがそれぞれ作られていることが分かる。

```
❯❯❯ kubectl get service -n istio-system
istio-ingressgateway        LoadBalancer   10.103.255.236   10.103.255.236   15020:31097/TCP,80:31587/TCP,443:32153/TCP,15029:30056/TCP,15030:32475/TCP,15031:32379/TCP,15032:32322/TCP,31400:32143/TCP,15443:32376/TCP   3d15h
```

```
❯❯❯ kubectl get deployments.apps -n istio-system
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
istio-ingressgateway   1/1     1            1           3d15h
```

そして、このDeploymentによって管理されているPodの定義を見てみると、

```
❯❯❯ kubectl get pod -n istio-system istio-ingressgateway-6489d9556d-sp8f2 -o yaml | grep image:
    image: docker.io/istio/proxyv2:1.5.2
```

のようになっているのが分かる。
このproxyvというのが中でEnvoyが動いてる。
この状態でRoutingの設定を覗いてみると、まだなにも定義していないためすべて404になるようになっている。

```
❯❯❯ istioctl -n istio-system proxy-config route istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' -
- name: http.80
  virtualHosts:
    - name: blackhole:80
      domains:
        - '*'
      routes:
        - name: default
          match:
            prefix: /
          directResponse:
            status: 404
  validateClusters: false
```

### GatewayリソースとGatewayリソースに紐付けたVirtualServiceの作成
では次に下記リソースをapplyし、IngressGatewayにAttachしたGatewayリソースと、VirtualServiceを作ってみる。

```
❯❯❯ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
```

そうすると、下記のようなルーティングルールが作成されている。

```
❯❯❯ istioctl -n istio-system proxy-config route istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' -
- name: http.80
  virtualHosts:
    - name: '*:80'
      domains:
        - '*'
        - '*:80'
      routes:
        ...
        - match:
            path: /productpage
            caseSensitive: true
          route:
            cluster: outbound|9080||productpage.default.svc.cluster.local
            timeout: 0s
            retryPolicy:
              retryOn: connect-failure,refused-stream,unavailable,cancelled,resource-exhausted,retriable-status-codes
              numRetries: 2
              retryHostPredicate:
                - name: envoy.retry_host_predicates.previous_hosts
              hostSelectionRetryMaxAttempts: '5'
              retriableStatusCodes:
                - 503
            maxGrpcTimeout: 0s
          metadata:
            filterMetadata:
              istio:
                config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/bookinfo
          decorator:
            operation: productpage.default.svc.cluster.local:9080/productpage
          ...
```
このように、IngressGatewayにAttachしたGatewayを作成し、さらにそのGateway指定したVirtual Serviceを作ると、
L4/LBから直接ルーティングされてくるEnvoyにルーティングルールが追加される。
また、向き先としてはPodのIPが解決されている。

```
❯❯❯ istioctl -n istio-system proxy-config endpoint istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' - | grep "outbound|9080||productpage.default.svc.cluster.local" -A 30
- name: outbound|9080||productpage.default.svc.cluster.local
  addedViaApi: true
  hostStatuses:
    - address:
        socketAddress:
          address: 172.17.0.12
          portValue: 9080
      weight: 1
```

### Gatewayに紐付けないVirtualService
次に下記リソースをapplyし、reviewsサービスにはv2に50%、v3に50%の割合でルーティングされるようにしてみる。

```
❯❯❯ kubectl apply -f samples/bookinfo/networking/destination-rule-reviews.yaml
❯❯❯ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v2-v3.yaml
```

まずIngress Gateway側のEnvoyの設定を確認する。

```
❯❯❯ istioctl -n istio-system proxy-config route istio-ingressgateway-6489d9556d-sp8f2 -o json | yq -y '.' -
```
今回はGatewayにAttachしていないVirtualServiceを作成したので、特にルーティング設定に変化がないことが分かる。

ではアプリケーション側のPodに配置されたEnvoyの設定はどうだろうか？

```
❯❯❯ istioctl proxy-config route productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' -
- name: reviews.default.svc.cluster.local:80
      domains:
        - reviews.default.svc.cluster.local
        - reviews.default.svc.cluster.local:80
      routes:
        - match:
            prefix: /
          route:
            weightedClusters:
              clusters:
                - name: outbound|80|v2|reviews.default.svc.cluster.local
                  weight: 50
                - name: outbound|80|v3|reviews.default.svc.cluster.local
                  weight: 50
            timeout: 0s
            retryPolicy:
              retryOn: connect-failure,refused-stream,unavailable,cancelled,resource-exhausted,retriable-status-codes
              numRetries: 2
              retryHostPredicate:
                - name: envoy.retry_host_predicates.previous_hosts
              hostSelectionRetryMaxAttempts: '5'
              retriableStatusCodes:
                - 503
            maxGrpcTimeout: 0s
          metadata:
            filterMetadata:
              istio:
                config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews
          decorator:
            operation: reviews:80/*
```

長いので省略しているが、reviewsへのトラフィックのときに下記２つの向き先へ50%の割合でルーティングしているのが分かる。

- outbound|80|v2|reviews.default.svc.cluster.local
- outbound|80|v3|reviews.default.svc.cluster.local

endpointの設定を見てみると、それぞれv3のPodのIP、v2のPodのIPを指していることが読み取れる。

```
❯❯❯ istioctl proxy-config endpoint productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' - | grep reviews -A 30
- name: outbound|9080|v3|reviews.default.svc.cluster.local
  addedViaApi: true
  hostStatuses:
    - address:
        socketAddress:
          address: 172.17.0.10
          portValue: 9080
- name: outbound|9080|v2|reviews.default.svc.cluster.local
  addedViaApi: true
  hostStatuses:
    - address:
        socketAddress:
          address: 172.17.0.11
          portValue: 9080
```

ちなみにこのルーティング設定は、Bookinfoアプリケーション内のそれぞれのサービスのサイドカーEnvoyの設定に組み込まれている。
(正確にはIstio Pilotの仕組みを使って設定が動的にInjectされている)
つまり、リクエストを送る側、つまりクライアント側のプロキシ(Envoy)でロードバランシング、ルーティング制御を行い、Podにトラフィックを送っているのである。

### DestinationRuleを削除してみる
最後に上記までの状態からDestinationRuleだけ削除してみる。

```
❯❯❯ kubectl apply -f samples/bookinfo/networking/destination-rule-reviews.yaml
```

ルーティングルールとしては変化していないように見える。

```
❯❯❯ istioctl proxy-config route productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' -
clusters:
                - name: outbound|80|v2|reviews.default.svc.cluster.local
                  weight: 50
                - name: outbound|80|v3|reviews.default.svc.cluster.local
                  weight: 50
```

だが、エンドポイント定義としてはv2, v3が消えてしまっているのが分かる。

```
❯❯❯ istioctl proxy-config endpoint productpage-v1-7f44c4d57c-nfnjd -o json | yq -y '.' - | grep reviews

- name: outbound|9080||reviews.default.svc.cluster.local
```

## まとめ
- VirtualService, DestinationRuleはすべてIngress GatewayのEnvoyもしくは、サイドカーEnvoyの設定に作用する
  - Gatewayに紐付けたVirtualServiceは、Ingress GatewayのEnvoyのルーティング設定に作用する
  - Gatewayに紐づけていないVirtualServiceは各サービスのサイドカーEnvoyに、アウトバウンドのルーティング設定として作用する
    - これによってクライアント側でロードバランシング、ルーティングの制御を行っている
  - DestinationRuleはEnvoyのEndpoint設定に作用する