TT Lab
はじめる
学ぶ 学習パス コース

GitOpsとArgo CD

調整ループ — 戻す力と消す力

TT Labで続きを見る

一言でいうと

selfHealはクラスターをリポジトリ側に元に戻す力で、pruneはリポジトリにないものを消す力ですが、2つは方向が違い、危険度も違います。

なぜ必要なのか

gitops-manifestラボでは、手でドリフトを作りました。kubectl scaleでreplicasを5に上げ、kubectl diffが終了コード1でそれを知らせてくれて、もう一度kubectl applyを実行して3に戻しました。ここで質問を1つだけすれば、GitOpsの最後の部品が出てきます。そのapplyを誰が実行するのか、という質問です。

人が実行するなら、それはGitOpsではなく、単によく整理されたデプロイスクリプトです。人は忘れ、休暇を取り、別の障害に掴まります。4つ目の原則である「自己修復」が求めるのは、そのapplyをループが実行するようにすることです。人の決意の代わりに、周期がその仕事をします。

どう動くのか

Argo CDのApplication Controllerは、informerでリソースの変化を見守りながら、各Applicationごとに、この循環を回ります。

반복:
  1. repo-server 에 원하는 상태(렌더된 매니페스트)를 요청
  2. 대상 클러스터에서 실제 상태를 조회
  3. 정규화한 뒤 둘을 비교 (3-way diff)
  4. Sync 상태 갱신   → Synced / OutOfSync
  5. Health 상태 갱신 → Healthy / Progressing / Degraded / Missing
  6. 자동 동기화가 켜져 있으면 동기화 실행
  7. 다음 주기까지 대기 (기본 180초)

このコードブロックの韓国語の行は、順に、repo-serverに望む状態(レンダリングされたマニフェスト)を要求する、対象クラスターから実際の状態を取得する、正規化したうえで両者を比較する(3-way diff)、Sync状態を更新する(Synced / OutOfSync)、Health状態を更新する(Healthy / Progressing / Degraded / Missing)、自動同期が有効なら同期を実行する、次の周期まで待つ(デフォルトは180秒)、という意味です。

ここで必ず区別しなければならない2つの軸が出てきます。Sync状態は「リポジトリと同じか」であり、Health状態は「正しく動いているか」です。SyncedなのにDegradedということがありえます。リポジトリが指示したとおりにデプロイしたけれど、イメージが取得できずにPodがCrashLoopBackOffになっている場合です。反対に、OutOfSyncなのにHealthyということもありえます。誰かが手で増やしたPodが、問題なく動いている場合です。2つの軸を混ぜて見ると、障害の原因を見誤ります。

ステップ3の比較には、正規化が挟まっています。Kubernetesが自動で埋める値(resourceVersion、uid、generation、creationTimestamp、managedFields、ほとんどのstatus)を、両側から取り除いたうえで比較します。この過程がないと、何も変わっていないのに毎回差があると報告されます。

selfHealがすること

selfHeal: trueは、ステップ4でOutOfSyncが出たときに、ステップ6に進ませるスイッチです。そのため、kubectl scaleやkubectl editで行った修正は、次の調整で消えます。すぐには消えないという点が重要です。デフォルトの周期があるので、「直したあとしばらくはうまくいっていたのに、急に元に戻った」ように感じられます。この時間差のせいで、原因を見当違いの場所で探すことがよくあります。

selfHealを切ると、Argo CDは差を画面に表示するだけのダッシュボードになります。実務で本番にselfHealを有効にする判断は、緊急の手動介入を許可するかどうかという組織の質問であり、技術的な好みではありません。有効にすると応急処置が静かに元に戻され、無効にするとドリフトが積み上がります。ほとんどのチームは有効にして、その代わり、応急処置をすぐにコミットに昇格させる規律を一緒に作ります。

pruneがすること、そしてなぜ怖いのか

prune: trueは、方向が反対です。判断の基準はこうです。

1. 저장소를 렌더해 "있어야 할 오브젝트" 목록을 만든다
2. 클러스터에서 이 앱이 소유한 오브젝트를 찾는다
   (argocd.argoproj.io/tracking-id 어노테이션 = 앱:그룹/종류:네임스페이스/이름)
3. 2에 있는데 1에 없는 것 = 삭제 대상

このコードブロックの韓国語の行は、順に、リポジトリをレンダリングして「あるべきオブジェクト」の一覧を作る、クラスターからこのアプリが所有するオブジェクトを探す(所有マークはアプリ・グループ/種類・ネームスペース/名前を記録したアノテーション)、2にあって1にないものが削除の対象、という意味です。

怖いのは項目3です。source.pathを空のディレクトリに誤って変えたコミット1つで、項目1の一覧が0個になり、そのアプリが管理していたすべてのオブジェクトが削除の対象になります。レビュアーが1文字の打ち間違いを見逃すだけで十分です。そのため、防御の仕組みがあります。

仕組み すること
allowEmpty: false レンダリング結果が空なら同期を拒否する
PruneLast=true ほかのリソースをすべて合わせたあとで、最後に削除する
Prune=false(リソースのアノテーション) そのオブジェクトだけを削除の対象から除外する
orphanedResources.warn 管理の対象外のオブジェクトを、警告としてだけ知らせる

selfHealは、最悪の場合が「自分の手動の変更が消える」ことで、pruneは、最悪の場合が「データが消える」ことです。危険の度合いが違うので、PVCのような状態を保管するリソースには、Prune=falseを付けておくほうが安全です。

元に戻してはいけないフィールド

調整ループは真面目なので、ほかのコントローラーが正当に所有するフィールドまで、元に戻します。HPAがspec.replicasを8に上げるとArgo CDが3に下げ、HPAがまた8に上げます。この無限ループの解決策は、スイッチを切ることではなく、所有権を明示することです。

spec:
  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas

ServerSideApply=trueも、同じ問題を別の層で扱います。フィールドの所有権をAPIサーバーが追跡するようにして、自分が宣言していないフィールドを他者が管理している状況を、正常として扱います。

現場での姿

1つ目は、このラボ環境にはArgo CDコントローラーがないことです。Podは特権なしで起動し、その中のkwokクラスターは、etcd・apiserver・controller-manager・schedulerだけを本物として動かします。そのため、CRDを登録してApplicationを作ることはできても、それがひとりでSyncedに変わることはありません。これを隠さないのがこのカリキュラムの方針で、あとに続くラボは、コントローラーの真似をする代わりに、コントローラーが行う判断を手でコードに書いてみるようにします。すでに同じことの半分はやっています。gitops-manifestラボのステップ8で作ったsync.shが、まさにステップ3–6を一度回すスクリプトです。コントローラーは、それを忘れずに繰り返す存在にすぎません。

2つ目は、「なぜ元に戻ったのか」の正体です。運用中に最もよく来る問い合わせがこれです。設定を直したのに、数分後に元に戻っている。答えはいつも同じです。selfHealが有効で、その変更がリポジトリにないからです。このとき怒る相手はツールではなくプロセスです。リポジトリを経由していない変更は、そもそも存在しない変更として扱うと、チームが合意したからです。

3つ目は、自動同期の範囲を環境ごとに分けることです。よくある折衷は、devにはpruneとselfHealをどちらも有効にし、本番にはselfHealだけを有効にするか、そもそも手動同期にしておくことです。誤ってマージされた削除が、すぐに本番に反映される事故を防ぐためです。どちらを選んでも、その選択をApplicationのYAMLに書いてリポジトリにコミットすること自体がGitOpsです。

次のラボですること

すぐ次のラボで、このループを8つのステップで1周させます。Applicationにスイッチと無視するフィールドを宣言したあと、正規化の前処理、ドリフトの判定、削除候補の計算、空の結果の防御を、それぞれスクリプトとして作ります。宣言したフィールドは元に戻り、他者に任せたフィールドはそのまま残ることを、サーバーサイドの適用で自分で確認し、最後に、リポジトリと同じなのにPodが起動できない状態を作って、2つの軸がなぜ別々にあるのかを見ます。