自作 Docker コンテナのインテグレーションテストを CI 環境に乗せるべく Wercker と CircleCI を使ってみた

# はじめに

Docker コンテナを作成している中、エンバグしても気が付かなかったことがあったため、
docker-compose を使って Docker コンテナの build と実行を行うインテグレーションテスト(e2e.sh)を作成しました。
(インテグレーションテストのソースコードは[こちら](https://github.com/weseek/mongodb-awesome-backup/blob/master/test/e2e.sh)にあります)

今回、その作成したインテグレーションテストを CI 環境に乗せるべく Wercker と CircleCI を使ってみることにしました。

結果的に Wercker は internal/docker-build により起動したコンテナから外部ネットワークの名前解決が出来ないため構築することはできず、CircleCI では構築することが出来そうだと分かりました。

Wercker で試してダメだった点も記載しますので、これからインテグレーションテストを作成しようとしている/した方への何か参考になれば幸いです。

# Wercker とは

Wercker とは CI を実現するための SaaS です。

Wercker アカウントを作成した後、Web 画面の指示に従って設定を行えば簡単に CI 環境が構築できます。

詳細な設定を行いたい場合は、基本的には[wercker の公式ドキュメント](http://devcenter.wercker.com/docs/home)を参考にすることになります。

# Wercker クイックスタート

wercker を使い始める概要は次のとおり。

1. Wercker アカウントを登録する (GitHub OAuth あり)
1. Wercker に対象アプリケーション設定を登録する
    - GitHub のアプリケーション設定に WebHook の設定が追加される
    - そのため、リポジトリに対して WebHook 設定を追加できる権限が必要
1. リポジトリルートに wercker.yml を用意して pipeline の `build` を定義する
1. リポジトリに push する

すると wercker.yml に記載された内容でビルド処理が行われる。

- wercker.yml は 1 から作成しても Wercker の Web ウィザードに従って作成したものをコピペしてもどちらでもよい
- 独自の pipeline を定義して実行したい場合は wercker.yml に定義を記載し、Wercker の Workflows > Editor から設定する
- Wercker の internal/docker-build は EC2 container 内部は外部の名前解決が出来ない

# Wercker のビルド制限について

'18/06/28 現在未確認。(ビルド回数等の制限はない？)

# ビルドステップで Docker container を使う

Wercker のビルドステップ自体も Docker container で行われます。
メインとなるステップで使うコンテナイメージは wercker.yml の box オプションで指定されます。(個人リポジトリ指定も可能)
※ wercker-labs/docker イメージは '18/06/28 現在、Docker Hub に存在しません。詳細は[こちら](#wercker-labs/docker%20イメージは%20'18/06/28%20現在、Docker%20Hub%20に存在しない)。

ここでは、メインステップの中でコンテナを使う方法について簡単に説明します。

ステップの中で常に動作させたいのか、単発で実行させたいのかによって、次の2種類の方法があります。

どちらの方法で起動したコンテナも、メインステップのコンテナ及び internal/docker-run で実行したコンテナにアクセスできる。(未確認だがそのような記載がある)

- service
    - ビルドステップが開始されるタイミングでコンテナを起動させ、常に起動したままにしてビルドステップが終了するタイミングで終了させたい場合に使う
    - 用途例としてはアプリケーションが DB を使う場合の DB を起動する場合
- internal/docker-run
    - ビルドステップ内で、一度実行したい場合に使う
    - 用途例としては Docker container を開発していて起動テストをする場合
- internal/docker-build
    - ビルドステップ内で、イメージをビルドする場合に使う
    - 用途例としては Docker container を開発していてビルドを行う場合
    - <font color="red">'18/07/01 現在、container 内から外部ネットワークの名前解決が出来ない</font>(詳細は本記事内の[こちら](wercker-labs/docker%2Eイメージは%2E'18/06/28%2E現在、Docker%2EHub%2Eに存在しない)を参照)。そのため、wercker を使って Docker compose を使ったテストを使うことは諦めた。

# Wercker にて docker-compose を使ったインテグレーションテスト e2e.sh を実行する設定(動作せず)

<font color="red">'18/07/01 現在、container 内から外部ネットワークの名前解決が出来ない</font>ため、結果的に動作しない設定です。internal/docker-build, internal/docker-run を使った設定ファイルの内容をぼんやりイメージするために利用してください。

```yaml:Werckerにてdocker-composeを使ったインテグレーションテストe2e.shを実行する設定(wercker.yml)
box: debian:jessie

# build mongodb-awesome-backup
build: &build_pipeline
  steps:
    - internal/docker-build:
        dockerfile: Dockerfile
        image-name: mongodb-awesome-backup

# test mongodb-awesome-backup
test: &test_pipeline
  steps:
    - internal/docker-build:
        dockerfile: test/Dockerfile.e2e
        image-name: mongodb-awesome-backup-e2e-test
  
    - internal/docker-run:
        image: mongodb-awesome-backup-e2e-test
        name: mongodb-awesome-backup-e2e-test

# --- For wercker.yml developer
# build and test mongodb-awesome-backup by wercker CLI
dev:
  <<: [ *build_pipeline, *test_pipeline ]
```

# CircleCI とは

CircleCI とは CI を実現するための SaaS です。Wercker と同様のサービスです。

CircleCI アカウントを作成した後、Web 画面の指示に従って設定を行えば簡単に CI 環境が構築できます。

詳細な設定を行いたい場合は、基本的には[CircleCI の公式ドキュメント](https://circleci.com/)を参考にすることになります。

結果的に、CircleCI の方ではインテグレーションテストを実行することが出来ました。

# CircleCI のビルド制限

Free プランの場合は次の制約がある。
詳細や有料プランについては https://circleci.com/pricing/ を参照のこと。

- repos数 無制限
- users数 無制限
- 1,500 build minutes per month
- 1 container
- 1 concurrent job
- community support

# CircleCI クイックスタート

基本的には Circle CI のウィザードに従えばよい。

1. CircleCI アカウントを登録する (GitHub OAuth あり)
1. CircleCI に対象アプリケーション設定を登録する
    - GitHub のアプリケーション設定に WebHook の設定が追加される
    - そのため、リポジトリに対して WebHook 設定を追加できる権限が必要
1. リポジトリルートに ./circleci/config.yml を用意して build を設定する
1. リポジトリに push する

# ビルドステップで Docker container を使う

CircleCI のビルドステップ自体も Docker container で行われます。
docker/docker-compose コマンドを使うためには、それぞれインストールを行い、`- setup_remote_docker` step を追加します。

下記、設定の参考です。

```yaml:CircleCIにてdocker-composeを使ったインテグレーションテストe2e.shを実行する設定(./circleci/config.yml)
version: 2
jobs:
  build:
    docker:
      - image: ubuntu:xenial

    environment:
      DOCKER_VER: 17.03.0-ce
      DOCKER_COMPOSE_VER: 1.11.2

    steps:
      - checkout

      # Install some packages
      - run:
          name: Install packages
          command: |
            apt-get update
            apt-get install -y curl

      # Install docker client
      #   ref. https://github.com/CircleCI-Public/circleci-demo-docker/blob/docker-compose/.circleci/config.yml
      - run:
          name: Install Docker client
          command: |
            set -x
            curl -L -o /tmp/docker-${DOCKER_VER}.tgz https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VER}.tgz
            tar -xz -C /tmp -f /tmp/docker-${DOCKER_VER}.tgz
            mv /tmp/docker/* /usr/bin

      # Install docker-compose
      #   ref. https://github.com/CircleCI-Public/circleci-demo-docker/blob/docker-compose/.circleci/config.yml
      - run:
          name: Install Docker Compose
          command: |
            set -x
            curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VER}/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
            chmod +x /usr/local/bin/docker-compose

      # It is needed to activate the remote docker environment
      #   see. https://circleci.com/docs/2.0/docker-compose/
      - setup_remote_docker

      # Build and run integration test
      - run:
          name: Run integration tests
          command: |
            test/e2e.sh
```

# その他
## wercker-labs/docker イメージは '18/06/28 現在、Docker Hub に存在しない

wercker のビルドステップ内で Docker container を起動する方法を探すと、いつかの記事で `wercker-labs/docker` イメージを使った例が紹介がされていますが、'18/06/28 現在 image は リポジトリ内に存在しませんでした。('14 - '15 辺りでは利用できたのでしょう)

```
Running wercker version: 1.0.1271 (Compiled at: 2018-06-25T20:30:14Z, Git commit: c09a42cc4d0da2eab5d045a06c73d20609ca8c98)
Using config:
box: wercker-labs/docker

build:
  steps:
    - script:
      name: docker version
      code: docker version

    - script:
      name: docker ps
      code: docker ps -a

    - script:
      name: uname
      code: uname -a

      
Pulling repository docker.io/wercker-labs/docker
Error: image wercker-labs/docker:latest not found
```

## internal/docker-build で起動した Docker コンテナは外部の名前解決が出来ない

結論として、'18/07/01 現在解決方法は見つかってません。

発覚したきっかけは Alpine で作成していた Dockerfile で `apk add` コマンドが実行できなかったこと。
temporary error と書かれていたため何度か実行してみたが必ず失敗した。

```text:werckerのdocker_build実行時ログ(抜粋)
  : <snip>
RUN apk add --no-cache     coreutils     bash     tzdata     py2-pip     mongodb-tools
 ---> Running in fd671d634a35
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz: temporary error (try again later)
WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz: temporary error (try again later)
  : <snip>
```

そこで、名前と IP アドレス双方に対して ping を打ってみたところ、名前は `bad address 'dl-cdn.alpinelinux.org'` となり、IP アドレスは ping が通じたことから、名前解決が出来ていない様子だった。
resolv.conf の設定を見るに、Amazon EC2 を使っている様子。
EC2 search に書かれていた名前から調べてみると内部のみの名前解決しかできないとの説明文を発見した。
(https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-instance-addressing.html)

```text:weckerのdocker_build実行時ログ(抜粋)
  : <snip>
Step 3 : RUN ping -c 5 dl-cdn.alpinelinux.org || true
 ---> Running in af467fb100d3
ping: bad address 'dl-cdn.alpinelinux.org'
 ---> d8e7416565b0
Removing intermediate container af467fb100d3
Step 4 : RUN ping -c 5 151.101.72.249 || true
 ---> Running in f110121c573f
PING 151.101.72.249 (151.101.72.249): 56 data bytes
64 bytes from 151.101.72.249: seq=0 ttl=37 time=180.290 ms
64 bytes from 151.101.72.249: seq=1 ttl=37 time=179.775 ms
64 bytes from 151.101.72.249: seq=2 ttl=37 time=179.742 ms
64 bytes from 151.101.72.249: seq=3 ttl=37 time=179.736 ms
64 bytes from 151.101.72.249: seq=4 ttl=37 time=179.813 ms

--- 151.101.72.249 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 179.736/179.871/180.290 ms
 ---> 3632b71d66a3
Removing intermediate container f110121c573f
Step 5 : RUN cat /etc/resolv.conf || true
 ---> Running in 9414407c1ae8
# This file is managed by systemd-resolved(8). Do not edit.
#
# This is a dynamic resolv.conf file for connecting local clients directly to
# all known DNS servers.
#
# Third party programs must not access this file directly, but only through the
# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,
# replace this symlink by a static file or a different symlink.
#
# See systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 10.0.0.2
search ec2.internal
 ---> 8459f7337687
Removing intermediate container 9414407c1ae8
Step 6 : RUN cat /etc/hosts || true
 ---> Running in 7344c4a3665e
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
192.168.100.2	efd5d06095fb
 ---> 22037b37d78c
Removing intermediate container 7344c4a3665e
Step 7 : RUN apk add --no-cache     coreutils     bash     tzdata     py2-pip     mongodb-tools
 ---> Running in e52cebc6e900
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz: temporary error (try again later)
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz: temporary error (try again later)
ERROR: unsatisfiable constraints:
  : <snip>
```

internal/docker-build のソースコードを読んでみて、extra_hosts を使って解決が出来そうだが、実際に追加してみても /etc/hosts にホストが追加されなかった。
Docker API を見てみると docker build 時に extrahosts が設定できるようになったのは v1.28 からでだが、Wercker 実行環境の Docker Deamon のバージョンが古いのか？
(ref. https://docs.docker.com/engine/api/v1.28/#operation/ImageBuild)

* internal/docker-build ステップでは NewDockerBuildStep が実行されている
    * https://github.com/wercker/wercker/blob/eb81b6223a387a6d90ddbcfdafff24c33c8dd8ff/docker/step.go#L36
* dockerOption に Docker build 時のオプションを設定している
    * https://github.com/wercker/wercker/blob/65ae606c9b5a9fe95085d580356ceae58e17224e/docker/docker_build.go#L52
* Docker build 時のオプションは次の項目がある
    * `Dockerfile`, `Tags`, `BuildArgs`, `SuppressOutput`, `Remove`, `ForceRemove`, `Labels`, `ExtraHosts`, `Squash`, `PullParent`, `NoCache`
    * https://github.com/wercker/wercker/blob/65ae606c9b5a9fe95085d580356ceae58e17224e/docker/docker_build.go#L228-L240