BITRISEでPrivateなDockerイメージを利用してビルドする(Push時のみ)
自分のDockerイメージを作成したので、CIサービスでも利用して、環境を持ち運べるようにしたいなと思いました。
BITRISEがモバイルアプリのCIサービスとして良いという話を見かけたので、BITRISE上で自分のDockerイメージを利用してビルドするのを試しました。
タイトルにも書いてあるように、一部制約があります。
BITRISE
CIサービスの一つです。Werckerと比較してですが、workflowが扱いやすいです。GUIのエディターがあるのと、やりたいことがstepという単位でコンポーネント化されてるので、追加・削除が容易です。
ローカル環境で試すためのBITRISE CLIというものも用意されていて、非常に便利でした。
今回は共有のためにbitrise.ymlをコミットしてますが、BITRISE上でいじるものなのでコード管理する必要はないです。BITRISE CLIを利用してローカルで試す際はbitrise.ymlは必要です。しかし、BITRISE上だと容易に変更出来すぎてしまうので、コード管理しておいてもいいかなとも思いました。bitrise.ymlを管理していても、BITRISE上では無視されます。
利用しているbitrise.ymlはこちらです
Dockerイメージを取得して任意のコマンドを実行する
scriptで記述します。
- registryにログイン
- イメージをpull
- コマンドを実行
これらを行います。
registryにログイン
docker login
をします。
今回はDockerHubだったので指定はしてないですが、任意のregistryを指定して、ログインします。
$ docker login -u $USERNAME -p $PASSWORD
privateな変数はBITRISEのSecret Environment Variables
で指定します。BITRISE CLIで試すときは、 .bitrise.secrets.yml
を作成して、そちらに定義します。
- .bitrise.secrets.yml
envs: - USERNAME: xxxxxxxx - PASSWORD: xxxxxxxx - DANGER_GITHUB_API_TOKEN: xxxxxxxxxxxxx
イメージをpull
無事ログインできたらpullします。
イメージのcacheは、現在対応していないようです(一部イメージのみ対応。後述)。
docker pull $DOCKERHUB_IMG_ID
$DOCKERHUB_IMG_ID
は自分で App Environment Variables
に定義しています。
envs: - DOCKERHUB_IMG_ID: sakebook/docker-android-alpine:25.0.3_3
コマンドを実行
コンテナを残す必要はないのでrmオプションを付けてrunします。
BITRISE上でDockerを使うと、 /root
はマウントできません。マウント可能なのは /bitrise
以下だけです。
なので、Gradle本体や依存関係をcacheさせるには、Gradleを実行する位置を調整する必要があります。
マウント位置を、Dockerのユーザホームに指定します。
そして、ワーキングディレクトリは指定がなければ /
になってしまうので、マウントした位置に移動してソースを見つけられるようにします。
rm -rf local.properties
は、ローカルで実行したときにDocker上のANDROID_HOMEが上書きされてしまうのを防ぐために削除します。Android Studioを立ち上げ直すと再び作成されるので心配ありません。
もろもろを合わせると、実行部分は次のようになります。
- script@1.1.4: title: run test use docker inputs: - content: |- rm -rf local.properties docker run --rm \ -v $BITRISE_SOURCE_DIR:$DOCKER_HOME \ -w $DOCKER_HOME \ -e BITRISE_IO=true \ -e GIT_REPOSITORY_URL=$GIT_REPOSITORY_URL \ -e BITRISE_PULL_REQUEST=$BITRISE_PULL_REQUEST \ -e DANGER_GITHUB_API_TOKEN=$DANGER_GITHUB_API_TOKEN \ $DOCKERHUB_IMG_ID bash -c \ " bundle install bundle exec danger ./gradlew :multilinedivider:testDebug -PdisablePreDex "
$DOCKER_HOMEは利用するDockerイメージに応じて変えてください。自分のDockerイメージではユーザを作成していないので/root
にしています。
envs: - GRADLEW_PATH: "./gradlew" - GRADLE_BUILD_FILE_PATH: build.gradle - DOCKER_HOME: "/root"
注意点として、Docker上でビルドすると、環境変数を引き継いであげる必要があります。
CIだと、Pull Requrst(PR)なのかPUSHなのかまたはどのブランチからなのか、などトリガーに応じて処理を分けることがあります。
自前のDocker上で実行してしまうと、それらの環境変数が適応されないので、eオプションで適宜必要なものを渡してあげます。
先程の例だと、BITRISE上で、PRをトリガーに実行されたということを渡しています。
Dangerを使わなかったり、PRと認識させたくない場合、一行しか実行しなくて良い場合などはもっとシンプルになります。
制約
BITRISEだと、PRがトリガーのビルドの場合 Secret Environment Variables
が利用できません。
なので、PrivateなDockerイメージを利用しようとした場合、USERNAMEとPASSWORDをApp Environment Variables
に定義する必要があります。
これについては議論があるようです。自分は見当違いな質問を投げたときに教えてもらいました。
PRで利用できてしまうと、悪意のあるユーザにSecret Environment Variables
が漏洩するからです。
Gradle本体と依存関係のcache
cacheするにはBITRISEのCache:Push
stepを使用します。
docker run実行時に変更したpathを指定することでcacheすることができます。
$HOME
も追加しているのは、dockerを利用しない場合のためです。
合わせて不要なものも除外しておきます。
- cache-push@1.1.3: inputs: - cache_paths: |- $BITRISE_SOURCE_DIR/.gradle $BITRISE_SOURCE_DIR/.m2 $HOME/.gradle $HOME/.m2 - ignore_check_on_paths: |- $HOME/.gradle/caches/*.lock $HOME/.gradle/*.bin $BITRISE_SOURCE_DIR/.gradle/*.lock $BITRISE_SOURCE_DIR./.gradle/*.bin
Dockerイメージのcache
Dockerイメージを取得する部分を速くできないか検証しました。
docker save
でpullしたDockerイメージを保持し、次回以降はdocker load
で前回利用したDockerイメージを再利用というのを試しました。
どちらもGradle周りはcache出来ている状態です。
docker load
を使うと、一応、USERNAMEとPASSWORDなしでPrivateなDockerイメージをPRで利用可能にすることもできます。
- Dockerイメージのcacheなし
- Dockerイメージのcacheあり
思ったような結果にはなりませんでした。 Dockerイメージを取得する部分も大差なく、むしろDockerイメージをcacheする部分で時間がかかってしまい、結果的に遅くなってしましました。
これはBITRISEのcacheの仕組みが、特定のディレクトリを使いまわすような仕組みではなく、ファイルをアップロード & ダウンロードする仕組みになっているからです。
また、Pull Requestのときはcacheをアップロードしないです。cacheのダウンロードは行われます。
一部イメージだと可能
BITRISEが提供しているDockerイメージだと、CIの実行環境に既にcacheされているので高速化できます。
そして自前のDockerイメージを利用するときは、BaseのDockerイメージにBITRISEが提供しているDockerイメージを使うことが推奨されています。
- Android関連のツールが既にインストールされている
- ディレクトリ構成がBITRISEで利用するように出来上がっている
- 必要な環境変数がで定義されている
- BaseにするDockerイメージがcacheされている
という利点があるためです。
まとめ
今回自前のDockerイメージを利用しましたが、Ubuntuのイメージが用意されていてビルドもできるので、BITRISEで自前のイメージを使う旨味はないと思いました。どうしてもカスタマイズしないとできないことがある場合に使うほうが良いです。
あと、Secret Environment Variables
が利用できないのが辛いです。Dangerも利用しようとしてたのですが、この制限から一旦利用をやめました。
と言いつつ、自分はイメージを育てていきたいので、Pushのときは利用して、PRのときは利用しない(できない)ようにします。
参考
Bitrise - Mobile Continuous Integration and Delivery - iOS & Android Build Automation / Bitrise
Docker support on bitrise.io / Bitrise DevCenter
How to use your own Docker image for your builds / Bitrise Discussions
Docker login failed, only PR trigger / Bitrise Discussions
How to cache Gradle dependencies / Bitrise Discussions
About caching / Bitrise DevCenter
Using bitrise.io custom docker image option / Bitrise DevCenter
AlpineベースのAndroidビルド環境のDockerイメージを作成した
なるべく小さく、sdkmanagerにも対応させた、CI用のDockerイメージを作成しました。 Emulatorなどを利用したAndroid Testにはまだ未対応ですが、Unit Testは実行できます。
PublicなDockerHubに上げたかったのですが、ライセンス違反という話があるのでPrivateにしてあります。
↑なぜか画像がアイコンになってくれない。。
詰まったところ
sdkmanagerの実行で失敗
bashも入ってないため、sdkmanagerの実行で失敗します。
sdkmanager: line 159: syntax error: unexpected "(" (expecting "}")
apk add bash
が必要でした。
glibcがない
純粋なAlpineのイメージだと、glibcが入ってないためapptでコケました。
Wikiにインストール方法が載っていたのですが、うまく入れられませんでした。
調べたら、こちら が既にAndroidを動かせる状態であったのですが、glibcだけを入れたイメージがあった ので、そちらをベースに作成することにしました。
内部的にはどちらもこのリポジトリに依存してるみたいです。
sdkmanagerを利用すると怒られる
sdkmanagerを利用するとWarningが表示されました。
Warning: File ~/.android/repositories.cfg could not be loaded.
ファイルを作成することでWarningが消えました。
改善したいところ
SDKも含めると結局大きい
Dockerイメージ自体を置いておけたり、キャッシュを効かせられないと、時間がかかります。 しかし、今まで利用してたイメージのサイズが2GB程度だったのに比べて、今回作成したイメージは1GBを切った(DockerHubのtagの数字)ので、そういう意味では小さくできました。
ライセンス周りの承認をいい感じにしたい
現状は承認ファイルのようなものを固定で入れてます。
しかし、 SDK Toolsの25.3.0から sdkmanager
で承認ファイルを管理できます。
$ sdkmanager --licenses 5 of 5 SDK package licenses not accepted. Review licenses that have not been accepted (y/N)?
あとは y
を数回押すだけで、 $ANDROID_HOME/licenses/
に、承認ファイルが作成されます。
次のようにすれば自動で全部Acceptできるはずです。
$ (while sleep 1; do echo "y"; done) | sdkmanager --licenses
DLできるSDK Toolsが更新されたときに合わせてDockerfileも更新しようと思っています。
作成してみて
なんとなく触ってたDockerも、なるべくレイヤーを少なくすることだったり、registryにpushすることなどができて理解が深まりました。
ただ、ベストプラクティスのようなものがわかっていないので今後利用していきながら修正してければなと思います。
参考
Alpine Linux入門 -内部構造とapkでパッケージインストール編- / tehepero note(・ω<)
お前のDockerイメージはまだ重い💢💢💢 / Speaker Deck
公開用DockerイメージにAndroid SDKを含めるのはライセンス違反という話 / Islands in the byte stream
Running glibc programs / Alpine Linux Wiki
frol/docker-alpine-glibc / GitHub
sgerrand/alpine-pkg-glibc / GitHub
SDK Tools Release Notes / Android Studio
android - Automatically accept all SDK licences / Stack Overflow
RecyclerViewで複数のDividerを実装するライブラリ「MultiLineDivider」を公開しました
以前の記事で、RecyclerViewで複数のDivider実装する方法をまとめたのですが、これを使うには ItemDecoration
を実装したクラスを作る必要がありました。
そのあたりをよしなにして導入できるライブラリを公開しました。
使い方
DividerItemDecoration
と同じように RecyclerView
に追加します。
あとは ViewHolder
に VerticalDivider
, HorizontalDivider
, NoDivider
を任意で実装するだけです。
使用上の注意
Kotlinで書いたので、Javaのみのプロジェクトだと dependencies
に追記が必要です。
compile "org.jetbrains.kotlin:kotlin-stdlib:1.0.0"
作成上の小話
過去に作成したライブラリは、SonatypeからMaven Centralに登録してたのですが、Android StudioのリポジトリのデフォルトがJCenterに変わっていたので、JFrog Bintry経由で登録しました。
参考
RecyclerViewで複数のDividerを実装する / メモ2ブログ
Building your own Android library::codepath/android_guides Wiki / GitHub
Bintray – Your Universal Distribution Platform / JFrog Bintry
How to use Maven Local repository for gradle build / Gradle Forums
Kotlin android library mavenAndroidJavadocs task fail::novoda/bintray-release / GitHub