mike-neckのブログ

Java or Groovy or Swift or Golang

Gradleからdockerを起動して分散テストさせてみた #gradle #docker

昨日のエントリーの続きです。

mike-neck.hatenadiary.com

Gradle万能派の僕には、納得がいかなかったので、最後までやってみることにしました。


切り捨てたこと

まず、分散テストは何が目的なのか考えると、テスト結果をマージすることが目的なので、以下のことは切り捨てました。

  • dockerの外でコンパイルしたクラスファイルの共有(昨日のドハマリその2により、共有してもコンパイルしてしまうので諦めた)
  • srcディレクトリーの共有(Javaファイルを生成するタスクがある場合はsrcディレクトリーの共有をすると、各コンテナがJavaファイル生成を行ってしまうので、諦めた)

【2015/10/20 12:19追記】 つまり、コンパイル結果を共有しても再コンパイルされるので、buildディレクトリーの共有は諦めました

この結果、妥協するのは次の点です。

  • 各コンテナでソースコード生成タスクを実行すること
  • 各コンテナでメインのクラスファイルのコンパイルを行うこと
  • 各コンテナでテストのクラスファイルのコンパイルを行うこと

共有するファイル

以上から、共有するファイルは以下に絞りました。

  • caches/modules-2/files-2.1 - dependencyファイルを共有して、無駄にjarのダウンロードを避けるため
  • caches/modules-2/metadata-2.15 - 上記と同じ
  • build/test-resultディレクトリー - 分散してテスト実行した結果を書き込むため

さらに、諦めたこと

昨日はdocker-composeからdockerコンテナを起動していましたが、docker-composeから起動すると、何故かbuild/dependency-cacheディレクトリーの生成に失敗してビルドが成功できないという事象がありました。

  • なお、docker runからコンテナを起動した場合は、上記のディレクトリー生成に失敗することはありません
  • docker runからコンテナを複数起動した場合も同様に、上記のディレクトリー生成に失敗することはありません
  • docker-composeからコンテナを一つ起動した場合でも、上記のディレクトリー生成に失敗する

これらの現象を加味した結果、docker-composeでのコンテナ起動は諦めて、docker runによって複数スレッドで並行してコンテナを起動する方法を採択しました。


成果

以上をまとめて、複数のdockerコンテナでテストを並列実行するようなサンプルプロジェクトが完成しました。

github.com

このプロジェクトのdistTestを実行すると、dockerコンテナが複数起動してテスト環境を隔離したままテストを並列実行できます。

ログはこんな感じ

$ gradle distTest
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:copyProjectFiles
:createTestResultsDir UP-TO-DATE
:writeDockerfile UP-TO-DATE
:dockerBuild
Sending build context to Docker daemon 77.82 kB
Step 0 : FROM mikeneck/gradle
 ---> 1720aa922a94
Step 1 : USER root
 ---> Using cache
 ---> 01a3316dc9d8
Step 2 : RUN mkdir -p /home/gradle/.gradle/caches/modules-2/files-2.1 &&   mkdir -p /home/gradle/.gradle/caches/modules-2/metadata-2.15 &&   mkdir -p /home/gradle/project/build/dist-test
 ---> Using cache
 ---> 1b8cee94ad1e
Step 3 : VOLUME /home/gradle/.gradle/caches/modules-2/files-2.1
 ---> Using cache
 ---> 736cda47bacb
Step 4 : VOLUME /home/gradle/.gradle/caches/modules-2/metadata-2.15
 ---> Using cache
 ---> 533e5c464749
Step 5 : VOLUME /home/gradle/project/build/dist-test
 ---> Using cache
 ---> f7befe5611a8
Step 6 : WORKDIR /home/gradle/project
 ---> Using cache
 ---> b20935239c3b
Step 7 : ADD src/ /home/gradle/project/src
 ---> Using cache
 ---> 1d936eab6fdc
Step 8 : ADD buildSrc/ /home/gradle/project/buildSrc
 ---> dc816d72e386
Removing intermediate container 5c19b832caa7
Step 9 : ADD build.gradle /home/gradle/project/build.gradle
 ---> 3498c2975adc
Removing intermediate container cc9eca27ba49
Step 10 : ADD settings.gradle /home/gradle/project/settings.gradle
 ---> 4cd61cc8b129
Removing intermediate container 4351a94629ec
Step 11 : ADD gradle.properties /home/gradle/project/gradle.properties
 ---> 5b752a803e30
Removing intermediate container 81cfa4d1944e
Step 12 : RUN mkdir -p /home/gradle/project/buildSrc/.gradle &&   chown -R gradle -R /home/gradle/.gradle &&   chown -R gradle /home/gradle/project &&   chown -R gradle /home/gradle/project/build &&   chown -R gradle /home/gradle/project/build/dist-test
 ---> Running in 1f6fd2e5a6ad
 ---> bb126c3e38b8
Removing intermediate container 1f6fd2e5a6ad
Step 13 : USER gradle
 ---> Running in d5f708078091
 ---> 83a26282ebe7
Removing intermediate container d5f708078091
Successfully built 83a26282ebe7
:prepareDocker
:runTestOnDocker
:removeContainer
:reportDistributedTest
:distTest

BUILD SUCCESSFUL

Total time: 3 mins 35.146 secs

なお、このテストはjavajo-gradleでくっそ遅いテストとしてテスト並列実行の題材にしたもので、maxParallelForksを4に設定すると35秒くらいでおわるものです。したがって、上のような妥協した部分がもろに影響して、ビルドが遅くなっているという印象は否めません。

元ネタのツイートから考慮する限り、速度より環境の隔離に重点が置かれているようなので、まあ、ビルドが遅いのは諦めてください。

あと、このプロジェクトはbuildSrcによってカスタムプラグインを作成しないと、dockerで分散テスト実行できないので、プロジェクトの作りにどうしても依存してしまいます。したがって、汎用的に分散実行するような方法はないと思われます。

また、Gradle plugin portalで調べましたが、docker関連のプラグインはdockerコンテナのマネージ/イメージのDocker Hubへのプッシュなどのものしかないので、やはり分散テスト実行するためには、プロジェクトべったりにプラグイン(buildSrcプロジェクト)を作りこむ必要があるでしょう。


もし、Gradleに関して、何かご相談があれば、伺いますので、気軽にご相談ください(もち、有償)。

以上