Jenkinsパフォーマンスチューニング:包括的なリソース管理ガイド

コアリソースの割り当てを最適化してJenkinsのパフォーマンスをマスターしましょう。この包括的なガイドでは、CPU使用率の調整、マスターの適切なJVMヒープメモリの設定、ワークスペースとアーティファクトのディスクI/Oの戦略的管理に関するベストプラクティスを詳しく説明します。規律あるリソース管理を通じて、ビルドレイテンシを削減し、安定した効率的なCI/CD運用を実現するための実践的な手順を学びます。

Jenkinsパフォーマンスチューニング:包括的なリソース管理ガイド

Jenkinsのパフォーマンスチューニングは、通常、人々が既にイライラし始めた後に行われます。プルリクエストがキューに滞留し、UIが応答しなくなり、ビルドが奇妙なエージェントエラーで失敗したり、コントローラの再起動が必要になったりします。その修正は、単一の魔法のようなJVMフラグであることはほとんどありません。Jenkinsは、コーディネーターと、雑多な作業を行うマシンの群れで構成されているため、有用なチューニング作業はリソース管理、つまりCPU、メモリ、ディスク、ネットワーク、エグゼキュータ、プラグイン、保持、エージェント設計です。

このガイドでは、実際のCI/CDシステム向けの実践的なJenkinsパフォーマンスチューニングに焦点を当てます。目標は、Jenkinsから最後のベンチマークポイントを絞り出すことではありません。目標は、ビルドを予測可能に保ち、コントローラを健全に保ち、次のボトルネックがどこから来るかを明確にすることです。


Jenkinsのリソース消費を理解する

Jenkins自体と、エージェントを通じて実行されるジョブは、CPUサイクル、RAM、ディスクI/Oという3つの主要リソースを消費します。パフォーマンスのボトルネックは、これらのリソースが過小評価されていたり、過剰に予約されていたり、不適切に構成されている場合に発生することがよくあります。

1. CPUの割り当てと管理

CPUの可用性は、Jenkinsがタスクをスケジュールする速度と、個々のビルドの実行速度に直接影響します。ここでの管理ミスは、多くの場合、高いロードアベレージと顕著な遅延につながります。

マスターとエージェントのCPU割り当て

重い処理(コンパイル、テスト)は、JenkinsコントローラではなくJenkinsエージェントに委任するのが標準的なプラクティスです。古いドキュメントではこれらを「マスター」と「スレーブ」と呼ぶ場合がありますが、現在のJenkins用語はコントローラとエージェントです。コントローラは、調整、UI提供、APIインタラクションのために予約しておく必要があります。

  • コントローラノード: 同時リクエストを処理するのに十分なCPUを割り当てますが、ワークロードは低く抑えます。小規模または中規模のインストールでは数コアで実行できる場合がありますが、ビジーなコントローラは固定ルールではなく測定が必要です。
  • エージェントノード: これらはCPUパワーの大部分を受け取り、予想される同時ビルド負荷に基づいてスケーリングされる必要があります。

エグゼキュータスロットの制限

CPUの競合を制御する最も効果的な方法の1つは、同時ビルド数を制限することです。

マスターノードの場合:

メインのJenkins設定ページまたはエージェントのノード設定で、エグゼキュータの数を直接設定します。

$N$ CPUコアを持つエージェントがある場合、エグゼキュータの数を$N$よりわずかに少なく設定すると(例:$N-1$、またはビルドが非常にCPU集約型の場合は$N/2$)、システムが完全に飽和するのを防ぎ、OSとJenkinsのバックグラウンドタスクに余裕を持たせることができます。

エージェントの設定例:

新しいエージェント(ノード)を設定するときは、「エグゼキュータ数」フィールドを探します。ハードウェアの能力に基づいて控えめに設定します。

# エージェント設定スニペット(概念)
NUM_EXECUTORS = 4  # 重いビルドを実行する8コアマシンの場合

2. メモリ(RAM)管理

RAMが不足すると、過剰なスワッピング(データのディスクへのページング)が発生し、パフォーマンスが著しく低下します。JenkinsはJava仮想マシン(JVM)に大きく依存しているため、ヒープサイズの設定が重要です。

JenkinsコントローラのJVMヒープサイズのチューニング

コントローラのJVMヒープサイズは、最も重要なメモリ設定の1つです。

これは通常、Jenkinsを起動する前にJENKINS_JAVA_OPTIONS環境変数を変更することで設定されます(例:/etc/default/jenkinsやsystemdサービスファイル)。

ベストプラクティス: オペレーティングシステム、ファイルシステムキャッシュ、監視エージェント、およびサイドプロセスに意味のあるメモリを残します。多くのチームは、Javaにすべてを与えるのではなく、システムRAMの大部分よりもヒープを低く抑えています。

JVMオプションの例:

サーバーに16GBのRAMがある場合、妥当な開始点は8GBのヒープであり、その後、ガベージコレクションログと実際の使用状況に基づいて調整します。

export JENKINS_JAVA_OPTIONS="-Xms8192m -Xmx10240m -Djava.awt.headless=true -XX:MaxMetaspaceSize=512m"
  • -Xms: 初期ヒープサイズ。
  • -Xmx: 最大ヒープサイズ。多くの本番環境では、実行中のヒープサイズ変更を避けるために、これを-Xmsと等しく設定します。

監視とガベージコレクション(GC)

メモリ使用量が多いと、頻繁で長時間のガベージコレクションの一時停止が発生することがよくあります。GCログ(追加のJVMフラグで有効化)を監視して、ヒープサイズが適切かどうか、プラグインやビルドプロセス内にメモリリークがないかを特定します。

3. ディスクI/Oの最適化

ディスクのパフォーマンスは、特に大規模なアーティファクト、依存関係キャッシュ、頻繁なチェックアウト/削除を扱う場合、CI/CD速度の静かな殺し屋となることがよくあります。

ワークスペースとログ用の個別ボリューム

可能であれば、書き込みアクティビティの多い領域をコアなJenkinsインストールから分離します。

  1. Jenkinsホーム($JENKINS_HOME): ここには、設定、ビルドレコード、システムログが保存されます。信頼性の高い中程度の速度のストレージ(SSD推奨)が必要です。
  2. ビルドワークスペース: これらのディレクトリでは、大規模で頻繁な読み取り/書き込み/削除操作が発生します。理想的には、ワークスペースが存在するプライマリディレクトリを最速の利用可能なストレージ(NVMe/SSD) に配置します。

ヒント: ワークスペースに使用されるファイルシステム(例:ext4XFS)が適切に維持され、十分なiノードがあることを確認します。

ビルドキャッシュ戦略の活用

スマートなキャッシングによるディスクアクティビティの最小化は、大きなパフォーマンス向上につながります。

  • 依存関係キャッシュ: Maven、Gradle、npm、またはpipを設定して、エージェントノード上の共有の永続的なキャッシュを使用し、ビルドごとに依存関係を再ダウンロードしないようにします。
  • ワークスペースのクリーンアップ: 古いワークスペースを積極的にクリーンアップします。ワークスペースを保持しておくとデバッグに役立つ場合がありますが、ディスク容量を消費し、数が多すぎるとディスク操作が遅くなります。
    • cleanWs()などのパイプラインステップを使用するか、特定の期間後にワークスペースを自動的に削除するようにエージェント設定を構成します。

ネットワークファイルシステム(NFS/SMB)

警告: ネットワークリンクとストレージアレイが非常に高いスループットと低レイテンシでない限り、ビルドワークスペースなどの書き込み量の多いボリュームにはネットワークファイルシステム(NFSまたはSMB)を使用しないでください。ネットワークレイテンシは、I/Oバウンドのタスクに大きなオーバーヘッドをもたらします。

高度なパフォーマンス手法

ベースラインのリソース割り当てを超えて、いくつかのアーキテクチャおよび運用上のチューニングポイントが大きなメリットをもたらす可能性があります。

エグゼキュータの最適化とスケーリング

予測できない負荷がある環境では、動的スケーリングが鍵となります。

クラウドネイティブエージェント(エフェメラルエージェント)

オンデマンドでプロビジョニングされるJenkinsエージェント(例:Kubernetes、Docker、EC2プラグイン経由)を使用します。これらのエージェントは、必要なときに正確に起動され、その後終了されます。これにより、アクティブなビルド中にのみリソースが消費され、アイドル状態で永続的に実行されるエージェントによる無駄なオーバーヘッドを回避できます。

プラグイン管理

プラグインは、コントローラのメモリフットプリントと処理負荷に大きく寄与する可能性があります。

  1. プラグインの監査: インストールされているプラグインを定期的に確認します。未使用または古いプラグインはメモリを消費し、パフォーマンスの低下を引き起こす可能性があるため、削除します。
  2. 作業のオフロード: 可能な限り、プラグインがコントローラではなくエージェントで重い処理を実行するように設定します。たとえば、レポートを生成したりインデックスを作成したりするツールは、エージェントで実行する必要があります。

パフォーマンス監視ツールの活用

事後対応的なチューニングだけでは不十分です。プロアクティブな監視が不可欠です。監視ツールを統合して、主要なメトリクスを追跡します。

  • システムレベル: CPU使用率、RAM使用量、ディスクI/O待機時間。
  • Jenkinsレベル: ビルドレイテンシのパーセンタイル(P95、P99)、キュー時間、エグゼキュータ使用率。

Prometheus/Grafanaなどのツールや、組み込みのJenkins監視機能(Metricsプラグインなど)は、リソース調整を正当化するために必要な可視性を提供します。

ベストプラクティスのまとめ

リソース ベストプラクティス 実践的なヒント
CPU 重い負荷はエージェントに委任する。 安全性のため、エージェントのエグゼキュータをコア数よりわずかに少なく設定する。
メモリ(マスター) JVMヒープサイズ(-Xmx)をチューニングする。 物理RAMの50〜75%を割り当て、Xms=Xmxに設定する。
ディスクI/O ワークスペースには高速なローカルストレージ(SSD/NVMe)を使用する。 書き込み量の多いビルドディレクトリにはNFS/SMBを使用しない。
ワークロード 積極的なキャッシングを実装する。 依存関係マネージャー(Maven/npm)を設定して、エージェント上の永続的な共有キャッシュを使用する。
アーキテクチャ エフェメラルで動的なエージェントを使用する。 KubernetesまたはDockerプラグインを活用して、キュー深度に基づいてリソースをスケーリングする。

コントローラから始める:退屈に保つ

コントローラは退屈であるべきです。それは褒め言葉です。退屈なコントローラは、ビルドをスケジュールし、ジョブ設定を保存し、ページを提供し、エージェントと通信し、メタデータを書き込みます。テストスイートを実行したり、コンテナをビルドしたり、巨大な依存関係ツリーをスキャンしたり、ギガバイト単位のレポートを公開したりしません。コントローラが単なる別のビルドマシンになると、すべてのチームが爆発の影響範囲を共有することになります。

小規模なシングルマシンインストールや非常に意図的な例外がない限り、コントローラのエグゼキュータ数はゼロに設定します。この1つの変更により、偶発的なワークロードがシステム内で最も重要なノードに着地するのを防ぎます。ジョブが本当にそこで実行する必要がある場合は、その理由を尋ねてください。多くの場合、答えは「ツールがそこにインストールされているから」であり、より良い修正は、そのツールを使用してエージェントイメージを構築することです。

コントローラのCPUをエージェントのCPUとは別に監視します。ビルドが実行されていないときにコントローラのCPUが高い場合は、プラグインアクティビティ、ブランチインデックス、ログレンダリング、セキュリティレルムルックアップ、またはジョブ履歴が多すぎることが原因である可能性があります。ピークビルド時間中にコントローラのCPUが高い場合は、パイプラインをスケジュールしすぎているか、大規模なログをシリアル化しているか、他の場所で処理する必要があるレポートを処理している可能性があります。

メモリチューニングも同じパターンに従います。ヒープを大きくするとガベージコレクションのプレッシャーを軽減できますが、プラグインのリークをしばらく隠し、最終的な一時停止を悪化させる可能性もあります。GCログを有効にし、フルコレクション後の古い世代の使用状況に注意し、プラグインのアップグレード前後のメモリ動作を比較します。ヒープ使用量が一日中上昇し、決して戻らない場合は、リークや暴走ジョブを除外するまで、それを正常な成長と呼んではいけません。

ワークロードに応じてエグゼキュータをチューニングし、コア数だけで判断しない

一般的な「コアあたり1エグゼキュータ」というショートカットは、単なる出発点の推測にすぎません。ほとんどの時間をネットワークダウンロードの待機に費やすビルドは、C++をコンパイルしたりブラウザテストを実行したりするビルドよりも多くの同時実行性に耐えることができます。数千の小さなファイルを作成するジョブは、CPUがビジーに見えるずっと前にディスクを飽和させる可能性があります。Docker-in-Dockerを実行するジョブは、ストレージドライバの制限やネットワークの制限に驚くべき方法で直面する可能性があります。

CPU負荷の高いビルドの場合は、控えめに開始します。8コアのエージェントでは、4つのエグゼキュータが8つよりも平均ビルド時間が短くなる場合があります。I/O負荷の高いビルドの場合は、同時実行性をゆっくりと増やしながら、ディスク待機時間とファイルシステムレイテンシを測定します。メモリ負荷の高いビルドの場合は、ビルドごとの常駐メモリを追跡し、OSキャッシュ用のスペースを残します。Jenkinsエージェントでのスワップアクティビティは、通常、エグゼキュータ数が多すぎるか、ジョブにより大きなマシンが必要であることを示しています。

ラベルはリソース管理の一部です。一部のジョブがDockerを必要とし、一部が大容量メモリを必要とし、一部がライセンスコンパイラを必要とする場合、すべてを汎用的なlinuxラベルに送信しないでください。リソースプロファイルを説明するラベルを作成します。次に、ラベルごとにキュー時間を確認します。これにより、より多くのlinux-dockerエージェント、より多くのメモリ集約型エージェント、または希少な環境に固定されているジョブが少ないかどうかがわかります。

ディスクはしばしば隠れたボトルネック

Jenkinsは多くのファイルを作成、読み取り、削除します。ソースチェックアウト、依存関係キャッシュ、テストレポート、カバレッジファイル、ビルドアーティファクト、アーカイブされたログ、一時ファイルはすべてディスクに触れます。ディスクが遅いと、ビルドはランダムに遅く見えます。ディスクがいっぱいになると、ビルドは人間の時間を大幅に無駄にする方法で失敗します。

可能な場合は、ビジーなワークスペースを高速なローカルストレージに配置します。インフラストラクチャが許せば、$JENKINS_HOME、ワークスペース、大規模なキャッシュに個別のボリュームを使用します。この分離により、コントローラの設定やビルドメタデータを危険にさらすことなく、ノイズの多い部分を拡張しやすくなります。また、トラブルシューティングも明確になります。ワークスペースI/Oが飽和している場合、どこを見ればよいかがわかります。

ネットワークファイルシステムには注意してください。NFSとSMBは一部の共有アセットには適している場合がありますが、多数の小さなファイルを含むアクティブなワークスペースでは、しばしば問題が発生します。JavaScriptのインストール、Mavenビルド、または数千の一時ファイルを作成するテストスイートは、ネットワークレイテンシを数分の無駄な時間に変える可能性があります。ネットワークストレージを使用する必要がある場合は、生のスループット数値を信頼するのではなく、実際のワークロードをベンチマークします。

保持設定は重要です。すべてのアーティファクトを永久に保持することは高くつきます。履歴をまったく保持しないことは、インシデントレビュー中に苦痛です。実用的な設定では、デバッグとコンプライアンスのために十分なビルドを保持し、長期アーティファクトをアーティファクトリポジトリに公開し、古いログとワークスペースを自動的に期限切れにします。正確な保持期間はチームによって異なりますが、決定は明示的である必要があります。

新しい問題を引き起こさないキャッシング

キャッシングは、Jenkinsのパフォーマンスを向上させる最も速い方法の1つです。また、キャッシュが慎重に設計されていない場合、奇妙なビルドを作成する最も簡単な方法の1つでもあります。

依存関係マネージャーの場合は、共有ダウンロードに実際のリポジトリまたはパッケージプロキシ(Nexus、Artifactory、プライベートnpmレジストリ、Mavenプロキシ、言語固有のキャッシュサービス)を優先します。次に、ローカルのエージェントごとのキャッシュを使用して、繰り返しのダウンロードを回避します。これにより、すべてのジョブが1つの壊れやすい共有ディレクトリに書き込むことなく、速度を向上させることができます。

Dockerビルドの場合は、依存関係レイヤーが安定するようにDockerfileの命令を順序付けます。最初にマニフェストファイルをコピーし、依存関係をインストールしてから、ソースの残りをコピーします。適切な場所でBuildKitキャッシュマウントを使用します。エージェントがエフェメラルである場合は、共通のツールチェーンを既に含む事前構築済みのベースイメージを検討します。ビルドのたびに巨大なイメージをプルすると、動的エージェントの利点が損なわれる可能性があります。

テストキャッシュの場合は、正確性について正直になりましょう。コンパイラキャッシュと依存関係キャッシュは、適切にキー設定されていれば通常は安全です。テスト結果の再利用は、ビルドシステムが入力を正確に理解していない限り、より危険です。高速な間違ったビルドは、低速な正しいビルドよりも悪いです。

実際に役立つ監視

Jenkinsダッシュボードは、いくつかの明白な質問に答える必要があります。互換性のあるエグゼキュータが不足しているためにジョブが待機していますか?エージェントの接続または起動に失敗していますか?コントローラはガベージコレクションに時間を費やしすぎていますか?ディスクはクリーンアップがデータを削除するよりも速くいっぱいになっていますか?一部のジョブがほとんどのエグゼキュータ時間を消費していますか?

ラベルごとのキュー時間、エージェントごとのエグゼキュータ使用率、ジョブごとのビルド期間、コントローラヒープ、GC一時停止時間、ディスク使用量、ディスクI/O待機時間、エージェント起動失敗、リモーティング切断を追跡します。パーセンタイルは平均よりも有用です。中央値のビルドは問題ないが、最も遅い10%がひどい場合、ユーザーはそれでもJenkinsを信頼できないと感じるでしょう。

チューニングのための短い変更ログを保持します。ヒープサイズ、エグゼキュータ数、プラグインバージョン、保持ポリシー、エージェントイメージ、キャッシュパスを変更したときにメモします。その履歴がなければ、最終的にはグラフを見つめて、先週の火曜日に何が起こったのか疑問に思うことになります。

賢明なチューニングループ

1つのボトルネックを選びます。意味のある1つのことを変更します。通常のピークトラフィックを含むのに十分な期間測定します。改善に役立ち、新しい障害モードを作成しなかった場合は、変更を維持します。改善が理論上のみ現れる場合は、ロールバックします。

たとえば、Mavenジョブが依存関係の解決に6分かかる場合は、リポジトリプロキシとエージェントローカルキャッシュを追加します。その後もキュー時間が高いままの場合は、影響を受けるラベルにエージェントを追加します。ビルドが静かなときにコントローラUIがまだ遅い場合は、プラグイン、ジョブ数、ブランチインデックス、ヒープ動作を確認します。各ステップで問題を絞り込み、Jenkinsを推測の山に変えることを防ぎます。

CPU、メモリ、ディスク、キャッシング、エージェント容量に体系的に対処することで、Jenkinsをドラマチックでなくします。これこそが最高のCI改善です。開発者はツールについて考えるのをやめ、コードの出荷に戻ることができます。