Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する連載。今回は、先進的なリリース戦略「プロフレッシブデリバリー」を「Argo Rollouts」で実践する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
Kubernetesやクラウドネイティブをより便利に利用する技術やツールの概要、使い方を凝縮して紹介する本連載「Cloud Nativeチートシート」。前回の記事「ブルーグリーンデプロイとカナリアリリースを理解したいならOSSの『Argo Rollouts』で実践しよう」では、Argo Rolloutsを利用して、ブルーグリーンデプロイとカナリアリリースを実践しました。今回は、プログレシッブデリバリー(Progressive Delivery)について解説し、Argo Rolloutsを用いて実践します。
「プログレッシブデリバリー」とは、RedMonkのJames Governor氏がMicrosoftのSam Guckenheimer氏のリリースモデル「Progressive Experimentation」を聞き、その話をLaunchDarklyのAdam Zimman氏に共有して議論したのをきっかけに生まれた言葉です。
従って広義の意味でのプログレッシブデリバリーとは、ダークローンチ、フィーチャーフラグ、カナリアリリース、ブルーグリーンデプロイ、A/Bテストによって定義される新しいデリバリーモデルを指します。
一方で、プログレッシブデリバリーは継続的デリバリー(Continuous Delivery)の進化系とも捉えることができます。継続的デリバリーが「より小さい機能でより頻繁にリリースを繰り返し、徐々に変更する」戦略を取るのに対し、プログレッシブデリバリーは「最初は少人数にリリースしながら、自動的にアプリケーションやサービスの状態を分析、評価し、徐々に大人数のユーザーにリリースする」戦略といえます。
従って狭義の意味でのプログレッシブデリバリーとは、カナリアリリースを自動化し、分析と評価を用いてリリースとロールバックを自動化するデプロイ戦略を指します。
具体的に継続的デリバリーと何が違うのか見ていきましょう。従来の継続的デリバリーにおけるデプロイフローがこちらです。
それに対し、プログレッシブデリバリーはこちらです。
新バージョンをリリースする際には、まず一部のテストユーザーおよびトラフィックに対してのみ反映します。ここまでは一般的なカナリアリリースと同様です。
異なる部分は、カナリアテストが完了した後に、エンドユーザーおよび本番トラフィックに対して反映する前段階として、デプロイされたアプリケーションに対してメトリクスなどを用いた「分析」を行い、問題なければデプロイの継続を、異常が見つかれば即座にロールバックを実行するという戦略を明示的に追加している点です。
従来のデプロイフローの中に明示的に「分析」や「自動ロールバック」という観点を加えることで、パイプラインによるデプロイスピードと、デプロイに伴うリスクの軽減の両立を目指しています。
Argo Rolloutsは、Kubernetes上のアプリケーションに対し、プログレッシブデリバリーの中でも、上記で述べた「分析」と「自動ロールバック」機能を提供します。
リリースされたカナリアに対して、モニタリングツール、オブザーバビリティツールで取得されたメトリクス、あるいは、HTTP/HTTPSアクセスによって取得されたREST APIのレスポンスや「Kubernetes Job」を利用したテスト実行結果などを利用してアプリケーションの「分析」を行い、「分析」の結果から「リリースに異常がある」と判断した場合にはリリース作業を中断して「自動ロールバック」ができます。
また、上記機能を応用してブルーグリーンデプロイやA/Bテストの自動化にも利用できます。
Argo Rolloutsは、前回の記事で紹介したカナリアリリースの機能を拡張して利用しているので、カナリアリリースと同様にトラフィックの制御も自動化ができます。下図で、前回利用したカナリアリリースのフローとプログレッシブデリバリーで自動化できる部分を示します。
アプリケーションの分析、カナリア比率の変更とトラフィック制御が自動化され、リリースが完全に自動化されていることを確認できます。
Argo Rolloutsでは、分析に下記のようなツールや手段を用いることができます。「Prometheus」「Amazon CloudWatch」のようなオブザーバビリティー/モニタリングツールのメトリクスを利用できる他、Kubernetes Jobを用いて独自のスクリプトで分析したり、Rest APIの返却するJSONの値を調べたり(Argo Rolloutsでは「Web Metrics」と表現されている)することもできます。「Datadog」「New Relic」など商用のオブザーバビリティーツールもサポートに含まれます。
※最新情報は、Analysis & Progressive Deliver(画面左メニューのAnalysisのプラグイン一覧)をご覧ください。
Argo Rolloutsでは、上記のツールからクエリによって値を取得し、その値が条件に一致するかどうかで、分析できます。例えば、Prometheusを利用した場合、「PromQL」によって、メトリクスを抽出し、抽出したメトリクスの条件を指定できます。
Argo Rolloutsでは、トラフィック制御に「Ingress」を利用します。対応する「Ingress Controller」は次の通りです。
Argo Rollouts v1.4までは、トラフィック制御機能はArgo Rollouts本体に組み込まれていましたが、Argo Rollouts v1.5からは、プラグインを用いることで拡張できます。プラグイン拡張によって、Gateway APIなどIngress Controller以外のトラフィック制御も今後利用できるようになります。
ただし、Gateway APIのサポートはまだ試験段階なので、商用環境で利用する場合は注意してください。
※最新動向については、Traffic management¶をご覧ください。
ここからは、利用方法を交えながら、プログレッシブデリバリーを解説します。本稿で用いるサンプルアプリケーションは、エンドユーザーからHTTPリクエストが送信された際に、「Hello World!!!」と応答するシンプルなプログラムです。また、Ingressとして「Nginx Ingress Controller」を用いています。
デプロイにおける各リソースの動きをArgo Rolloutsコントローラー視点で整理すると、次の通りです。
今回は、カナリアリリース後にアプリケーションを分析するのではなく、リリース前から継続的に分析するようにしています。これによって、リリース前は分析結果が正常になっていることを確認できる(リリース前の状態が問題ないことと、分析が間違っていないこと)他、リリース実行中のカナリアPodの作成/増加中やトラフィックの切り替え時などでも影響がないことを確認できます。
また今回のサンプルでは、本番トラフィックは既にリリースされたバージョンに向けたままにし、「分析」で送信されるトラフィックのみカナリアに送信されるように設定する、「ダークカナリアリリース」という手法を採用しています。
これを応用した例として、以下ユースケースを考えてみましょう。
このようなサービスの場合「エンタープライズ契約中の有料会員」を初期段階でカナリア用トラフィックとして割り当てるのは、非常にリスクが高いので避けるべきです。一方で「個人利用目的の無料会員」はそういったリスクが比較的小さいので、カナリア用トラフィックとして適しています。
そこで、あらかじめサービス利用形態を識別するHTTPリクエストヘッダ(例:「X-User-Type: Free-User」)を付与しておき、そのヘッダを参照してカナリア用トラフィックか否かを判断するように設定するといいでしょう。これによって「個人利用目的の無料会員」が新バージョンを先行利用し、問題なければ「エンタープライズ契約中の有料会員」に段階的に新バージョンを提供するといった、高品質なリリースを実現できます。
ここからは、サンプルを動かしてプログレッシブデリバリーの動作を確認します。手順は次の通りです。
サンプルリポジトリの「manifests/04_Progressive」フォルダ配下にプログレッシブデリバリーに関連するサンプルがあります。
ファイル構成は次の通りです。
04_Progressive ├── analysis-template.yaml # AnalysisTemplate定義 ├── ingress.yaml ├── kustomization.yaml ├── namespace.yaml ├── rollout.yaml # Rollout定義 ├── service-canary.yaml # CanaryのService定義 └── service-stable.yaml # StableのService定義
Argo Rolloutsによるプログレッシブデリバリーのポイントは、次の通りです。
特にRolloutリソース(rollout.yaml)はArgo Rollouts独自リソースで、今回のハンズオンの肝となるリソースです。プログレッシブデリバリーでは、下記のようにRolloutリソース(rollout.yaml)を定義します。
apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: name: progressive-demo spec: replicas: 5 revisionHistoryLimit: 1 selector: matchLabels: app: progressive-demo template: metadata: labels: app: progressive-demo spec: containers: - name: hello-world image: registry.gitlab.com/cloudnativetips/argorollouts-sample/hello-world:v1 imagePullPolicy: Always ports: - name: http containerPort: 8080 protocol: TCP resources: requests: memory: 32Mi cpu: 5m strategy: # この部分が「Rollout」独自の拡張定義 canary: stableService: progressive-demo-stable # StableのService定義 canaryService: progressive-demo-canary # CanaryのService定義 dynamicStableScale: true # StableのPod数のトラフィックに比例した変動 analysis: # 「分析」定義(詳細は後述) templates: - templateName: progressive-analysis args: - name: ingress-ip value: "192.168.49.2" - name: target-endpoint value: pg.dev.sample.io - name: canary-flag value: "true" - name: error-threshold value: "5" steps: # プログレッシブデリバリー工程(詳細は後述) - setCanaryScale: replicas: 1 - pause: {duration: 3m} - setWeight: 40 - setCanaryScale: matchTrafficWeight: true - pause: {duration: 1m} - setWeight: 60 - pause: {duration: 1m} trafficRouting: nginx: stableIngress: progressive-demo # Ingress定義 additionalIngressAnnotations: # カナリア用トラフィック対象となるリクエスト判定の定義 canary-by-header: X-Canary canary-by-header-value: "true"
前回の記事でも説明しましたが、定義をよく見ると、Deploymentリソースに似ていることが分かると思います。RolloutリソースはDeploymentリソースの拡張なので、strategy定義の部分でブルーグリーンデプロイやカナリアリリースなどのデプロイ戦略を定義します。今回利用するプログレッシブデリバリーはカナリアリリースを拡張したものなので、ここではカナリアリリースを設定しています。
前回、Argo Rolloutsによる単純なカナリアリリースを紹介しましたが、前回のサンプルと比較すると、分析のためのanalysisの項目が異なります。
Argo CDを用いてサンプルをデプロイし、旧バージョン(v1)がデプロイされている初期状態を作ります。
今回はArgo CDに登録するための設定済み定義ファイルを「manifests/argocd/application-progressive.yaml」に用意しています。
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: progressive-demo spec: project: default source: repoURL: https://gitlab.com/cloudnativetips/argorollouts-sample.git # 同期対象となるGitリポジトリのURL targetRevision: main # 同期対象となるブランチ名 path: manifests/04_Progressive # 同期対象となる「Kustomize」のファイルパス destination: server: https://kubernetes.default.svc
もし、サンプルリポジトリを自分の環境にForkする場合には「repoURL」の部分を変更して利用してください。
下記コマンドでデプロイできます。
kubectl -n argocd apply -f manifests/argocd/application-progressive.yaml
無事設定が完了すると下記画像のようなカードがArgo CDダッシュボード上に表示されていることを確認できます。
この状態では、まだArgo CDに登録されているだけです。デプロイされているわけではないので、先のカードの「Sync」ボタンから同期します。
無事同期が完了すると、カード表示が切り替わることを確認できるので、カードを選択してデプロイされたサンプルを確認します。
ステータスが「Synced」となっており、旧バージョン(v1)のPodが5台稼働していることを確認できます。
デプロイ時の動きを確認します。今回は次の手順に沿って作業します。
・1. 初期状態の確認
ノンストップデプロイを体感するために、今回はデプロイ実行中エンドユーザーに成り代わって常に本番トラフィックを流し続けます。次のコマンドによって、1秒間隔で本番トラフィック用リクエストの送信を開始します。
# 1秒間隔で本番トラフィック用リクエストを送信(「Ctrl」+「C」キーで停止) TARGET_ENDPOINT=http://pg.dev.sample.io CANARY_FLAG=false ERROR_THRESHOLD=5 go run apps/analysis-job/cmd.go
コマンド出力結果として、常に旧バージョン(v1)からレスポンスが返却されることを確認できれば準備万端です。
2023/05/01 00:00:00 [START] analysis-job 2023/05/01 00:00:00 Send request[1] 2023/05/01 00:00:00 Receive response[1]: {version: v1} 2023/05/01 00:00:01 Send request[2] 2023/05/01 00:00:01 Receive response[2]: {version: v1} 2023/05/01 00:00:02 Send request[3] 2023/05/01 00:00:02 Receive response[3]: {version: v1} 2023/05/01 00:00:03 Send request[4] 2023/05/01 00:00:03 Receive response[4]: {version: v1} 2023/05/01 00:00:04 Send request[5] 2023/05/01 00:00:04 Receive response[5]: {version: v1} ...
・2. プロモート実行
次に、デプロイ対象のイメージバージョンを新バージョン(v2)に変更します。
Argo CDはGitOpsツールなので書き換えたRolloutリソースを通常はGitにPushしますが、筆者が利用したGitリポジトリを利用してもらう関係上、今回はArgo CDの機能を使ってイメージバージョンを一時的に変更します。
Argo CDダッシュボードから「App Details」ボタンをクリックし、「Parameters」タブの該当箇所を「v2」に変更し「Save」ボタンで保存します。
ステータスが「OutOfSync」に変わった後に「Sync」ボタンをクリックし、デプロイが開始されることを確認します。
デプロイが開始されると、既存の旧バージョン(v1)リソースに加えて新たに新バージョン(v2)リソースが作成され、旧バージョン(v1)Podが5台と新バージョン(v2)Podが1台稼働している状態になります。
それと同時にAnalysisTemplateリソースからAnalysisRunリソース、AnalysisRunリソースからKubernetes Jobリソースが生成されて「分析」が開始することを確認します。
これらの挙動は、次の定義によります。
... spec: strategy: canary: ... analysis: # AnalysisTemplate呼び出し templates: # 「分析」に用いるAnalysisTemplate名 - templateName: progressive-analysis args: # AnalysisTemplateに渡す引数 - name: ingress-ip value: "192.168.49.2" - name: target-endpoint value: pg.dev.sample.io - name: canary-flag value: "true" - name: error-threshold value: "5" ...
apiversion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: name: progressive-analysis spec: # AnalysisTemplate/AnalysisRun定義 args: # AnalysisTemplateで用いる引数定義 - name: ingress-ip - name: target-endpoint - name: canary-flag - name: error-threshold metrics: - name: success-rate initialDelay: 10s # 遅延時間(カナリア用Podの作成完了を待つ) provider: job: # Analysis種別 spec: # Kubernetes Job定義 backoffLimit: 1 template: spec: hostAliases: - ip: "{{ args.ingress-ip }}" hostnames: - "{{ args.target-endpoint }}" containers: - name: analysis-job image: registry.gitlab.com/cloudnativetips/argorollouts-sample/analysis-job:latest imagePullPolicy: Always env: - name: TARGET_ENDPOINT value: "http://{{ args.target-endpoint }}/" - name: CANARY_FLAG value: "{{ args.canary-flag }}" - name: ERROR_THRESHOLD value: "{{ args.error-threshold }}" restartPolicy: Never
デプロイを開始して3分経過すると、いよいよ本番トラフィックの一部(40%)をカナリアに流し始めます。本番トラフィック用のリクエスト結果を確認すると、上記変更によって旧バージョン(v1)と新バージョン(v2)からのレスポンスが混ざり合っていることを確認します。
2023/05/01 00:03:20 Send request[200] 2023/05/01 00:03:20 Receive response[200]: {version: v1} 2023/05/01 00:03:21 Send request[201] 2023/05/01 00:03:21 Receive response[201]: {version: v1} 2023/05/01 00:03:22 Send request[202] 2023/05/01 00:03:22 Receive response[202]: {version: v2} # 新バージョンからのレスポンス 2023/05/01 00:03:23 Send request[203] 2023/05/01 00:03:23 Receive response[203]: {version: v1} 2023/05/01 00:03:24 Send request[204] 2023/05/01 00:03:24 Receive response[204]: {version: v2} # 新バージョンからのレスポンス ...
Argo Rolloutsコントローラーはトラフィック割合に応じて適切にReplicasを設定するので、旧バージョン(v1)Podの台数が5から3に減少し、新バージョン(v2)Podの台数が1から2に増加します。
その後1分経過すると、もう1段階カナリア側のトラフィック割合が増加(+20%)します。
先ほどと同様に、旧バージョン(v1)Podの台数が3から2に減少、新バージョン(v2)Podの台数が2から3に増加します。
これらの挙動は、次の定義によります。
steps: - setCanaryScale: replicas: 1 # カナリアのReplicasを固定値で指定 - pause: {duration: 3m} # 3分間待機 - setWeight: 40 # 全体トラフィックのうち40%をカナリアに流す - setCanaryScale: matchTrafficWeight: true # カナリアのReplicasをトラフィック割合から自動計算 - pause: {duration: 1m} # 1分間待機 - setWeight: 60 # 全体トラフィックのうち60%をカナリアに流す - pause: {duration: 1m} # 1分間待機
サンプルでは、ネームスペースごとに定義されるAnalysisTemplateを利用しましたが、クラスタ全体で定義できる「ClusterAnalysisTemplate」も提供されています。
プロダクトごとではなく複数プロダクト横断で標準的な分析方法を管理したい場合に有用なリソースです。ClusterAnalysisTemplateを利用するには、AnalysisTemplateをClusterAnalysisTemplateとして定義し、Rolloutリソースのテンプレート指定で下記のようにclusterScopeオプションを指定します。
analysis: templates: - templateName: progressive-analysis clusterScope: true # この行を追加
・3. 最終状態の確認
デプロイが完了したので、トラフィックは全て新バージョン(v2)環境で処理されるようになりました。
デプロイ前には存在していた旧バージョン(v1)のリソースならびにデプロイ作業中に存在していた「分析」リソースは削除され、新バージョン(v2)のリソースのみが存在している状態です。
本番トラフィック用のリクエスト結果を確認すると、確かに新バージョン(v2)からレスポンスが返却されることを確認できます。
2023/05/01 00:06:00 Send request[360] 2023/05/01 00:06:00 Receive response[360]: {version: v2} 2023/05/01 00:06:01 Send request[361] 2023/05/01 00:06:01 Receive response[361]: {version: v2} 2023/05/01 00:06:02 Send request[362] 2023/05/01 00:06:02 Receive response[362]: {version: v2} 2023/05/01 00:06:03 Send request[363] 2023/05/01 00:06:03 Receive response[363]: {version: v2} 2023/05/01 00:06:04 Send request[364] 2023/05/01 00:06:04 Receive response[364]: {version: v2} ...
以上が、プログレッシブデリバリーにおけるデプロイ実行時の動きです。
先ほどは新バージョンに異常がなかった際の動きでしたが、100%問題なくリリースできるとは限りません。ここでは新バージョンに異常があり、エンドユーザーへの影響を最小限にするために迅速に旧バージョンにロールバックされた際の動きを確認します。次の手順に沿って作業します。
各ステップでの動作を詳しく見ていきましょう。
・1. 初期状態の確認
今回は、ロールバックされた際でもノンストップであることを体感するために、エンドユーザーに成り代わって常に本番トラフィックを流し続けておきます。次のコマンドによって、1秒間隔で本番トラフィック用リクエストの送信を開始します。
# 1秒間隔で本番トラフィック用リクエストを送信(「Ctrl」+「C」キーで停止) TARGET_ENDPOINT=http://pg.dev.sample.io CANARY_FLAG=false ERROR_THRESHOLD=5 go run apps/analysis-job/cmd.go
常に旧バージョン(v2)からレスポンスが返却されることを確認できれば準備万端です。
2023/05/01 01:00:00 [START] analysis-job 2023/05/01 01:00:00 Send request[1] 2023/05/01 01:00:00 Receive response[1]: {version: v2} 2023/05/01 01:00:01 Send request[2] 2023/05/01 01:00:01 Receive response[2]: {version: v2} 2023/05/01 01:00:02 Send request[3] 2023/05/01 01:00:02 Receive response[3]: {version: v2} 2023/05/01 01:00:03 Send request[4] 2023/05/01 01:00:03 Receive response[4]: {version: v2} 2023/05/01 01:00:04 Send request[5] 2023/05/01 01:00:04 Receive response[5]: {version: v2} ...
・2. プロモート実行
次に、デプロイ対象のイメージバージョンを新バージョン(error)に変更します。
Argo CDはGitOpsツールなので、書き換えたRolloutリソースを通常はGitにPushしますが、筆者が利用したGitリポジトリを利用するために、今回はArgo CDの機能を使ってイメージバージョンを一時的に変更します。
Argo CDダッシュボードから「App Details」ボタンをクリックし、「Parameters」タブの該当箇所を「error」に変更して「Save」ボタンで保存します。
ステータスが「OutOfSync」に変わった後に「Sync」ボタンをクリックし、デプロイが開始されることを確認します。
デプロイが開始されると、既存の旧バージョン(v2)リソースに加えて新たに新バージョン(error)リソースが作成され、旧バージョン(v2)Podが5台と新バージョン(error)Podが1台稼働している状態になります。
それと同時にAnalysisTemplateリソースからAnalysisRunリソース、AnalysisRunリソースからKubernetes Jobリソースが生成されて「分析」が開始することを確認します。
本稿では「分析」としてKubernetes Jobを用いたカナリアテスト用独自スクリプトを実行しており、1分間の内HTTPステータスコードが正常(200)ではないレスポンス総数が5回超過した場合に対象サーバを「異常」と判断しています。
※Argo Rolloutsでは他にも外部サービスと連携したメトリクスベースの「分析」が可能です。詳細については前述の通りです。
Kubernetes Jobのログを確認すると、1分間の内HTTPステータスコードが異常(500)となるレスポンス総数が6回となり、異常終了しているのが分かります。
しばらくすると「分析」の結果、「新バージョンに異常がある」と判断されて自動的にロールバックされます。
・3. 最終状態の確認
先の工程でロールバックされたことによってトラフィックは全て旧バージョン(v2)環境で変わらず処理されます。
従って本番トラフィック用のリクエスト結果を確認すると、本来であれば新バージョン(error)からレスポンスが返ってくるはずですが、ロールバックされたことで初期状態と変わらず旧バージョン(v2)からレスポンスが返ることを確認します。
2023/05/01 01:02:00 Send request[120] 2023/05/01 01:02:00 Receive response[120]: {version: v2} 2023/05/01 01:02:01 Send request[121] 2023/05/01 01:02:01 Receive response[121]: {version: v2} 2023/05/01 01:02:02 Send request[122] 2023/05/01 01:02:02 Receive response[122]: {version: v2} 2023/05/01 01:02:03 Send request[123] 2023/05/01 01:02:03 Receive response[123]: {version: v2} 2023/05/01 01:02:04 Send request[124] 2023/05/01 01:02:04 Receive response[124]: {version: v2} ...
以上が、プログレッシブデリバリーにおけるロールバック時の動きです。いかがでしたでしょうか。
カナリアリリースをベースとしつつも、デプロイの正当性を「分析」という形で常時モニタリングすることで、従来、運用作業者が手作業で確認していた項目が自動化されて運用負荷の軽減につながります。
多くのプロジェクトでは、アプリケーションやサービスを商用環境にリリースする際にリリース審査などの人手を介したフローを通してリリースしていると思います。プログレッシブデリバリーではそういったフローが人手を介さず自動化されるので「リリースプロセス」も見直す必要があり、「ツールを導入すればいい」という単純な話ではなくなります。
それに関連して「組織の文化」も変える必要があるでしょう。
プログレッシブデリバリーは、従来のデリバリーとは一線を画す非常にチャレンジングなデプロイ戦略ですが、上手に運用できればその効果は絶大です。読者の皆さんにはぜひチャレンジしてもらえればと思いますし、本稿がその手助けとなれば幸いです。
Copyright © ITmedia, Inc. All Rights Reserved.