Argo Rollouts + Istio を連携してトラフィックシェーピングによるカナリアリリース

Istioは Kubernetesクラスタ外部からのリクエストトラフィックを受ける Ingress コントローラーに代わって利用することができる。それもグラフィカルに状態を表示する視覚化、そして、トラフィック・シェーピング機能を持つ優れたものである。それに加えて、Argoプロジェクトファミリーの一つである Argo Rollout は コンテナ化アプリケーションのバージョンアップやリリースに対して、予め定めた段階的なリリースを推進してくれる。 この二つを組み合わせる事で、CICDの運用に有用なインフラとして活用する事できる。


![スクリーンショット 2020-07-28 13.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/f0c01eac-b861-22aa-614e-6c8c9483ee75.png)

本記事では、IBMクラウドの**マネージドIstio**とArgo Rollout を利用して、機能の検証を実施した。


## Argo Rollouts とは

Argoロールアウトは、KubernetesコントローラとCRDのセットであり、ブルー・グリーン・デプロイ、カナリアリリース、カナリア分析、実験的配信など、Kubernetesへの段階的な配信などの高度なデプロイ機能を提供する。Argoロールアウトは、Istio サービスメッシュと統合し、ロールアウト中にシェーピング機能を利用してトラフィックを新しいバージョンに徐々にシフトする。さらに、ロールアウトは、さまざまなプロバイダーからのメトリックを参照および解釈して、主なKPIを検証し、ロールアウト時に、自動昇格またはロールバックを進める。

参考資料
* Argo Rollouts - Kubernetes Progressive Delivery Controller, https://argoproj.github.io/argo-rollouts/



## KuberneteクラスタへのArgo Rolloutのインストール

本記事では、Kubenetesクラスタがデプロイされ、`kubectl コマンド`で操作可能な状態から始める。ここで利用したKubernetesのバージョンは、1.17である。

参考資料のクイックスタートでは、GitHubから直接デプロイしていたが、それでは内容の把握が十分にできないので、一度、ローカルにクローンしてから利用する。

~~~
$ git clone -b stable https://github.com/argoproj/argo-rollouts
~~~

Argoロールアウトのカスタムコントローラーを動かす名前空間 argo-rollouts を作成してから、インストールする。

~~~
export ANS=argo-rollouts
$ kubectl create namespace $ANSa
~~~

kustomzie の 構成ファイルが、以下のパスに置かれているので、これを利用する。

~~~
$ cd argo-rollouts/manifests/cluster-install
$ kubectl create -k ./ -n $ANS
~~~

しばらくすると、Argo Rollout の カスタムコントローラーが立ち上がってくるので、この状態になれば準備完了である。

~~~
$ kubectl get pod -n $ANS
NAME                             READY   STATUS    RESTARTS   AGE
argo-rollouts-6bcb4945db-dp58j   1/1     Running   0          13s
~~~


## Kubectl Argo-rollouts プラグインのインストール

Argoロールアウトには、kubectlコマンドのプラグインが提供されている。これを利用する事で見やすい表示を得ることができる。 参考資料にはダウンロードする方法もあるが、ここでは、macOSのパッケージマネージャー brew を使ってインストールした。

~~~
$ brew install argoproj/tap/kubectl-argo-rollouts
~~~

一応、バージョンも確認しておく。

~~~
$ kubectl argo rollouts version
kubectl-argo-rollouts: v0.8.3+a0dfe6d
  BuildDate: 2020-06-04T00:59:12Z
  GitCommit: a0dfe6d61bf303ff3ab4669dfb756468d4c54d17
  GitTreeState: clean
  GoVersion: go1.13.1
  Compiler: gc
  Platform: darwin/amd64
~~~


## マネージド Istio セットアップ

次に Istio のセットアップに進む。アップストリームの Kubernetes を使用する場合には、手作業でインストールしても良いが、クラウドの便利な機能を利用する。

IBM Cloud のマネージド Istio は、クラウドのウェブコンソールからクリックして起動しておく。起動を確認するには、`ic ks cluster addon ls -c <k8sクラスタ名>` を実行する。以下の例では、ワーカーノード x ２のクラスタで、Istioを実行したが、リソース不足のメッセージが出たので、ワーカーノード x3 体制で実施した。

2ノード時の表示では、リソースが足りないことを表示している。このまま実行してもリソース不足で正常に動かない可能性があるので、ノードを追加することにした。

~~~console:
$ ic ks cluster addon ls -c tok05-b3c.4x16
OK
名前    バージョン   正常性の状態   正常性の状況
istio   1.6        warning      Cluster resources low, not enough workers in Ready state. 
~~~

クラウドのウェブコンソールから、3ノードに増強した結果、警告が消え、正常を表す表示に変わったので、次の作業へ進む。

~~~console:
$ ic ks cluster addon ls -c tok05-b3c.4x16
OK
名前    バージョン   正常性の状態   正常性の状況
istio   1.6        normal       Addon Ready  
~~~


## Istio モニタリング機能の有効化

マネージド Istio では 視覚化のためのツールを簡単に起動できるように設定されている。最初から起動するとリソースを大量に消費するので、ユーザーへの配慮がされている様だ。 Kialiなどを有効化するために、次のコマンドで実行中の設定を編集する。

~~~console:
$ kubectl edit cm managed-istio-custom -n ibm-operators
~~~

上記のコマンドで起動したエディタで `istio-monitoring: "true"` を追加する。これで保存すれば、自動的にKialiなどモニタリングのポッドが起動する。

~~~yaml:
apiVersion: v1
data:
  _example: ""
  istio-ingressgateway-zone-1: tok05
  istio-monitoring: "true"
kind: ConfigMap
metadata:
  annotations:
    armada-service: addon-istio
<以下省略>
~~~


## istioctl コマンドのセットアップ

GitHub https://github.com/istio/istio の master をクリックして tag から最新リリースのバージョンを確認する。その値を**ISTIO_VERSION**の値としてセットする。以下の操作で、`istioctl`コマンドが利用できるようになる。

~~~console:
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.6.5 sh -
cd istio-1.6.5
export PATH=$PWD/bin:$PATH
~~~

動作確認のために `istioctl version` を実行しておく。


## アプリケーションの名前空間作成とIstio管理対象に設定

Argo Rollouts が対象とする名前空間を作成して、Istioの管理対象とするために、名前空間にラベルを設定する。

~~~console:
export AN=argo-apl
kubectl create ns $AN
kubectl label namespace $AN istio-injection=enabled
~~~


## Argo ロールアウト アプリケーションの構成ファイル作成

アプリケーションを動かすためのマニフェストを準備する。 ここで動作させるコンテナは https://hub.docker.com/r/argoproj/rollouts-demo/tags にあるイメージで、tag に色が設定されている。起動すると何色に対してリクエストされたか判る様にWeb画面が表示される。

~~~
.
├── gateway.yaml         Istio の Ingress gateway と連携するための設定
├── kustomization.yaml   マニフェストの実行順番を書いておき、このファイルを使ってデプロイする
├── rollout.yaml         Argoロールアウトのマニフェストで、ロールアウトの仕様を記述、カナリアリリース時の割合もここに定義する
├── services.yaml        カナリアリリース対象のポッドへのサービスの定義と、元のバージョンへのサービスと２種類
└── virtualsvc.yaml      Istio の 仮想サービスの定義でシェイプの初期値をセット
~~~

続いて各構成ファイルの内容を確認する。

最初に Kustomization をみる事で、全体の適用順番を知ることができる。

~~~yaml:kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- rollout.yaml
- services.yaml
- gateway.yaml
- virtualsvc.yaml
~~~

次は、ArgoロールアウトのCRDに対する構成指示である。
`trafficRouting` に `Istio` を指定する。そして、`steps` で最初のカナリアリリースの段階で `setWeight: 5`を指定する。そして、`pasue: {}`を入れる事で、`kubectl argo` コマンドからの指示を待つ。

ポッドテンプレートのスペックが記述されているが、デプロイメントコントローラーなどポッドのコントローラーが指定が無いことに注目して欲しい。Argoコントローラーが、ReplicaSetsコントローラーに指示を与え、ポッドの起動をおこなっている。

~~~yaml:rollout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-demo
spec:
  replicas: 1
  strategy:
    canary:
      canaryService: rollouts-demo-canary
      stableService: rollouts-demo-stable
      trafficRouting:
        istio:
          virtualService:
            name: rollouts-demo-vsvc
            routes:
            - primary # At least one route is required
      steps:
      - setWeight: 5
      - pause: {}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollouts-demo
  template:
    metadata:
      labels:
        app: rollouts-demo
        istio-injection: enabled
    spec:
      containers:
      - name: rollouts-demo
        image: argoproj/rollouts-demo:blue
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        resources:
          requests:
            memory: 32Mi
            cpu: 5m
~~~

サービスは 名前を変えて２種類設定する。

~~~yaml:services.yaml
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-canary
spec:
  ports:
  - port: 80
    targetPort: http
    protocol: TCP
    name: http
  selector:
    app: rollouts-demo
    # This selector will be updated with the pod-template-hash of the canary ReplicaSet. e.g.:
    # rollouts-pod-template-hash: 7bf84f9696
---
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-stable
spec:
  ports:
  - port: 80
    targetPort: http
    protocol: TCP
    name: http
  selector:
    app: rollouts-demo
    # This selector will be updated with the pod-template-hash of the stable ReplicaSet. e.g.:
    # rollouts-pod-template-hash: 789746c88d
~~~

Istio Ingressゲートウェイを繋げるための設定

~~~yaml:gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: rollouts-demo-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"
~~~

Istio Ingressゲートウェイから、転送先のサービスを記述する。 `weight` に初期値を設定しておく。これはArgoロールアウトのCRDから自動変更されるので、このファイルを手動で変更して再適用する必要は無い。

~~~yaml:virtualsvc.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: rollouts-demo-vsvc
spec:
  gateways:
  - rollouts-demo-gateway
  hosts:
  - "*"
  http:
  - name: primary
    route:
    - destination:
        host: rollouts-demo-stable
      weight: 100
    - destination:
        host: rollouts-demo-canary
      weight: 0
~~~


## アプリケーションのでデプロイ

kubectl の Kustomzieの機能を利用して、アプリケーションをデプロイする。

~~~console:
$ kubectl apply -k . -n $AN
service/rollouts-demo-canary created
service/rollouts-demo-stable created
rollout.argoproj.io/rollouts-demo created
gateway.networking.istio.io/rollouts-demo-gateway created
virtualservice.networking.istio.io/rollouts-demo-vsvc created
~~~

Argoロールアウト カスタムリソースが起動する。その管理下でポッドが起動しているのが判る。そして、READY の値が 2/2 となっているので Istio のインジェクトションが働いて Istio Proxy のサイドカーが注入されていることが判る。

~~~console:
$ kubectl get ro  -n $AN
NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE
rollouts-demo   1         1         1            1

$ kubectl get pod  -n $AN
NAME                             READY   STATUS    RESTARTS   AGE
rollouts-demo-594bbbb9bf-r89tn   2/2     Running   0          6m26s
~~~

Istio のオブジェクト gateway も起動が確認できる。

~~~console:
$ kubectl get gateway -n $AN
NAME                    AGE
rollouts-demo-gateway   7m27s
~~~


## アプリケーションへのアクセス

インターネットからアクセスのため Istio イングレスゲートウェイの外部IPアドレスを確認する。

~~~console:
$ kubectl get svc istio-ingressgateway -n istio-system
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                                                      AGE
istio-ingressgateway   LoadBalancer   172.21.166.163   165.192.86.82   15021:30016/TCP,80:31811/TCP,443:30768/TCP,15443:32152/TCP   3d1h
~~~

このIPアドレスにブラウザからアクセすると、宇宙人の画面が表示される。水玉とバーグラフはブラウザからのトラフィックの結果を示しており、blueのタグの付いたコンテナから応答を受けていることを表している。

![ss-argo-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/44f65f26-17f0-2790-75e9-3f345f6cc4c1.png)


## モニタリング

`kubectl argo` プラグインを利用した表示で、Stableバージョンが動作しているのが判る。

![ss-argo-plugin-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/47fbba97-12f4-9b6f-9b24-29839eaec6ab.png)

Isito に組み込まれている Kiali から視覚化した様子。Isitoイングレスゲートウェイ - サービス - ポッド のリクエストの流れを直感的にみることができる。これは便利だ。
 
![ss-kiali-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/740f54bf-28a6-7ed8-3925-988eb66f4a9c.png)



## カナリアリリース

次にカナリアリリースを開始する。 コマンドラインから、次にリリースするイメージ `rollouts-demo=argoproj/rollouts-demo:yellow` を指定する。これにより予め設定したカナリアリリースのサービスへトラフィックが回される様になる。その比率は、rolloutes.yamlで指定した 5％となる。

~~~console:
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
$ kubectl argo rollouts get rollout rollouts-demo
~~~

まずは、Argoプラグインの画面で確認する。黄色文字がカナリアリリースしたコンテナイメージだ。

![ss-argo-plugin-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/648b6a5b-9595-0d96-ce72-a47abe9a88b1.png)


Argoアプリケーションの表示では、5％に相当するトラフィック量の黄色の水玉が表示された。

![ss-argo-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/362be6a1-19ea-15f3-19a3-32b1bf3a8692.png)


Kialiの画面を確認すると、Istioによって二つのサービスへトラフィックが分流されていることが把握できる。

![ss-kiali-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/0a848667-fe28-e413-1e3d-f51565d5dcc2.png)


## カナリアリリース プロモーション

カナリアリリースの評価結果が良好なので、手動で本番サービスに昇格させることにする。

~~~console:
$ kubectl argo rollouts promote rollouts-demo
~~~

以下はコマンド実行直後の状態である。`revision: 2`のINFOが stable に変わって、本番リリースされたことが確認できる。

![ss-argo-plugin-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/1b1a6778-5824-8a89-bf5e-e43c41abb58d.png)


水玉は黄色一色に変わった。

![ss-argo-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/63a2c2ab-a723-5445-6cfd-64fdde31cda1.png)


Kiali の表示では、Stable 側のサービスにトラフィックが流れる、後からデプロイした Yellowタグのポッドが、Stableとして扱われいるのが見て判る。

![ss-argo-plugin-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42829/37ba43cc-c0b4-8558-1db8-247c660d4f3e.png)



## まとめ

Istioを利用しないでArgoロールアウトを使用した場合、トラフィックシェーピングが出来ない代わりに、ポッドの存在数で割り当て量をコントロールする。つまり、カナリアリリースで10%を指定すると、ステーブルのポッドを9個起動して、その中に１個のカナリアポッドを起動する。しかし、これでは無駄なリソースを使用することになり、サービスのランダムな割り振りに依存した制御となりエレガントでは無い。それに対して、Istioを組み合わせる事で、正確なトラフィック制御、グラフィカルで直感的なビュー、そして、サービスメッシュとしての拡張性を与えることができる。

Istioはサービスメッシュの側面が強調されるが、実際、Ingress ゲートウェイとして利用すると、従来のイングレスコントローラーを使用するよりも、多くの利点を受けることができる。もっと、Isitoを使わなけえれば勿体ない。

Argoロールアウトは、デプロイメントコントローラーにとって代わり、ポッドのレプリカ数をコントロールする。Istioと統合して利用できる点でデプロイメントコントローラーを駆使して、ロールアウトするよりも、運用は楽になるのではないかと期待される。


## 参考資料

* Getting Started, https://argoproj.github.io/argo-rollouts/getting-started/
* Roleout Demo, https://github.com/argoproj/rollouts-demo
* https://github.com/argoproj/argo-rollouts
* https://www.youtube.com/watch?v=2WSJF7d8dUg