argo-rolloutsのPromoteについて調べる
argocdのGUI上でargo-rolloutsリソースに対してできるオペレーションに、 PROMOTE
やPROMOTE-FULL
がありますが、
ドキュメントをざっと調べた限り、詳細な挙動が分からなかったのでコードを見てみました。
画面のサンプル
利用できない操作は半透明にして非活性化されています。
argo-rolloutsとは
Deploymentリソースに対してblue-greenやcanaryといった拡張機能を追加することができるCRDsとcontrollerを提供してくれています。
Deploymentの代わりとなるリソースのため、v1.0からは Workload Referencing
と呼ばれる既存のDeploymentを参照できる機能も導入されているようです。
PromoteのAPIがcallされるまで
ブラウザで、 PROMOTE
またはPROMOTE-FULL
ボタンをクリック -> promoteのAPIをfetchしていました。
長くなるので、フロントエンドの詳細は今回は割愛します。
APIの定義
APIのインターフェースを確認してみました。 ブラウザから叩かれるAPIでもあるので、grpc-gatewayを利用して、RESTのインターフェースも定義しています。
rpc PromoteRollout(PromoteRolloutRequest) returns (github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.Rollout) { option (google.api.http) = { put: "/api/v1/rollouts/{namespace}/{name}/promote" body: "*" }; }
https://github.com/argoproj/argo-rollouts/blob/master/pkg/apiclient/rollout/rollout.proto#L187-L192
APIの実装
ProtoBufから生成されたgoファイル(rollout.pb.go
)で書かれているinterfaceを満たすstructを生成し、登録しています。
func (s *ArgoRolloutsServer) newGRPCServer() *grpc.Server { grpcS := grpc.NewServer() var rolloutsServer rollout.RolloutServiceServer = NewServer(s.Options) rollout.RegisterRolloutServiceServer(grpcS, rolloutsServer) return grpcS }
https://github.com/argoproj/argo-rollouts/blob/master/server/server.go#L130-L135
interfaceで定義しているPromoteRolloutメソッドを実装している箇所。
rolloutIfがrolloutのデータや振る舞いが定義されています。
Patch処理もここで定義されていますが、内部ではclient-goを利用していました。
実際に、Promoteも行うメソッドが promote.PromoteRollout
です。
func (s *ArgoRolloutsServer) PromoteRollout(ctx context.Context, q *rollout.PromoteRolloutRequest) (*v1alpha1.Rollout, error) { rolloutIf := s.Options.RolloutsClientset.ArgoprojV1alpha1().Rollouts(q.GetNamespace()) return promote.PromoteRollout(rolloutIf, q.GetName(), false, false, q.GetFull()) }
https://github.com/argoproj/argo-rollouts/blob/master/server/server.go#L384-L387
Promoteのロジック
メソッド呼び出し元で、skipCurrentStepとskipAllStepsはfalseにしているので、ここのif処理は常にスキップされます。
trueをセットした呼び出しはtestコードしかなさそうでした。
if skipCurrentStep || skipAllSteps { if ro.Spec.Strategy.BlueGreen != nil { return nil, fmt.Errorf(skipFlagsWithBlueGreenError) } if ro.Spec.Strategy.Canary != nil && len(ro.Spec.Strategy.Canary.Steps) == 0 { return nil, fmt.Errorf(skipFlagWithNoStepCanaryError) } }
v0.9とv0.10+で互換性を持たせるために getPatches
を実行しています。
status subresourcesと呼ばれるCRDがv0.10からのみ使用されているからのようです。
specPatch, statusPatch, unifiedPatch := getPatches(ro, skipCurrentStep, skipAllSteps, full)
PROMOTE-FULL
ボタンをクリックしたときはstatus.promoteFullフィールドにtrueをセット。
case full: if rollout.Status.CurrentPodHash != rollout.Status.StableRS { statusPatch = []byte(promoteFullPatch) }
PROMOTE
ボタンをクリックしたときはもう少し複雑です。
rolloutがpaused状態であったらpausedフィールドをfalseにし、clearPauseConditionsPatchフィールドに値があればnullをセットしています。 paused状態で処理がpendingとなっているのをクリアにするようにしています。
if rollout.Spec.Paused { specPatch = []byte(unpausePatch) } if len(rollout.Status.PauseConditions) > 0 { statusPatch = []byte(clearPauseConditionsPatch)
strategyをcanaryにしている場合はcanaryのstepを1つ進めるようにindexをインクリメントしています。
_, index := replicasetutil.GetCurrentCanaryStep(rollout) if index != nil { if *index < int32(len(rollout.Spec.Strategy.Canary.Steps)) { *index++ } statusPatch = []byte(fmt.Sprintf(clearPauseConditionsPatchWithStep, *index)) unifiedPatch = []byte(fmt.Sprintf(unpauseAndClearPauseConditionsPatchWithStep, *index)) }
いざ、Patch実行
client-goを使って、Patchをapi-serverに対してリクエストしています。
if statusPatch != nil { ro, err = rolloutIf.Patch(ctx, name, types.MergePatchType, ... } if specPatch != nil { ro, err = rolloutIf.Patch(ctx, name, types.MergePatchType, ... }
どういったときに利用できるのか
B/Gを利用している場合であれば、 postPromotionAnalysis
が一時的な何らかの問題で失敗してしまった時に手動で処理を進めたい際に利用できそうです。
promoteを使えば、rolloutを最初からやり直し(podの再作成)することなく済むかと思います。
Canaryであれば、例えば sleep 120s
のstepをスキップしたいときなどに使えそうです。