Argo CDの自動同期はどこまで任せるか――認証基盤を全断させて決めた3つの境界線
2026-07-17
Argo CDの自動同期はどこまで任せるか――認証基盤を全断させて決めた3つの境界線のカバー画像

Argo CDを導入すると、いずれ次の3つをどこまで有効にするかという問いに突き当たります。

  • automated:Gitとクラスタの差分を検知して自動同期する
  • prune:Gitから消えたリソースをクラスタからも削除する
  • selfHeal:クラスタへ直接加えられた変更をGitの状態へ戻す

Argo CDでは、automated syncを有効にしただけでは自動的なpruneは行われません。pruneselfHealは、必要に応じて個別に有効化する機能です。

「全部有効にするのがGitOpsの理想」という考え方もあれば、「本番環境でpruneを使うのは怖い」という考え方もあります。

私自身は、この境界線を事故から学びました。

クラスタのSSOを担う認証基盤が、Argo CDの同期処理とHelm chartのhookの組み合わせによって全断したのです。

認証基盤が停止すると、そのサービスだけが使えなくなるのではありません。認証を依存させている管理画面や運用ツールにもログインできなくなります。

この記事では、障害が起きた構造、復旧方法、恒久対策と、そこから決めた「自動に任せる範囲」「慎重に自動化する範囲」「手動に残す更新」の3つの境界線を整理します。

先に結論

私の現在の方針は、次の3分類です。

  1. ステートレスで作り直せるアプリは、automated・prune・selfHealを有効にする
  2. hookや初期化処理を持つchart、認証や入口を担う横断基盤は、同期処理を理解してから自動化する
  3. 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-installpre-upgradeを、Argo CDのPreSync hookへ変換します。

また、before-hook-creationが指定されたhookは、新しいhookを作成する前に既存のhookリソースが削除されます。削除ポリシーが指定されていない場合も、Argo CDは原則としてBeforeHookCreation相当として扱います。

つまり、このchartでは同期時に次のことが起きます。

  1. 既存のServiceAccountやConfigMapが削除される
  2. 新しいリソースが作成される
  3. 初期化処理が実行される
  4. 通常のアプリケーションリソースが同期される

同期が最後まで完了すれば、大きな問題は表面化しません。

しかし、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回の失敗がクラスタ全体へ波及するため、自動化する前に同期の中身を確認します。

最低限、次の点を確認します。

  1. helm templateやKustomize buildの最終出力を見る
  2. helm.sh/hookが付いたリソースを列挙する
  3. hookの中にServiceAccountやConfigMapなどの常設リソースがないか確認する
  4. hook-delete-policyによって何が削除されるか確認する
  5. ランダムなSecretやpasswordを生成していないか確認する
  6. 初期化Jobやmigration Jobが途中で失敗した場合を考える
  7. PreSyncが完了し、通常のSyncで失敗したときに何が残るか確認する
  8. その基盤に依存しない監視・復旧経路を確保する

今回の教訓は、「automated syncは危険」ではありません。

理解していないchartのライフサイクルを、automated syncで繰り返し実行することが危険でした。

境界線3: 戻せない更新は、開始を手動にする

第3分類は、ApplicationそのものをArgo CDの管理外にするという意味ではありません。

Gitでmanifestを管理し、Argo CDで差分も確認します。ただし、更新を実行するタイミングだけは人が決めます。

対象は次のような更新です。

  • DBのメジャーバージョンアップ
  • DBスキーマの破壊的変更
  • データ変換や移行処理
  • ストレージ形式の変更
  • rollback手順が確認できていない更新
  • バックアップ取得後に実行すべき処理
  • 複数サービスを決めた順番で止める必要がある更新

この分類では、通常時から永続的に自動同期を無効にするとは限りません。

大きな更新を行う期間だけ自動同期を止め、次の順番で実行します。

  1. release noteと生成manifestのdiffを確認する
  2. バックアップを取得する
  3. 復元手順を確認する
  4. 依存サービスの状態を確認する
  5. 手動で同期する 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 editkubectl 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自体が壊れている、同期を待てない、といった状況では直接操作が必要です。

ただし、その場合は次のように扱います。

  1. 必要に応じてautomated syncやselfHealを一時停止する
  2. クラスタへ緊急対応を行う
  3. 変更内容をGitへ反映する
  4. Gitとクラスタのdiffがなくなったことを確認する
  5. 自動同期を戻す

直接操作を禁止するのではなく、直接操作を恒久状態にしないことが重要です。

緊急対応の内容がGitへ戻されていなければ、次回の同期で再び障害を起こす可能性があります。

GitOpsには「Gitが生きている」という前提がある

Git revertを正規のrollback手段にすると、別の前提が生まれます。

Gitへアクセスできることです。

私の構成ではGitサーバもクラスタ内にあるため、Gitサーバが停止すると、次のような循環依存が起こります。

  1. Gitサーバが停止する
  2. Argo CDが最新のmanifestを取得できない
  3. Gitを参照して復旧したいアプリを同期できない
  4. 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つです。

  1. 作り直せるものは自動に任せる
  2. 横断基盤は、失敗途中の状態まで理解してから任せる
  3. 戻せない更新は、開始を手動にする

GitOpsの目的は、すべてを自動にすることではありません。

同じ操作を繰り返しても、意図した状態へ安全に収束できる範囲を増やすことです。

自動同期によって事故が起きた後も、私はautomated syncをやめませんでした。

代わりに、chartが同期時に何をするのかを確認し、自動化できる構造へ直してから任せるようにしました。

次に読む記事

Argo CDの自動同期はどこまで任せるか――認証基盤を全断させて決めた3つの境界線
http://notes.midnightstops.com/posts/41/
作者
kairo
公開日
2026-07-17