3倍どころか10倍速い! Jenkins のビルドを高速化して、シャアを超えた男

実はガンダムを見たことがないので、時々社内のガンダムトークについていけないことがあります、中村です。今日は、 Jenkins のビルドを高速化し、リリースまでの時間を大幅に短縮した、改善策をご紹介します。全体で2~3倍程度、一部処理では何と10倍以上もビルドを高速化できました。


改善策1 : push 型デプロイから pull 型デプロイへ

まずはデプロイ時の方式を、push型デプロイからpull型デプロイに変更した改善策を説明します。ここで、push型デプロイは中央サーバからデプロイ用ファイルを転送する方式で、pull 型デプロイは各サーバがファイルを取得する方式とします。

各フェーズの構成は、下図のようになっています。

社内スレーブ + push型デプロイ

当初の構成は、社内に物理サーバを用意し、その上にJenkinsのスレーブを立てていました。そのスレーブから、WARファイルを各アプリサーバに転送するpush 型デプロイ方式を採用していたのです

AWSスレーブ + push型デプロイ

社内で物理サーバを立ててメンテナンスをしていくのは、メンテナンスコストがかかります。そこで、AWS上にインスタンスを立て、さらに、Dockernizeを進めてスレーブのメンテナンスコストを軽減しました。

この時、AWS上にスレーブが移動します。社内からのみ接続できるように制限したアプリサーバと接続させるために、社内のルータにVPN接続しました。Dockernizeによって、スレーブのメンテナンスコストを軽減し、スレーブの複数台化を進められました。(なお、Dockernizeの経緯や詳細については、2016年4月に開催されたコンテナ勉強会での発表資料を参照ください)

しかし、ここで問題が発生します。デプロイ処理にDockerコンテナ & VPNが介在したことによって、WARファイルの転送速度が劇的に遅くなってしまいました。これは、アプリサーバが複数台ある環境ほど、顕著な状況でした。

AWSスレーブ + pull型デプロイ

スレーブからWARファイルをpushする方式だと、どうしても転送速度がネックになります。そこで、ひと先ずWARファイルをS3にアップロードして、アプリサーバ側からそのS3を取得するpull型デプロイに変更しました。この方式だと、転送処理のボトルネックだったVPN接続を回避することができます。またスレーブにかかっていた負荷をS3に移動できます。

改善結果

プロダクション環境へのデプロイ処理において、最終的な「AWSスレーブ+pull型デプロイ」は「社内スレーブ+push型デプロイ」と比べて2倍。「AWSスレーブ+push型デプロイ」と比べると何と10倍もの高速化となりました。

社内スレーブ+push型 AWSスレーブ+push型 AWSスレーブ+pull型
11分 55分53秒 5分28秒

改善策2 : テストケースを自動分割して、よりアグレッシブに並列実行

改善前:テストケースの手動分割

テストは時間がかかる処理です。規模が小さいうちは問題ありませんが、テスト時間が長くなると、大きなボトルネックになります。

テストの実行時間を減らす一番の常套手段は、テストを並列化することです。

実際に、テストケースを3分割した並列実行も試みましたが、これ以上の並列化は少し難しいと感じていました。その理由として、効率よく並列化するには、実行時間まで考慮してテストケースを均等に振り分ける必要があるからです。並列数が2~3ならばともかく、それ以上の並列を考えたときに、均等に分割できるように手動で調整するのは至難の業でした。

改善後:テストケースの自動分割(+ 一部手動分割)

手動でテストケースの分割・調整は難しいので、ここを自動化できれば問題を解決できそうです。JenkinsではParallel Test Executor Pluginで実現できます。

しかし、このプラグインを使う際には、一つ条件があります。このプラグインでは、自動分割のインプットとして、直前のテスト結果を利用します。つまり、初回ビルド時はテストが実行されていないので、自動分割できません(正確には、利用できるテスト結果がない場合、まったく並列化せずにテスト実行しようとします)。

この挙動を回避するため、利用できるテスト結果がない場合は、予め設定しておいた内容で分割するようにしました。Jenkinsfileの具体的なコードは、以下の通りです。

def stepsForParallelTest = [:]

if (canFindPreviousTestResult()) {
// 直前のテスト結果から、テストケースを9つに自動分割
    def splits = splitTests parallelism: count(9), generateInclusions: true
    for (int i = 0; i < splits.size(); i++) {
        def split = splits[i]
        stepsForParallelTest["java-${i}"] = parallelDynamicTestStep(i, split)
    }

} else {
// 参考となるテスト結果がないので、予め設定していた内容でテストケースを3分割
    def testProfiles = ["test-1", "test-2", "test-3"]
    for (def i = 0; i < testProfiles.size(); i++) {
        def profile = testProfiles[i]
        stepsForParallelTest[profile] = parallelFixedTestStep(profile)
    }
}

parallel stepsForParallelTest

“spritTests”が上記プラグインで提供されている自動分割用のステップです。プラグインの詳細については、CloudBeesのブログエントリも参考にしてください。

改善結果

自動分割できた場合、並列数が3から9になるので、改善の効果もほぼ3倍になりました。

改善前 改善後
12分09秒 4分47秒

全体のリリース時間に対する改善結果

それぞれの改善策は、あくまでデプロイに関する処理/ テストに関する処理といった、単体作業の処理の改善です。しかし、リリース作業にはデプロイ / テストだけでなく、コンパイルやパッケージングなど、全てを含めた時間を考慮する必要があります。

ヌーラボでは、「社内ドッグフーディング環境」と「プロダクション環境」の2種類があります。それぞれの環境に対して、改善策1と2を実施後の結果は、下記の表のようになりました。

社外のユーザに直接影響があるのは、プロダクション環境へのリリースですが、社内ドッグフーディング環境はより頻繁にリリースが行われるため、このリリース時間も2倍近く高速化できていることは見逃せません。

  改善前 改善後 改善結果
社内ドッグフーディング環境へのリリース 47分57秒 25分06秒 約2倍
プロダクション環境へのリリース 81分38秒 23分51秒 約3.5倍

まだまだ改善の余地はあるものの、ビルドにかかる時間はおおむね許容範囲となり、作業時のストレスが軽減されます。

時間短縮の内訳をみてみましょう。改善策2によってコンパイル〜ユニットテスト終了までが7~8分となりました。これは、git pushして、次の作業に入る前にテスト失敗に気付けるレベルで、開発者の生産性の向上に影響してきます。

また、リリースまでの一連の流れが25分程度となっています。これは、急いでリリースしたい場合も、 git push からテストを含めて30分以内にリリースできる計算です。もちろん、クリティカルな場面に備えて、より高速化できると望ましいため、引き続き改善を検討していきます。

Jenkins ビルド時間の可視化

ここまで改善策とその結果について紹介しました。改善の前提として重要なのは、どのくらいビルド時間がかかっているのかを可視化しないと分かりづらいということです。

Jenkinsでは、2016年8月のJenkins勉強会でも紹介した、Jenkins 2でデフォルトサポートされているPipeline Stage View Pluginが使えるでしょう。このプラグインでは、下図のようにそれぞれのビルド時間を表示できます。全体の中でもTestとDeployがボトルネックだということが分かり、それが今回の改善策につながりました。

改善結果の確認も、感覚的に早くなったで終わらせるのではなく、早くなっていることをしっかりと数値化して可視化できていると、より一層効果的です。下図は改善策1の改善結果を表していますが、可視化をしたことで、高速化されていることを自信を持ってお伝えできます。

もちろん、可視化の手段はJenkins以外で構いません。ちょうど先日ブログが公開されていたはてなブログのデプロイ高速化では、同社が提供している監視ツールのMackerelを利用しているようです。


本エントリでは、Jenkinsのビルド時間の高速化手法について、紹介しました。

  • (設定にちょっと手間がかかるけれど)pull型デプロイは push型よりボトルネックとなっている負荷を分散させやすい
  • テストの並列化を進める際は、Parallel Test Executor Pluginのような、自動調整する仕組みがあった方がいい
  • ビルド時間の確認は、Pipeline Stage View Pluginなどを使って可視化したほうが分かりやすい

ヌーラボでは、自分もシャアを超えてみたいというエンジニアを募集しています。実際の応募の前に「ちょっと話を聞いてみたい」という要望も受け付けていますので、みなさんの気軽な応募をお待ちしています。

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる