ArgoRolloutsによるカナリアデプロイの動作検証メモ

# 1. はじめに
以前ArgoRolloutsを利用したBlue/Greenデプロイの検証結果をまとめましたが、今回はカナリアデプロイの動作について検証します。
以前実施したBlue/Greenデプロイ方式の動作検証結果はこちらとなります。

https://qiita.com/yuuman/items/77742a2597866fa32a0b

環境はBlue/Greenデプロイ検証と同じ`mninikube`を利用します。
またアプリもBlue/Greenデプロイ検証で利用したアプリを利用するため、Blueアプリ・Greenアプリと呼びます。

# 2. ArgoRolloutsを利用したカナリアデプロイについて
ArgoRolloutsではデプロイ戦略にカナリアデプロイが利用できます。
カナリアデプロイでは指定した重みに基づいてリクエストを新旧のアプリに振り分けます。
通常のRolloutとServiceの構成では、リクエストの重みのみが設定可能ですが、Ingressを利用することでヘッダー情報によるリクエストの振り分けることも可能です。

今回は下記の方式について検証することにします。

1. 通常の重み付けによるリクエストの振り分け方式
2. Nginx Ingress Controllerによる振り分け方式 

Ingressを利用するトラフィック制御はNginx以外にもALBやistioなども利用可能です。


# 3. 通常の重み付けによるリクエストの振り分け方式
## 3.1 概要
下図の順にカナリアデプロイが動作することを検証します。

![カナリアデプロイフロー1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/2b1be08b-29f2-5f1c-7db8-65b440de31b9.png)

今回の例では下記の順でGreenアプリがデプロイされ、リクエストが振り分けられます。

(1). リクエストの20%をGreenアプリに振り分ける
(2). 手動でのPromote後にリクエストの50%をGreenアプリに振り分ける
(3). Promote完了の60秒後にリクエストの80%Greenアプリに振り分ける
(4). すべてのリクエストをGreenアプリに振り分ける

## 3.1 マニフェストファイルの作成
Rolloutリソースに対応するServiceリソースを作成します。

```yaml:canary-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: rollout-canary-rest-api-app-svc
spec:
  selector:
    app: rollout-canary-rest-api-app
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
```

Blue/Greenデプロイと同様、マニフェストファイルのkindは`Deployment`ではなく`Rollout`になります。
また、`Rollout`の構造は`Deployment`とよく似ていますが、`strategy:`にデプロイ戦略の設定を追記します。
カナリアリリースの場合は、`canary.steps`で各Stepでの操作を指定します。

```yaml:canary-rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-canary-rest-api-app
spec:
  replicas: 5
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-canary-rest-api-app
  template:
    metadata:
      labels:
        app: rollout-canary-rest-api-app
    spec:
      containers:
        - name: rollout-canary-rest-api-app
          image: rest-api-app:blue
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
  strategy:
    canary:
      steps:
        - setWeight: 20
        - pause: {}
        - setWeight: 50
        - pause: {duration: 60}
        - setWeight: 80
        - pause: {}
```

## 3.2 カナリアデプロイの実行
カナリアデプロイを実行するため、Serviceリソースを立ち上げます。
また、`minikube`のコマンドを利用してServiceのURLを取得します。

```shell
$ kubectl apply -n sample canary-service.yaml

$ minikube service -n sample rollout-canary-rest-api-app-svc --url
http://192.168.49.2:30576
```

### 3.2.1 Blueアプリのリリース
Rolloutリソースをデプロイします。
Rolloutリソースのイメージタグは`blue`を指定しているため、レスポンスも`blue`となります。

```shell
$ kubectl apply -n sample -f canary-rollout.yaml
$ curl http://192.168.49.2:30576/sample/version 
{"appVersion":"blue","env":" default"}
```

ArgoRolloutでのダッシュボードは下記のとおりとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/03d4a1a2-a053-ff92-237c-16d7f7967908.png)

### 3.2.2 Greenアプリのリリース
`canary-rollout.yaml`のイメージタグを`green`に変更し、Rolloutリソースをデプロイします。
すでにBlueアプリがデプロイ済みのため、新規にGreenアプリのアプリが立ち上がります。
リクエストの振り分けは20%となっているため、1/5の確率でGreenアプリからレスポンスが返ります。

```shell
$ kubectl apply -n sample -f canary-rollout.yaml
$ curl http://192.168.49.2:30576/sample/version 
{"appVersion":"blue","env":" default"}
{"appVersion":"blue","env":" default"}
{"appVersion":"blue","env":" default"}
{"appVersion":"blue","env":" default"}
{"appVersion":"green","env":" default"}
```

ArgoRolloutでのダッシュボードでもGreenアプリのPodが立ち上がっていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/f0f372af-6609-57f1-efde-bb83f1cb3b9c.png)

その後、`promote`コマンドを実行しGreenアプリのPod数を増やします。
リクエストを50%ずつに割り振るため、`blue`/`green`それぞれのアプリのPod数は3となります。

```shell
$  kubectl argo rollouts promote -n sample rollout-canary-rest-api-app
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/46d8ddde-5127-f5a9-1a6a-35acd9de817e.png)

`promote`コマンド実行完了の60秒後にリクエストの振り分けを80%にするためPod数の調整が自動で実行されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/9f1befb9-1bad-a5cc-693f-01ead6db0c6d.png)

Pod数の自動調整後`promote`コマンドを実行します。
`promote`コマンド実行により`blue`バージョンのPodは縮退し、すべてのPodが`green`バージョンに切り替わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/04a24dc4-cc0c-3b81-86b6-ddddee76b7d8.png)


# 4. Nginx Ingress Controllerによる振り分け方式 
## 4.1 概要 {#nginx-abstract}
Nginx Ingress Controllerによりリクエストを振り分けます。
Nginx Ingress Controllerによるトラッフィク制御については下記に記載されています。

https://argo-rollouts.readthedocs.io/en/stable/features/traffic-management/nginx/

今回検証したアプリの構成は下図のとおりです。

![nginx-ingress-controller.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/03c030e3-6ebf-4649-7bfc-6be4940b2c37.png)

また、今回の検証に当たりこちらの記事を参考にしました。

https://qiita.com/tomozo6/items/1bfc65a86a528f63d205

## 4.2 MinikubeのIngress設定
Minikube環境を利用しているのでIngressを有効化する必要があります。
下記コマンドによりIngressを有効化します。

```
$ minikube addons enable ingress
```

https://kubernetes.io/ja/docs/tasks/access-application-cluster/ingress-minikube/

## 4.3 マニフェストファイルの作成
Serviceのマニフェストを下記のように定義します。
[4.1 概要](#nginx-abstract)に記載した通り、Serviceは2つ必要となります。

```yaml:rollouts-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: rollout-canary-rest-api-app-svc-stable
spec:
  selector:
    app: rollout-canary-rest-api-app
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: rollout-canary-rest-api-app-svc-canary
spec:
  selector:
    app: rollout-canary-rest-api-app
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
```

次にNginx Ingress Controllerリソースをデプロイする必要があります。
Ingressのマニフェストは下記のとおりです。

```yaml:ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rollout-canary-stable-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: rollout-canary-rest-api-app.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: rollout-canary-rest-api-app-svc-stable
                port:
                  number: 8080
```

最後にRolloutリソースを定義します。

```yaml:rollout-canary.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-canary-rest-api-app
spec:
  replicas: 4
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollout-canary-rest-api-app
  template:
    metadata:
      labels:
        app: rollout-canary-rest-api-app
    spec:
      containers:
        - name: rollout-canary-rest-api-app
          image: rest-api-app:blue
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
  strategy:
    canary:
      stableService: rollout-canary-rest-api-app-svc-stable
      canaryService: rollout-canary-rest-api-app-svc-canary
      trafficRouting:
        nginx:
          stableIngress: rollout-canary-stable-ingress
          annotationPrefix: nginx.ingress.kubernetes.io
          additionalIngressAnnotations:
            canary-by-header: 'X-Canary'
            canary-by-header-value: 'true'
            canary-weight: '0'
      steps:
        - setWeight: 10
        - pause: {}
```

`strategy:canary:`に2つのServiceを設定します。
また、`trafficRouting`にリクエストの振り分けルールを定義します。
そして、`steps`に重みを設定します。

この設定により、リクエストのヘッダに`X-Canary:true`が設定されたリクエストはカナリア面に振り分けられます。
一方でヘッダが設定されていないリクエストは重みの設定に従い、10%の確率でカナリア面に振り分けられます。
また、これら以外にも`Cookie`によるリクエストの振り分けも可能です。

ルーティングの制御ルールには優先度があり、`canary-by-header`が最も優先度高く`canary-by-cookie`、`canary-weight`の順となります。

詳細は下記を参照ください。

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary


## 4.4 カナリアデプロイの実行
## 4.4.1 Blueアプリのリリース
Nginxを利用するため、IPではなくホスト名でアクセスする必要があります。
まずはMinikubeが公開しているIPを下記コマンドで調べます。

```shell
$ minikube ip
```

判明したIPを`/etc/hosts`に設定します。
ホスト名にはIngressのマニフェストに記載した`host`部となります。

```:/etc/hosts
# Ingress hosts configuration
192.168.49.2   rollout-canary-rest-api-app.info
```

Rolloutリソースをデプロイします。
Rolloutリソースのイメージタグは`blue`を指定しているため、レスポンスも`blue`となります。

```shell
$ curl http://rollout-canary-rest-api-app.info/sample/version
{"appVersion":"blue","env":" default"}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/84c3b1e7-4502-94d2-03d7-8406ceaea663.png)

## 4.4.2 Greenアプリのリリース
`canary-rollout.yaml`のイメージタグを`green`に変更し、Rolloutリソースをデプロイします。
すでにBlueアプリがデプロイ済みのため、新規にGreenアプリが立ち上がります。
Podの数は下図のとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/177076/4c467174-cedf-294c-2aa9-7a142d3b3bbf.png)

リクエストの振り分けは10%となっているため、1/10の確率でGreenアプリからレスポンスが返ります。

```shell
$ curl http://rollout-canary-rest-api-app.info/sample/version
{"appVersion":"blue","env":" default"}
{"appVersion":"blue","env":" default"}
　　・・・
{"appVersion":"green","env":" default"}
```

次に、リクエストヘッダーを付与してリクエストすると、Greenアプリからレスポンスが返ります。

```shell
$ curl -H 'X-Canary:true' http://rollout-canary-rest-api-app.info/sample/version
{"appVersion":"green","env":" default"}
```

以上のことからNginx Ingress Controllerによる振り分け制御が有効化されていることがわかります。