PipeCDではデプロイ開始までに何が起こっているのか?GitOpsの仕組み
本記事では、PipeCDでDeploymentが始まるまでのプロセスを解説します。PipeCDはPull型なので、デプロイ開始までには様々な処理があります。
Pendingって何?ってやつ
想定読者:
- 「デプロイが思ったように始まらない...」時のトラブルシューティングをしたい方
- GitOpsの仕組み・実現方法を理解したい方
- PipeCDにコントリビューションしたい方
全体像
デプロイ開始までの状態遷移は下図のようになっています。
[1]~[3]の各フェーズは別々のコンポネント(goroutine)によって非同期に実行されています。この仕組みによって、Pipedが途中で落ちてもデプロイ再開が可能になっています。
各コンポネントは非同期
1. Trigger: 起動
PipeCDは以下の4種類の契機でApplicationのデプロイを開始します:
- [A] Git上の変更を検知(これがメイン)
- EventWatcherによるCIからの連携もこれに含まれます
- [B] UI/pipectlから手動起動
- [C] ドリフト発生時の自動デプロイ(要設定)
- [D] Deployment Chain(詳細割愛)
上記4点の詳細はapp.pipecd.yaml
のtrigger
で設定できます。Cのみデフォルトで無効です。
[A] Git上の変更を検知
Gitリポジトリから最新のコミットを取得した上で、以下2点を満たしている場合です。
- 未デプロイの新規コミットが存在すること
-
app.pipecd.yaml
と同階層以下のファイルに変更が1件以上あること
2.の検知対象に関しては、paths
/ignores
の設定で調整できます。これにより、例えば別ディレクトリにある共通テンプレートファイルの変更も検知できるようになります。
[B] UI/pipectlから手動起動
Push型のような起動パターンです。
UIからの場合、Applicationページにて起動できます。「アプリではなく環境側の問題でデプロイに失敗した後の再デプロイ」などで便利です。
UIからの手動トリガー
また、pipectlからも以下のコマンドで起動できます。「CI(例外的)やワークフローから直接デプロイしたい場合」などに便利です。
pipectl application sync --app-id={app-id}
[C] ドリフト発生時の自動デプロイ(要設定)
Git上の定義(=理想状態)と実際の環境とが異なっているケースです。PipeCDではDrift Detectionという、triggerとは別の仕組みによってドリフトを継続的に検出します。
このトリガーはデフォルトで無効であり、ドリフト検出後は手動修復が基本となっています。厳格にGitをSingle Source of Truthとして扱いたい方向けのトリガーと言えます。
あるApplicationが以下の4条件を満たした場合に、デプロイがトリガーされます。
- 「ドリフト検出時の自動デプロイ」を有効化していること
-
trigger.onOutOfSync.disabled
という項目をtrue
に設定します(デフォルトはfalse
)
-
- ドリフトが発生していること(ステータスが
OutOfSync
である) - デプロイ中ではないこと
- デプロイ完了から所定時間経過していること(デフォルトは5分)
- これにより、「デプロイ完了したがドリフト検出機構が遅れていて、誤ってドリフト検出&デプロイが走ってしまう」という事象を防ぎます
-
trigger.onOutOfSync.minWindow
という項目で設定できます
triggerのコード
trigger
パッケージで完結していて、trigger.go
がエントリポイントで、各判定ロジックはdeterminer.go
にあります。
個人的に、trigger
パッケージを読むとPipeCDの挙動の見通しが一気に良くなります。
2. Plan: パイプラインの確定
「どのようなパイプラインでデプロイするか」を決定するフェーズです。主に2点行います。
- Quick SyncかPipeline Syncかの確定
- 具体的なStageの確定
plannerインスタンスはgoroutineとしてDeploymentと1:1で動作し、複数のDeploymentが同時並行で処理されます。
また、同一Applicationに対して複数のDeploymentが同時に走らないように制御されています。
2.1. Quick SyncかPipeline Syncかの確定
Quick SyncとPipeline Syncとは?
PipeCDには以下の2種類のデプロイ戦略があります:
-
Quick Sync
- 単一のStage(
K8S_SYNC
やECS_SYNC
など)でデプロイを実行 - 「Gitとの同期のみ必要で、Analysis等は不要」なケースに最適
- 単一のStage(
-
Pipeline Sync
- 定義されたパイプラインに従ってデプロイを実行
- Canaryデプロイなど、段階的なデプロイが必要なケースに最適
戦略の決定ロジック
Quick Syncが選択される条件(以下のいずれかを満たす場合):
- [A] ユーザがUIから「Quick Sync」を選択して手動でデプロイを起動した
- [B]
app.pipecd.yaml
にpipeline
が設定されていない - [C] (k8sのみ)
app.pipecd.yaml
のplanner.commitMatcher.quickSync
の条件を満たす - [D] (Terraform以外) そのApplicationで過去に成功したデプロイが存在しない
- [E] (k8sのみ) workloadやconfigに変更がない (詳細)
Pipeline Syncが選択される条件(以下のいずれかを満たす場合):
- [A] ユーザがUIから「Pipeline Sync」を選択して手動でデプロイを起動した
- [B]
app.pipecd.yaml
でplanner.alwaysUsePipeline
がtrue
に設定されている(デフォルトでfalse
) - [C] (k8sのみ)
app.pipecd.yaml
のplanner.commitMatcher.pipeline
の条件を満たす
判定フローチャートの詳細
2.2. 具体的なStageの確定
あまり特筆すべき事項はありません。
-
Quick Syncの場合
- ユーザ定義のパイプラインがないため、K8sであれば
K8S_SYNC
、ECSであればECS_SYNC
といったStageを生成
- ユーザ定義のパイプラインがないため、K8sであれば
-
Pipeline Syncの場合
- ユーザ定義のパイプラインに従って、Stageを生成
plannerのコード
Planのコアロジックはplanner
パッケージにPlatform毎に実装されています。controller
パッケージはその実行を制御する役割を担っています。
3. Schedule: パイプラインの実行
schedulerコンポネントがパイプラインの進行管理を担います。主に2点行います。
- パイプラインの各Stageの実行
- 失敗時のロールバック実行
plannerと同様に、schedulerインスタンスはgoroutineとしてDeploymentと1:1で動作し、複数のDeploymentが同時並行で処理されます。
3.1. パイプラインの各Stageの実行
Plan段階で生成されたパイプラインに基づいて、各Stageを順次実行します。
具体的な処理内容はPlatform毎に異なります。
3.2. 失敗時のロールバック実行
いずれかのStageで失敗した場合には、ロールバックStageを実行します。
3.1.同様に、ロールバックStageのStage名および処理内容はPlatform毎に異なります。
ロールバックStageはいつ生成されるのか?(コードリーディングしたい方向け)
実はPlanの段階でロールバックStageも生成されています。Visible:false
によって、実行されるまでUI上では非表示になっています。
コードの例:
schedulerのコード
controller
パッケージのscheduler.go
のRun()
,executeStage()
メソッドが中心です。
各Stageの具体的な処理内容は、executor
パッケージにあります。
executor
パッケージは「このStageでこの処理を実行する」を記述しているため、比較的理解しやすいと思います。
Appendix: controllerとは何者なのか(コードリーディングしたい方向け)
controllerは、plannerとschedulerの管理者です。ユーザが意識することはありません。
planner,schedulerが属するcontrollerパッケージには、デプロイのフロー制御に関するコードが含まれます。そこに存在するcontroller.go
がエントリポイントであり、controllerはPiped起動時から永続的に動作します。
controllerは10秒間隔で下記を行っています。
- 新しいDeploymentに対して、planner/schedulerが必要なら作成する
- 完了済のplanner/schedulerを削除する
- ユーザがDeploymentをキャンセルした場合は、planner/schedulerに通知する
おおよそこのような関係です。triggerとcontroller間で直接的な依存関係はありません。
おわりに
PipeCDには他にも様々な機能・コードがありますが、本記事で解説したtrigger,controllerが中軸となっています。
「PipeCDにこんな機能があったらいいな」と思った際に、「この辺のコードを読めばヒントを得られそう」と感じていただけたら幸いです。
GitOpsに関しては「Gitをどう効率的に扱っているか」「ドリフトをどう検出しているか」も追加で解説が必要かと思うので、いずれ解説するかもしれません。
Discussion