トラス構造が幾何学的に組まれた建築物のファサード
技術解説

Argo CDのGitOpsハブが20,000オブジェクトでOOM落ちする理由と対策

目次を見る

Argo CD(Kubernetesマニフェストの差分をGitリポジトリと同期させ続けるGitOpsツール)を使い、複数クラスタを一つのハブから集中管理している方に向けた内容です。クラスタ数が増えるほどハブが重くなると考えがちですが、実際にボトルネックを決めているのは別の指標です。

大規模検証では、Argo CDのapplication controller(同期対象アプリケーションの現状態と期待状態を比較し続けるコンポーネント)が、1ハブあたりキャッシュオブジェクト数15,000〜20,000件付近でOOM(メモリ不足によるプロセス強制終了)を起こす事例が報告されています。クラスタ数ではなく、監視対象オブジェクト数がしきい値を決めている点が重要です。

何が起きるか:クラスタ数を見ていても壁にぶつかる

GitOpsハブの容量計画で、まず「何クラスタまで管理できるか」を気にする方は多いはずです。しかし実際の負荷は、クラスタ数ではなくオブジェクト数と同期キューの深さに比例します。

たとえば小さなアドオンだけを積んだクラスタが1,000個ある場合と、アプリケーション数が多く依存リソースツリーが深いクラスタが数十個ある場合を比べます。後者のほうが、application controllerが保持する状態量ははるかに多くなります。クラスタ数だけを見て容量を見積もると、この差を見落とします。

実測では、Argo CDが特定のアドオン展開シナリオで約21GBのメモリを消費した事例が報告されています。同種のパターンを扱う別ツール(Sveltos)では約2GB程度に収まったという比較も紹介されています。ただしこれは特定構成・特定用途での結果であり、一般的なベンチマークとして提示されたものではありません。それでもメモリ消費の差の大きさは無視できないものでした。

なぜ起きるか:中央集権的な同期モデルの構造的コスト

原因を段階的に分解すると、まず前提として、Argo CDのapplication controllerは各Applicationリソースについて「Gitに書かれた望ましい状態」「クラスタ上の実際の状態」の両方をメモリ上にキャッシュします。

次に、Applicationが増えるほど、キャッシュされるリソースツリーと監視対象(watch)の数が線形以上に増えていきます。この監視対象の総量が、Kubernetes標準のwatch機構(APIサーバーの変更通知をコントローラーが購読する仕組み)を通じてコントローラーのメモリを圧迫します。

さらに、同期作業自体もキュー(reconcile queue)に積まれて処理されます。キューが詰まると同期の遅延が起き、遅延の間にも状態変化が積み重なり、メモリ・CPUの両面で負荷が雪だるま式に増えていきます。

重要なのは、レプリカを増やす、シャーディング(複数コントローラーにクラスタを分散させる仕組み)を導入する、リソース制限をチューニングするといった対策が、いずれも「壁に到達するまでの時間を延ばす」効果はあっても、「中央集権的なモデルが大量の状態を1プロセスで保持し続ける」という構造そのものは変えないという点です。動的シャーディングを使い、コントローラーレプリカを3〜6台に分散させた検証でも、負荷分散という一定の効果はありつつ、根本的なメモリコストの曲線は変わらなかったと報告されています。

自分の環境が該当するか確認する方法

手を動かして確認できる項目を整理します。

# application controller の現在のメモリ使用量を確認
kubectl top pod -n argocd -l app.kubernetes.io/name=argocd-application-controller

# OOMKilled の履歴がないか確認
kubectl get pod -n argocd -l app.kubernetes.io/name=argocd-application-controller -o json | jq '.items[].status.containerStatuses[].lastState.terminated.reason'

# 管理下の Application 数を数える(クラスタ数ではなくこちらが本命の指標)
kubectl get applications -n argocd --no-headers | wc -l

あわせて確認したい設定・数値は以下の通りです。

  • argocd-cmapplication.instanceLabelKeycontroller.status.processors の設定値(並列処理数のチューニング状況)
  • application controllerのDeploymentに設定されているメモリの limits / requests(実消費との乖離)
  • Argo CDのメトリクス argocd_app_reconcile_countworkqueue_depth(Prometheusで取得できる場合はGrafana等で推移を確認)
  • 管理対象アプリケーションが持つ子リソース数(Helmチャートの展開後リソース数が多い構成は特に注意)

目安として、Application数と展開後リソースツリーの合計が15,000〜20,000オブジェクトに近づいている、あるいはOOMKilledの履歴が繰り返し出ている場合は、チューニングの延命効果だけに頼らない判断が必要です。

対策の手順

段階を踏んで対応を検討します。

1. まず現状把握として、上記コマンドでメモリ使用量とOOM履歴、Application数を記録します。数値を時系列で残しておくと、今後の増加ペースを予測する材料になります。

2. 次に短期対策として、application controllerのシャーディングを有効にし、クラスタを複数コントローラーに分散させます。ARGOCD_CONTROLLER_REPLICAS の増加と、動的クラスタ分散設定を組み合わせることで、当面の余裕を作れます。ただしこれは時間稼ぎであり、根本解決ではない点を認識しておきます。

3. あわせてhydrated manifests(Gitに事前展開済みのマニフェストを置き、コントローラー側での展開処理を減らす手法)の採用を検討します。展開処理の負荷そのものを減らせるため、一定の緩和効果があります。

4. 中長期的には、アーキテクチャの見直しを検討します。監視対象オブジェクトが増え続ける前提であれば、集中型の1コントローラーがすべてを抱える構成ではなく、各クラスタ側に軽量なエージェントを配置し、ドリフト検知や分類処理をスポーク側に分散させるモデルとの比較が有効です。IaC(Terraform/Pulumi等でインフラをコード管理する手法)でハブのアーキテクチャを定義している場合、コントローラーの配置構成自体をモジュール化しておくと、将来の切り替えコストを抑えられます。

5. SLO(サービスレベル目標)の観点では、「同期完了までのリードタイム」や「OOM発生頻度」をSLI(サービスレベル指標)として明示的に定義し、しきい値を超えたらアラートが飛ぶようにしておくと、壁に到達する前に手を打てます。

まとめ

GitOpsハブの容量計画は、クラスタ数ではなくオブジェクト数と同期キューの深さで考えるべきものです。

15,000〜20,000オブジェクト前後という具体的な目安を踏まえ、まずは kubectl top とApplication数のカウント、OOMKilled履歴の確認から始めるのが現実的な第一歩です。

シャーディングやhydrated manifestsは有効な緩和策ですが、根本的なメモリコストの曲線を変えるものではない点を忘れないようにします。

数値が壁に近づいてきたら、集中型コントローラーに依存し続けるか、分散型アーキテクチャへの移行を検討するか、早めに判断材料を揃えておくことが望まれます。

参考

"Your GitOps Hub Will Become the Bottleneck Long Before Cluster Count Tells You"

この記事について: 本記事は AI を活用して作成し、forva AI 編集部が内容を確認・監修しています。

AI 駆動開発のご相談は forva AI へ。まずはお気軽にどうぞ。