Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GitOpsに潜む競合を、構造から見直す

Avatar for g0xu g0xu
September 04, 2026
240

 GitOpsに潜む競合を、構造から見直す

Avatar for g0xu

g0xu

September 04, 2026

Transcript

  1. 今日話すこと 前提 — GitOps と proto descriptor、そしてこの構成 2. 事象 —

    リリースの「順序」で不具合が起きた 3. その場しのぎをやめる — なぜこの構成なのかを辿る 4. 構造で解く — 根本原因と責任境界の見極め 5. 学び 1. 3
  2. 前提① GitOps / ArgoCD を単一の情報源(source of truth)にして、宣言的にクラスタを同期する運用モデル。 ArgoCD が Git

    の状態を検知し、クラスタへ自動同期する 同期の単位は Application(=「この Git のこのパスを、このクラスタへ」) リポジトリを更新すれば、対応する Application が自動で同期される ポイント:「何を1つの Application にまとめるか」が、後々の挙動を左右する GitOps = Git 参考: Argo CD 公式ドキュメント / Core Concepts (Application) / OpenGitOps(GitOps 原則) 5
  3. ArgoCD の Application リソース(例) apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name:

    api namespace: argocd spec: project: default source: # ← 何を repoURL: https://github.com/xxx/k8s-manifest.git path: app/helm/api # この「1つのパス」を配信 targetRevision: main helm: valueFiles: [values/prd.yaml] destination: # ← どこへ server: https://kubernetes.default.svc namespace: api syncPolicy: automated: # ← Git 変更を自動で同期 prune: true selfHeal: true 6
  4. 前提② proto descriptor とは にデプロイされている api は gRPC サービス。しかしクライアントは HTTP/JSON

    で叩きたい。 Istio のサイドカー(Envoy)の grpc_json_transcoder が、HTTP/JSON ⇄ gRPC を相互変換 その変換に必要な「API 定義の辞書」が proto descriptor(pb) GKE ⇒ pb(辞書)と image(API 実装)は、常に互いに整合している必要がある ずれると transcoder がサービスを解決できず、機能不整合が起きる gRPC-JSON transcoding: HTTP/JSON リクエストを、descriptor を辞書に gRPC 呼び出しへ変換する Envoy の機能 参考: Envoy gRPC-JSON Transcoder / Istio EnvoyFilter / Protocol Buffers 7
  5. 前提③ 対象システムの構成 今回の api は、2つの ArgoCD Application で構成されている。 api Application

    … アプリ本体(Rollout)を配信 config Application … proto descriptor(pb)を ConfigMap として配信 api の Istio サイドカー(Envoy)がマウントして使う この pb は api + もう1つのワークロードから参照される(1対2) リリースの流れ: アプリのリリースで、CI が1コミットで imageTag(api) と pb(config) を同時更新 → ArgoCD が api / config それぞれの Application を「独立に」同期する image と pb は「1リリースで一緒に変わる」 。しかし同期するのは別々の Application。 9
  6. 事象:リリース時の競合 api のリリースで、Pod のロールアウトと proto descriptor(pb)の更新の競合が発生。 正しい順序は pb(config)を先に更新 → その後に

    api の Pod をロールアウト しかし両者は別 Application で、この順序が保証されない api が先にロールアウトすると、新 Pod が「古い pb」を掴む → 今後も再発しうる 「たまたま順番が合えば動く」状態になっていた。 11
  7. 何が起きるのか pb (辞書)と image(実装)がずれると: 古い辞書で変換され 機能不整合(サービスは在るが descriptor の中身が古い) →502 エラー(アプリチームからの報告)

    本来は pb を先に更新しておく必要があるのに、api が先行すると、新しい Pod が「古い pb」を掴んで起動する ——これが競合の正体。 12
  8. その場しのぎの誘惑 「すぐ塞ぐ手」はいくつもありました。 pb と image を別々にリリースする Application 間の同期順序を制御する Argo CD

    の機能を使う でも——「なぜこの構成なのか」を問わずに塞ぐと、別の形でまた牙をむく。 まず根本原因にたどり着くことにしました。 14
  9. 根本原因 と pb は、別々の Application(api / config)に分かれて独立同期される → 2つの Application

    間に、同期順序を保証する仕組みがない image 壊れているのは「リリースフロー(source)」ではなく、「どう Application に分けたか」。 16
  10. なぜ、こう分かれたのか(歴史を辿る) 時期 内容 当時の課題 pb は 複数のワークロードから参照される(1対多)のに、各自が別々に管理。重複・不統一が課題だった 対応 pb を

    専用の config Application に集約(DRY・単一フロー化) 。当時としては妥当な判断 副作用 pb が api 本体と別の Application に切り離され、独立同期で順序保証のない競合を生んだ 今回向き合っているのは、この 副作用 です。 17
  11. ここまでのまとめ 事象:リリース時、Pod ロールアウトと pb 更新が競合する 本質:pb(辞書)と image(実装)がずれると機能不整合 原因:pb と image

    が別 Application で独立配送され順序保証がない = リリースフローではなく Application の分け方 に起因 背景:過去の DRY 一元化のトレードオフが表面化した ここから「では、どう直すか」へ。 19
  12. 解決策の検討 案 概要 評価 リリース PR 分割 pb と image

    の PR を分ける ✕ CI 再設計・人手依存で形骸化 ArgoCD 順序制御(Beta) Application 群の同期順を制御 ✕ Beta・自動同期を無効化・目的外の流用 同期待ちの hook 先に config を確認してから api △ GA だが自作物の保守・SPOF 化 構造で解く(本命) Application の分け方そのものを見直す ◎ 競合が構造的に消える 21
  13. 「どこで・誰が直すべきか」 責任境界の見極めが、この対応の肝でした。 リリースフロー(アプリ/BE 側)は妥当 → 触らない 競合は infra 側の Application

    の分け方が原因 → infra 側で直すのが正しいスコープ ただし pb の配送元(CI)に手が入るため、実装は アプリ/BE 側と連携して進める 「自分たちの都合で、健全な他チームのフローを変えさせない」 原因のある場所で、責任を持って直す。 24