HA構成のArgoCDパフォーマンス最適化への道

メディア統括本部 サービスリライアビリティグループ(SRG)の石川 雲(@ishikawa_kumo)です。
#SRG(Service Reliability Group)は、主に弊社メディアサービスのインフラ周りを横断的にサポートしており、既存サービスの改善や新規立ち上げ、OSS貢献などを行っているグループです。
本記事では、HA構成のArgoCDパフォーマンスの課題を解決するための方法を紹介します。具体的には、リソース増加によるデプロイ遅延に対処するための最適化手法や、Shardingを活用した負荷分散の実装方法について解説するものです。
なにかの役に立てば幸いです。

ArgoCDが遅い


弊社Aプロダクトの継続的デリバリー(CD)機能をArgoCDに移行してから、早くも4年が経ちました。この期間中、レガシーシステムから移設してきたサービスが増え続け、我々のCD環境で管理しているリソースも急速に拡大してきました。移行当初は、スムーズなデプロイを実現していたものの、ここ数年で発生してきた新たな課題により、最適化の必要性が浮き彫りになっています。

ある日、デプロイの遅延に気づく

ある日のこと、バックエンド開発者から「デプロイの時間が30分以上かかる」との報告を受けました。すぐにArgoCDのUIを確認すると、衝撃的な状況に直面しました。
全体の半数以上のArgoCD Applicationが、などの状態のまま数分間動かず、デプロイが停滞していたのです。さらに、特に多くのリソースを管理しているApplicationを開こうとしたところ、UIがフリーズし、動作が非常に遅くなる事態が頻発。最終的には、 というエラーメッセージが表示され、しばらく経つとChrome Tabがクラッシュになり、何も操作ができなくなりました。

現状調査

弊社Aプロダクトのパブリッククラウド部分は、単一のArgoCDで一元管理されており、ArgoCDが所在するEKSクラスタが複数のEKSクラスタを管理し、マルチクラスタデプロイを実現しています。この環境では、Primary Application経由で全てのArgoCD Applicationが管理されており、各Sub ApplicationはHelm Chartを用いて 分別定義されています。
ApplicationのSource Repoは用途に応じて、三つに分かれています。
  • ArgoCD Application 定義 (Helm Chart)
  • Application Manifest (Kustomize)
  • ClusterコンポーネントCRD Manifest (Kustomize + Helm Chart)
Application数が約250、ArgoCD経由で管理・トラッキングしているリソース数が約3万です。
ArgoCDの構成はHAのデフォルト構成です。

調査結果

報告を受けて調査を始めた時点では、Application Controllerの2 CPU 6GB Memory Limitはそれぞれを超えてました。
Repo Serverのうち一つは、CrashLoopBackを繰り返していました。Repo Server内のログに、 が頻繁に出現し、Pod Eventでは、 のメッセージが現れています。Repo Server自体はResource Profileが設定されていませんでした。Redisのうち二つは、最近コンテナ再起動があったようです。
また、ArgoCD専用のモニタリングが存在していませんでした。

どうすればいいのか

まずArgoCD関連Podのメトリクスを取得し、Datadogで専用ダッシュボードを作ることにしました。
PodTemplateSpecに以下のAnnotationを入れれば、Datadog側提供してくれているArgoCDダッシュボードでメトリクスを確認できます。詳細はこちらのリンクです。
収集可能なメトリクスはPrometheusと違うため、詳細はこのリストになります。

ArgoCD Application Controllerのスケール不足がかなり目立ちます。調査を進める中で、ArgoCDのHA構成に関しても再検討の必要性を感じ、いくつかの参考資料を基に詳細な調査を行いました。
  1. ArgoCDの公式ドキュメントに掲載されているHA構成に関するガイドラインです。特に詳細に書かれており、全ての可能性を網羅していますが、バージョンによって構成が変わることがあるため、バージョンとリリースノートを確認しながら進める必要があります。
  1. Sync 10,000 Argo CD Applications in One Shot. By Jun Duan, Paolo Dettori, Andy Anderson: ArgoCDのスケーラビリティについての定量的研究を紹介している資料です。1万件のアプリケーションを同期する際のパフォーマンスに関するベンチマークとRepoServer数・同期間隔などの実験データが掲載されており、ArgoCDのアプリケーション管理における負荷の指標として非常に有用です。
  1. Argo CD Benchmarkingシリーズ. By Andrew Lee: ArgoCDのパフォーマンスを影響する要素に関して一番包括的な分析です。今回のケースでのボトルネックを特定するのに役立ちました。

ArgoCDパフォーマンスを影響する要素


ArgoCDの主要コンポーネント

Argo CDのパフォーマンスを理解するためには、主要コンポーネントの役割と動作を正確に把握することが重要です。
API Server(
役割:
  • 認証と認可のゲートウェイとして機能し、すべての操作リクエストを受け付ける
  • CLI、Web UI、またはWebhookなどからのリクエストを処理する
  • などのCRDリソースを操作する
動作:
  1. UI、CLI、またはGit Webhookイベントなどを通じてAPIリクエストを受け取る
  1. JWT、SSOを使用してユーザを認証し、RBACポリシーを適用する
  1. リクエスト内容に応じて、Application CRDリソースのCRUDを行う

2. Repo Server(
役割:
  • Gitリポジトリからソースコードを取得し、Kubernetesマニフェストを生成する
  • Helm、Kustomize、Plain YAMLなど、さまざまな形式のソースを処理する
動作:
  1. Application Controllerからのリクエストに応じて、指定されたGitリポジトリとリビジョンからソースコードを取得する
  1. Helm、Kustomizeなどを使用して、Kubernetesマニフェスト生成する
  1. 生成されたマニフェストをローカルファイルシステムにキャッシュする
  1. 一部の情報(マニフェストのハッシュ値)をメモリに保存する

3. Application Controller(
役割:
  • リソースを監視し、望ましい状態(Desired State)と実際の状態(Live State)を比較・同期する
  • Kubernetesクラスタ上のリソースを適切に作成、更新、削除する
動作:
  1. KubernetesのInformerを使用してリソースのイベントを監視し、イベント発生時に再調整(Reconciliation)をキューにPushする
  1. キューからを取得し、再調整を開始する
    1. Repo Serverを呼び出し、最新のマニフェストを取得する(Desired State)
    2. Kubernetes APIを通じて、現在のリソース状態を取得する(Live State)
    3. Desired StateとLive Stateを比較し、差分を計算する
    4. 差分に基づいて、必要なリソースCRUD操作を決定する
💡
  • reconcileのgoroutineは、OperationProcessorの数ごとに作られます
  • 差分の計算と同期操作は、ArgoCD GitOps エンジンが利用されます

コアとなるメトリクス

Application Controller
  • Workqueue Work Duration Seconds
    • Datadog Metrics:
      ArgoCDのApplication ControllerがWorkQueue内でアイテムを処理する際にかかる時間を示すメトリクスです。処理時間が長い場合、ボトルネックが発生している可能性があるため、監視が必要です。
  • Workqueue Depth
    • Datadog Metrics:
      などのキューの深さ(未処理のアイテム数)を示すメトリクスです。
  • Process CPU Seconds
    • Datadog Metrics:
      Application Controllerの消費CPU時間を示すメトリクスです。複数のApplication Controllerを使用している場合、平均値を取ってパフォーマンスの監視を行います。KubernetesのPod CPUメトリクスも代用可能です。
RepoServer
  • Git Request Duration Seconds
    • Datadog Metrics:
      ArgoCDのRepo ServerがGitリクエストを処理する際の時間を示すメトリクスです。Gitリポジトリへのアクセスに時間がかかる場合、同期の遅延が発生する可能性があります。

要素1: RepoServer数

ArgoCDのパフォーマンスを向上させるためには、Repo Serverのレプリカ数を増加させることが有効です。レプリカ数を増加させることで、アプリケーション全体の同期時間が短縮されます。レプリカ数を適切に増やすことで、同期処理が並列化され、処理が高速化されます。
Sync 10,000 Argo CD Applications in One Shotの記事では、レプリカ数を3倍にすることで、全体の同期時間が1/3に短縮された結果が報告されています。

要素2: 同期間隔

ArgoCDはデフォルトで3分間隔( 設定)で同期を行いますが、リソースが多い場合、この間隔を適切に調整することでパフォーマンスを改善できます。再同期の間隔が短すぎると、 の深さが増え続け、常にピーク状態となり処理が滞る原因となります。同期間隔を延長することで、キューの消化時間が確保され、結果として全体の同期時間を短縮できます。
Argo CD Benchmarkingの第一弾では、本来30分経っても終わらない同期が、同期間隔を3minから6minに上げたことで、6分間から12分間をかけて、OutOfSyncの数が0になりました。

要素3: Controllerプロセッサ数の調整

ArgoCDでは の2つのプロセッサ数を調整することが可能です。これらのプロセッサ数を増やすことで、多くのリソースを一度に処理する際のパフォーマンスを向上させることができます。
  • controller.status.processors: Applicationの状態を監視・更新するためのプロセッサ数
  • controller.operation.processors: Kubernetesへの操作を実行するプロセッサ数
Argo CD Benchmarking第二弾の記事では、これらのプロセッサ数を2倍に増加させることで、同期時間が33%短縮されたことが報告されています。ただし、プロセッサ数を増加させる際は、Kubernetes APIサーバへのリクエストの処理能力(Kubernetes Client QPS/Burst)とのバランスも重要です。プロセッサ数を増加させても効果が見られない場合、Kubernetes Client QPS/Burstの設定がおすすめです。

要素4: Kubernetes API Serverリクエスト関連

の設定を最適化することで、Kubernetes APIへのリクエスト数を増加させ、より多くのリソースを効率的に同期できます。ただし、Kubernetes API Serverへの過剰な負荷を回避するため、設定には注意が必要です。
ArgoCD Benchmarking第一弾では、の値を調整することで、以下のように同期時間の削減に成功しました。
  • デフォルトの設定の2倍: 67%の同期時間削減
  • デフォルトの設定の3倍: 77%の同期時間削減
の設定は以下の環境変数を通じて調整可能です。現時点はConfigMapで調整できません。

要素5: Helm/KustomizeとMonoRepoの使用

HelmやKustomizeを使用してApplicationのマニフェストを生成する際、MonoRepo環境において特にパフォーマンスに影響を与えることがあります。Monorepoには複数のApplicationが含まれており、リポジトリ全体を扱うため、生成プロセスが複雑化します。
Repo ServerはGit RepoをローカルにCloneし、マニフェストを生成します。もしマニフェスト生成に伴いリポジトリのファイルに変更が必要な場合、1つのRepo Server レプリカあたり1つの並行処理しか許可されないため、Monorepo内に多数のApplicationがある場合、これがボトルネックとなりパフォーマンスが低下します。
特に、50以上のApplicationを含むMonorepo環境では、複数のマニフェスト生成プロセスが発生する際、この並行処理制限によって処理が遅くなることが多いです。HelmやKustomizeの使用が必須で、かつMonorepoを採用している場合、この制限を考慮した構成が必要です。

要素6: Application Controller数(Shard数)

ArgoCDのApplication ControllerはStatefulSetとしてデプロイされ、スケールアウトはShardingの方式で行われます。複数のApplication Controller Shardを利用することで、負荷分散やパフォーマンスの向上を図ることが可能です。
ArgoCDのHAドキュメントでは、Application Controllerが複数クラスタを管理している場合、またはメモリを大量に消費している場合、Shardすることが勧められました。
If the controller is managing too many clusters and uses too much memory then you can shard clusters across multiple controller replicas.
Application ControllerのShardingは、現在以下の三つのアルゴリズムでサポートされています。
    • Applicationに付与されたに基づいて非均一な分散を行います。この方式では、分散が偏る可能性があり、必ずしも均等な負荷分散が保証されません。ただし、複数のクラスタを管理している場合、クラスタ単位での分散が行われます。Legacy方式はArgoCDの多くのバージョンで使用可能です。
    • 全てのShardに対してApplicationを均等に分配するシンプルなアルゴリズムです。このアルゴリズムは、OSのスケジューリングなどでも広く使われており、優先度を考慮しない分散を行います。ArgoCDでは、すべてのApplicationに順番をつけ、全Shardに順番通りに振り分けます。ただし、複数クラスタを管理している場合、分散はクラスタ単位で行われます。Round-robin方式は2.8.x以降使用可能です。
    • コンシステントハッシュ法を使用してApplicationを分散します。均等な負荷分散に加え、Shardやクラスタが追加・削除された際のリソースの再配置を最小限に抑えるメリットがあります。Consistent-hashingも優先度を考慮せず、クラスタ単位で分散されます。Consistent-hashingは2.12.x以降使用可能です。
後ほどの実データで紹介しますが、上記3つのアルゴリズムは以下のようなケースでは最適とは言えません。
  • 複数のクラスタを管理し、クラスタに優先度が存在する場合
  • 複数のクラスタを管理し、各クラスタのリソース量が違う場合
  • Applicationのリソース使用量が動的に変動する場合
ArgoCD Benchmarking第二弾では、CPUとメモリの変動率を評価基準として、3つのアルゴリズムを比較しました。結果として、Consistent-hashingが最も安定したパフォーマンスを示しました。
Shardingの設定は、以下の3つの箇所で行います。
  • StatefulSet環境変数:
  • StatefulSet replicas数
  • cmd-params ConfigMap の設定

Shardingを試してみる

現在利用されている 3 種類の Sharding アルゴリズムは、すべて均等なリソース分配を目的として設計されています。しかし、マルチクラスタ環境においては、クラスタ単位での分配しか対応できないため、クラスタごとの優先度やリソース数に違いがある場合、パフォーマンスが最適化されないケースが多く報告されています。これに関して、GitHub Issueにおいてもアルゴリズムに対する不満が長年にわたって提起されています。
我々の最初の導入においても、まさにこのShardingアルゴリズムの制約に直面しました。

Shardingアルゴリズムの比較

Aプロダクトは、複数のクラスタで構成されています。各クラスタには異なる優先度が設定されており、リソースの数もそれぞれ異なります。ArgoCD側のCluster一覧は以下のようになっています。
  • Develop (): 開発環境、優先度: 低、リソース数: 9000+
  • Staging (): ステージング環境、優先度: 低、リソース数: 9000+
  • Production (): 本番環境、優先度: 高、リソース数: 9000+
  • Sandbox (): サンドボックス環境、優先度: 高、リソース数: 3000+
  • Shared (): 全環境で共通利用される環境、優先度: 中、リソース数: 2000+
  • In-cluster (): ArgoCDが存在するクラスタ、優先度: 中、リソース数: shd-eksと同じ
ArgoCDの各コンポーネントは、Sharedクラスタであるにデプロイされています。ArgoCDのデフォルト設定では、ArgoCDが稼働するクラスタ()をとしてローカルクラスタとして管理しますが、このの管理は設定で無効化することが可能です。無効化した場合、すべてのApplicationは、を使って登録されたリモートクラスタ経由で操作されます。
💡
のリソース数が同じについて
Aプロダクトでは、ArgoCDにおける各クラスタのProjectおよびApplicationが、shd-eksに集約されています。には、すべての環境におけるArgoCDリソース()の定義がPrimary Projectに含まれていますが、これらのリソースは実際にはshd-eksで管理されています。
そのため、in-clusterのリソース数は、shd-eksと同じになりますが、が実際にリソースを管理しているわけではなく、定義が存在しているに過ぎません。Shardingメカニズムによって、内のすべてのリソースが複数のShardに分配され、負荷分散が行われています。
我々は最初、ArgoCD v2.7+ を利用していたため、デフォルトのアルゴリズムを使用していました。その結果、Shardの負荷状況は以下の通りです。3つのShardが存在しているにもかかわらず、6つの負荷分散対象が最初の2つのShardに集中し、CPU使用率が高くなってしまいました。これにより、Podのスケジューリングも健全とは言えない状況でした。
その後、v2.8+ にアップグレードし、アルゴリズムを試しました。このアルゴリズムにより、3つのShardすべてが使用されるようになりましたが、のShardにが同時に配置されたため、CPU使用率の分散が実現されていませんでした。
さらに、v2.12+ までアップグレードし、アルゴリズムを導入しました。しかし、このアルゴリズムの動きはリリース直後だったためか、と大きな違いは見られませんでした。むしろ、負荷が高いが同じShardに配置され、結果的にControllerが頻繁に再起動する事態が発生しました。
の場合、各アルゴリズムのパフォーマンスは、同じShard内に配置されるクラスタの組み合わせを変えただけに過ぎません。Shard数を増やすことで、リソースが集中してしまう問題を回避し、負荷の分散をより適切に行うことが可能です。
以下は、アルゴリズムを使用し、Shard数を3から5に増やした結果です。リソースが多いクラスタは効果的に分散され、パフォーマンスが改善されました。

Shardingの最適解

Shardを増やすことで、負荷の高いクラスタが同じShardに配置されるリスクを減らすことができますが、クラスタの優先度に基づく問題を完全には解決できません。
我々は最終的に、手動Shardingを導入することで、現時点での最適解に近づきました。具体的には、マルチクラスタを管理するにおいて、各クラスタのShard Indexを手動で指定する方法です。これにより、優先度の高いクラスタを単独のShardに配置し、優先度の低いクラスタはリソースの使用状況を考慮して他のShardに分散させました。
この手動Shardingの代替として、にする上、Round-Robinアルゴリズムを選択する方法も考えられます。この場合、似た効果を実現できますが、各Shardが通常500MB以上のメモリを消費するため、コンピューティングリソースに余裕がある環境でのみ推奨されます。
また、Shardingのデメリットとして、次の点を挙げる必要があります。
  • Shardが停止した場合、そのShardのタスクは他のShardに引き継がれない
そのため、各ShardのPod監視が非常に重要になります。
v2.9以降では、Shardが停止した際に動的に調整を行う機能がリリースされました。この機能では、Controllerの種類がからへと変更され、新しいConfigMapを利用してControllerの数を動的に監視します。ただし、今後のバージョンで仕様変更される可能性があるため、現時点では様子を見ることを推奨します。

改善結果

今回の改善結果について、主要なコアメトリクスをいくつか取り上げます。

対応前

改善対応が行われる前の段階では、メトリクス情報を収集しながら現状を分析していました。CPUの使用は常に1.8以上を維持し、メモリも十分に割り当てていたものの、4GBまで増加する状態でした。WorkQueueの深さは常時100以上を保ち、ピーク時には350に達し、2時間ごとにそのピークに近づく状況が続いていました。WorkQueueの滞留が発生した理由は、WorkQueueの平均処理時間が長かったことが要因だと判断しています。

Shard有効化の第一段階

v2.7でLegacy Shardを有効化した際、最初の切り替え時にはControllerの再起動が必要となり、これによりWorkQueueの深さが一時的に500まで上昇しましたが、その後Controllerのレプリカが起動することで次第に減少していきました。
結果として、WorkQueueの処理時間は60%以上削減されましたが、同じShardに複数のクラスタが同居していたため、処理時間に大きな変動が見られました。複数のクラスタが同時に処理を開始した際に処理時間が増加し、同時処理がないときには処理時間が短縮される傾向が確認されました。

Shard有効化の第二段階

v2.8で Shardを導入しました。驚くほどCPU使用率とWorkQueueの変動は安定し、全体的なパフォーマンスも改善されました。前節で紹介した通り、を利用している場合の各Shardへ与える負荷は均等的に分散されていませんが、全体的なパフォーマンスは悪くありませんでした。
しかし、Repo Serverの動作は依然として不安定で、エラーが頻発し、Gitリポジトリとの接続が不安定な状態が続いていました。実際、Git Request Durationのメトリクスからも、全Repoとの接続時間に大きな変動が見られ、この問題はキャッシュ期間が短かったことが原因でした。Repo Serverのキャッシュ時間はデフォルトで24時間設定されていますが、Aプロダクトでは過去の設定によりディスク使用量を抑えるために8時間に設定されていたため、接続が不安定になっていたのです。
さらに、ネットワーク環境の影響も一部原因と考えられ、リトライ回数を増加させる対応を行いました。

Sharding実験および最適解の適用

前節の実験で話した通り、v2.12での導入は、期待した改善効果は得られませんでした。最終的に、我々は手動Shardingに切り替え、AプロダクトのArgoCD運用がやっと落ち着きました。
総数200以上のApplicationのうち、の状態にあるApplicationは常時10個以内に抑えられ、の状態のApplicationはなくなりました。
リソース数が2000を超えるApplicationにおいても、同期の開始から終了まで1分以内に完了し、UI上のエラーは全て解消されました。
各コンポーネントのリソース使用量に関しても、CPUの使用は平均900M、メモリは700MBと安定しており、多数のApplicationが同時に同期される状況でも、全体のパフォーマンスは平穏を保っています。

その他の調整

以下の調整は対照的な実験を設定していないため、上記のグラフ分析には反映されていませんが、いくつかのエラーを解消するのに役立ったため、ここでリストアップします。
  1. 同期期間の延長
    1. 3分ごとの同期はAプロダクトでは必要とされていなかったため、同期期間を5分に延長しました。また、複数のApplicationが同時に同期する際の負荷を軽減するため、jitter設定を導入しました。これにより、5m~(5m+1m)区間の間隔でランダムに同期されるようになります。
  1. Helm/KustomizeとMonorepoの対処
    1. Aプロダクトでは、特定のリポジトリでプロダクト用Applicationのマニフェストを管理し、別のリポジトリで運用者が管理するApplicationのマニフェストを管理しています。それぞれKustomizeとHelmを多用しているため、Repo Serverのタイムアウトを延長し、Helmの並列実行を許可しました。
  1. K8S_CLIENT_QPS/BURSTの調整
    1. Controller、Repo Server、API Serverをそれぞれ2倍に増強しました。

UIパフォーマンス

改善対応の開始前と手動Sharding運用後の比較では、次のような結果を得ることができました。
改善前(FCP, LCP)改善後(FCP, LCP)
Applicationリスト712ms, 1.8s562ms, 1.48s
リソース数800+のApplication詳細820ms, 950ms 778ms, 848ms
リソース数2000+のApplication詳細645ms, 2.45s600ms, 1.12s
全体として、FCPで11%からLCPで最大27%ほどの改善が確認されましたが、特定のページではLCPが50%以上改善されるケースも見られました。
朝10時から12時の間、複数の開発者が同時に利用する時間帯では、改善前のデータは一度しか測定できなかったため、正確な数値は出せていませんが、体感としてはパフォーマンスが大幅に向上したことがわかりました。
しかし、一部リソース数2000+のApplicationでは、画面描画に遅れが発生し続け、特に情報量の多いリソース詳細を開く際には、画面全体がフリーズしてしまう事象は依然として解決されていません。
Dev Toolの測定によると、 エンドポイントのストリーム転送量は、リソース数1000以下のApplicationでは最大でも分間2.8MBの転送量に達しました。一方、リソース数2000以上のApplicationでは最小でも分間13MBの転送量を記録しました。
Github Issueでも、ArgoCD UIのパフォーマンス問題が多く報告されており、特にリソース数が多い場合のパフォーマンス劣化が問題視されています。
現在一番期待されている解決方法は、Server-sideページングです。Roadmapでは一応解決される予定ですが、現在はまだ対処されていません。

管理外リソース問題

ArgoCDのResource Tracking方法には、主にAnnotation方式とLabel方式の2種類があります。
デフォルトで使用されるLabel方式では、ArgoCDが管理するリソースにラベルが付与され、そのリソースによって生成されたChildリソースにも同じラベルが適用されます。これにより、ArgoCD Applicationの管理対象リソースとして扱われる仕組みです。Aプロダクトは、2021年のCNDTで Kubevelaを利用している登壇がありました。ここはその後日談のようなものです。
例えば、Aプロダクトにおいては、70以上のKubeVela Applicationを管理しているArgoCD Applicationがありますが、KubeVelaによって生成されるリソースが合計で2500以上に達していました。この場合、ArgoCDのUI描画速度やControllerの同期処理に大幅な遅延が発生し、パフォーマンスの低下が顕著になりました。
このリソース管理の問題は、現在もAプロダクトでは解決されておらず、引き続き課題となっています。

終わりに


ここまで読んでいただき、ありがとうございます。
ArgoCDの運用は導入初期は簡単ですが、Application数が増えるにつれて気をつけるべき点も増えてきます。幸い、ArgoCDコミュニティの活発な活動のおかげで、近年ではパフォーマンスに関するパラメータが多く追加されています。今後、ArgoCDへの貢献機会があれば、ぜひ挑戦してみたいと思います。
 
SRG では一緒に働く仲間を募集しています。 ご興味ありましたらぜひこちらからご連絡ください。
 
このエントリーをはてなブックマークに追加