Jenkins による Android アプリの CI 環境を Amazon EC2 を使ってマスター・スレーブ構成で構築する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

jenkins-banner

はじめに

AWS 上に Jenkins による Android アプリの CI 環境を構築する機会がありましたので、記録として残しておきます。

Jenkins 単体であれば下記の記事のままで大体OKなのですが、

Amazon Linux に Android エミュレータを入れるには色々と難があります。そこで、Android アプリのビルドやテストを行うマシンを Ubuntu を使った、マスター・スレーブ構成の CI 環境を構築してみました。

下図のような構成となる CI 環境を構築します。

android-ci-01

マスターマシンの構築

EC2 の起動

次の記事の通り、普通に Amazon Linux のインスタンスを起動します。

キーペアはダウンロードしておきます。EC2 インスタンス起動後、SSH 接続で作業を進めます。

$ ssh -i <keypair name>.pem ec2-user@<ip address>

JDK のインストール

JDK のインストールは scp でやる方法もありますが、インスタンス内で完結させたかったので、下記を参考にリンク貼る方法でインストールします。

$ sudo wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u25-b17/jdk-8u25-linux-x64.rpm"
$ sudo rpm -ivh jdk-8u25-linux-x64.rpm
$ sudo ln -s /usr/java/jdk1.8.0_25/bin/java /usr/bin/java8
$ sudo rm /usr/bin/java
$ sudo ln -s /usr/bin/java8 /usr/bin/java

これで、JDK のバージョンは 1.8 になっているはずです。

$ java -version
java version "1.8.0_25"

Git のインストール

SCM に Git を使うので、インストールしておきます。

$ sudo yum -y install git

Jenkins のインストール

Jenkins は yum リポジトリにインポートしてからインストールします。現時点での最新バージョンは 2.9 です(2016/06/20 現在)。

$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
$ sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
$ sudo yum -y install jenkins

インストールが終わったら、自動起動登録しつつ起動しておきましょう。

$ sudo chkconfig jenkins on
$ sudo service jenkins start
Starting Jenkins                                           [  OK  ]

Jenkins は最近 Version 2 になりました。セットアップ方法がちょっと変わっています。本記事は構築が主の目的なので、セットアップ方法については割愛します。

スレーブマシンの構築

EC2 の起動

スレーブマシンは、Ubuntu サーバーとして構築します。

android-ci-02

詳細な設定については割愛します。概ね次のような設定でインスタンスを起動します。

  • Ubuntu サーバーにする (エミュレータを起動するため)
  • t2.medium くらいにする (エミュレータを実行するのでそこそこ必要なため)
  • EBS は 100 GB とかにする (Android SDK が結構ディスク容量を食うため)

マスターマシンと同様に、キーペアはダウンロードしておきます。EC2 インスタンス起動後、SSH 接続で作業を進めます。Amazon Linux サーバーに SSH 接続する場合とユーザーが異なるので注意してください。Ubuntu サーバーの場合は ubuntu ユーザーで SSH 接続します。

$ ssh -i <keypair name>.pem ubuntu@<ip address>

JDK のインストール

Open JDK をインストールする方法が簡単です。

$ sudo add-apt-repository ppa:openjdk-r/ppa
$ sudo apt-get update
$ sudo apt-get install openjdk-8-jdk

これで、JDK がインストールされているはずです。

$ java -version
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-8u91-b14-0ubuntu4~14.04-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)

Git のインストール

SCM に Git を使うので、インストールしておきます。

$ sudo apt-get install git

Android SDK のインストール

最新の SDK のダウンロード URL は、下記のページで公開されています。

EC2 内で最新の SDK をダウンロードし、展開します。現時点での最新バージョンは 24.4.1 です(2016/06/20 現在)。

$ wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
$ sudo tar -xvzof android-sdk_r24.4.1-linux.tgz
$ sudo mv android-sdk-linux/ /usr/local/

Android SDK のセットアップを行うため、環境変数を設定します。必須ではありませんが、コマンド入力を楽にするために行います。以下を .bash_profile に書いておきましょう。

$ export PATH=$PATH:/usr/local/android-sdk-linux/tools/
$ export ANDROID_HOME=/usr/local/android-sdk-linux/

Android アプリのビルドに必要な Build-tools などを入れていきます。まずはパッケージをリストアップしましょう。

$ android list sdk --all --extended

上記コマンドでダウンロードに失敗する場合は、SSL/TLS 通信が実行できていない可能性があります。下記コマンドを実行してから再度行ってみてください。

$ /var/lib/dpkg/info/ca-certificates-java.postinst configure

以下のパッケージを入れます。

  • Android SDK Build-tools
  • SDK Platform
  • Android Support Repository
  • Android Support Library

これらのパッケージを入れるには update コマンドを使います。主要なオプションは次の通りです。--all を付けないと古い SDK バージョンが対象にならないので、注意してください。

オプション オプション(フル) 意味
-a --all 全てのパッケージを対象にする
-u --no-ui GUI を使用しない
-t --filter Filter してインストールする

例えば、次のようにフィルタリングしてインストールします。

$ android update sdk -a -u -t platform,platform-tools,build-tools-24.0.0,extra-android-support,extra-android-m2repository

以上で、Android アプリのビルド、テストが行える環境が整いました。念のため、適当な Android アプリプロジェクトを clone し、ビルドできるか試しておきましょう。

$ git clone https://github.com/suwa-yuki/AndroidDemoApp.git && AndroidDemoApp
$ ./gradlew build

...省略...

:app:assembleReleaseUnitTest
:app:testReleaseUnitTest
:app:test
:app:check
:app:build

BUILD SUCCESSFUL

Total time: 26.777 secs

This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.10/userguide/gradle_daemon.html

Android エミュレータのセットアップ

Android エミュレータ を起動するためには AVD (Android Virtual Device) を用意する必要があります。

AVD の作成及びエミュレータの起動は「Android Emulator Plugin」を使うことで Jenkins のジョブに集約することができますが、スレーブが生まれ変わったら SDK のダウンロードからやり直し、AVD も作り直しになってしまうのでスピード感が出ません。

そのような場合は、スレーブマシンの AMI を作る前に、AVD の作成までを行っておき、ジョブで即座に起動できるようにします。

次のコマンドは armeabi-v7a のイメージをダウンロードした後に、適当な AVD (testing という名称) を作成しています。

$ android update sdk -u -t sys-img-armeabi-v7a-android-23
$ android create avd -f -a -c 32M -s WVGA800 -n testing -t android-23 --abi armeabi-v7a

Jenkins Slave としての準備

ジョブのワークスペースとなるディレクトリを予め用意しておきます。

$ sudo mkdir /var/lib/jenkins && sudo chmod 755 /var/lib/jenkins

AMI にする

仕上げとして、構築した EC2 インスタンスを AMI にします。

android-ci-03

AMI の ID を控えておきましょう。

マスターマシンとスレーブマシンを連係させる

次に、マスターマシンの設定で、スレーブマシンを管理する設定を行います。今回は「Amazon EC2 Plugin」を使います。

このプラグインを使うと、ジョブにキューが登録されたタイミングでスレーブとなる EC2 インスタンスを起動し、その中でジョブの処理を実行してくれます。並行ジョブ数が多いと、その分インスタンスもスケールアウトしてくれます。反対に、ジョブが無くなったらスレーブを Terminate してくれます。

プラグインをインストールした後、「Jenkins の管理」から「システムの設定」を開き、一番下にある「クラウド」で「Amazon EC2」を追加します。

android-ci-04

設定は次のとおりです。まずは EC2 の設定です。

android-ci-05

①は新しく追加する設定の名前です。ここは EC2 インスタンスに付く名前ではなく、Jenkins 上の呼び名です。

②は EC2 インスタンスに付与する AWS の認証情報を EC2 インスタンスプロフィールから取得する設定です。EC2 の起動及び終了できるポリシーがマスターマシン自体に付与されていればOKです。

③は EC2 インスタンスを起動するリージョンです。テストですので、なるべく安価に運用できるリージョンの方が良いと思います。

④は EC2 インスタンスに使用するキーペアを入力します。

⑤では疎通確認のテストを実施することができます。ここまで入力できたら試しに押してみましょう。

続けて、AMI の設定です。

android-ci-06-2

⑥は AMI 設定の説明文です。こちらは AMI の設定に反映されるものなどではなく、Jenkins 上で識別しやすくするための説明文です。

⑦は AMI の ID です。先ほど作成した Android の環境構築を行った EC2 インスタンスを元にした AMI の ID を設定します。

⑧は⑦で設定した AMI が Jenkins 上で参照可能かどうか確認するためのテストです。一度行っておきましょう。

⑨は EC2 インスタンスのインスタンスタイプです。デフォルトでは t1.micro が設定されているので注意してください *1。ここでは t2.medium としています。Android エミュレータを起動する必要があるため、そこそこ必要です。

⑩は AZ の設定です。ここでは、マスターマシンと同じ AZ に配置しています。

⑪はセキュリティグループの設定です。ここも、マスターマシンと同じセキュリティグループにしていますが、マスターマシンのみ許可する方が適切だと思います。

⑫はスレーブマシン上で Jenkins ジョブの各ファイルを展開するディレクトリの指定です。前述している /var/lib/jenkins を設定しています。

⑬は SSH 接続するユーザの指定です。Ubuntu の場合は ubuntu になります。

⑭は SSH 接続の詳細設定です。ルートコマンド実行のためのプレフィックスは sudo、ポートは 22 です。

⑮はジョブに設定する、ジョブを実行するラベル式の設定です。あとでジョブ設定で使います。

⑯は EC2 インスタンスのスレーブマシンとしての扱いの設定です。ここでは「このマシーンを特定ジョブ専用にする」を設定しています。つまり、ジョブ実行によって起動した EC2 インスタンスは、対象のジョブでしか使われません。別のジョブを実行するときには新しく EC2 インスタンスが起動するようになります。

あとは、高度な設定です。ここで行う設定は少しだけです。

android-ci-07

⑰は VPC のサブネットの設定です。ここではマスターマシンと同じサブネットにしていますが。VPC の中に閉じた EC2 インスタンスにすることもできます。その方がよりセキュアですね。

⑱は EC2 インスタンスに付与する Label の設定です。最低限 Name だけ付けておいた方が良いでしょう。

CI する

環境構築が整ったところで、実際に CI してみましょう。CI を行う対象として、シンプルな Android アプリプロジェクトを作成しました。GitHub で公開しています。

ジョブの設定は、要点だけ解説します。

「実行するノードの制限」は、スレーブマシンを使う時に行う設定です。Jenkins の AMI 設定の Labels (前述の⑮) で設定したラベル式を設定します。

android-ci-08

続いてビルド処理に「シェルの実行」を追加します。

android-ci-09

次のようなコマンドを入力します。

$ export ANDROID_HOME=/usr/local/android-sdk-linux
$ export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools
$ emulator -avd testing -no-snapshot-load -no-snapshot-save -no-window -noaudio &
$ ./wait-for-emulator.sh
$ ./gradlew connectedCheck

なお、Android エミュレータが起動するまでの待ち合わせを行うスクリプトは次のようになっています。

#!/bin/bash

export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"

while true; do
  BOOTUP=$(adb shell getprop init.svc.bootanim | grep -oe '[a-z]\+')
  if [[ "$BOOTUP" = "running" ]]; then
    break
  fi

  echo "Got: '$BOOTUP', waiting for 'running'"
  sleep 5
done

こちらのシェルスクリプトは こちら で公開されている Circle CI のダミープロジェクトの内容を参考にしています *2。複数の Android エミュレータを対象とする場合は こちら を参照してください。

それでは、CI を回してみましょう。スレーブが立ち上がり、ジョブが実行できていることが確認できます。

android-ci-10

まとめ

AWS から CI as a Service 出てくれないかな。。

参考

Jenkins について

Android SDK について

Ubuntu について

脚注

  1. t1.micro は旧世代のインスタンスタイプで、代わりに t2.micro インスタンスを使うことが推奨されています。
  2. Circle CI のダミーでは stopped を待ち合わせていますが、ここでは running を待ち合わせています。