Argo Rolloutsで柔軟なデプロイを実現する
Argo Rolloutsを使って柔軟なデプロイを行う方法をまとめます。Argo Rolloutsを利用することで、ブルー/グリーンデプロイやカナリアリリース、ロールバックが簡単に実現できます。
本文中のコード: https://github.com/hosimesi/code-for-techblogs/tree/main/argo_rollouts_canary
記事の趣旨
Kubernetes上でのアプリケーションデプロイ手法の一つであるカナリアリリースを、Argo Rolloutsを用いて実現する方法をご紹介します。カナリアリリースを活用することで、新機能のリリースや更新時のリスクを最小限に抑えつつ、ユーザーへの影響を軽減できます。
以前にECSでカナリアリリースを実現する記事を書きましたが、今回はGKEとArgo Rolloutsを用いてカナリアリリースを行います。切り戻し時にメトリクスを自動で設定できるなど、異なる点を確認するために本記事を書きました。
今回、Argo Rolloutsを使って以下の3点のカナリアリリースを構築してみます。
- サービスレベルでのPod数ベースのベーシックなカナリアリリース
- トラフィックのパーセンテージに基づくカナリアリリース
- Analysisに基づく自動ロールバック機能を持つカナリアリリース
今回は、これらをGoogle Cloudを用いて実現します。
事前知識
カナリアリリースとは
カナリアリリースとは、新しいバージョンのアプリケーションを一部のユーザーに対して先行してリリースする手法です。限定的な範囲で新バージョンを提供し、動作やユーザーからのフィードバックを確認します。問題がなければリリース範囲を段階的に拡大し、最終的に全ユーザーに新バージョンをリリースします。
この手法により、万が一問題が発生しても影響を受けるユーザーを最小限に抑え、迅速な切り戻し(ロールバック)が可能です。また、カナリアバージョンへのトラフィック割合を調整することで、A/Bテストとして新機能の効果検証にも活用できます。
Argo Rolloutsとは
Argo Rolloutsは、Kubernetes上で多様なデプロイ戦略を実現するためのオープンソースのコントローラーです。コントローラーとCRD(カスタムリソース定義)のセットで構成されており、ブルー/グリーンデプロイやカナリアデプロイなど、さまざまなデプロイ戦略を実現できます。また、「Argo」を冠したプロダクト群として、ワークフローオーケストレーションツールのArgo Workflows、CDツールのArgo CD、イベントドリブンの自動化ツールのArgo Eventsなどがあります。
Argo Rolloutsの機能
デプロイ戦略
公式ドキュメントによると、Argo Rolloutsが提供する主要な機能は以下の通りです。
- ブルー/グリーンデプロイメント
- カナリアデプロイメント
- 高度なトラフィックルーティングのためのIngressコントローラーやサービスメッシュとの統合
- ブルー/グリーン分析およびカナリア分析のためのメトリックプロバイダーとの統合
-
成功または失敗の指標に基づいた自動昇格またはロールバック
Rolloutは、KubernetesのDeploymentに相当するCRDです。Deploymentでは実現が難しい高度なデプロイ戦略を可能にするため、Deploymentを置き換えて使用します。
デフォルトではServiceを通じてリクエストの振り分けを行いますが、Ingressやサービスメッシュと統合することで、Ingressレベルでのリクエスト振り分けにも対応しています。
リクエストの振り分け
リクエストの振り分け方法は以下の通りです。
- パーセンテージに基づく振り分け
- L7レベルのヘッダー値による振り分け
- ミラーリングによるトラフィックのコピー
監視
デフォルトでコントローラーのメトリクスをPrometheus形式でエクスポートしているため、Grafanaと連携してメトリクスを監視することも可能です。
Google Cloudのインフラ準備
今回は必要なリソースをすべてTerraformで作成します。
サービスアカウントの準備
まず、GKEのノードプールに付与するサービスアカウントを作成します。
# GKE用のservice account
resource "google_service_account" "gke_sa" {
account_id = "gke-sa"
display_name = "gke用のservice account"
}
クラスターの準備
GKEのクラスターを準備し、Argo Rollouts用のノードプールを作成します。
provider "google" {
project = var.project
region = var.region
}
# GKE Cluster
resource "google_container_cluster" "gke_cluster" {
name = "gke-cluster"
location = "asia-northeast1-a"
project = var.project
deletion_protection = false
remove_default_node_pool = true
initial_node_count = 1
}
# GKE Node Pool
resource "google_container_node_pool" "argo_rollouts_node_pool" {
name = "argo-rollouts-node-pool"
location = "asia-northeast1-a"
cluster = google_container_cluster.gke_cluster.name
project = var.project
node_count = 1
autoscaling {
max_node_count = 1
min_node_count = 1
}
node_config {
preemptible = true
machine_type = "e2-medium"
service_account = google_service_account.gke_sa.email
metadata = {
disable-legacy-endpoints = "true"
}
oauth_scopes = [
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring",
]
labels = {
preemptible = "true"
}
tags = ["argo-rollouts-node-pool"]
}
}
適用
$ cd path/to/terraform
$ terraform init
$ terraform plan
$ terraform apply
k8sマニフェストの作成
クラスターへの接続
先ほど作成したクラスターに接続するために、以下のコマンドを実行します。
$ gcloud container clusters get-credentials gke-cluster --zone asia-northeast1-a --project <your-project-id>
作成したクラスターに接続できていることを確認します。
$ kubectl config get-contexts
Argo Rolloutsのインストール
Argo Rolloutsをインストールします。グローバルにインストールする方法と、ネームスペースを限定してインストールする方法がありますが、今回は標準的なグローバルインストールを行います。
まず、argo-rollouts
というネームスペースを作成します。
$ kubectl create namespace argo-rollouts
次に、Argo Rolloutsをインストールします。
$ kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
GKEで動作させる場合、サービスアカウントに権限を付与する必要があります。
$ kubectl create clusterrolebinding <your-name>-cluster-admin-binding --clusterrole=cluster-admin --user=<your-email>@gmail.com
また、kubectl
コマンドでArgo Rolloutsを操作するために、プラグインをインストールします。
$ brew install argoproj/tap/kubectl-argo-rollouts
アプリケーションの作成
チュートリアル
まずは簡単に動作確認するために、チュートリアルを参考に進めていきます。公式が提供しているイメージを使って、Serviceレベルでのリクエストの振り分けを行います。この方法は簡単ですが、Pod数に基づくルーティングになるため、大まかな振り分けになります。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: { duration: 10 }
- setWeight: 60
- pause: { duration: 10 }
- setWeight: 80
- pause: { duration: 10 }
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollouts-demo
template:
metadata:
labels:
app: rollouts-demo
spec:
containers:
- name: rollouts-demo
image: argoproj/rollouts-demo:blue
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 5m
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-demo
上記のマニフェストを適用します。
$ kubectl apply -f ./k8s/tutorial/rollout.yaml
$ kubectl apply -f ./k8s/tutorial/service.yaml
次に、新しいイメージに更新します。
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
すると、Canary用のPodが作成されてヘルシーになっていることが確認できます。StatusがPausedになっているのは、Rolloutの戦略で手動実行になっているためです。
ダッシュボードでも確認してみます。(※ダッシュボードの構築方法は後述します。)
次のフェーズに進むには、以下のコマンドを実行します。
$ kubectl argo rollouts promote rollouts-demo
すると、10秒ごとにトラフィックの割合が変わっていきます。Podが一つずつ入れ替わっていれば成功です。
途中で中止したい場合は、以下のコマンドで停止し、戻したいイメージを指定して切り戻すことが可能です。
$ kubectl argo rollouts abort rollouts-demo
$ kubectl argo rollouts set image rollouts-demo rollouts-demo=argoproj/rollouts-demo:yellow
ALBでの振り分け
先ほどはServiceレベルでPod数に基づくリクエストの振り分けを行いましたが、より精密なトラフィック管理を実現するために、Ingressと統合して振り分けを行います。ここでは、Google CloudのマネージドサービスであるGoogle Cloud Load Balancingを活用します。こちらのコードを参考に作成していきます。
Gateway APIとの統合
AWSのALBではデフォルトで統合されていますが、Google Cloudで統合するにはGateway APIを使用する必要があります。まず、GKEクラスターがGateway APIを使用できるように設定を変更します。先ほどのTerraformファイルを少し修正します。
resource "google_container_cluster" "gke_cluster" {
name = "gke-cluster"
location = "asia-northeast1-a"
project = var.project
deletion_protection = false
# Gateway APIの設定
gateway_api_config {
channel = "STANDARD"
}
remove_default_node_pool = true
initial_node_count = 1
}
gloudコマンドを使っても同様に更新できます。
gcloud container clusters update CLUSTER_NAME \
--gateway-api=standard \
--region=COMPUTE_REGION
次に、プロキシを配置するためのサブネットを作成します。こちらもTerraformファイルに追記します。
resource "google_compute_subnetwork" "argo_rollouts_subnet" {
name = "argo-rollouts-subnet"
ip_cidr_range = "10.1.1.0/24"
region = var.region
network = "default"
purpose = "REGIONAL_MANAGED_PROXY"
role = "ACTIVE"
}
こちらもgcloudコマンドを使うことで作成可能です。
gcloud compute networks subnets create demo-subnet \
--purpose=REGIONAL_MANAGED_PROXY \
--role=ACTIVE \
--region=asia-northeast1 \
--network=default \
--range=10.1.1.0/24
そして、ゲートウェイを作成します。
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
name: internal-http
spec:
gatewayClassName: gke-l7-rilb
listeners:
- name: http
protocol: HTTP
port: 80
ゲートウェイを適用します。
$ kubectl apply -f ./k8s/alb/gateway.yaml
この時、動作確認のためゲートウェイのIPアドレスを取得しておきます。
$ kubectl get gateways.gateway.networking.k8s.io internal-http -o=jsonpath="{.status.addresses[0].value}"
Roleの作成
ゲートウェイを操作するための必要な権限を持つクラスターロールを作成します。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gateway-controller-role
rules:
- apiGroups:
- "*"
resources:
- "*"
verbs:
- "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gateway-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: gateway-controller-role
subjects:
- namespace: argo-rollouts
kind: ServiceAccount
name: argo-rollouts
こちらも適用します。
$ kubectl apply -f ./k8s/alb/clusterrole.yaml
HTTPRoute設定の準備
GKEでHTTP/HTTPSロードバランサを使用するために、Ingressの代わりにGatewayAPIを使用し、HTTPRouteを作成します。
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
name: argo-rollouts-alb-http-route
spec:
parentRefs:
- kind: Gateway
name: internal-http
rules:
- backendRefs:
- name: argo-rollouts-alb-stable-service
port: 80
- name: argo-rollouts-alb-canary-service
port: 80
$ kubectl apply -f ./k8s/alb/httproute.yaml
Serviceの作成
Stable用のサービスとCanary用のサービスを作成します。
apiVersion: v1
kind: Service
metadata:
name: argo-rollouts-alb-canary-service
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-alb-demo
---
apiVersion: v1
kind: Service
metadata:
name: argo-rollouts-alb-stable-service
spec:
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: rollouts-alb-demo
$ kubectl apply -f ./k8s/alb/service.yaml
Rolloutの設定を更新
Rolloutリソースを修正して、Gateway APIとの統合を有効にします。strategy
セクションでcanary
戦略を設定し、trafficRouting
ディレクティブでGateway APIの設定を追加します。
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-alb-demo
namespace: default
spec:
replicas: 10
strategy:
canary:
canaryService: argo-rollouts-alb-canary-service
stableService: argo-rollouts-alb-stable-service
trafficRouting:
plugins:
argoproj-labs/gatewayAPI:
httpRoute: argo-rollouts-http-route
namespace: default
steps:
- setWeight: 30
- pause: {}
- setWeight: 60
- pause: {}
- setWeight: 100
- pause: {}
revisionHistoryLimit: 2
selector:
matchLabels:
app: rollouts-alb-demo
template:
metadata:
labels:
app: rollouts-alb-demo
spec:
containers:
- name: rollouts-alb-demo
image: kostiscodefresh/summer-of-k8s-app:v1
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 5m
$ kubectl apply -f ./k8s/alb/rollout.yaml
ConfigMapの作成
Gateway APIがプラグインになるため、コントローラーにインストールしていきます。今回はConfigMap経由で記載して適用し、コントローラーを再起動します。
apiVersion: v1
kind: ConfigMap
metadata:
name: argo-rollouts-config
namespace: argo-rollouts
data:
trafficRouterPlugins: |-
- name: "argoproj-labs/gatewayAPI"
location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.4.1/gatewayapi-plugin-linux-amd64"
$ k apply -f ./k8s/alb/rollout.yaml
kubectl rollout restart deployment/argo-rollouts -n argo-rollouts
マニフェストの適用
更新したマニフェストを適用します。先ほど実行したコマンドは以下のとおりです。
$ kubectl apply -f ./k8s/alb/gateway.yaml
$ kubectl apply -f ./k8s/alb/clusterrole.yaml
$ kubectl apply -f ./k8s/alb/service.yaml
$ kubectl apply -f ./k8s/alb/httproute.yaml
$ kubectl apply -f ./k8s/alb/rollout.yaml
上記の手順により、Gateway APIとArgo Rolloutsを統合して、より精密なトラフィック管理が可能になります。これにより、ロードバランサレベルでトラフィックの割合を指定して、新旧バージョンのアプリケーションを安全かつ効率的にロールアウトできます。
リクエストを投げてみる
上記の内部LBに向かってリクエストを投げ、予定通りの割り当てになっているかを確認します。まずはクライアント用のpodを作って適用し、そのPodの中に入ります。
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-container
image: curlimages/curl:latest
command: ["sleep", "3600"]
$ kubectl apply -f ./k8s/alb/pod.yaml
$ kubectl exec -it test-pod -- sh
そして、その中でリクエストを投げます。
$ curl http://10.146.15.221/callme
下記のように割合に近しいバージョンのレスポンスが返ってくれば成功です。
自動での切り戻し
Argo Rolloutsはリクエストの振り分けを柔軟にするだけではなく、問題が起こった場合に自動で切り戻すこともできます。例えば、新しいアプリケーションでエラーが起こった場合でも、ヘルスチェックなどを通過していると、通常では手動で切り戻すしかできません。一方でArgo Rolloutsを使うと、設定したメトリクスに応じて自動で切り戻すことが可能です。
今回は、レスポンスがver: 1.0
の場合を成功とし、それ以外の場合を失敗と定義します。そして、ver: 2.0
のカナリアリリースを始めた時に自動でロールバックされるかを確認してみます。先ほどのリソースにいくつか追加します。
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: rollouts-auto-demo-response-check
spec:
args:
- name: serviceName
metrics:
- name: check-ver-1-0
interval: 30s
count: 3
successCondition: 'result | regexFind("ver: 1.0") != ""'
failureLimit: 1
provider:
web:
url: 'http://{{args.serviceName}}/callme'
method: GET
timeoutSeconds: 5
こちらは、成功・失敗の定義のテンプレートになっており、ver: 1.0
の場合を成功と定義しています。次に、rollout.yaml
のsteps
を更新します。
steps:
- setWeight: 20
- analysis:
templates:
- templateName: rollouts-auto-demo-response-check
args:
- name: serviceName
value: rollouts-auto-demo.default.svc.cluster.local
- pause: {}
- setWeight: 40
- analysis:
templates:
- templateName: rollouts-auto-demo-response-check
args:
- name: serviceName
value: rollouts-auto-demo.default.svc.cluster.local
- pause: { duration: 10 }
- setWeight: 60
- analysis:
templates:
- templateName: rollouts-auto-demo-response-check
args:
- name: serviceName
value: rollouts-auto-demo.default.svc.cluster.local
- pause: { duration: 10 }
- setWeight: 80
- analysis:
templates:
- templateName: rollouts-auto-demo-response-check
args:
- name: serviceName
value: rollouts-auto-demo.default.svc.cluster.local
- pause: { duration: 10 }
ここでは、各ステップで自動ロールバックが可能になるように、先ほど作成した分析テンプレートを指定します。
$ kubectl apply -f ./k8s/analysis/analysistemplate.yaml
$ kubectl apply -f ./k8s/analysis/rollout.yaml
これはテストで簡単に確認できる項目ですが、実用ではレイテンシなど、さまざまなツールと連携してカスタムメトリクスを基準に設定できます。
ダッシュボード
Argo Rolloutsはロールアウトの状況を確認するためのダッシュボードを提供しているので、そちらを使って確認してみます。
$ kubectl argo rollouts dashboard
http://0.0.0.0:3100
にアクセスするとDashboard画面があるので、CLIで挙動を確認していたものがGUIで操作できます。
まとめ
今回はArgo Rolloutsを使って、通常のPod数での振り分けから、自動ロールバックまでを試してみました。問題があった時に手動で切り戻すことが多かったので、とても便利だなと感じました。
他にもミラーリングがあったりと、本番運用に役立ちそうなリッチな機能が揃っているので、カナリアリリースの選択肢の一つになりそうです。
参考
Discussion