LIFULL Advent Calendar 2018 2日目
GCPでのCI/CD実現
2018年に開催されたCloud Next '18では、様々なGCPの新プロダクトが発表された。その中で盛り上がっていたのはML・AI関連プロダクトと、CI/CDに関するものだった。
WebならCloud BuildでCIを実施、Spinnakerでカナリアリリース含めた自動デプロイがすべてGCP上で実現できる。
それらの中で今回は第一歩として、Cloud Buildについていろいろと触って試してみた。
やること
Cloud Build + Cloud Source Repository + Container Registryを利用し、試しにAndroidのプロジェクトをビルドしてみる。
手順
手順はシンプルで以下の通り。1番の準備が少し厄介だが、そこさえクリアすれば特段ハマることなく出来た。
- Androidビルド用のDockerイメージを作成し、Container Registryにpushする
- 任意のAndroidプロジェクトを作成する(今回はHello Worldレベルの簡単なもので準備)
- Cloud Source Repositoryにリポジトリを作成し、Cloud Buildと連携させる
- ソースコードをpushするとビルドが走るようにする
下準備 Cloud Storageにバケットを作成
いくつかのStorageバケットを利用するため、以下のバケットを任意の名前で作成する(後述)。
- Android SDK License 用バケット
- Build Cache用バケット
- Debug Buildバケット
Source RepositoryやContainer RegistryでもStorageを利用するが、勝手に作成されるため割愛。
順を追って自動ビルドを実現する
1. Androidビルド用のDockerコンテナイメージを作成し、Container Registryにpushする
作成方法はgithubにサンプルがあるため、それを参考にする。
https://github.com/GoogleCloudPlatform/cloud-builders-community/tree/master/android
Android SDKパッケージの記述リストを作る
githubサンプルにあるスクリプトからpackages.txtというファイルを生成する。
今回生成したのは以下の通り。
extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2
extras;google;google_play_services
platforms;android-28
extras;android;m2repository
platform-tools
extras;android;gapid;3
patcher;v4
build-tools;28.0.2
各種Storageバケットの作成
cacheやビルドファイル、ライセンス用の保存領域としてCloud Storageを使用するため、必要なバケットを作成する。今回作成したのは以下の通り。
Bucket name | 用途 |
---|---|
android_build_caches | ビルドキャッシュ用 |
debug_build_etet | デバッグビルド(apk)保存用 |
licenses_bucket | Android SDKライセンスディレクトリ用 |
Android SDKライセンスをGCP Storageに上げる
以下のコマンドで、SDKライセンスをGCP Storageにアップロードする。
$ gsutil rsync -d [ANDROID_SDK_HOME]/licenses/ gs://[_LICENSES_BUCKET]
※ もしCloud Build上でビルドしたときに、「You have not accepted the license agreements of the following SDK components」と出てしまった場合は以下のコマンドでライセンス同意をして再度licensesディレクトリをrsyncさせる
$ [path_to_sdk]/tools/bin/sdkmanager --licenses
コンテナビルドをsubmitする
$ gcloud builds submit --config cloudbuild.yaml . --substitutions=_ANDROID_SDK_LICENSE=$ANDROID_SDK_LICENSE
CloudBuildを見てみると、ビルドプロセスが走っていることがわかる。
buildがパスすると、Container Registryにイメージがpushされていることが確認できる。
"android-builder"という名前でイメージが作成出来ている。
これでSource Repositoryにpushしたコードを作成したDockerイメージでビルドすることができるようになる。
Dockerfileは以下の通り。
FROM openjdk:8-slim
ENV DEBIAN_FRONTEND noninteractive
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8'
ARG ANDROID_SDK_TOOLS_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip"
ARG ANDROID_SDK_LICENSE
ARG ANDROID_BUILD_CACHE
ARG GRADLE_DISTRIBUTION="https://services.gradle.org/distributions/gradle-4.6-bin.zip"
ENV GRADLE_BUILD_CACHE=${ANDROID_BUILD_CACHE}
ENV DOCKER_ANDROID_DISPLAY_NAME mobileci-docker
ENV GRADLE_TARGETS="assembleDebug"
ENV GRADLE_HOME=/usr/local/gradle-4.6
ENV ANDROID_HOME /android-sdk
ENV PATH ${ANDROID_HOME}/tools:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools/bin:/bin:$PATH
#Install underlying development packages and languages
ADD packages.txt .
COPY gradle-build /bin
COPY licenses licenses
# Prepare to install packages
RUN apt-get update && \
apt-get install -y locales && \
sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen && \
locale-gen && \
dpkg-reconfigure locales && \
apt-get install -y apt-utils && \
dpkg --add-architecture i386 && \
# install packages
apt-get install -qq -y \
software-properties-common \
curl \
wget \
build-essential \
zip \
unzip \
--no-install-recommends && \
# Install gradle
wget --quiet $GRADLE_DISTRIBUTION && \
unzip -qq gradle-4.6-bin.zip -d /usr/local/ && \
ln -s /usr/local/gradle-4.6/bin/gradle /usr/local/bin/gradle && \
# Install android sdk
mkdir -p /root/.android && touch /root/.android/repositories.cfg && \
wget --quiet --output-document=android-sdk.zip ${ANDROID_SDK_TOOLS_URL} && \
unzip -qq android-sdk.zip -d $ANDROID_HOME && \
cp -r licenses /$ANDROID_HOME/licenses && \
echo ${ANDROID_SDK_LICENSE} >> $ANDROID_HOME/licenses/android-sdk-license && \
echo "sdk.dir=${ANDROID_HOME}" > android_local.properties && \
sdkmanager --package_file=packages.txt && \
chmod -R +x bin && \
rm $ANDROID_HOME/licenses/* && \
keytool -genkey -v -keystore /root/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" && \
keytool -list -v -keystore /root/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
CMD ["sh", "-c", "build-debug", "${GRADLE_TARGETS}"]
2. 任意のAndroidプロジェクトを作成する
今回は、簡単なHello Worldを表示するだけのプロジェクトを作成するだけ。
3. Cloud Source Repositoryにリポジトリを作成し、Cloud Buildと連携させる
cloudbuild.yamlファイルの作成
Androidプロジェクト直下にcloudbuild.yamlという名前でビルド用のファイルを作成する。
build cacheなどは、前述で作成したStorageバケット名を指定する。
(注) githubのサンプルでは、gs://が指定されていないため、ビルド成功してもapkやキャッシュがStorgaeに保存されなかった。
steps:
#Copy the build cache locally, if it exists.
- name: 'gcr.io/cloud-builders/gsutil'
args: ['rsync', 'gs://${_ANDROID_BUILD_CACHE}/', '/build_cache']
id: copy_build_cache_local
volumes:
- name: 'build_cache'
path: '/build_cache'
#Android build and distribute with gradle
- name: 'gcr.io/$PROJECT_ID/android-builder'
args: ['gradle-build','${_ANDROID_SDK_LICENSE}','assembleDebug']
id: gradle_build
waitFor:
- copy_build_cache_local
volumes:
- name: 'build_cache'
path: '/build_cache'
#Copy debug apk to cloud storage bucket
- name: 'gcr.io/cloud-builders/gsutil'
args: ['-q','cp', 'app/build/outputs/apk/debug/app-debug.apk', 'gs://${_DEBUG_BUILD_BUCKET}/$PROJECT_ID-$BRANCH_NAME-$SHORT_SHA-debug.apk']
waitFor:
- gradle_build
#Repopulate the build-cache
- name: 'gcr.io/cloud-builders/gsutil'
args: ['-q','cp', '/build_cache/dot_gradle.zip', 'gs://${_ANDROID_BUILD_CACHE}']
waitFor:
- gradle_build
volumes:
- name: 'build_cache'
path: '/build_cache'
timeout: 1200s
Cloud BuildでTriggerの設定する
GCPコンソール → トリガー → トリガーを追加 でBuildのトリガーを追加する。
今回のサンプルでは、masterブランチへのpushでBuildされるように設定。
以下が、設定した内容。
4. ソースコードをpush
ソースコードがmasterにpushされると、Cloud BuildのTriggerにより自動的にビルドが実行される。
数分待つと、ビルドが完了しビルドステップが全て成功している状態となっている。
ビルドで何をやってるか確認してみる
cloudbuild.ymlのstepに従ってビルドが実行されているのがわかる。
1.キャッシュ読み込み
Building synchronization state...
Starting synchronization...
Copying gs://android_build_caches/dot_gradle.zip...
[0 files][ 0.0 B/108.2 MiB]
[0 files][ 64.2 MiB/108.2 MiB]
[1 files][108.2 MiB/108.2 MiB]
Operation completed over 1 objects/108.2 MiB.
2.gradleのビルド
~ 略 ~
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
:app:transformDexArchiveWithDexMergerForDebug
:app:mergeDebugJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:checkDebugLibraries
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug
:app:validateSigningDebug
:app:packageDebug
:app:assembleDebug
BUILD SUCCESSFUL in 1m 25s
28 actionable tasks: 28 executed
Exit status is: 0
3.出来上がったapkをStorageに保存
.gradle/ディレクトリがzipファイルとして上がっていることが確認できる。
4.ビルドキャッシュをStorageに保存
gradleキャッシュがない場合とある場合のビルド速度の違い
dot_gradle.zipファイルがある場合は、余計な依存関係のダウンロードが必要なくなるため、ビルド時間が短くなる。
実際に試してみると、1分ほどビルド時間が短くなっているのがわかる。
キャッシュなし | キャッシュあり |
---|---|
次にやること
ビルドしてapkのアップロードまで出来たので、次はテストコードを書いてUT実行 → Fabricベータ公開までできそうなので試してみたい。