Istioでラウンドロビンで負荷分散してみる

# TL;DR

Webアプリケーションに来るリクエストをIstioで負荷分散してみました。
Github: https://github.com/cacapouh/istio-example

# デプロイ対象のWebアプリケーション

リクエストの回数をレスポンスするアプリケーションをデプロイします。
以下はそのためのPythonスクリプトおよびDockerfileです。

```python:main.py
import os
from bottle import route, run

count = 0

@route('/')
def index():
    global count
    count = count + 1
    return "{}: {}!".format(count, os.environ["HOSTNAME"])

run(host='0.0.0.0', port=8080)
```

```Dockerfile:Dockerfile
FROM python:3.9

RUN mkdir /work
WORKDIR /work

COPY requirements.txt /work
RUN pip install -r ./requirements.txt

COPY main.py /work
ENTRYPOINT python main.py
```

以下は上記のDockerfileのビルド方法です。
ここでは`simple-app`という名前でビルドしています。

```shell:ビルド方法
docker build . -t simple-app
minikube image load simple-app:latest # Minikubeの場合
```

# デプロイ対象のマニフェストファイル群

この記事で取り扱うマニフェストファイルは以下の5つです。

* deployment.yml
    * Webアプリをデプロイするための[Deployment](https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/)を取り扱います
* service.yml
    * Webアプリにアクセスするための[Service](https://kubernetes.io/ja/docs/concepts/services-networking/service/)を取り扱います
* gateway.yml
    * Istioの[Gateway](https://istio.io/latest/docs/reference/config/networking/gateway/)を取り扱います
    * Gatewayリソースとは、外部からのトラフィックを受け付けて、
ロードバランシングするプロキシです
* destination_rule.yml
  * Istioの[DestinationRule](https://istio.io/latest/docs/reference/config/networking/destination-rule/)を取り扱います
  * DestinationRuleとは、特定のサービスへのトラフィックを制御するためのリソースです。
* virtual_service.yml
  * Istioの[Virtual Service](https://istio.io/latest/docs/reference/config/networking/virtual-service/)を取り扱います
  * VirtualServiceリソースとは、トラフィックのルーティング設定を行うものです

## deployment.yml

以下は、PythonのWebアプリを取り扱うDeploymentリソース設定です。
ラウンドロビンの挙動を確かめる関係でレプリカ数を2にしています。

```yml:deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-app-deployment
  labels:
    app: simple-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: simple-app
  template:
    metadata:
      labels:
        app: simple-app
    spec:
      containers:
      - name: simple-app
        image: "simple-app:latest"
        imagePullPolicy: IfNotPresent
```

## service.yml

以下はPythonのWebアプリにアクセスするためのService設定です。

```yml:service.yml
apiVersion: v1
kind: Service
metadata:
  name: simple-app-service
spec:
  selector:
    app: simple-app
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
```

## gateway.yml

以下はIstioのGatewayの設定です。

80ポートへのアクセスであれば、
どのようなホストでもHTTPリクエストを受け付けるようにしています。

`spec.selector`は、どのIstioイングレスゲートウェイのPodがそのGatewayリソースの設定を使用するかを決定するために使用されます。ここでは、IstioがデフォルトでイングレスゲートウェイのPodに付与するラベル`ingressgateway`を設定しています。

```yml:gateway.yml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: istio-ingressgateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
```

## destination_rule.yml

以下はIstioのDestinationRule設定です。
`simple-app-service`へのリクエストをラウンドロビンで負荷分散させるようにしています。

```yml:destination_rule.yml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: simple-app-destinationrule
spec:
  host: simple-app-service
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
```

## virtual_service.yml

以下はIstioのVirtualService設定です。
`istio-ingressgateway`というGatewayを介して入るトラフィックを、
`simple-app-service`というサービスの80ポートに流すようにしています。

```yml:virtual_service.yml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: simple-app-virtualservice
spec:
  hosts:
  - "*"
  gateways:
  - istio-ingressgateway
  http:
  - route:
    - destination:
        host: simple-app-service
        port:
          number: 80
```

# デモンストレーション

上記のマニフェストファイル群は、Helm Chartでまとめていまして、
[こちらのリポジトリ](https://github.com/cacapouh/istio-example)で`helm install simple-app ./helm`コマンドを実行することにより、デプロイ可能です。

以下は、デプロイした後にリクエストを1000回送った結果です。

```shell:リクエストを1000回送るスクリプト
for i in {1..1000}
do
   curl "http://127.0.0.1:80"
   echo "Request $i completed"
done
```

```txt:スクリプト実行結果
1: simple-app-deployment-dd44b5ff4-6fc7q!Request 1 completed
1: simple-app-deployment-dd44b5ff4-nk7z4!Request 2 completed
2: simple-app-deployment-dd44b5ff4-6fc7q!Request 3 completed
3: simple-app-deployment-dd44b5ff4-6fc7q!Request 4 completed
2: simple-app-deployment-dd44b5ff4-nk7z4!Request 5 completed
4: simple-app-deployment-dd44b5ff4-6fc7q!Request 6 completed
3: simple-app-deployment-dd44b5ff4-nk7z4!Request 7 completed
4: simple-app-deployment-dd44b5ff4-nk7z4!Request 8 completed
5: simple-app-deployment-dd44b5ff4-6fc7q!Request 9 completed
6: simple-app-deployment-dd44b5ff4-6fc7q!Request 10 completed
5: simple-app-deployment-dd44b5ff4-nk7z4!Request 11 completed
7: simple-app-deployment-dd44b5ff4-6fc7q!Request 12 completed
...(中略)...
495: simple-app-deployment-dd44b5ff4-nk7z4!Request 990 completed
496: simple-app-deployment-dd44b5ff4-6fc7q!Request 991 completed
497: simple-app-deployment-dd44b5ff4-6fc7q!Request 992 completed
496: simple-app-deployment-dd44b5ff4-nk7z4!Request 993 completed
497: simple-app-deployment-dd44b5ff4-nk7z4!Request 994 completed
498: simple-app-deployment-dd44b5ff4-6fc7q!Request 995 completed
498: simple-app-deployment-dd44b5ff4-nk7z4!Request 996 completed
499: simple-app-deployment-dd44b5ff4-6fc7q!Request 997 completed
499: simple-app-deployment-dd44b5ff4-nk7z4!Request 998 completed
500: simple-app-deployment-dd44b5ff4-6fc7q!Request 999 completed
500: simple-app-deployment-dd44b5ff4-nk7z4!Request 1000 completed
```

`deployment.yml`でレプリカ数を2つに設定していましたので、
標準出力には2つのPod名が登場しています。

カウント値がちょうど500対500になっているので、正常にラウンドロビンで負荷分散できていそうです :thumbsup: