前回のエントリでは CircleCI 上で Docker を使ってビルドする方法については敢えて触れませんでした。
今回は、CircleCI 上で任意のバージョンの JVM を使ってビルドする為に Docker コンテナを使ってみます。
試行錯誤した結果の circle.yml はこちらになります。参考にしたい方はどうぞ。
machine: timezone: Asia/Tokyo environment: GRADLE_OPTS: -Xmx4G -Dorg.gradle.daemon=true -XX:+HeapDumpOnOutOfMemoryError services: - docker post: - sudo service mysql stop - sudo service postgresql stop
dependencies: cache_directories: - ~/docker override: - if [[ -e ~/docker/image.tar ]]; then docker load -i ~/docker/image.tar; else docker pull java:openjdk-8; fi - if [[ ! -e ~/docker/image.tar ]]; then mkdir -p ~/docker; docker save java:openjdk-8 > ~/docker/image.tar; fi - docker run --name gige -v ~/.gradle:/root/.gradle -v `pwd`:/build -dit java:openjdk-8 bash - chmod +x ./gradlew - sudo lxc-attach --keep-env -n "$(docker inspect --format '{{.Id}}' gige)" -- bash -c "cd /build && ./gradlew -v" - sudo lxc-attach --keep-env -n "$(docker inspect --format '{{.Id}}' gige)" -- bash -c "cd /build && ./gradlew testClasses"
test: override: - sudo lxc-attach --keep-env -n "$(docker inspect --format '{{.Id}}' gige)" -- bash -c "cd /build && ./gradlew --full-stacktrace check" post: - docker stop gige
何故 Docker を使うのか。#
CircleCI のマニュアルによると Docker は現在ベータサポート中です。つまり、本格的に利用出来るわけでは無いのです。
CircleCI currently offers beta support for running Docker within build containers.
前回のエントリではapt-get
コマンドを使って毎回 Java8 をインストールしていました。それは、CircleCI が/var/cache/apt
をキャッシュとして保持してくれないからです。
しかし、apt のローカルキャッシュは兎に角サイズがデカい。CircleCI のキャッシュレストア処理はキャッシュが大きければ大きいだけ時間がかかる仕様なので、黙ってても凄く大きいディレクトリをキャッシュするのは望ましくないのです。
ファイルサイズをやり方次第である程度コントロール可能な Docker コンテナのイメージならあるいは現実的なのではないかと考えたわけです。
もう一つ、apt のインストールプロセスが circle.yml の中で一際異彩を放っており、何か大げさな感じがするのが嫌なのです。Docker を使えばもっと簡素な表現になるんじゃないかと思っていた時期が僕にもありました。
CircleCI は Docker コンテナ及びそのイメージをキャッシュしてくれない#
いや、ちゃんとマニュアル読めって話なんですけども。
Docker images aren't cached automatically. At the moment, we have no good method to cache them although we are trying to find a technical solution.
という訳で、circle.yml の中にシェルスクリプトでキャッシュ処理を書くことになります。
dependencies: cache_directories: - ~/docker override: - if [[ -e ~/docker/image.tar ]]; then docker load -i ~/docker/image.tar; else docker pull java:openjdk-8; fi - if [[ ! -e ~/docker/image.tar ]]; then mkdir -p ~/docker; docker save java:openjdk-8 > ~/docker/image.tar; fi
cache_directories
で Docker のコンテナイメージを保存する適当なディレクトリを指定します。
最初の if 文では、イメージが存在している時にはdocker load
でコンテナイメージを読み出しつつ、イメージが存在していない時には、docker pull
でコンテナイメージを DockerHub から取ってきています。
標準でサポートされている Java のコンテナイメージはこれです。
このリポジトリには何か色々な理由で Oracle の Java は入っていません。
二番目の if 文では、イメージが存在していない時だけdocker save
でコンテナイメージを所定のディレクトリ内に格納しています。
依存ライブラリのキャッシュは Mount volume オプションで指定する#
ビルドする際に Gradle がダウンロードしてくるライブラリや Gradle のランタイムは CircleCI が自動的にキャッシュしてくれるディレクトリに配置しましょう。また、GitHub からチェックアウトしてきたソースコードも Docker コンテナ内から見える位置に配置します。
dependencies: override: - docker run --name gige -v ~/.gradle:/root/.gradle -v `pwd`:/build -dit java:openjdk-8 bash
ここでは、 -v ~/.gradle:/root/.gradle
によって、ホスト側の~/.gradle
ディレクトリをコンテナ内の/root/.gradle
にマウントしています。:(コロン)より左側は~(チルダ)を使っても余り問題ないのですが、:(コロン)より右側は絶対パスにしましょう。
次に、-v `pwd`:/build
で、ホスト側のカレントディレクトリ、つまりソースコードリポジトリのルートディレクトリをコンテナ内の/build
にマウントしています。
CircleCI は docker exec をサポートしていない#
これもマニュアル通りですね、はい。なので、回避措置としてlxc-attach
を使います。
dependencies: override: - docker run --name gige -v ~/.gradle:/root/.gradle -v `pwd`:/build -dit java:openjdk-8 bash - chmod +x ./gradlew - sudo lxc-attach --keep-env -n "$(docker inspect --format '{{.Id}}' gige)" -- bash -c "cd /build && ./gradlew -v" - sudo lxc-attach --keep-env -n "$(docker inspect --format '{{.Id}}' gige)" -- bash -c "cd /build && ./gradlew testClasses"
lxc-attach
を使うには、まずコンテナを名前付きで起動する必要があります。ここでは、docker run --name gige
というふうに--name
を使って名前付けしています。
これによって、docker inspect --format '{{.Id}}' gige
というふうにコンテナの名前からコンテナの ID を得られるからです。
--keep-env
オプションは、ホスト側の環境変数をコンテナ内で利用するために指定しています。
少々細かい話ですけども、bash コマンドの-c
オプションより後ろの部分は"(ダブルクォート)でくくらないと期待通りの動作になりません。
Gradle Daemon がなぜか動かない#
環境変数はちゃんと渡されているのですけども、Gradle Daemon が動きません。この辺まできて、凄く辛みがあるのでこれ以上時間をかけて調査をする気が失せてきました。
ええ、もう毎回apt-get
しますよ。うるさいなら、その部分だけシェルスクリプトとして切り出せば良いんだし…うう…