GitOpsの調整モデルとドリフト是正
Gitと実クラスタのズレを自動で埋めるGitOpsの調整ループを原理から。望ましい状態と観測状態の差分計算、ドリフト検知、自己修復とロールバックの仕組みまで掴めます。
- 1.GitOpsの中核は宣言的な望ましい状態(Git)と観測状態(クラスタ)の差分をエージェントが継続的に計算し、実体を望ましい状態へ収束させるpull型の調整ループ。命令的な手続きではなく到達すべきゴールを宣言する。
- 2.ドリフトはGitと実体のズレで、調整ループが各サイクルで検知する。手動kubectlやコントローラの上書きが原因で、selfHeal有効時はGitへ自動で引き戻す。無効時はOutOfSyncとして可視化のみ。
- 3.ロールバックは『前のコミットへGitを戻す』だけで、調整ループが古い望ましい状態へ収束させる。push型CIと違い資格情報をクラスタ外へ出さず、監査もGit履歴で完結する。
調整ループ:望ましい状態へ収束させる
GitOpsの本質は、デプロイの自動化ツールではなく制御理論的な調整ループ(reconciliation loop)にあります。系には二つの状態があります。望ましい状態(desired state)——Gitリポジトリに宣言的に書かれた、あるべき構成。そして観測状態(observed / actual state)——クラスタ内に実在する現実の構成。エージェント(Argo CD や Flux)は、この二つを周期的に突き合わせ、差分があれば実体を望ましい状態へ寄せます。
loop forever:
desired = fetch_and_render(git_repo) # Gitの宣言を実リソースへ展開
observed = read_live_state(cluster) # APIサーバーから現状を取得
diff = compute_diff(desired, observed) # 望ましい状態 − 観測状態
if diff is not empty:
apply(diff) # 実体を望ましい状態へ寄せる
sleep(interval) # 次サイクルまで待つ
ここで決定的に重要なのは、GitOpsが命令的(imperative)ではなく宣言的(declarative)である点です。命令的なデプロイは「このスクリプトを実行せよ」という手続きの列で、途中で失敗すると中途半端な状態に陥り、再実行が安全とは限りません。対して宣言的モデルは「最終的にこうあってほしい」というゴールだけを述べ、そこへ至る手順は調整ループが毎サイクル計算し直します。だから何度回しても同じ結果に収束する=冪等であり、一度ズレても次のサイクルで自動的に戻る自己安定性を持ちます。これは Kubernetes のコントローラそのものの設計思想(/devops/iac/ の宣言的IaCとも同根)を、デプロイ全体へ拡張したものです。
調整ループは負帰還制御そのものです。望ましい状態が「設定値(setpoint)」、観測状態が「現在値」、その差が「誤差」で、apply が誤差を縮める「制御出力」に当たります。誤差がゼロに近づくよう連続補正するため、外乱(手動変更や障害)で系が乱れても自律的に設定値へ戻ります。同じ枠組みは /devops/autoscaling-control-theory/ のオートスケーリングにも現れ、GitOpsは「構成」を制御対象にした帰還ループだと捉えると見通しが良くなります。
pull型とpush型:なぜpullなのか
調整をどこから駆動するかで、GitOpsは従来のCIデプロイと根本的に分かれます。
| 観点 | pull型(GitOps) | push型(従来のCD) |
|---|---|---|
| 駆動主体 | クラスタ内エージェントが自分でGitを監視 | 外部CIがクラスタへapplyを送り込む |
| 資格情報の所在 | クラスタ内に留まる(外へ出さない) | CI側がクラスタの認証情報を保持 |
| ドリフト検知 | 常時、毎サイクル比較できる | デプロイ時のみ。以後のズレは見えない |
| ネットワーク方向 | クラスタ→Git/レジストリへ外向き | CI→クラスタへ内向き(受口を開ける) |
| 真実の源 | Gitの状態=実体(収束保証あり) | 最後にapplyした内容(その後は不定) |
push型では、CIパイプラインがビルド後に kubectl apply や Helm でクラスタへ変更を送り込みます(/devops/ci-cd/)。この方式はデプロイ「イベント」を起こすだけで、デプロイ後にクラスタが手動操作やコントローラの誤動作でズレても、CIはそれを知りません。さらにCI側がクラスタの認証情報を持つため、CI基盤が侵害されると本番への書き込み権限が漏れます。
pull型は逆に、クラスタ内のエージェントが自分でGitとイメージレジストリを監視し、変更を検知したら自分で取り込みます。資格情報がクラスタ境界の外へ出ず、外部から本番APIへの受口を開ける必要もありません。そして何より、デプロイが単発イベントではなく継続的なプロセスになります。エージェントは平時も回り続けているので、デプロイ後に生じたドリフトも毎サイクル検知できる——これがpull型の最大の利点です。
ドリフト:定義・原因・検知
**ドリフト(drift)**とは、Gitが宣言する望ましい状態と、クラスタの観測状態がズレた状態を指します。原因は主に三つです。
- 帯域外(out-of-band)の手動変更:誰かが
kubectl editでレプリカ数を変えた、急場をしのぐためにイメージを直接差し替えた。Gitを経由しない変更はすべてドリフトになります。 - 他コントローラによる書き込み:HPA(水平オートスケーラ)が
replicasを、別のオペレータがアノテーションを書き換える。Gitが固定値を宣言していると、調整ループと他コントローラが同じフィールドを奪い合います。 - 環境側の既定値注入・ミューテーション:admissionコントローラやデフォルト値補完で、applyした覚えのないフィールドが付与され、見かけ上の差分が生じます。
検知の原理は調整ループの diff そのものです。各サイクルで望ましい状態と観測状態をフィールド単位で比較し、不一致があればそのリソースを OutOfSync(Flux なら drift detected)と判定します。ここで実装上の難所が何を「差分」とみなすかです。
APIサーバーは status、デフォルト値、自動採番(clusterIP 等)、他コントローラの管理フィールドなど、Gitに書かれていない値を実体へ付け加えます。これらを素朴に「Gitにない=差分」と扱うと、調整ループは毎サイクル消そうとして無限に揺れます(フラッピング)。だから比較は managed fields(自分が適用したフィールド)に限定するか、ignoreDifferences で特定パス(例 HPA が管理する replicas)を比較対象から除外します。Kubernetes の server-side apply は、どのフィールドをどのマネージャが所有するかを記録し、所有権の衝突を検出する仕組みで、この問題を構造的に扱います。
OutOfSync 判定の流れ:
1. Gitのマニフェストをレンダリング(Helm/Kustomize展開後)
2. 比較対象フィールドを正規化(status除去・既定値・順序を揃える)
3. 望ましい状態 と 観測状態 をフィールド単位で突合
4. 無視パス(ignoreDifferences)を除外
5. 残差があれば OutOfSync、なければ Synced
自己修復とロールバック
ドリフトを検知した後の振る舞いは、自己修復(self-heal)が有効か否かで大きく変わります。
- self-heal 無効:ドリフトは OutOfSync として可視化されるだけで、自動修正はしません。誰かが手動で同期(sync)を実行するまで実体はズレたまま。これは「意図しない自動上書きを避けたい」「手動変更を一時的に許容したい」場面の設定です。
- self-heal 有効:ドリフトを検知すると調整ループが自動でGitの望ましい状態へ引き戻します。手動で
kubectl editしてもすぐ巻き戻されるため、実体を変える唯一の正規ルートがGitだけになる——これがGitOpsの規律を強制する核心機能です。
この自己修復があるからこそ、ロールバックが極めて単純になります。push型では「前のバージョンを再デプロイする」ために逆向きのパイプラインを走らせますが、GitOpsでは違います。
GitOpsのロールバック:
git revert <壊れたコミット> または 古いコミットへ git reset してpush
↓
望ましい状態が「前の正常な構成」に戻る
↓
調整ループが次サイクルで実体をそこへ収束させる
つまりロールバックとは「望ましい状態を過去の地点へ戻すGit操作」にすぎず、あとは調整ループが勝手に実体を合わせます。デプロイもロールバックも「Gitを書き換える→収束を待つ」という同じ一つの操作に統一されるのです。さらにGitOpsはヘルスチェックと自動ロールバックの連動も持ちます。新しい望ましい状態を適用後、Pod が Ready にならない・カスタムヘルスが Degraded を返すといった収束失敗を検知したら、自動で前の状態へ戻す——これは段階的リリース(/devops/deployment-strategies/ のカナリア・ブルーグリーン)と組み合わせ、悪い変更が全体へ広がる前に断つために使います。
self-heal は強力ですが、間違った望ましい状態をGitにコミットすると、その間違いを全力でクラスタへ押し付け続けます。緊急時に手動で当てた応急処置も、self-heal が即座に巻き戻して障害を再発させることがあります。だから運用では、(1) 緊急対応中は対象アプリの自動同期を一時停止できるようにする、(2) Gitへのマージ前にスキーマ検証・dry-run・差分レビューを必須化する、(3) 収束失敗時の自動ロールバックと組み合わせて「悪い状態に固定されない」ようにする——という安全装置を併用します。調整ループは正しい状態だけでなく間違った状態にも忠実に収束することを忘れてはいけません。
収束の保証と境界条件
調整ループは万能ではなく、収束するための前提条件があります。
第一に、望ましい状態のレンダリングが決定的でなければなりません。Helm のテンプレートに「現在時刻」や乱数、外部APIの応答を埋め込むと、同じGitコミットからサイクルごとに異なるマニフェストが生成され、永遠に Synced になりません(自己誘発ドリフト)。望ましい状態はGitの内容だけから一意に定まる必要があります。
第二に、調整は**最終的整合(eventual consistency)**です。Gitへpushした瞬間に実体が変わるのではなく、次の調整サイクル(ポーリング間隔、または Webhook 通知)でようやく反映されます。間隔を短くすれば反映は速くなりますが、Git/APIサーバーへの負荷とレート制限が増えます。「pushしたのに本番が変わらない」のはバグではなく、まだ収束サイクルが来ていないだけのことが多い。
第三に、観測と健全性は別物です。調整ループが Synced(=実体がGitと一致)でも、アプリが正しく動いているとは限りません。Synced は「宣言どおりに存在する」を保証するだけで、「正しく機能する」はヘルスチェックと/devops/observability/(メトリクス・ログ・トレース)が別途見る領域です。GitOpsはデプロイの真実性を担保しますが、ランタイムの正しさまでは担保しません。
GitOpsの核は「宣言的な望ましい状態」と「観測状態」の差分を継続的に詰める調整ループ=冪等な負帰還制御、と即答できること。pull型がpush型に優る理由は、資格情報をクラスタ外へ出さない・ネットワークを外向きにできる・デプロイ後のドリフトも常時検知できる、の三点。ドリフトの原因(手動変更・他コントローラ・既定値注入)と、差分計算で managed fields に限定し ignoreDifferences で除外する理由を説明できること。ロールバックが「Gitを前のコミットへ戻すだけ」で済む理由は、調整ループが古い望ましい状態へ自動収束するから。そして self-heal は「間違った望ましい状態にも忠実に収束する」両刃である点を押さえておくこと。
まとめ
- GitOpsの中核は、望ましい状態(Git)と観測状態(クラスタ)の差分を継続的に詰める調整ループ。命令的手続きでなく宣言的ゴールを述べるため冪等で、ズレても次サイクルで自動収束する負帰還制御である。
- pull型はクラスタ内エージェントが自分でGitを監視する方式で、資格情報を外へ出さず、デプロイ後のドリフトも常時検知できる。push型はデプロイ時しか実体を知らない。
- ドリフトは手動変更・他コントローラ・既定値注入で生じる。検知は毎サイクルの差分計算だが、環境が注入したフィールドを差分にしないためmanaged fields 限定と ignoreDifferences が要る。
- self-heal 有効ならドリフトをGitへ自動で引き戻し、実体を変える正規ルートをGitに一本化する。ただし間違った望ましい状態にも忠実に収束するため、自動同期の一時停止・マージ前検証・収束失敗時ロールバックの安全装置を併用する。
- ロールバックはGitを過去のコミットへ戻すだけ。あとは調整ループが収束させる。Synced は「宣言どおり存在する」を保証するだけで、ランタイムの正しさは別途ヘルスチェックと可観測性が見る。
DevOps/インフラ Article
GitOpsの調整モデルとドリフト是正を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
GitOps
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
ドリフトはGitと実体のズレで、調整ループが各サイクルで検知する。手動kubectlやコントローラの上書きが原因で、selfHeal有効時はGitへ自動で引き戻す。無効時はOutOfSyncとして可視化のみ。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「GitOps / Kubernetes」に近いか確認する。
- 強みである「GitOpsの中核は宣言的な望ましい状態(Git)と観測状態(クラスタ)の差分をエージェントが継続的に計算し、実体を望ましい状態へ収束させるpull型の調整ループ。命令的な手続きではなく到達すべきゴールを宣言する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。