Argo CDを導入すると、いずれ次の3つをどこまで有効にするかという問いに突き当たります。
automated:Gitとクラスタの差分を検知して自動同期するprune:Gitから消えたリソースをクラスタからも削除するselfHeal:クラスタへ直接加えられた変更をGitの状態へ戻す
Argo CDでは、automated syncを有効にしただけでは自動的なpruneは行われません。pruneとselfHealは、必要に応じて個別に有効化する機能です。
「全部有効にするのがGitOpsの理想」という考え方もあれば、「本番環境でpruneを使うのは怖い」という考え方もあります。
私自身は、この境界線を事故から学びました。
クラスタのSSOを担う認証基盤が、Argo CDの同期処理とHelm chartのhookの組み合わせによって全断したのです。
認証基盤が停止すると、そのサービスだけが使えなくなるのではありません。認証を依存させている管理画面や運用ツールにもログインできなくなります。
この記事では、障害が起きた構造、復旧方法、恒久対策と、そこから決めた「自動に任せる範囲」「慎重に自動化する範囲」「手動に残す更新」の3つの境界線を整理します。
先に結論
私の現在の方針は、次の3分類です。
- ステートレスで作り直せるアプリは、automated・prune・selfHealを有効にする
- hookや初期化処理を持つchart、認証や入口を担う横断基盤は、同期処理を理解してから自動化する
- DBスキーマ変更、データ移行、戻せない処理を伴う更新は、同期の開始を手動に残す
今回の事故で重要だったのは、automated syncやpruneが一律に危険だった、という話ではありません。
問題は、chartが生成したリソースの一部がhookとして扱われ、同期のたびに削除・再作成される構造を理解しないまま自動化していたことでした。
また、自動同期を有効にした環境での正規のrollbackは、クラスタを直接書き換えることではなく、原則としてGitのrevertです。
緊急時にkubectlで直接直すことはあります。しかし、その変更を残すならGitにも反映しなければ、次の同期やselfHealで元に戻ります。
事件: 認証基盤が全断した日
ある日、クラスタ内の複数の管理画面へログインできなくなりました。
最初はトークン検証のエラーとして現れ、やがて認証サービス自体が正常に応答しなくなりました。
認証基盤のPodを再起動しても、状況は改善しません。
調査して分かったのは、単一の不具合ではなく、次の3つが重なった障害でした。
原因1: 常時必要なリソースがhookになっていた
利用していたchartでは、初期化用のJobだけでなく、ServiceAccount、Role、設定用ConfigMapなど、本体の稼働中にも必要なリソースにHelm hookのアノテーションが付いていました。
指定されていたのは、主に次のような設定です。
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation
Argo CDはHelmのpre-installとpre-upgradeを、Argo CDのPreSync hookへ変換します。
また、before-hook-creationが指定されたhookは、新しいhookを作成する前に既存のhookリソースが削除されます。削除ポリシーが指定されていない場合も、Argo CDは原則としてBeforeHookCreation相当として扱います。
つまり、このchartでは同期時に次のことが起きます。
- 既存のServiceAccountやConfigMapが削除される
- 新しいリソースが作成される
- 初期化処理が実行される
- 通常のアプリケーションリソースが同期される
同期が最後まで完了すれば、大きな問題は表面化しません。
しかし、1と2の間や、2と3の間で同期が停止すると、常時必要なリソースが消えた状態や、初期化が完了していない状態が残る可能性があります。
Argo CDの公式ドキュメントでも、Helm hookはArgo CD hookへ変換されるものの、両者のライフサイクルが完全に同じではないと説明されています。
特に、Jobと違って完了状態を持たないServiceAccountのようなリソースをhookとして使う場合、削除タイミングや成功判定が直感的ではなくなります。
ここで明確にしておきたいのは、今回リソースが消えた直接の原因はpruneではないという点です。
Gitから削除されたリソースを消すpruneではなく、hookに設定された削除ポリシーによって、同期処理の一部として削除されていました。
原因2: 同期ごとに変わりうる内部キーがあった
もう1つの問題は、chartのレンダリング時に内部通信用のキーが生成される構成でした。
そのキーに対応する情報を初期化JobがDBへ登録し、両者がそろって初めて認証基盤が正常に動きます。
Helmには既存リソースを参照して値を再利用する仕組みがあります。しかし、私が採用しているKustomizeからHelm chartをレンダリングする構成では、生成時にクラスタ上の既存値を引き継げず、同期ごとに異なる値が生成される状態になっていました。
Argo CDは差分比較のためにchartを繰り返しレンダリングします。chart内でランダム値を生成すると、比較のたびにmanifestが変わり、Applicationが継続的にOutOfSyncとなることが公式ドキュメントでも注意されています。
この構成では、同期が最後まで完了すれば、新しいキーとDB上の情報が同じ同期の中で整合します。
しかし、キーを含むSecretだけが更新され、対応する初期化処理が完了しなければ、クラスタ内のキーとDB上の情報が一致しなくなります。
原因3: 不安定な状態で同期が途中停止した
障害発生時は、別の要因によってKubernetes APIへの応答が不安定になっていました。
その状態で複数回の同期処理が発生し、いずれも途中で正常に完了しませんでした。
結果として、次の状態が重なりました。
- hookの削除処理だけが実行された
- 常時必要なリソースの再作成が完了しなかった
- 内部キーを含むSecretが更新された
- 対応する初期化処理が完了しなかった
- DB上の情報とクラスタ上のキーが一致しなくなった
誰かが設定を変更したわけではありません。
同じGitの内容を同期しようとしただけで、同期の途中状態が積み重なり、認証基盤が停止しました。
ここでは「部分sync」という言葉は使いません。
Argo CDではselective syncが特定の機能名として使われており、selective syncではhook自体が実行されません。今回起きたのはselective syncではなく、通常の同期が途中で失敗し、不完全な状態が残った事故です。
復旧: 正常な同期を1回完走させる
原因が分かってからの復旧は、意外に単純でした。
Kubernetes APIが安定していることを確認したうえで、Application全体をもう一度同期し、処理を最後まで完了させました。
これにより、
- 必要なServiceAccountやConfigMapが再作成される
- 初期化Jobが正常に完了する
- クラスタ内のキーとDB上の情報が再び一致する
- 認証基盤のPodが正常に起動する
という状態に戻りました。
ただし、「もう一度同期すれば直る」と分かるまでには時間がかかりました。
原因を理解しないまま同期を繰り返せば、不安定なAPIサーバへさらに負荷をかけたり、同じ途中状態を繰り返したりする可能性があります。
復旧操作よりも重要だったのは、同期中に何が削除され、何が作り直されるのかを把握することでした。
恒久対策1: 常時必要なリソースからhookを外す
最初の恒久対策は、常時存在すべきリソースをhookとして扱わないことです。
Kustomizeのpatchを使い、ServiceAccount、Role、ConfigMapなどから、次のアノテーションを除去しました。
helm.sh/hook
helm.sh/hook-weight
helm.sh/hook-delete-policy
これらを通常のKubernetesリソースとしてArgo CDに管理させることで、同期のたびに削除される構造をなくしました。
一方で、実行後に完了する初期化Jobについては、処理順序を制御する必要があるため、hookとして残しています。
判断基準は単純です。
- 完了する処理はhookにできる
- 存在し続けることが役割のリソースはhookにしない
chartの設計を全面的に否定したわけではありません。
Helmで直接install/upgradeする前提では成立する設計でも、Argo CDへ持ち込むとhookの意味が変わることがあります。
chartとデプロイツールの組み合わせを含めて評価する必要があります。
恒久対策2: ランダム生成をやめる
2つ目の恒久対策は、同期ごとに変わりうるキーを固定することです。
chartが提供している外部Secret参照の設定へ切り替え、レンダリング時にランダム生成する構成をやめました。
キーそのものはGitへ保存せず、クラスタのSecret管理経路から供給します。
この変更により、同期が途中で失敗しても、
- Secretの値が勝手に変わらない
- DBへ登録済みの情報との対応が維持される
- chartの再レンダリングだけで差分が発生しない
という状態になりました。
重要なのは、Secretを外部管理したことそのものではありません。
再実行するたびに結果が変わる処理を、GitOpsの同期経路から外したことです。
自動同期へ載せる処理は、何度実行しても同じ状態へ収束することが理想です。
境界線1: 作り直せるものは全自動にする
第1分類は、automated・prune・selfHealをすべて有効にするアプリです。
私の環境では、次の条件を満たすものを対象にしています。
- Podを削除してもデータを失わない
- DB migrationを自動実行しない
- Helm hookや特殊な初期化処理を持たない
- 旧バージョンへイメージタグを戻せる
- 一時的に停止しても他のアプリへ波及しない
ブログや小規模なWebツールなどが該当します。
一部では、イメージ更新のGit commitまで自動化しています。これらは何度も無人でデプロイされていますが、現在まで大きな事故は起きていません。
この分類では、自動化によって失うものより、次の利点の方が大きいと判断しています。
- Gitへのcommitだけでデプロイが完了する
- クラスタ上の手動変更が残らない
- 削除したmanifestがクラスタに残り続けない
- 環境間の差が生まれにくい
自動化に向いているのは、単に「ステートレス」と書かれているアプリではありません。
いつ実行されても安全で、失敗しても作り直せるアプリです。
境界線2: hookと横断基盤は、中身を理解してから自動化する
第2分類は、自動同期を使うものの、導入前と更新前の確認を増やすアプリです。
対象になるのは、たとえば次のようなものです。
- Helm hookを持つchart
- 認証基盤
- Ingressや証明書などの入口
- コンテナレジストリ
- Secret管理基盤
- 複数のアプリが依存する共通サービス
これらも、すべて手動で運用するわけではありません。
正常時の更新回数は自動化した方が安全です。しかし、1回の失敗がクラスタ全体へ波及するため、自動化する前に同期の中身を確認します。
最低限、次の点を確認します。
helm templateやKustomize buildの最終出力を見るhelm.sh/hookが付いたリソースを列挙する- hookの中にServiceAccountやConfigMapなどの常設リソースがないか確認する
hook-delete-policyによって何が削除されるか確認する- ランダムなSecretやpasswordを生成していないか確認する
- 初期化Jobやmigration Jobが途中で失敗した場合を考える
- PreSyncが完了し、通常のSyncで失敗したときに何が残るか確認する
- その基盤に依存しない監視・復旧経路を確保する
今回の教訓は、「automated syncは危険」ではありません。
理解していないchartのライフサイクルを、automated syncで繰り返し実行することが危険でした。
境界線3: 戻せない更新は、開始を手動にする
第3分類は、ApplicationそのものをArgo CDの管理外にするという意味ではありません。
Gitでmanifestを管理し、Argo CDで差分も確認します。ただし、更新を実行するタイミングだけは人が決めます。
対象は次のような更新です。
- DBのメジャーバージョンアップ
- DBスキーマの破壊的変更
- データ変換や移行処理
- ストレージ形式の変更
- rollback手順が確認できていない更新
- バックアップ取得後に実行すべき処理
- 複数サービスを決めた順番で止める必要がある更新
この分類では、通常時から永続的に自動同期を無効にするとは限りません。
大きな更新を行う期間だけ自動同期を止め、次の順番で実行します。
- release noteと生成manifestのdiffを確認する
- バックアップを取得する
- 復元手順を確認する
- 依存サービスの状態を確認する
- 手動で同期する 6.動作確認後に自動同期へ戻す
自動化の対象にするのは、いつ実行されてもよい処理です。
実行時刻、順番、バックアップの有無によって安全性が変わる操作は、自動同期の開始条件から外します。
pruneはApplication単位のON/OFFだけではない
pruneを有効にするかどうかも、Application全体の二者択一で考える必要はありません。
Argo CDには、リソース単位でpruneを制御する方法があります。
重要なリソースについては、次のような選択肢があります。
Prune=false:そのリソースを自動削除しないPrune=confirm:削除前に人の承認を要求するPruneLast=true:他のリソースが正常になった後、最後にpruneする
Argo CDの公式ドキュメントでも、Namespaceのような重要リソースにはPrune=confirmを使用でき、PruneLast=trueを指定すると、ほかの同期処理とHealth確認が成功した後にpruneを実行できると説明されています。
私の場合、次のように使い分けています。
- 作り直せるリソースは通常のprune
- PVCや復旧に時間がかかるリソースは削除方法を個別に確認
- Namespaceなど影響範囲が大きいものは自動削除させない
- リソースの置き換えでは、必要に応じてpruneを最後に回す
怖いからpruneをすべて無効にすると、Gitから削除した古いリソースがクラスタへ残り続けます。
一方で、すべてを無条件に削除させる必要もありません。
削除されて困るリソースだけ、削除条件を厳しくする方が実用的です。
rollbackはGit revertを正規経路にする
selfHealを有効にすると、kubectl editやkubectl patchで直接変更した内容は、Gitとの差分として検知されます。
その後の差分検知とself-healによって、クラスタはGitに書かれた状態へ戻されます。
そのため、壊れたイメージを以前のバージョンへ戻す場合も、原則として次のように対応します。
git revert <問題を入れたcommit>
git push
または、イメージタグを戻す修正commitを作ります。
Argo CDの公式ドキュメントでは、automated syncが有効なApplicationに対して、Argo CDのrollback操作は実行できないとされています。
厳密には、自動同期を一時的に無効にすれば、Argo CD側の履歴からrollbackすることもできます。
それでも私は、Gitの履歴を戻す方法を正規経路にしています。
理由は、クラスタだけを過去へ戻すと、Gitには壊れた状態が残り、次にどちらが正しいのか分からなくなるからです。
緊急時のkubectl操作は否定しない
GitOpsだからといって、障害中にkubectlを絶対に使わないわけではありません。
Gitへアクセスできない、Argo CD自体が壊れている、同期を待てない、といった状況では直接操作が必要です。
ただし、その場合は次のように扱います。
- 必要に応じてautomated syncやselfHealを一時停止する
- クラスタへ緊急対応を行う
- 変更内容をGitへ反映する
- Gitとクラスタのdiffがなくなったことを確認する
- 自動同期を戻す
直接操作を禁止するのではなく、直接操作を恒久状態にしないことが重要です。
緊急対応の内容がGitへ戻されていなければ、次回の同期で再び障害を起こす可能性があります。
GitOpsには「Gitが生きている」という前提がある
Git revertを正規のrollback手段にすると、別の前提が生まれます。
Gitへアクセスできることです。
私の構成ではGitサーバもクラスタ内にあるため、Gitサーバが停止すると、次のような循環依存が起こります。
- Gitサーバが停止する
- Argo CDが最新のmanifestを取得できない
- Gitを参照して復旧したいアプリを同期できない
- Gitサーバの復旧が先に必要になる
GitOpsの根にあるGit自体をクラスタ内へ置く場合、Gitとは別の場所に次のものを用意しておく必要があります。
- repoの複製
- Gitサーバを使わずに取得できるbootstrap manifest
- Secretの復旧手順
- Argo CDを経由しない最低限の復旧経路
この循環依存と復旧設計については、別記事で詳しく整理しています。
向いている人・向かない人
自動同期が向いている人
- manifestをGitへ一元化できている
- クラスタへの直接変更を恒久状態にしない
- build後の最終manifestを確認できる
- 障害時にGitから変更を戻せる
- chartのhookや初期化処理を確認できる
全自動にする前に立ち止まった方がよい人
- chartのvaluesだけを見て、生成manifestを確認していない
- DB migrationの実行条件を把握していない
- Secretがレンダリングごとに生成されるか分からない
- hookが何を削除・再作成するか分からない
- Gitが停止した場合の復旧経路がない
- 認証基盤が停止するとArgo CDにも入れない
automated syncは、chartの設計を安全なものへ変えてくれる機能ではありません。
chartが持っている動作を、自動で繰り返し実行する機能です。
自動化する前に、その動作が何度繰り返されても安全かを確認する必要があります。
まとめ
Argo CDの自動同期の境界線は、機能のON/OFF表だけでは決まりません。
私が判断するときに見るのは、次の問いです。
同期が途中で失敗したとき、何が削除され、何が更新され、何が古いまま残るかを説明できるか。
説明でき、失敗しても作り直せるアプリは全自動にする。
hookや初期化処理を持ち、障害がクラスタ全体へ波及する基盤は、同期の中身を理解してから自動化する。
データ移行や戻せない処理を伴う更新は、同期を開始する判断を人に残す。
今回の事故から得た境界線は、次の3つです。
- 作り直せるものは自動に任せる
- 横断基盤は、失敗途中の状態まで理解してから任せる
- 戻せない更新は、開始を手動にする
GitOpsの目的は、すべてを自動にすることではありません。
同じ操作を繰り返しても、意図した状態へ安全に収束できる範囲を増やすことです。
自動同期によって事故が起きた後も、私はautomated syncをやめませんでした。
代わりに、chartが同期時に何をするのかを確認し、自動化できる構造へ直してから任せるようにしました。
次に読む記事
- クラスタ内Gitの落とし穴|k3s GitOpsを全損から復旧する設計 ― GitOpsの正本であるGit自体が停止した場合の循環依存と復旧
- 実際に試したセルフホストソフトウェア一覧【2026年版】 ― 実際に運用したセルフホスト製品と、継続利用の判断