Enjoy Architecting

Twitter: @taisho6339

Istio + Argo Rolloutとカナリアリリース

本記事について

Argo Rolloutを導入すると、様々なデプロイ戦略を実践することができる。 ただ少し実際の運用イメージが見えづらかったので整理してみる。

Argo Rolloutについての概要

Argo Rolloutとは通常のk8sのDeploymentに加え、カナリアリリースなどの様々なアップデート戦略をサポートするためのコンポーネント。 Rolloutという、Deploymentに対し、様々なPodのアップデート戦略をつけ加えたCRDを用いる。 Deployment同様に内部ではReplicaSetを管理していて、 アップデートのたびに新しいReplicaSetを作り、設定の通りにPodを移行させる。

カナリアリリース

一部のリクエストを新しいバージョンに流し、なにかあればロールバック、何も無ければ徐々にトラフィックを増やし、最終的に100%に切り替える。 ArgoRolloutでは、Rolloutリソースにどのように移行していくかをステップごとに宣言する。 設定できるのは下記のような項目。

  • setWeightでカナリアバージョンの比重を設定する
  • pauseでその状態でどのくらい待機するかを定義する
  • analysisで同期的な新バージョンのヘルスチェックを挟む

ヘルスチェックについて

B/Gやカナリアの場合、定期的にヘルスチェックを行い、失敗した場合にロールバックすることができる。 実際にはAnalysisTemplateに記述し、Rolloutで指定することで、実行時にAnalysisRunリソースが作られ、 指定したヘルスチェックを実行する。 AnalysisTemplateでは、下記の実行形式をサポートしている。

  • Prometheusサーバからのメトリクス取得

    • 一定時間内の200エラーが95%以上など
    • 一定時間内の5xx系エラーが1%以下など
  • Job Metrics

  • WebHook

    • クラスタ内、クラスタ外に対して指定タイミングでリクエストを送り、その結果でもって判別する
    • 結果はJSONである必要があるが、形式は問わない(パースの設定をすることができる)

ちなみにAnalysisを定義しない場合はロールバックは自動で行わない。

結局Argo Rolloutを使って担保されるものはなに?

カナリアリリースの想定実行フロー

  • 新バージョンデプロイ => 数%のトラフィックを新バージョンへ流す => 一定時間経って問題なければ自動的に新バージョンをStableバージョンを切り替え
  • 新バージョンデプロイ => 数%のトラフィックを新バージョンへ流す => manual approveで新バージョンをStableバージョンへ切り替え

やらないといけないこと

  • DeploymentはRolloutに置き換える
  • カナリアリリース用のServiceを作成する
  • VirtualServiceでカナリアリリースサービスとStableリリース用サービスで比重を0%, 100%に設定しておく
  • ヘルスチェック用のリソース定義(AnalysisTemplate)
  • Rolloutでstable用Service, canary用Service, virtualService, AnalysisTemplateを指定しておく

挙動FAQ

  • Podが存在しないときはどうなる?

    • Deploymentと同様で、UpdatePolicyによらず、新規のReplicaSetおよびPodが作られる
  • どのようにWeightをコントロールしている?

    • Traffic Routingの設定を組み合わせることでリクエストベースのWeigh制御ができるが、何も指定しないときはReplicaSetのPodの割合で調整する
    • IstioのようなTraffic Routing機能を持つコンポーネントと連携すると分散ポリシーを移譲することができる
  • Istioと連携するとどうなる?

    • ルーティングがリクエストの割合ベースで分散できる
    • WeightはArgoRolloutのPodがVirtualServiceの設定を随時書き換えている
  • カナリア用のServiceの向き先はStableと同じでいいの?

    • 良い。Argo Rolloutが随時Serviceのセレクタに条件を付け足している

How to Use

設定例

  • stable = v1, canary = v2のケース
  • AnalysisTemplateを使って、500エラーが一定期間内に3回でたらNGという定義を行っている
    • istioが持つprometheusに対してクエリを投げている
  • この設定では、比重を10%にし10分待ち、以降徐々に10分毎に20%ずつ増やす。50%になったら、そのまま待ち手動によるResumeを待つ。
apiVersion: v1
kind: Service
metadata:
  name: reviews
  labels:
    app: reviews
    service: reviews
spec:
  ports:
    - port: 9080
      name: http
  selector:
    app: reviews
---
apiVersion: v1
kind: Service
metadata:
  name: reviews-canary
  labels:
    app: reviews
    service: reviews
spec:
  ports:
    - port: 9080
      name: http
  selector:
    app: reviews
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: reviews
  labels:
    app: reviews
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
  template:
    metadata:
      labels:
        app: reviews
        version: v2
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
        - name: reviews
          image: docker.io/istio/examples-bookinfo-reviews-v2:1.15.0
      volumes:
        - name: wlp-output
          emptyDir: {}
        - name: tmp
          emptyDir: {}
  strategy:
    canary:
      analysis:
        templates:
          - templateName: failure-count
        args:
          - name: service-name
            value: reviews-canary.default.svc.cluster.local
      stableService: reviews
      canaryService: reviews-canary
      trafficRouting:
        istio:
          virtualService:
            name: reviews
            routes:
              - primary
      steps:
        - setWeight: 10
        - pause: { 10m }
        - setWeight: 30
        - pause: { 10m }
        - setWeight: 50
        - pause: { }
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: failure-count
spec:
  args:
    - name: service-name
  metrics:
    - name: failure-count
      interval: 30s
      failureCondition: result >= 3
      failureLimit: 3
      provider:
        prometheus:
          address: "http://prometheus.istio-system.svc.cluster.local:9090"
          query: |
            sum(irate(
              istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[30s]
            ))

GitOpsとの兼ね合い

  • カナリアリリースが完了し、100%新バージョンに移行したあとに同期されたらどうなる?

    • 結論: 何も変わらない
      • ArgoRolloutがいじった結果とリソースが一緒になる
      • StableとCanaryのServiceの向き先が両方共新Podになっていて、VirtualServiceもStableに100%向ける設定になっている
  • カナリアリリース実行中に同期されたらどうなる?

    • 結論: 一度ルーティングの割合がStable 100%, Canary 0%にリセットされた後、Rolloutによって同期される直前の状態に戻される

カナリアリリース時と通常リリース時でのフローの違い

デバッグ方法

  • RolloutとAnalysisRunリソースを見るとデプロイの挙動が分かる
 kd rollouts.argoproj.io
 kd analysisruns.argoproj.io

Analysisの設定は実運用上どうすれば良い?

  • IstioでPrometheusを有効にしてインストールし、Istioのメトリクスによって判別する

    • --set values.prometheus.enabled=trueをつけてistioctl manifest applyすればprometheusが有効になるので、これをつかってメトリクスを取得することはできる
    • ただ、素直に入れても可用性は担保されていないので、別途冗長化するなどの可用性対応が必要になってくる
  • Stackdriverなどにメトリクスを集約している場合、Goなどでデータを引っ張ってきた上で判定する

  • カナリアリリースした結果失敗してロールバックしたよね、というのはどのように検知すればよいか?

    • Argo Rolloutの機能では通知できないので、自前の検証プログラムを使ってAnalysisRunを動かす場合は、プログラムの中にエラーケースでアラートするように記載しておく
    • 例えばStackdriverならCloud Monitoringを活用して、ロールバック条件と同じ条件でアラートが飛ぶようにポリシー設定しておく