OpenShiftでProgressive Deliveryやってみた！（Argo Rollouts応用編-Istio+Prometheus連携）


# 背景
こちらの記事は以下の記事の続きとなりますので、背景は以下の記事をご参照ください。
[OpenShiftでProgressive Deliveryやってみた！（Argo Rollouts基礎編-CLI）](https://qiita.com/strada/items/a498c7f4aea2c53016eb)
[OpenShiftでProgressive Deliveryやってみた！（Argo Rollouts基本編-GUI）](https://qiita.com/strada/items/f3254661aaad5ea1d0b9)
[OpenShiftでProgressive Deliveryやってみた！（Argo Rollouts応用編-Prometheusのメトリクスと連携）](https://qiita.com/strada/items/26f3be3b52b12113b01c)

# シナリオ
今回の検証で実施するProgressive Deliveryのシナリオは以下のとおりです。

- Rolloutのコンテナ・イメージを新しいバージョンに更新
    - 新しいバージョンはアクセスすると500 InternalServerErrorが発生する
- Argo Rolloutsによりトラフィックの50%がCanaryに割り当てた後一時停止
- アプリケーションの検証実施
    - 50%の確率でCanaryに割り当てられるのでInternalServerErrorが応答される
- Argo Rolloutsで一時停止しているRolloutを再開（Promote）
- Argo Rolloutsの分析が実行される
    - Canaryのサービスへのトラフィック上で過去5分の間の500 InternalServerErrorの回数をPrometheusから取得
        - 5回以下の場合はRolloutが継続し完了する
        - 5回より多く出力されている場合はRolloutがロールバックされる


# 構成手順

## サンプルアプリケーションの拡張
OpenShift v4.6のマニュアル内でも使用されている以下のサンプルアプリケーションで500 InternalServerErrorが出力される新しいバージョンを作成します。

https://github.com/brancz/prometheus-example-app

- 前述したリポジトリをFORKして以下の箇所を編集
    - コンテキストルート（/）へのアクセスを200 正常応答から500 InternalServerErrorに変更

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/afdb89d5-6155-f1da-3a00-35e93e4ebbc6.png)

- コンテナ・イメージをビルドするためOpenShiftでアプリケーションを作成

```
PS D:\git> oc new-app --as-deployment-config --name prometheus-example-app golang~https://github.com/strada501/prometheus-example-app.git
--> Found image ca0cba7 (11 months old) in image stream "openshift/golang" under tag "1.13.4-ubi8" for "golang"

    Go 1.13.4
    ---------
    Go Toolset available as a container is a base platform for building and running various Go applications and frameworks. Go is an easy to learn, powerful, statically typed language in the C/C++ tradition with garbage collection, concurrent programming support, and memory safety features.

    Tags: builder, golang, golang113, rh-golang113, go

    * A source build using source code from https://github.com/strada501/prometheus-example-app.git will be created
      * The resulting image will be pushed to image stream tag "prometheus-example-app:latest"
      * Use 'oc start-build' to trigger a new build
    * This image will be deployed in deployment config "prometheus-example-app"
    * The image does not expose any ports - if you want to load balance or send traffic to this component
      you will need to create a service with 'oc expose dc/prometheus-example-app --port=[port]' later

--> Creating resources ...
    imagestream.image.openshift.io "prometheus-example-app" created
    buildconfig.build.openshift.io "prometheus-example-app" created
    deploymentconfig.apps.openshift.io "prometheus-example-app" created
--> Success
    Build scheduled, use 'oc logs -f buildconfig/prometheus-example-app' to track its progress.
    Run 'oc status' to view your app.
```

- ビルド・ログを確認し、`Push successful`を確認

```
PS D:\git> oc logs -f buildconfig/prometheus-example-app
Cloning "https://github.com/strada501/prometheus-example-app.git" ...
        Commit: c65b7cdbd27e27ad4e3c22c1e94de67c83a8a4fa (Update main.go)
        Author: Yasuyuki Kubota <strada@jp.ibm.com>
        Date:   Thu Jul 15 14:13:08 2021 +0900
Caching blobs under "/var/cache/blobs".
Getting image source signatures
Copying blob sha256:47db82df7f3f4393c1f19c362a2db2c47ca049b6fb20bef041dfc9bdb12a4504
Copying blob sha256:71391dc11a78542160544b68e45bc123ff55a2e84aeb6fa99b672d75765bc2f8
Copying blob sha256:6cc5487eb078291c64e0539c6d57546606b3936b71eabc7eec7960d069927ba4
Copying blob sha256:77c58f19bd6e67185938abb6bbb6ec229e07a5e607453904294d982de141d2f0
Copying blob sha256:0b1d2fb42752674d5c08548ef22a1c25885dd8588c0df75278a6a17fa1362e80
Warning: Pull failed, retrying in 5s ...
Getting image source signatures
Copying blob sha256:47db82df7f3f4393c1f19c362a2db2c47ca049b6fb20bef041dfc9bdb12a4504
Copying blob sha256:0b1d2fb42752674d5c08548ef22a1c25885dd8588c0df75278a6a17fa1362e80
Copying blob sha256:71391dc11a78542160544b68e45bc123ff55a2e84aeb6fa99b672d75765bc2f8
Copying blob sha256:77c58f19bd6e67185938abb6bbb6ec229e07a5e607453904294d982de141d2f0
Copying blob sha256:6cc5487eb078291c64e0539c6d57546606b3936b71eabc7eec7960d069927ba4
Copying config sha256:ca0cba75b144d9ec831c913b2146a6e9746c6cf43a4f6314047915f934c0b2f0
Writing manifest to image destination
Storing signatures
Generating dockerfile with builder image image-registry.openshift-image-registry.svc:5000/openshift/golang@sha256:ff3b2217e9a0bbb05536713606bdf49d0c9ef7754bd323482c41317a896c5bf2
STEP 1: FROM image-registry.openshift-image-registry.svc:5000/openshift/golang@sha256:ff3b2217e9a0bbb05536713606bdf49d0c9ef7754bd323482c41317a896c5bf2
STEP 2: LABEL "io.openshift.build.commit.id"="c65b7cdbd27e27ad4e3c22c1e94de67c83a8a4fa"       "io.openshift.build.commit.ref"="master"       "io.openshift.build.commit.message"="Update main.go"       "io.openshift.build.source-location"="https://github.com/strada501/prometheus-example-app.git"       "io.openshift.build.image"="image-registry.openshift-image-registry.svc:5000/openshift/golang@sha256:ff3b2217e9a0bbb05536713606bdf49d0c9ef7754bd323482c41317a896c5bf2"       "io.openshift.build.commit.author"="Yasuyuki Kubota <strada@jp.ibm.com>"       "io.openshift.build.commit.date"="Thu Jul 15 14:13:08 2021 +0900"
STEP 3: ENV OPENSHIFT_BUILD_NAME="prometheus-example-app-1"     OPENSHIFT_BUILD_NAMESPACE="kubota-test"     OPENSHIFT_BUILD_SOURCE="https://github.com/strada501/prometheus-example-app.git"     OPENSHIFT_BUILD_COMMIT="c65b7cdbd27e27ad4e3c22c1e94de67c83a8a4fa"
STEP 4: USER root
STEP 5: COPY upload/src /tmp/src
STEP 6: RUN chown -R 1001:0 /tmp/src
STEP 7: USER 1001
STEP 8: RUN /usr/libexec/s2i/assemble
/tmp/src ~
go: downloading github.com/prometheus/client_golang v1.3.0
go: extracting github.com/prometheus/client_golang v1.3.0
go: downloading github.com/prometheus/common v0.7.0
go: downloading github.com/prometheus/client_model v0.1.0
go: downloading github.com/beorn7/perks v1.0.1
go: downloading github.com/cespare/xxhash/v2 v2.1.1
go: downloading github.com/golang/protobuf v1.3.2
go: downloading github.com/prometheus/procfs v0.0.8
go: extracting github.com/prometheus/client_model v0.1.0
go: extracting github.com/beorn7/perks v1.0.1
go: extracting github.com/cespare/xxhash/v2 v2.1.1
go: extracting github.com/prometheus/common v0.7.0
go: extracting github.com/prometheus/procfs v0.0.8
go: extracting github.com/golang/protobuf v1.3.2
go: downloading github.com/matttproud/golang_protobuf_extensions v1.0.1
go: extracting github.com/matttproud/golang_protobuf_extensions v1.0.1
go: finding github.com/prometheus/client_golang v1.3.0
go: finding github.com/prometheus/client_model v0.1.0
go: finding github.com/beorn7/perks v1.0.1
go: finding github.com/cespare/xxhash/v2 v2.1.1
go: finding github.com/prometheus/common v0.7.0
go: finding github.com/golang/protobuf v1.3.2
go: finding github.com/matttproud/golang_protobuf_extensions v1.0.1
go: finding github.com/prometheus/procfs v0.0.8
~
STEP 9: CMD /usr/libexec/s2i/run
STEP 10: COMMIT temp.builder.openshift.io/kubota-test/prometheus-example-app-1:1b4719fa
Getting image source signatures
Copying blob sha256:226bfaae015f1d5712cfced3b5b628206618eaacf72f4a44d0e4084071996319
Copying blob sha256:70056249a0e202adae10aa45fef56ac4cc6497619767753515022bc9c1278251
Copying blob sha256:0bfe5b62a1ad05827328d7629f8a9a7e16642fd14f40070a395fcd16917eb4f7
Copying blob sha256:7fbe9fcfa2da3fadd05c2e7caabc3da06c1a7fc36c14f5b6dddedc5ffb42be11
Copying blob sha256:1ee5a618948c954f5939610cdea18abc9b4ea49837b2284dfcc704c107e8fdd8
Copying blob sha256:a5e344f477e23a8dcafc25df603a178a9470835a1524867bfd1db3e3de38f41c
Copying config sha256:fda5edea5118eeb68890239e18a706f07cee81550e4029540c9cfaa8834a5ee5
Writing manifest to image destination
Storing signatures
--> fda5edea511
fda5edea5118eeb68890239e18a706f07cee81550e4029540c9cfaa8834a5ee5

Pushing image image-registry.openshift-image-registry.svc:5000/kubota-test/prometheus-example-app:latest ...
Getting image source signatures
Copying blob sha256:a5e344f477e23a8dcafc25df603a178a9470835a1524867bfd1db3e3de38f41c
Copying blob sha256:47db82df7f3f4393c1f19c362a2db2c47ca049b6fb20bef041dfc9bdb12a4504
Copying blob sha256:6cc5487eb078291c64e0539c6d57546606b3936b71eabc7eec7960d069927ba4
Copying blob sha256:71391dc11a78542160544b68e45bc123ff55a2e84aeb6fa99b672d75765bc2f8
Copying blob sha256:77c58f19bd6e67185938abb6bbb6ec229e07a5e607453904294d982de141d2f0
Copying blob sha256:0b1d2fb42752674d5c08548ef22a1c25885dd8588c0df75278a6a17fa1362e80
Copying config sha256:fda5edea5118eeb68890239e18a706f07cee81550e4029540c9cfaa8834a5ee5
Writing manifest to image destination
Storing signatures
Successfully pushed image-registry.openshift-image-registry.svc:5000/kubota-test/prometheus-example-app@sha256:5613eb8474ea6ab372ec6c1c98a9517be61cb39ccaf20214c348c4ab04c3b227
Push successful
PS D:\git>
```

- `Successfully pushed`以降のコンテナ・イメージのURIをメモしておく

```
image-registry.openshift-image-registry.svc:5000/kubota-test/prometheus-example-app@sha256:5613eb8474ea6ab372ec6c1c98a9517be61cb39ccaf20214c348c4ab04c3b227
```

- Podがデプロイされて`Running`となっていることを確認

```
PS D:\git> oc get po
NAME                                  READY   STATUS      RESTARTS   AGE
prometheus-example-app-1-56mtd        1/1     Running     0          18s
prometheus-example-app-1-build        0/1     Completed   0          94s
prometheus-example-app-1-deploy       0/1     Completed   0          23s
```

- Podに接続して稼働確認

```
PS D:\git> oc rsh prometheus-example-app-1-56mtd
sh-4.4$ curl -si http://localhost:8080/
HTTP/1.1 500 Internal Server Error
Date: Thu, 15 Jul 2021 05:16:11 GMT
Content-Length: 0

sh-4.4$ exit
exit
PS D:\git>
```
## OpenShift Service Meshのインストール
OpenShift Service Meshのインストール手順については以下の記事を参照してください。

https://qiita.com/strada/items/578d0b552da1e9d2a1f1

## Argo Rolloutsのインストール
Argo Rolloutsのインストール手順については以下の記事を参照してください。

https://qiita.com/strada/items/a498c7f4aea2c53016eb


## Prometheus Operatorのインストール

Prometheus Operatorのインストール手順については以下の記事を参照してください。

https://qiita.com/strada/items/23bccc596be587fe003c

## Argo RolloutsおよびOpenShift Service Mesh用のリソースを作成
以下のマニュアルを参考にArgo Rolloutsのリソースを作成します。

https://argoproj.github.io/argo-rollouts/features/traffic-management/istio/

今回は[Host-level Traffic Splitting](https://argoproj.github.io/argo-rollouts/features/traffic-management/istio/#host-level-traffic-splitting)の例を使用


Istioと連携する場合はstrategy->canaryの下に以下の設定を追加します

```
  strategy:
    canary:
      canaryService: canary-svc  # required
      stableService: stable-svc  # required
      trafficRouting:
        istio:
          virtualService: 
            name: rollout-vsvc   # required
            routes:
            - primary            # optional if there is a single route in VirtualService, required otherwise
```

- istio-systemのistio-ingressgatewayのRouteを取得

```
PS D:\git> oc get route -n istio-system
NAME                                                 HOST/PORT                                                                                                                                                   PATH   SERVICES               PORT    TERMINATION          WILDCARD
grafana                                              grafana-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud                                                     grafana                <all>   reencrypt/Redirect   None
istio-ingressgateway                                 istio-ingressgateway-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud                                        istio-ingressgateway   8080                         None
jaeger                                               jaeger-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud                                                      jaeger-query           <all>   reencrypt            None
kiali                                                kiali-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud                                                       kiali                  <all>   reencrypt/Redirect   None
kubota-test-bookinfo-gateway-525eca1d5089dbdc        kubota-test-bookinfo-gateway-525eca1d5089dbdc-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud               istio-ingressgateway   http2                        None
kubota-test-rollouts-demo-gateway-525eca1d5089dbdc   kubota-test-rollouts-demo-gateway-525eca1d5089dbdc-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud          istio-ingressgateway   http2                        None
prometheus                                           prometheus-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud                                                  prometheus             <all>   reencrypt/Redirect   None
PS D:\git>
```

- Prometheusと連携するためのAnalysisTemplateのマニフェストファイルを作成
    - Prometheusのアドレスにはistio-systemのistio-ingressgatewayのRouteのアドレスを設定
    - Prometheusのクエリーにはcanaryで発生した直近5分の500 InternalServerErrorの回数を取得するクエリーを設定

```prometheus-anlysis-template.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: failure-count
spec:
  metrics:
  - name: http_error_count
    successCondition: result[0] <= 5
    provider:
      prometheus:
        address: "http://istio-ingressgateway-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud"
        query: |
          delta(http_requests_total{code="500",namespace="kubota-test",service="rollouts-demo-canary"}[5m])

```



- Rolloutのマニフェストファイルを作成
    - IstioのSidecarを注入するためAnnotationsに`sidecar.istio.io/inject: "true"`を追加
    - ステップは以下の通り
        - 50%をcanaryに割り当て一時停止
        - 再開されたらfailure-countのAnalysisTemplateを実行

```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: 50
      - pause: {}
      - analysis:
          templates:
          - templateName: failure-count
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollouts-demo
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "true"
      labels:
        app: rollouts-demo
    spec:
      containers:
      - name: rollouts-demo
        image: quay.io/brancz/prometheus-example-app:v0.2.0
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        resources:
          requests:
            memory: 32Mi
            cpu: 5m
```

- Serviceのマニフェストファイルを作成
    - OpenShift Service Meshでのトラフィックコントロールに対応するためStableとCanaryの2つのサービスを定義

```services.yaml
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-canary
  labels:
    app: 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
  labels:
    app: 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
```

- ServiceMonitorのマニフェストファイルを作成
    - 2つのサービスを定義するのでこちらも2つのMonitorを定義

```servicemonitors.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    team: frontend
    k8s-app: rollouts-demo-canary
  name: rollouts-demo-canary
spec:
  endpoints:
  - interval: 30s
    port: http
    scheme: http
  selector:
    matchLabels:
      app: rollouts-demo-canary
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    team: frontend
    k8s-app: rollouts-demo-stable
  name: rollouts-demo-stable
spec:
  endpoints:
  - interval: 30s
    port: http
    scheme: http
  selector:
    matchLabels:
      app: rollouts-demo-stable
```

- Gatewayのマニフェストファイルを作成

```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:
    - "*"
```

- VirtualServiceのマニフェストファイルを作成
    - コンテキストルート（/）と/errへのアクセスが検証用アプリケーションに割り振られるように設定
    - /apiへのアクセスがPrometheusに割り振られるように設定
        - サービスメッシュに参加しているプロジェクトではサービスのFQDN（prometheus.kubota-test.svc.cluster.local:9090）ではアクセスできないためingressgateway経由でアクセスできるようする

```virtualservice.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: rollouts-demo-vsvc
spec:
  gateways:
  - rollouts-demo-gateway
  hosts:
  - "*"
  http:
  - name: primary
    match:
    - uri:
        exact: /
    - uri:
        exact: /err
    route:
    - destination:
        host: rollouts-demo-stable
      weight: 100
    - destination:
        host: rollouts-demo-canary
      weight: 0
  - name: prometheus
    match:
    - uri:
        prefix: /api
    route:
    - destination:
        host: prometheus
```

- マニフェストファイルを以下の順番で適用
    - Gateway
    - VirtualService
    - AnalysisTemplate
    - Rollout
    - Service
    - ServiceMonitor


```
PS D:\git> oc apply -f .\gateway.yaml
gateway.networking.istio.io/rollouts-demo-gateway created
PS D:\git> oc apply -f .\virtualservice.yaml
virtualservice.networking.istio.io/rollouts-demo-vsvc created
PS D:\git> oc apply -f .\prometheus-anlysis-template.yaml
analysistemplate.argoproj.io/failure-count created
PS D:\git> oc apply -f .\rollout.yaml
rollout.argoproj.io/rollouts-demo created
PS D:\git> oc apply -f .\services.yaml
service/rollouts-demo-canary created
service/rollouts-demo-stable created
PS D:\git> oc apply -f .\servicemonitors.yaml
servicemonitor.monitoring.coreos.com/rollouts-demo-canary created
servicemonitor.monitoring.coreos.com/rollouts-demo-stable created
PS D:\git>
```

- Argo RolloutsのDashboardを起動

起動方法は以下の記事を参照してください。

https://qiita.com/strada/items/f3254661aaad5ea1d0b9

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/88b262d7-15a6-7a84-1b55-64a1d83d95e4.png)


- コンテナ・イメージを[500 InternalServerErrorが出力される新しいバージョン](#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E6%8B%A1%E5%BC%B5)に変更
    - なお、DashboardからだとOpenShiftの内部レジストリーを参照する形式のURIを正しく設定できないようなので以下のコマンドを実行して変更

```
PS D:\git> docker run --rm -v C:\Users\YASUYUKIKUBOTA\.kube\config:/.kube/config quay.io/argoproj/kubectl-argo-rollouts:master set image rollouts-demo rollouts-demo=image-registry.openshift-image-registry.svc:5000/kubota-test/prometheus-example-app@sha256:5613eb8474ea6ab372ec6c1c98a9517be61cb39ccaf20214c348c4ab04c3b227 --insecure-skip-tls-verify
rollout "rollouts-demo" image updated
PS D:\git>
```

- Dashboardから新しいバージョンのRolloutが開始されていることを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/e0d45bcb-a143-cc14-f026-7330025c3a42.png)

- 以下のコマンドを実行し、VirtualServiceの設定が変更されていることを確認

```
PS D:\git> oc describe vs rollouts-demo-vsvc
Name:         rollouts-demo-vsvc
Namespace:    kubota-test
Labels:       <none>
Annotations:  <none>
API Version:  networking.istio.io/v1beta1
Kind:         VirtualService
Metadata:
  Creation Timestamp:  2021-07-16T09:32:47Z
  Generation:          8
  Managed Fields:
    API Version:  networking.istio.io/v1alpha3
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:gateways:
        f:hosts:
    Manager:      kubectl-client-side-apply
    Operation:    Update
    Time:         2021-07-16T09:32:47Z
    API Version:  networking.istio.io/v1alpha3
    Fields Type:  FieldsV1
    fieldsV1:
      f:spec:
        f:http:
    Manager:         rollouts-controller
    Operation:       Update
    Time:            2021-07-16T10:32:56Z
  Resource Version:  735642
  Self Link:         /apis/networking.istio.io/v1beta1/namespaces/kubota-test/virtualservices/rollouts-demo-vsvc
  UID:               ac110ed1-dc46-4797-946f-6dad62d067e0
Spec:
  Gateways:
    rollouts-demo-gateway
  Hosts:
    *
  Http:
    Match:
      Uri:
        Exact:  /
      Uri:
        Exact:  /err
    Name:       primary
    Route:
      Destination:
        Host:  rollouts-demo-stable
      Weight:  50
      Destination:
        Host:  rollouts-demo-canary
      Weight:  50
    Match:
      Uri:
        Prefix:  /api
    Name:        prometheus
    Route:
      Destination:
        Host:  prometheus
Events:        <none>
PS D:\git>
```
StableとCanaryの割合がどちらも50%になっていることがわかります。


- ブラウザから以下のURLで検証用のアプリケーションに何度かアクセスし、500 InternalServerErrorを6回以上表示させる
    - http://istio-ingressgatewayのRoute/
- Stable（旧バージョン）にアクセスされた場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/f8c86182-25a9-8643-df61-dbb0fdfaef77.png)


- Canary （新バージョン）にアクセスされた場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/3e1c2c9f-5dc1-ccd9-38d6-07634a308a7d.png)


- Prometheus Serviceの9090ポートをlocalhostに転送し、以下のクエリーを実行する

```
delta(http_requests_total{code="500",namespace="kubota-test",service="rollouts-demo-canary"}[5m])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/3c755232-f3e2-d9ef-9098-d92425e52d8c.png)

結果が5より大きくなることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/0f9d6dbb-d4c2-cf6a-98df-5ef8bea4f17e.png)
上記のようにEmpty query resultが表示される場合はAnalysisTemplateが失敗するので、何度か新バージョンを表示させてから再度クエリーを実行します。

- DashboardからRolloutをPROMOTEする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/efc49e75-3834-ac36-c562-f5713e002597.png)

Prometheusのクエリーの結果が5より大きいためRolloutは失敗します。

- 以下のコマンドでAnalysisRunの内容を確認

```
PS D:\git> oc get analysisrun -o yaml
apiVersion: v1
items:
- apiVersion: argoproj.io/v1alpha1
  kind: AnalysisRun
  metadata:
    annotations:
      rollout.argoproj.io/revision: "2"
    creationTimestamp: "2021-07-16T10:15:58Z"
    generation: 2
    labels:
      rollout-type: Step
      rollouts-pod-template-hash: 59b469fdf7
      step-index: "2"
--- 中略 ---
    name: rollouts-demo-59b469fdf7-2-2
    namespace: kubota-test
    ownerReferences:
    - apiVersion: argoproj.io/v1alpha1
      blockOwnerDeletion: true
      controller: true
      kind: Rollout
      name: rollouts-demo
      uid: 5dc549be-f03e-4628-9e25-3182f7884180
    resourceVersion: "729720"
    selfLink: /apis/argoproj.io/v1alpha1/namespaces/kubota-test/analysisruns/rollouts-demo-59b469fdf7-2-2
    uid: b7932425-5502-4345-a603-94b269b0d1f0
  spec:
    metrics:
    - name: http_error_count
      provider:
        prometheus:
          address: http://istio-ingressgateway-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud
          query: |
            delta(http_requests_total{code="500",namespace="kubota-test",service="rollouts-demo-canary"}[5m])
      successCondition: result[0] <= 5
  status:
    message: metric "http_error_count" assessed Failed due to failed (1) > failureLimit (0)
    metricResults:
    - count: 1
      failed: 1
      measurements:
      - finishedAt: "2021-07-16T10:15:58Z"
        phase: Failed
        startedAt: "2021-07-16T10:15:58Z"
        value: '[16.311243770937153]'
      name: http_error_count
      phase: Failed
    phase: Failed
    startedAt: "2021-07-16T10:15:58Z"
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""
PS D:\git>
```

クエリーの結果が'[16.311243770937153]'だったため失敗したことがわかります。

- DashboardからRETRYを実施
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/7c67295c-ae17-0189-cbae-c76ac1a262ba.png)


- Canaryが公開されたら何度かCanaryの画面（InternalServerError）を表示させてから再度Prometheusでクエリーを実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/52a7b8e4-a2bf-2707-d38a-f143720b5e7a.png)

クエリーの結果が5以下であることを確認

- DashboardからPROMOTEを実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/d6b3138c-8999-d137-2d0f-609972381cd0.png)
今度はAnalysisRunが成功し、Rolloutが完了します。

以下のコマンドでVirtualServiceの設定を確認すると自動的にStableが100%に戻っていることがわかります。
```
PS D:\git> oc describe vs rollouts-demo-vsvc
Name:         rollouts-demo-vsvc
Namespace:    kubota-test
Labels:       <none>
Annotations:  <none>
API Version:  networking.istio.io/v1beta1
Kind:         VirtualService
Metadata:
  Creation Timestamp:  2021-07-16T09:32:47Z
  Generation:          7
  Managed Fields:
    API Version:  networking.istio.io/v1alpha3
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
      f:spec:
        .:
        f:gateways:
        f:hosts:
    Manager:      kubectl-client-side-apply
    Operation:    Update
    Time:         2021-07-16T09:32:47Z
    API Version:  networking.istio.io/v1alpha3
    Fields Type:  FieldsV1
    fieldsV1:
      f:spec:
        f:http:
    Manager:         rollouts-controller
    Operation:       Update
    Time:            2021-07-16T10:24:58Z
  Resource Version:  732846
  Self Link:         /apis/networking.istio.io/v1beta1/namespaces/kubota-test/virtualservices/rollouts-demo-vsvc
  UID:               ac110ed1-dc46-4797-946f-6dad62d067e0
Spec:
  Gateways:
    rollouts-demo-gateway
  Hosts:
    *
  Http:
    Match:
      Uri:
        Exact:  /
      Uri:
        Exact:  /err
    Name:       primary
    Route:
      Destination:
        Host:  rollouts-demo-stable
      Weight:  100
      Destination:
        Host:  rollouts-demo-canary
      Weight:  0
    Match:
      Uri:
        Prefix:  /api
    Name:        prometheus
    Route:
      Destination:
        Host:  prometheus
Events:        <none>
PS D:\git>
```

- 検証用のアプリケーションにアクセスすると100%が新しいバージョン（InternalServerError）に割り振られている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5259/144065d1-c4a4-4439-0a6f-8492a735d521.png)

- AnalysisRunの内容を確認

```
--- 省略 ---
  spec:
    metrics:
    - name: http_error_count
      provider:
        prometheus:
          address: http://istio-ingressgateway-istio-system.dte-ocp46-8uvivd-915b3b336cabec458a7c7ec2aa7c625f-0000.us-east.containers.appdomain.cloud
          query: |
            delta(http_requests_total{code="500",namespace="kubota-test",service="rollouts-demo-canary"}[5m])
      successCondition: result[0] <= 5
  status:
    metricResults:
    - count: 1
      measurements:
      - finishedAt: "2021-07-16T10:24:58Z"
        phase: Successful
        startedAt: "2021-07-16T10:24:58Z"
        value: '[0]'
      name: http_error_count
      phase: Successful
      successful: 1
    phase: Successful
    startedAt: "2021-07-16T10:24:58Z"
--- 省略 ---
```
クエリーの結果が'[0]'だったため成功になったことが分かる。

# まとめ
今回の記事の検証でArgo RolloutsとOpenShift Service Meshを連携することでトラフィックコントロールをServiceMeshと連携して実施できることがわかりました。
ServiceMeshと連携することで、Canaryへのアクセスのみのメトリクスを取得することが可能となったのでより精度の高い情報を元に分析を行うことが出来ることがわかりました。
また、ServiceMeshを利用しない場合のトラフィックの割り当ては全体のPod数（5）に対して、20%ならPodを1つCanaryにする、という方式となるため、Replica数がRolloutのステップに依存してしまうためよろしくありません。
ServiceMeshを利用している場合はこのような無駄も排除できます。

今後の課題としては、PrometheusのクエリーがEmpty query resultとなった場合にAnalysisRunが失敗してしまうため、失敗しない方法はないのかを調査していきたいと思います。