2014/05/21

Android+GradleなCI環境構築

ゴール:
 AWS EC2上にApache + Jenkinsの環境を構築。
 AndroidStudioで作成したプロジェクトをGithubへpush。
 GithubのWebhookでJenkinsにpush通知し、Gradleを使ってビルドさせます。

確認環境:
 クライアント:
  PC Mac OS X 10.9.2
  Gradle ver. 1.12
  Android Studio ver. 0.5.8
  Android Build tools gradle ver. 0.10.1
 サーバ:
  Amazon Linux AMI release 2014.03
  Apache/2.2.27(Unix) built:Apr 25 2014 22:26:04
  Jenkins ver.1.563-1.1
  JDK ver.1.7.0_55
  git ver. 1.8.3.1
  Gradle ver. 1.12
  Android SDK Build tools ver.19.1.0
  Android Build tools gradle ver. 0.10.1

AWS EC2インスタンス準備までは下記のサイトを参考に進めました。

参考:EC2にJenkinsによるCI環境を作成する | Developers.IO
http://dev.classmethod.jp/cloud/aws/jenkins_on_ec2/

EC2インスタンスタイプ:m1.small(試作程度なら micro でもOK)
EBSのボリュームサイズですが、初期の8GiBだとAndroidSDKのフルインストールが
ディスクフルとなって入りきらなかったので80GiBに増量して使用しています。

参考:ボリュームのストレージ領域を拡張する | AWS Documentation.
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ebs-expand-volume.html

EC2インスタンスにユーザ"ec2-user”でSSH接続
$ ssh -i ./jenkins-key.pem ec2-user@54.xxx.xxx.xxx

●前準備

EC2 AmazonLinuxのタイムゾーンは、米国時間が初期設定なので日本時間に設定します。
$ sudo cp -P /usr/share/zoneinfo/Japan /etc/localtime
$ date

●JDKのインストール

ビルド環境構築のため、まずはJDKを導入。
AmazonLinuxは初期状態でJRE1.7.0_55が入っているようです(2014.5.19現在)
$java -version
java version "1.7.0_55"
OpenJDK Runtime Environment (amzn-2.4.7.1.40.amzn1-x86_64 u55-b13)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)

JDKはrpmでインストールします。直接rpmファイルを取得するのが難しいので、
まず、Mac(ローカル)にサーバへインストールしたいJDKの.rpmをダウンロード。

DL先:Java SE Development Kit 7 Downloads | Oracle
http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
 => jdk-7u55-linux-x64.rpm

rpmファイルをscpでサーバに転送(最後のコロンを忘れずに)
$ scp -i ~/Documents/dev/jenkins-key.pem jdk-7u55-linux-x64.rpm ec2-user@54.xxx.xxx.xxx:

サーバのec2-userホームに.rpmファイルが転送されるのでこれをインストール。
$ sudo rpm -ivh jdk-7u55-linux-x64.rpm

インストールが完了したらJDK1.7.0_55に切り替え。
$ sudo alternatives --install /usr/bin/java java /usr/java/jdk1.7.0_55/bin/java 2000

切り替わったことを確認します。
$java -version
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

あとは環境変数を設定して、JDKの準備は完了。
$ export JAVA_HOME=/usr/java/jdk1.7.0_55/

●Apache+Jenkinsをインストール

とりあえずApacheをインストール。
$ sudo yum install httpd

次にJenkinsをインストール。
↓のコマンドを打ちたいところだけれど、これだとエラーがでました。
$ sudo yum install jenkins
> パッケージ jenkins は利用できません。

そのため、yumリポジトリにJenkinsを追加してからインストール。
$ wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
$ rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
$ sudo yum install jenkins

参考:RedHat Linux RPM packages for Jenkins | Jenkins CI
http://pkg.jenkins-ci.org/redhat/

インストールが終わったらJenkinsの設定を確認します。
$ sudo vim /etc/sysconfig/jenkins
Jenkinsのポート番号が8080であることを確認。
JENKINS_PORT=“8080"

念のため、8080ポートが使用済みでないか確認。
8080が未使用であれば問題ないので次へ(使用中なら別のポート番号を割り当ててください)
$ netstat -an | grep LISTEN
$ lsof -i -n -P

この時点で一度httpd, jenkinsサービスを起動して、アクセス確認してみます。
$ sudo service https start
$ sudo service jenkins start

うまくサービス開始できたら下記のURIにアクセス。
(Jenkinsのサービス起動には1分程かかる場合があります)
http://54.xxx.xxx.xxx:8080



今のURIでは不便なので、Jenkinsのドキュメントルートを変更します。
Jenkins設定ファイルのJENKINS_ARGSにドキュメントルートパスを指定します。
$ sudo vim /etc/sysconfig/jenkins
JENKINS_ARGS="--prefix=/jenkins”

これでJenkinsに下記のURIでアクセスできるようになります。
http://hogehoge:8080/jenkins

双方のサービスは自動起動するように設定しておきましょう。
$ sudo chkconfig httpd on
$ sudo chkconfig jenkins on

設定が反映されているか確認します。
$ /sbin/chkconfig --list | grep -e 'jenkins' -e 'httpd'

●Jenkinsユーザの設定

再度下記サイトを参考に、Jenkinsの初期設定(セキュリティ設定)を行います。
参考:EC2にJenkinsによるCI環境を作成する | Developers.IO
http://dev.classmethod.jp/cloud/aws/jenkins_on_ec2/

Jenkinsのホームディレクトリはパッケージ管理ソフトでインストールした場合、
/var/lib/jenkins になります。

●AndroidSDKのダウンロード

AndroidSDKをダウンロードします。
ダウンロードサイト:http://developer.android.com/sdk/index.html

次の手順でダウンロードリンクをコピー(2014.05.19現在ではr22.6.2が最新)
DOWNLOAD FOR OTHER PLATFORMS > SDK Tools Only > Linux32 > 64-bit

/usr/localにAndroidSDKを展開。
$ cd /usr/local/
$ sudo wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz
$ sudo tar -xvzof android-sdk_r22.6.2-linux.tgz

ビルドに必要なツールを落とします。
$ sudo ./android update sdk --no-ui

↑のコマンドだと、エミュレータイメージ等もまとめてダウンロードするのでサイズが大きくなります。
$ sudo android update sdk -t platform -u
などとすれば、ダウンロードしたいモジュールを絞ることができます。
(今回は面倒だったのですべてダウンロードしました)

ダウンロードが完了したら環境変数を設定。
$ export ANDROID_HOME=/usr/local/android-sdk-linux/

JenkinsにもANDROID_HOMEのパスを追加します。
Jenkins > Jenkinsの管理 > システムの管理 > グローバル・プロパティ
 キー:ANDROID_HOME
 値:/usr/local/android-sdk-linux/

念のためAndroidSDKのビルドツールバージョンを確認しておきましょう。
$ ls $ANDROID_HOME/build-tools/

今回は19.1.0を使用しています。
Gradleでビルドする際のBuildToolsバージョンとあわせる必要があるので覚えておきます。

●Github連携(GitHub設定)

Githubへのpushをトリガに、JenkinsのワークスペースへgitリポジトリをCloneします。
GitHubにはpushをトリガに特定URIを叩く機構(Webhook)が用意されています。
Webhookを使って、「GitHubにpushされたらJenkinsでビルド」を実現します。

GitHubとの連携には鍵認証が必要です。
Jenkinsからのアクセスとなるので、Jenkins用のsshキーを作成。
$ cd /var/lib/jenkins
$ sudo mkdir .ssh
$ sudo chown jenkins .ssh
$ sudo -u jenkins -H ssh-keygen -t rsa -C jenkins@54.xxx.xxx.xxx
$ sudo chmod 755 ../.ssh

次に、公開鍵(id_rsa.pub)の内容をGitHubのSSH Keyとして登録。
GitHub > AccountSetting > SSH Keys > Add SSH Key
Titleは適当に。Keyは公開鍵の内容をペースト。

GitHubに鍵の登録ができたら接続テストをします。
$ sudo -u jenkins ssh -T git@github.com

問答の結果、次のメッセージが表示されれば成功。
Hi YukiMatsumura! You've successfully authenticated, but GitHub does not provide shell access.

これでGitHub連携の準備は完了です。

●GitHub連携(Jenkins設定)

JenkinsがWebhookできるように設定します。

まず、AmazonLinuxにgitをインストール。
$ sudo yum install git-core

次はJenkinsの設定。
Jenkins > Jenkinsの管理 > プラグインの管理 > [利用可能]タブ から
"GitHub Plugin”をインストールし、Jenkinsを再起動。

再起動が終わったら、Webhookを受信するJenkinsジョブを準備します。
"新規ジョブ作成”からジョブを作成します。
ジョブの種類は"フリースタイル・プロジェクトのビルド”とし、適当なジョブ名をつけて作成。

ジョブの設定項目は下記を参照。
GitHub project:GitHubリポジトリのURI(ブラウザで確認する際のURIでOK)
ソースコード管理:Gitを選択
 Repository URI:先ほどGitHub projectで入力したURIのhttps:スキームを"git:"に変更。
ビルド・トリガ:"Build when a change is pushed to GitHub"を選択

●GitHub連携(GitHub Webhook設定)

ブラウザからWebhook対象のGitHubリポジトリに移動し、
Settings > Webhooks & Services > Servicesの[Add service] > [Jenkins GitHub plugin]
Jenkins hook urlにはJenkinsサーバのルートに/github-webhook/を追加した文字列を指定します。
http://54.xxx.xxx.xxx:8080/jenkins/github-webhook/

設定後、GitHub側でWebhookURI編集画面にある[Test service]を押下すれば接続テストができます。
対象のJenkinsジョブにある"GitHub Hook Log”にアクセスログが残っていれば疎通確認OKです。

●AndroidStudioのプロジェクトをGitHubにpush

AndroidStudioとGitHubの連携については割愛します。
今回は無用な問題を避けるためにAndroidプロジェクトの
build.gradleに指定する各ビルドツールのバージョンを明示的に指定します。
・プロジェクトルートのbuild.gradle
buildscript {
    dependencies {
        classpath 'com.android.tools.build:gradle:0.10.1'

・モジュール毎のbuild.gradle
android {
    buildToolsVersion "19.1.0"
com.android.tools.build:gradleのバージョンが変わるとビルドが通らないことがよくあります。
buildToolsVersionについてはAmazonLinuxに展開したAndroidSDK側にも同じver.のものが必要です。

プロジェクトをGitHubにPushしたらJenkinsのジョブを確認。
新しいビルドがスケジューリングされているのがわかります。
ワークスペースにはGitHubにあるファイルがCloneされます。

●ビルド

Cloneしたソースをビルドします。
先ほど作成したジョブの”設定”からジョブを再編集します。

Gradle Wrapperを使ってビルドするために下記を設定します。
ビルド > [ビルド手順の追加 > “シェルの実行”
 シェルスクリプト:./gradlew build

これで、GitHubからリポジトリCloneした後にGradleによるビルドが実行されます。
今の状態でビルドしてみましょう。
# GitHubからClone済みであればジョブの"ビルド実行”からビルド開始できます

ビルド成功ならばOK。ビルド失敗ならば原因解析します。

ジョブ画面のビルド履歴からビルド結果の詳細が確認できます。
ビルド失敗した場合はビルド結果画面にある”コンソール出力”で失敗理由を確認します。

・失敗例1
What went wrong:
A problem occurred configuring project ':app'.
> SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.

環境変数”ANDROID_HOME”がjenkinsに設定されていません。
先述のグローバル・プロパティにANDROID_HOMEを設定することで解決します。

・失敗例2
* What went wrong:
Execution failed for task ':app:mergeDebugResources'.
> /var/lib/jenkins/jobs/hogehoge/workspace/build/exploded-aar/com.android.support/appcompat-v7/19.1.0/res/drawable-xxhdpi/abc_ic_voice_search.png: Error: Cannot run program "/usr/local/android-sdk-linux/build-tools/19.1.0/aapt": error=2, No such file or directory

aaptは32bitアプリケーションなので、32bit用ライブラリをインストールする必要があります。
まずは標準Cライブラリ(glibc)をインストール。
$ yum install glibc.i686

次に共有ライブラリの依存関係を調べます。
$ ldd /usr/local/android-sdk-linux/build-tools/19.1.0/aapt

手元の環境では下記の結果が得られたので、"not found”のライブラリをインストールしていきます。
linux-gate.so.1 =>  (0xf772a000)
librt.so.1 => /lib/librt.so.1 (0xf771b000)
libdl.so.2 => /lib/libdl.so.2 (0xf7716000)
libpthread.so.0 => /lib/libpthread.so.0 (0xf76fc000)
libz.so.1 => not found
libstdc++.so.6 => not found
libm.so.6 => /lib/libm.so.6 (0xf75de000)
libgcc_s.so.1 => not found
libc.so.6 => /lib/libc.so.6 (0xf7421000)
/lib/ld-linux.so.2 (0xf772b000)

libz.so.1のパッケージを検索、インストール。
$ yum whatprovies libz.so.1
 zlib-1.2.7-10.17.amzn1.i686 : The compression and decompression library
 リポジトリー        : amzn-main
 一致          :
 Provides    : libz.so.1
$ yum install zlib-1.2.7-10.17.amzn1.i686

libstdc++.soも同じく。
$ yum whatprovides libstdc++.so.6
 libstdc++44-4.4.6-4.81.amzn1.i686 : GNU Standard C++ Library
 リポジトリー        : amzn-main
 一致          :
 Provides    : libstdc++.so.6
$ yum install libstdc++44-4.4.6-4.81.amzn1.i686

libgcc_s.so.1も同じく。
$ yum whatprovides libgcc_s.so.1
 libgcc44-4.4.6-4.81.amzn1.i686 : GCC version 4.4 shared support library
 リポジトリー        : amzn-main
 一致          :
 Provides    : libgcc_s.so.1
$ yum install libgcc44-4.4.6-4.81.amzn1.i686

・その他失敗例
ビルドに失敗する要因は様々ですが、開発環境とJenkinsとで異なるGradleバージョンを使用している
ことが原因である可能性もあります。

・おわりに
今回の内容にはセキュリティの観点が含まれていません。
実運用する際にはAWS, GitHub, Jenkinsのセキュリティ設定にご注意ください。

以上。