Wercker で Java をビルドする

http://www.wercker.com/

wercker は最近人気のクラウド型 CI ツールで、 docker イメージ上で CI を動かすことができるのが直感的で分かりやすいですし、github の private リポジトリでも無料で連携することができるのが個人的にはありがたいです。

個人的にもいくつかの言語で使用していますが、ここでは wercker 上で Java プログラムをビルドする一連の流れを書いていこうと思います。

# ユースケース
ここでは、以下のユースケースにもとづいて、実際の設定例を記述します。

- Java のプロジェクトを Maven で管理し、CI を実施。
  - clean && package
  - sonar:sonar
- CI 済みの環境を docker image に登録
  - 今回は Amazon ECR

# Workflows

今回は以下のようにしています。

1. build
  - wercker 起動時に最初に実行される pipeline。今回は特別な処理をせずに、メッセージの表示などを行っている
2. maven
  - maven に関する様々な処理を実施
  - 成功 / 失敗時に Slack に通知
3. docker (master のみ)
  - docker image の ECR への push を実施

![image.png](https://qiita-image-store.s3.amazonaws.com/0/44364/f6f79f9b-e69d-1103-7294-f92f39c9901c.png)

## Pipeline: build
デフォルトの pipeline なのでそのまま残していて、ちょっとしたメッセージを出しているだけです。実際には後続の処理において必要なファイルの作成やコピーなどがあればこちらで行うことになります。

```yaml:wercker.yml
box: busybox
build:
  steps:
    - script:
        name: echo
        code: |
          echo " ${WERCKER_GIT_REPOSITORY}"
```

## Pipeline: maven
maven を実行します。java と maven コマンドが使えるように box には maven を指定しています。

```yaml:wercker.yml
maven:
  box: maven:3.5.2-jdk-8-alpine
  steps:
    - java/maven:
        goals: clean package
        cache_repo: true
    - java/maven:
        goals: sonar:sonar
        maven_opts: -Dsonar.host.url=${SONAR_URL} -Dsonar.login=${SONAR_LOGIN_KEY}
        cache_repo: true
    - script:
        name: copy to output
        code: |
          cp target/sada4j-*.jar $WERCKER_OUTPUT_DIR/sada4j.jar
  after-steps:
    - slack-notifier:
        url: ${SLACK_WEBHOOK_URL_NOTIFY}
        channel: ${SLACK_ROOM_NOTIFY}
        notify_on: failed
        username: wercker maven notify
```

ここで実行していることについて、要点をピックアップします。


### java/maven step を用いた maven の実行
wercker にはあらかじめ maven 実行用の Step が定義されており、maven 実行時には step に java/maven を指定してあげると、maven の実行環境を自動的に構築してくれます。

指定可能なオプションなど詳細は以下のドキュメントを参照ください。
https://github.com/wercker/step-maven


### 環境変数の使用
worcker では環境変数を使用することができます。
http://devcenter.wercker.com/docs/environment-variables

Environment のレベルは以下の三種が定義できるようになっています。必要に応じて設定のスコープを調整する形になります。

- Organization Level
- Application Level
- Pipeline Level

なお wercker が標準でサポートしている環境変数は以下になります。
http://devcenter.wercker.com/docs/environment-variables/available-env-vars

### $WERCKER_OUTPUT_DIR への実行結果の保存
wercker では CI プロセスが docker container 上で行われることもあって、異なる Pipeline 間では通常はファイルが引き継がれません。

後続の Pipeline でもファイルを使用可能にする場合は、 `${WERCKER_OUTPUT_DIR}` で指定されているディレクトリにファイルを置く必要があります。
上記の例では、後続の docker image 生成処理のために、docker image に含めるファイル群を `${WERCKER_OUTPUT_DIR}` に設置しています。

### after-steps による Slack 通知
after-steps を使用すると Pipeline 中の処理が正しく動いてもそうでなくても必ず実行される Step を定義することが出来ます。
上記例では、 slack-notifier step を使用して Slack の通知を行っています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/44364/c74d0048-4e26-18d5-1087-27d659cf67e0.png)

slack-notifier の場合は `notify_on: failed` と記述しておくと step の処理が fail になった時のみ通知を送ることができます。

## Pipeline: docker
docker image を Amazon ECR に push をします。
Wercker では internal/docker-push という step を用いることで、任意の docker image を docker repository に push することが出来ます。

wercker の[公式ドキュメント](http://devcenter.wercker.com/docs/containers/pushing-containers)では以下の docker repository についての例が記述されていますが

- Docker Hub
- Quary.io
- Google Container Registry


ここでは ECR への push の仕方を例示しています。

```yaml:wercker.yml
docker:
  box: openjdk:8-jdk-alpine
  steps:
    - internal/docker-push:
        aws-access-key: ${AWS_ACCESS_KEY_ID}
        aws-secret-key: ${AWS_SECRET_ACCESS_KEY}
        aws-region: ${AWS_REGION}
        aws-registry-id: ${AWS_REGISTRY_ID}
        aws-strict-auth: false
        repository: sada4j
        tag: latest
        ports: 8080
        working-dir: ${WERCKER_SOURCE_DIR}
        entrypoint: java -jar sada4j.jar
```

### 汎用的な話

wercker による docker push は、Dockerfile を用いた docker build の定義を記述することは出来ません。
以下のようなルールで docker image が作られます。

- box として指定された docker image が base image となる
- box 上に置かれたファイルがまとめて ADD or COPY される 
  - box 上の状態がそのまま docker Image となると思ったほうが良いかも
- 以下のコマンド指定は、wercker.yml の中で明示的に記述する必要がある
  - CMD
  - ENTRYPOINT
  - EXPOSE
  - ENV
  - VOLUME
  - AUTHOR

指定可能なオプションの詳細については以下を参照ください。
http://devcenter.wercker.com/docs/steps/internal-steps#docker-push

上記のような仕様となっているため、運用上は以下のようなことに配慮する必要があります。


- box 上に存在する不要なファイルを取り除く
  - 一番手っ取り早いのは、今回の例のようにパッケージングと docker push を別の Pipeline で定義し、必要最低限のファイルだけ ${WERCKER_OUTPUT_DIR} 経由で渡すようにすることです。
- Dockerfile / docker-compose.yml との住み分け  
  - 「Can I Build Dockerfiles?」 「Currently, no.」 http://devcenter.wercker.com/docs/faq/can-i-build-dockerfiles
  - なので基本的に二重記述になります。うまい解決策はないものか…


### Amazon ECR 固有の話

ECR に push する場合は、以下の値を設定します。

- aws-access-key
- aws-secret-key
- aws-region
- aws-registry-id
  - 数字のアカウント ID です。
- repository (option)
- tag (option)

credential 情報は wercker.yml に直に書くのは好ましくないので、wercker の環境変数に設定することになります。

以上で、ひととおりユースケースどおり Java のビルドから docker repository への push まで行えるようになると思います。

# その他の Tips
## wercker-cli

wercker が他の CI SaaS とくらべて圧倒的に勝っていると感じているのが、local 環境で動作確認を行うために使えるツールの充実です。

wercker には wercker-cli というコマンドラインツールが提供されており、サーバー上での挙動をローカル環境でも再現し実行することが出来ます。

```shell
$ wercker build --pipeline maven
--> No Docker host specified, checking: /var/run/docker.sock
--> Executing pipeline
--> Running step: setup environment
Pulling from library/openjdk: latest
Digest: sha256:9745ed74401b23fb845b4eb7ae07ecb7dc2d40bece6bdb089975a20f76766401
Status: Image is up to date for openjdk:latest
--> Copying source to container
--> Running step: wercker-init
--> Running step: maven
02:19:34:  Hello from the Maven Wercker Step
(snip.)
```

また、Wercker サーバーに対するビルドの指示など、後述の API で行えることと同等のことも実施可能になっています。
詳しくは以下のサイトや、コマンドのヘルプ等を参照ください。
http://www.wercker.com/cli

なお、現在のところ OSX 版と Linux 版のみ提供されています。

## API

以下の API が提供されています。Application の設定や、ビルドの指示などが行なえます。
http://devcenter.wercker.com/docs/api/endpoints

## Wercker Source IP

CI / CD フローの都合上、Wercker と特定の社内ツールを結合して使用しなければいけないケースが発生することがあります。
その際は、 Wercker の Source IP アドレスを指定して特定の IP のみ接続許可をするような事が対応としては可能です。

以下のような形で Wercker の Source IP アドレスが公開されているので、参考までに。
https://s3.amazonaws.com/status.wercker.com/worker_ips/production/public.json

## ECR に登録されている docker image を box で使用する

wercker.yml の box 指定箇所で以下のように書きます。

```
  box:
    id: <docker image name>
    aws-access-key: ${AWS_ACCESS_KEY_ID}
    aws-secret-key: ${AWS_SECRET_ACCESS_KEY}
    aws-region: ${AWS_REGION}
    repository: <docker image name>
    aws-registry-id: ${AWS_REGISTRY_ID}
```