Androidのビルド用Dockerイメージの作成

この記事は[AndroidのCI環境をCircleCIからWerckerにした話](https://speakerdeck.com/horie1024/androidfalsecihuan-jing-wocirclecikarawerckernisitahua)に関連した投稿です。

# 概要
Wercker(ワーカー)で使用するDockerイメージの作成。Androidのビルドに必要な環境をDockerfileで記述しイメージを作成する。

# 2017/6/9追記

以下で解説している「**Dockerを利用する準備**」では、docker-toolboxを利用しdocker-machineを起動してDockerを利用していますが、現在ではDocker for Macを利用するのが一番簡単にDockerを利用する環境を構築できます。

[こちら](https://docs.docker.com/docker-for-mac/install/)からインストールするか以下のコマンドでインストールします。

```bash
brew cask install docker 
```

インストールが完了後、docker-machineの設定を行うこと無くDockerを利用可能になります。詳しくは以下のリンクを御覧ください。

- https://docs.docker.com/docker-for-mac/
- https://docs.docker.com/docker-for-mac/docker-toolbox/

# Dockerを利用する準備

Dockerはlibcontainer(バージョン0.9からLXCより移行)を使用してコンテナ型仮想化を行うためLinux上でしか動作しない。Mac上でDockerを動かすためには、Docker Machineを使いDocker Engineがインストールされた仮想環境を用意する必要がある。Docker Machineは`docker-machine`コマンドを使いDocker Engineがインストールされた仮想環境(Dockerホスト)を管理できる。

![machine.png](https://qiita-image-store.s3.amazonaws.com/0/43438/bc622d61-f63c-e97f-30f8-0a11014e173e.png "machine.png")
https://docs.docker.com/machine/overview/

一般的にDockerという場合Docker Engineを指す。DockerコンテナはDocker Engine上で動作し、Docker EngineはDocker daemonからなる。Docker daemonはREST APIのインターフェースを持ち、Docker Client(CLIなど)はREST API wrapperを介してDocker daemonとやりとりをする。

![docker_engine.png](https://qiita-image-store.s3.amazonaws.com/0/43438/24f9b367-9842-c390-111a-9a42d341398a.png "docker_engine.png")
https://www.docker.com/products/docker-engine

Docker EngineはCLIからの`docker`コマンドを受け付け、`docker run <image>`の実行や、`docker ps`でのコンテナリストの表示、`docker images`によるimageリストの表示などを行える。

## Docker Machineのインストール

以下のドキュメントを参考にDocker Toolboxをインストールすると必要なものが全て用意される。

https://docs.docker.com/engine/installation/mac/

### Homebrewを利用する場合

また、Homebrewを利用しているのであれば以下のコマンドでdockertoolboxインストールし、LaunchpadからDocker Quickstart Terminalを実行することでインストールできる。[参考](https://docs.docker.com/docker-for-mac/docker-toolbox/#/the-docker-toolbox-environment)

```bash
$ brew cask install docker-toolbox
```

### 確認

以下のコマンドを叩きバージョンが表示されればOK。

```bash
$ docker-machine version
```

## MacローカルにDockerホストを起動

1. ターミナルを開く
2. `docker-machine ls`コマンドで利用可能なVMを表示、VMが無ければ次のステップで作成する
3. `docker-machine create --driver virtualbox default`を実行  
	*  --driverでvirtualboxを指定し、最後の引数がVMの名前になる
4. `docker-machine ls`でVMが作られたことを確認
5. 作成したVMについて`env`コマンドを実行  
	* 作成したVMとDockerコマンドがやりとりするために必要な情報が表示されるので、exportを手動で実行するか`eval "$(docker-machine env default)"`を実行する

	```bash
	$ docker-machine env default
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://172.16.62.130:2376"
export DOCKER_CERT_PATH="/Users/<yourusername>/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval "$(docker-machine env default)"
	```

VMが起動していない場合は、`docker-machine start default`で起動しておく。

## Dockerfileの作成
Dockerfileを作成していくが、Dockerfileを実行しながら書いていくのは大変なので一旦コンテナを立ち上げ手動で環境を構築する。この際使用したコマンドはメモしておく。

### コンテナの起動

```
$ docker run -it ubuntu:16.04 sh
```

### Dockerfileへコマンドをまとめる

環境を構築できたらメモしておいたコマンドをDockerfileにまとめる。

```bash:Dockerfile
FROM ubuntu:16.04

MAINTAINER Ryosuke Horie

# Install sudo
RUN apt-get update \
  && apt-get -y install sudo \
  && useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo

# Install 32bit lib
RUN sudo apt-get -y install lib32stdc++6 lib32z1

# Install Java8
RUN apt-get install -y software-properties-common curl \
    && add-apt-repository -y ppa:openjdk-r/ppa \
    && apt-get update \
    && apt-get install -y openjdk-8-jdk

# Download Android SDK
RUN sudo apt-get -y install wget \
  && cd /usr/local \
  && wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz \
  && tar zxvf android-sdk_r24.4.1-linux.tgz \
  && rm -rf /usr/local/android-sdk_r24.4.1-linux.tgz

# Environment variables
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64
ENV ANDROID_HOME /usr/local/android-sdk-linux
ENV PATH $ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH

# Update of Android SDK
RUN echo y | android update sdk --no-ui --all --filter "android-23,build-tools-23.0.3" \
  && echo y | android update sdk --no-ui --all --filter "extra-google-m2repository,extra-android-m2repository"
```

https://github.com/horie1024/WerckerAndroidBuild/blob/master/Dockerfile

## Dockerfileからイメージを作成
Dockerfileを作成したらbuildコマンドでイメージを作成する。

```bash
$ docker build -t horie1024/wercker-android-build:0.1 .
```

`docker images`コマンドでimageが表示されればOK

```bash
$ docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
horie1024/wercker-android-build   0.1                 12e03f69297e        About an hour ago   2.023 GB
```

## imageからコンテナを作成する

`docker run`コマンドを実行しコンテナを作成する。

```bash
$ docker run -it horie1024/wercker-android-build:0.1 /bin/bash
```

作成したコンテナで名前解決ができない場合`--dns`オプションをつける。

```bash
$ docker run -it --dns=8.8.8.8 horie1024/wercker-android-build:0.1 /bin/bash
```

コンテナが使用するメモリを指定する場合は、`-m`オプションを使用する。

```bash
$ docker run -it -m 4G --dns=8.8.8.8 horie1024/wercker-android-build:0.1 /bin/bash
```

`-m`オプション、`--oom-kill-disable`オプションを使用するとOOMが発生しなくなる。

参考:[Docker run リファレンス#ユーザ・メモリの制限](http://docs.docker.jp/engine/reference/run.html#user-memory-constraints)

## Androidプロジェクトがビルドできるか試す

用意した[サンプルプロジェクト](https://github.com/horie1024/WerckerAndroidSample)をcloneしビルドしてみる。

```bash
$ git clone https://github.com/horie1024/WerckerAndroidSample.git && cd WerckerAndroidSample
$ ./gradlew assembleDebug
Incremental java compilation is an incubating feature.
:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:checkDebugManifest
:app:preReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAnimatedVectorDrawable2330Library UP-TO-DATE
:app:prepareComAndroidSupportAppcompatV72330Library UP-TO-DATE
:app:prepareComAndroidSupportSupportV42330Library UP-TO-DATE
:app:prepareComAndroidSupportSupportVectorDrawable2330Library UP-TO-DATE
:app:prepareDebugDependencies
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:compileDebugJavaWithJavac UP-TO-DATE
:app:compileDebugNdk UP-TO-DATE
:app:compileDebugSources UP-TO-DATE
:app:prePackageMarkerForDebug
:app:transformClassesWithDexForDebug UP-TO-DATE
:app:mergeDebugJniLibFolders UP-TO-DATE
:app:transformNative_libsWithMergeJniLibsForDebug UP-TO-DATE
:app:processDebugJavaRes UP-TO-DATE
:app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE
:app:validateDebugSigning
:app:packageDebug UP-TO-DATE
:app:zipalignDebug UP-TO-DATE
:app:assembleDebug UP-TO-DATE

BUILD SUCCESSFUL

```

問題無くビルドができた。

## イメージの公開についての注意

作成したイメージをDockerHubなどに登録すると`docker pull`することで様々な環境でイメージを利用できるようになりますが、Android SDKを含むDockerイメージをpublicで公開すると**再配布**としてAndroid SDKのライセンス違反となります。DockerHubのprivate repository、もしくはQuay.ioなどのDocker registryサービスのprivate repositoryの利用を検討してください。

Werckerからprivate repositoryを使用する方法は以下にまとめました。

* [WerckerでQuay.ioのPrivate Repositoryを使用する](http://qiita.com/Horie1024/items/f554f4eb7f2324b8fa06)

# 参考資料
* [公開用DockerイメージにAndroid SDKを含めるのはライセンス違反という話](http://gfx.hatenablog.com/entry/2016/06/21/112404)
* https://docs.docker.com/machine/overview/
* https://docs.docker.com/machine/get-started/
* https://www.docker.com/products/docker-engine
* http://www.slideshare.net/yuichi110/docker-introduction-42455180
* http://stackoverflow.com/questions/22701405/aapt-ioexception-error-2-no-such-file-or-directory-why-cant-i-build-my-grad