Argo CD の並行処理を有効にしたらハマった

Posted on

freee で Argo CD を使い始めて半年以上経ちましたが、Argo CD の Application はすでに 100 個以上あり、パフォーマンスの問題が出始めています。

Argo CD の High Availability のページを読むと、モノレポのスケーリングに関する記述があります。

Argo CD repo server maintains one repository clone locally and use it for application manifest generation. If the manifest generation requires to change a file in the local repository clone then only one concurrent manifest generation per server instance is allowed. This limitation might significantly slowdown Argo CD if you have a mono repository with multiple applications (50+).

パフォーマンス低下の対策として並行処理を有効にすることが挙げられています。

Argo CD determines if manifest generation might change local files in the local repository clone based on config management tool and application settings. If the manifest generation has no side effects then requests are processed in parallel without the performance penalty.

freee では manifest の管理に Helm と Helmfile を利用しており、Argo CD のカスタムプラグインで helmfile template を実行しています。カスタムプラグインを使っている場合、起点となるディレクトリ (spec.source.path) に .argocd-allow-concurrency というファイルを置いておけば並行処理が有効になります。こうすることで repo server 上で manifest の生成を同時並行してくれるようになります。

テンポラリファイルを生成するとレースコンディションが起きる

Argo CD の Application が増えてくると、まれに生成される manifest が期待と違うということが起きるようになりました(Argo CD 上で差分が出て気づく)。具体的な内容は書けませんが、期待と違う manifest が自動同期されてしまって障害にもなりました。

詳しく調べてみると、Secret の値を暗号化するために使っていた helm-secrets というプラグインがテンポラリファイルを生成していることと、Argo CD の並行処理を有効にしたことが原因でした。

secrets.yaml が存在するとき helmfile template を実行すると次のような処理が行われています。

  1. helm secrets dec を実行して secrets.yaml と同じディレクトリに復号された secrets.yaml.dec を作る
  2. secrets.yaml.dec をメモリに読み込み、secrets.yaml.dec を削除する (pkg/helmexec/exec.go#L293-L302)
  3. 2 の値をもとに tmp ファイルを作る (pkg/helmexec/exec.go#L317-L340)
  4. 3 の tmp ファイルを helmfile template に渡して、プレーンな状態の manifest を生成する
  5. tmp ファイルを削除する

テンポラリファイルを介して secrets.yaml を処理していることがポイントです。 helmfile コマンドが同じカレントディレクトリを共有して同時並行で実行されると、レースコンディションが起きてファイルが見つからない可能性があります(カレントディレクトリを共有して同時並行という条件が .argocd-allow-concurrency を置くことで成立する)。これによって secrets.yaml の値が設定されないという状況になっていました。

これは Helmfile や helm-secrets に問題があるわけではなく、並行処理を有効にしてはいけないケースで有効にしてしまったことが原因です。改めて Enable Concurrent Processing のセクションを読むと、ちゃんと "avoid creating temporal files during manifest generation" (manifest 作成時にテンポラリファイルを作成しない)と書いてありました。

今は並行処理を無効にして、repo server を早めにスケールアウトさせるように HPA を設定しています。

まとめ

同じ事象を再現させるのが難しかったので、Argo CD の並行処理が原因だということになかなか辿り着けませんでした(並行処理を有効にしたのは Argo CD を使いはじめた頃で、そのあと Application の数が増えてレースコンディションが起きやすくなった)。

最終的には debug ログを出力しながら、ソースコードを読み解くことでつじつまが合いました。レースコンディションが絡む調査は難しいですが、その分学びも多かったです。