【カスタム Docker イメージ】コンテナー上でRspec UnitTest 



#実施内容

IBM Cloud Continuous DeliveryのPipelineで、RubyのRspec Unit Testを実施します。 テストではPostgreSQLを利用するため、PostgreSQL、Rubyを動かすことができる **カスタム Docker イメージ**を作成し、Pipelineで利用します。
Pipelineは、IBM Cloudのツールチェーンのテンプレートから作成したものを利用します。アプリのソースコードは、Githubから取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205002/f0e2bc22-45cb-95dd-7f99-0fbff2d5a85f.png)

rubyのアプリは、BackendのAPIとして利用し、DBはPostgreSQLに接続しています。
DBの接続先は、環境変数で切り替えます。
Ruby側は、**ローカルでUnitTestが実施できる前提**であり、ここではその設定を記載していません。

#カスタム Docker イメージとは
IBM Cloud Continuous Deliveryのパイプラインで利用される基本イメージでは、利用できるツールが限られています。その際に、カスタムDockerイメージを利用し,必要なツールを利用することができます。
この記事では、カスタムDockerイメージを利用したUnitTestで実施した内容を簡単にですが記載したいと思います。

[IBM Cloud 資料 -カスタム Docker イメージの操作-](https://cloud.ibm.com/docs/ContinuousDelivery?topic=ContinuousDelivery-custom_docker_images#private_image_registry)
>パイプライン基本イメージは、すべてのビルドの要件をサポートしていないことがあります。 例えば、node、Java、またはその他のツールのバージョンを細かく制御する必要がある場合があります。 この問題に対処するには、一連の新規パッケージをインストールして、環境変数 (PATH など) を慎重に構成して環境をセットアップする最初のステップをパイプライン・ジョブに組み込みます。 しかし、さらに優れた方法は、ジョブの基礎として「カスタム Docker イメージ」を実行するためにパイプラインのサポートを使用することです。

## 考慮点

Continuous Delivery の Pipeline でカスタム・イメージを作成、利用するうえでの考慮です。

1. Pipeline用に何かのベースイメージ利用する、というような制約はなく、どんなイメージも利用可能。
1. Pipelineのステージやジョブ自体はパブリック・ワーカー上で動いているが、それはカスタム・イメージの外側で動いている。Gitや別ステージのビルド成果物からのファイルの取得や、出来上がったファイルのビルド成果物としての登録については、その外側のパブリック・ワーカーが実施する。
1. SCM/ビルド成果物からのインプットのファイルは、カスタム・イメージ内に展開されてくる。カスタムイメージ内は、pipelineで使用するスクリプトも展開される。WORKDIRを設定した場合、該当するディレクトリのみがカスタムイメージないのWORKDIRに展開される。処理実行後にも、WORKDIR以下にあるフォルダーをビルド成果物フォルダーとして指定できる。 （外側の処理が実行）![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205002/61acccb0-4cf5-1b9a-1e45-d9a4cb55f835.png)

1. カスタム・イメージには、SCM/ビルド成果物からのインプットのファイルの他に以下のファイルが追加される。  
  - `_customer_script.sh` : Pipeline上のジョブ定義上に書いたスクリプト
  - `_pipeline_script.sh` : （おそらく）パイプラインがJobを実行するためのスクリプト 
  - `_env` : 各環境変数
  - `_toolchain.json` : ツールチェーンの情報など 

1. Pipeline上のジョブ定義上に書いたスクリプトは、ジョブの実行時にファイル（`_customer_script.sh`)として保存されて、カスタム・イメージに引き渡される。 ジョブでは、`_pipeline_script.sh`が`_customer_script.sh`を呼びだす形で実行される。

```sh:_pipeline_script.sh(一部抜粋）
DOCKER_RUN="$DOCKER run --env-file='_env' -v `pwd`:`pwd` -w `pwd` --entrypoint='' '$DOCKER_IMAGE' ./_customer_script.sh"
```

また、コンテナーを利用する場合は、下記に注意します。
>ご使用の Docker イメージの Dockerfile の `ENTRYPOINT` と `CMD` はオーバーライドされ、呼び出されません。 このため、場合によっては、初期化ステップをスクリプトに追加する必要があります。

[IBM Cloud 資料 -カスタム Docker イメージの操作 スクリプトの指定-](https://cloud.ibm.com/docs/ContinuousDelivery?topic=ContinuousDelivery-custom_docker_images#specify_script)

# カスタムDcokerイメージの準備


## Dockerイメージの用意

### イメージの作成

Dockerイメージを作成します。postgresイメージをベースにして、rbenv経由でrubyを導入しました。  

```Dockerfile:Dockerfile
FROM postgres:11.5-alpine

ENV PATH "/root/.rbenv/bin:$PATH"
ENV PATH "/root/.rbenv/versions/2.7.1/bin:$PATH"
ENV PGDATA /usr/local/var/postgres
ENV DATABASE_HOST localhost
ENV DATABASE_PORT 5432
ENV DATABASE_USER postgres
ENV DATABASE_PASSWORD password

RUN echo $PATH && \
    apk update && \
    apk upgrade && \
    apk --upgrade --no-cache add --update --virtual .ruby-builddeps git gcc g++ musl openssl-dev zlib-dev make && \
    git clone https://github.com/rbenv/rbenv.git ~/.rbenv && \
    git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build && \
    rbenv install 2.7.1 && \
    rbenv global 2.7.1 && \
    apk del --purge .ruby-builddeps

COPY initdb.sql /docker-entrypoint-initdb.d/.
RUN crond restart
```

### リポジトリに登録

まず、Dockerイメージを用意し、オンラインのリポジトリに配置する必要があります。例えば、以下の二つ

- Docker hub
- IBM Cloud Contianer Registry

レジストリは、PublicでもPrivateでも可能です。Privateの場合は、Pipelineのステージの環境プロパティーにユーザー名・パスワードを設定します。

- Docker hub
Docker hubアカウントのユーザー名・パスワードをそのまま利用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205002/80c1f864-5f62-db3b-a41b-42fee9b92a9e.png)

- IBM Cloud Contianer Registry

`DOCKER_USERNAME`に`iamapikey`と記載。`DOCKER_PASSWORD`にはkeyを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205002/68126c9a-5ee3-41aa-aea9-adf39dedf577.png)

※ツールチェーンをテンプレートから作成するときに、API Keyも作成します。そのキーを再利用することができます。  
ツールチェーンの作成＞Kubernetes アプリの開発の画面のDelivery Pipelineの欄
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205002/f3775abd-5743-f7d1-5371-1d56f9e26696.png)

# Jobの編集

BUILDステージのUnit Testジョブを編集する。

- テスター・タイプ : カスタム Docker イメージ
- Docker イメージ名 : 作成したイメージ名を設定する。  
- 作業ディレクトリ : 自由に指定（今回は指定していないので、github上のコードが全て展開されます。）

[イメージ名の指定](https://cloud.ibm.com/docs/ContinuousDelivery?topic=ContinuousDelivery-custom_docker_images#docker_image_name)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205002/3de85e88-41f5-1723-14eb-6357801016bc.png)



Jobには、デフォルトで以下のスクリプトが用意されています。

```sh
#!/bin/bash
#set -x

if [ -f ./tests/run-tests.sh ]; then
  source ./tests/run-tests.sh
  RESULT=$?
  echo=$RESULT
  if [ ! -z "${FILE_LOCATION}"]; then
    if [ ${RESULT} -ne 0 ]; then STATUS=fail; else STATUS=pass; fi
      if jq -e '.services[] | select(.service_id=="draservicebroker")' _toolchain.json; then
        ibmcloud login --apikey ${IBM_CLOUD_API_KEY} --no-region
        ibmcloud doi publishtestrecord --type unittest --buildnumber ${BUILD_NUMBER} --filelocation ${FILE_LOCATION} \
          --buildnumber ${BUILD_NUMBER} --logicalappname ${IMAGE_NAME} --status ${STATUS}
      fi
    exit $RESULT
  fi
else
  echo "Test runner script not found: ./tests/run-tests.sh"
fi
```
# テストスクリプトの作成
テスト用スクリプトに合わせて、`/tests/run-tests.sh` を作成します。
考慮点の最後に記載した通り、`ENTRYPOINT` と `CMD`上書きされて呼び出されません。今回ベースに使用している`postgres`イメージの場合、呼び出し時にDBが起動されません。そのため、dbの起動スクリプトを最初に記載しています。
`apk add`の部分については、イメージを作る際にDockerfileに記載しておくことも可能です。しかし、イメージのサイズが大きくなってしまうため、今回はテスト時に実行しています。


```shell:run-tests.sh
#!/bin/sh

# PostgreSQLの起動
echo start postgres
docker-entrypoint.sh postgres &

echo version
ruby -v 

#Bundole installの実施。必要なツールをapkで取得。（nokogiriなどを使う場合は要注意）
echo bundle install
apk --upgrade --no-cache add libxml2-dev libxslt-dev gcc g++ musl make
bin/bundle install 
ERRCHK=$?
if [ ${ERRCHK} -ne 0 ]; then
  echo "Bundle install faild. Exit code: ${ERRCHK}"
  exit 1;
fi

#環境変数などの設定
echo Environment
export XXXXX_XXXXXX=xxxxxx

#テスト実行
echo test
RAILS_ENV=test; bin/rspec
ERRCHK=$?
if [ ${ERRCHK} -ne 0 ]; then
  echo "RSpec Test faild. Exit code: ${ERRCHK}"
  exit 1;
fi
```

# Jobの実行

最終的に成功して終了しました。

```log
Finished in 5 minutes 9 seconds (files took 12.22 seconds to load)
414 examples, 0 failures, 13 pending

Coverage report generated for RSpec to /home/pipeline/0c6813c0-71a3-4f0d-be6c-d1ad25af23f1/coverage. 1726 / 1877 LOC (91.96%) covered.

Finished: SUCCESS
```

環境変数が正しく設定されていなかったり、PostgreSQLが起動される前にテストが走ってしまtたりして、つまづくことがありました。
また、Docker for mac上でも、同じイメージを利用してコンテナーを起動・ソースコードをコンテナーにコピー・テストを実行することで、RspecUnitTestを行うことができました。