Selenium, Jenkins, Ruby(Capybara) の E2E テスト環境基盤を Docker で構築したときの知見のまとめ(書き方編)

## 概要

以下の図のような `E2E` テスト環境基盤　を構築しました。

![compose.jpg](https://qiita-image-store.s3.amazonaws.com/0/101975/5c1928ca-1b2e-3036-368d-32d7088813db.jpeg)


これは以下からコードを `clone` し `bash deploy.sh` を実行すると環境が出来上がったりします。
※`docker`, `docker-compose` が使えることが前提です

* [Github: toririn/e2e_docker](https://github.com/toririn/e2e_docker)

なんでこんな面倒な構成をするんだという所もありますが、とりあえず環境を作ってみたという所が大きいです。

いつの間にやら `docker-compose` の書き方も変わったりしていたのでいい勉強になりました。

ということでこの **テスト環境基盤の説明・デプロイ方法**と、 **`docker-compose.yml` の書き方**を記事にしました。

ただし内容が多いので2つの記事に分けています。具体的には下記のようになっています。

### **この記事**に書かれていること

* docker-compose の記載方法の説明
  * version
  * services
  * image
  * build
    * context
    * dockerfile
    * args
  * ports
  * volumes
  * links
  * depends_on
  * environment
* まとめ

### **もう一つの記事**に書かれていること

[Selenium, Jenkins, Ruby(Capybara) の E2E テスト環境基盤を Docker で構築したときの知見のまとめ(環境編)](http://qiita.com/toririn/items/537b0c3896744f334b62)

* `Selenium`, `Ruby Script`, `Jenkins` によるE2Eテスト環境基盤の説明
* 上記コンテナ達のデプロイ方法
* 上記 `Jenkins(docker)` の起動・初期設定・ジョブの実行
* まとめ


という事でこちらは **書き方編** になります。宜しくお願いします。

---

## docker-compose の記載方法の説明

### はじめに

`docker-compose.yml` で登場する `image` は以下の3種類です。
( `tag` を指定しているのは `tag` の `upgrade` で動かなくなるのを避けるためです)

* selenium
  * selenium/standalone-firefox-debug:3.0.1
* ruby
  * ruby:2.4
* jenkins
  * jenkins:2.32.1

これは `docker-compose.yml` に以下のように記載され分割されています。

```yaml
version: '2.0'
services:
  selenium:
    #...
  ruby:
    #...
  jenkins:
    #...
```

`docker-compose up -d` を実行すれば `docker-compose.yml` を読み込み、各イメージを作ってくれる訳です。

さて、今回 E2Eテスト環境基盤の構築のために作成した `docker-compose.yml` は以下のようになっています。
これをもとに記載方法を説明していきます。

```yaml:docker-compose.yml
version: '2.0'

services:

  # for e2e selenium
  selenium:
    image: selenium/standalone-firefox-debug:3.0.1
    container_name: selenium
    restart: always
    ports:
      - 5900:5900 #VNC
    expose:
      - 4444 #selenium
    volumes:
      - ./ruby/download:/home/seluser # for download file

  # for scripts
  ruby:
    build:
      context: ./ruby
      dockerfile: Dockerfile
      args:
        - password=${DOCKER_PASSWORD}
    container_name: ruby
    restart: always
    depends_on:
      - selenium
    volumes:
      - ./ruby:/usr/src/app
    environment:
      - SELENIUM_HOST=${SELENIUM_HOST}
      - SELENIUM_PORT=${SELENIUM_PORT}

  # for execute tool
  jenkins:
    build: ./jenkins/
    container_name: jenkins
    restart: always
    ports:
      - "18080:8080"
      - "50000:50000"
    volumes:
      - ./jenkins:/var/jenkins_home
    environment:
      - DOCKER_PASSWORD=${DOCKER_PASSWORD} 
```

---

### `version`: 全てを取り締まる全知全能なるもの

```yaml:docker-compose.yml
version: '2.0'
```

`version` は `docker-compose.yml` の記載が `version 1` のものか `version 2` のものかを判定するために使われます。

今回は複数のコンテナを `services` という項目内でまとめています。これは `version 2` の記載方法になります。

例えばこれを `version "1"` とかにするとエラーになります。

一番頭に書いておく大切なやつです。

### `services`: コンテナを統べる母なるもの

```yaml:docker-compose.yml
services:
  selenium:
    #...

  ruby:
    #...

  jenkins:
    #...
```

今回のように複数のコンテナを同時に起動したり、削除したりしたいときは、この `services`　を使うと実現ができます。

こうしてまとめていると `docker-compose up` を実行すると　`selenium`, `ruby`, `jenkins` をそれぞれコンテナ化してくれます。
またコンテナを削除をするときもまとめて削除をしてくれたりします。

### `image`: 教えよ、汝の名を

```yaml:docker-compose.yml
services:
  selenium:
    image: selenium/standalone-firefox-debug:3.0.1
    #...
```

`image` にはどの `Docker image` を使用するかを記載します。
ここで記載したイメージからコンテナを作成するのでとっても重要なものです。
例えば上記 `selenium` の例では `selenium/standalone-firefox-debug` という名前の付いたイメージの `3.0.1` のタグを付いたものを取得します。

タグというのはおおざっぱに言うとイメージの `version` のようなものです。
例えば `ruby` のイメージであれば `ruby:2.3` とすると `Ruby` の `version` が `2.3` のイメージを取得できます。 
タグは指定しなければ `latest` という扱いになり、 `latest （最新）`　というタグのついたイメージが取得されます。

### `build`: 創りしは新たな命、吹き込むは次への希望

```yaml:docker-compose.yml
services:
  ruby:
    build:
      context: ./ruby
      dockerfile: Dockerfile
      args:
        - password=${DOCKER_PASSWORD}
```


`build` は コンテナを作成するときの `Dockerfile` を指定するオプションです。
`context` や `args` の子オプションを持ちますがこれらは省略可能です。
例えば下記のように記載するとカレントディレクトリ(`docker-compose.yml` のあるディレクトリ)の `Dockerfile` という名前のファイルを見つけに行きます。

```yaml:docker-compose.yml
services:
  ruby:
    build: .
```

`context` には `Dockerfile` が置かれているディレクトリの場所を記載します
`dockefile` には イメージを `build` する際のファイルを指定します。
`args` には `Dockerfile` に渡す引数を記載します。今回はコンテナ作成時の `root` ユーザに設定するパスワードを `passwaord` という名前の引数で渡しています。[参考](https://github.com/toririn/e2e_docker/blob/master/ruby/Dockerfile#L3)

よって今回私が記載した `ruby` のイメージは `./ruby/Dockerfile` に `$DOCKER_PASSWORD` を引数として渡したイメージを作っているということです。

### `container_name`: これは運命。其方を示す無二なるもの

```yaml:docker-compose.yml
services:
  selenium:
    container_name: selenium
```

`container_name` でコンテナの名前を指定することができます。
通常コンテナを作成するときは、ランダムに友達のような名前がコンテナに割り振られます。
その名前を自分の好きな名前に指定ができるオプションです。

`Docker` の**コンテナの名前はとても大きな意味を持ちます。**
例えばコンテナの名前は必ず一意で無ければなりません。これは様々な操作やオプション設定の際に名前を使用することがあるからです。

(これ、ちょっと前の `version` だと使えなかったんですよねぇ……)

### `restart`: 不死なるもの。それは栄光か地獄か

```yaml:docker-compose.yml
services:
  selenium:
    restart: always
```

`restart` オプションを付けるとコンテナを自動で起動することができます。
今回は常に再起動をする `always` を指定していますが、回数や再起動をしないオプションも設定できます。

例えば**ホスト側を再起動した際に、通常コンテナは再起動しません。**
`docker ps` では見えなくなるので消えたかのように思いますが `docker ps -a` とすると見えます。
眠っちゃってるんですね。

このときにホスト側が起動(`Docker`が起動)したときに、自動でコンテナを起動できるので便利なオプションです。

ただしコンテナがエラーを起こして起動ができなかったときでも、 `always` は健在です。
こんな時はコンテナは起動と停止を繰り返すリビングデッドみたいになります。

### `ports`: 深淵をのぞく時、深淵もまたこちらをのぞいているのだ

```yaml:docker-compose.yml
services:
  selenium:
    ports:
      - 5900:5900
```

`ports` は一言でいうとポートフォワディングです。`<hostのPort>:<containerのPort>` という記載ルールでポート番号を記載します。

**通常コンテナはポートを外部に公開しません。**外部からコンテナ内部の特定のポートに接続をしたいときはこの設定を入れましょう。

ただしこの設定を入れると、ホストの外からもコンテナへ接続ができる状態にもなり、**セキュリティ的に危険にさらされる可能性があります。**

ポートフォワディングをするときはセキュリティ的に大丈夫かという観点を持ってやりましょう。
どうしてもセキュリティに関わるものをポートフォワディングしないといけないときは、ホスト側のポートの値を別の物にするのも手です。

### `expose`: モーゼは海を絶ち示した。我らの道はここなり

```yaml:docker-compose.yml
services:
  selenium:
    expose:
      - 4444
```

`expose` は指定したポートをコンテナの外へ公開するオプションです。
ただし `ports` とは異なり、公開に制限があります。
例えば上記の設定では `selenium` の `4444` ポートに接続できるのはホストと他のコンテナのみになります。

**接続の際はコンテナのIPを取得してポートを指定する必要があります。**

ports に比べて使い勝手がやや落ちるものの、セキュアな設定となります。


### `links`, `depends_on`:  縁を紡ぎ虹を架ける。其方は独りではない

```yaml:docker-compose.yml
services:
  selenium:
    #...

  ruby:
    depens_on:
      - selenium
```

`links`, `depends_on` はコンテナ間の接続のためのオプションで、ほぼ同じ意味合いです。
例えば上記のように `ruby` と `selenium` を接続させます。

こうすると `ruby` コンテナから `selenium` という名前で `selenium` コンテナの `IP` を参照することができるようになります。
これは `ruby` コンテナの `/etc/hosts` に記載が追加されるためです。

こうしておけば `ruby` コンテナから `ping` を以下のように飛ばしたりすることもできます。

```
ping selenium
```

また、 `links` と `depends_on` の違いですが、 `depends_on` を指定していると、コンテナの作成順序をコントロールすることができるようになります。

つまり上記の設定は `selenium` のコンテナが作成された後に `ruby` のコンテナを作成し、そして接続の関係を作るというオプションになります。


### `volumes`: 同じ月を眺めていると思えば、孤独な闇夜も恐れずにすむ

```yaml:docker-compose.yml
services:
  jenkins:
    volumes:
      - ./jenkins:/var/jenkins_home
```

`volumes` オプションを使えばホストとコンテナ間で、指定したディレクトリを共有することができます。
これにより以下の3つが実現されます。

* 1.コンテナに接続しなくても、ホスト側で共有ディレクトリに行った編集がコンテナに反映される
* 2.コンテナに接続しなくてもコンテナのファイルをホスト側からすぐに参照できる
* 3.コンテナを廃棄した後にもデータがホスト側に残る

上記の `jenkins` の `volumes` 設定は上記 `3` のために入れています。

これをすることでもし `jenkins` のコンテナを再作成しても、データを残すことができます。
※ **通常はコンテナを破棄すると中のデータもすべて削除されます**

よって `jenkins` のコンテナで設定したジョブや設定をいつまでも残すことができます。

逆に言うと本当に初期化したいときは `volumes` で設定しているディレクトリのファイルをホスト側で削除したりする必要がありますね。

### `environment`: 活け、其方の名は……ノビタニアン！

```yaml:docker-compose.yml
services:
  ruby:
    environment:
      - SELENIUM_HOST=${SELENIUM_HOST}
      - SELENIUM_PORT=${SELENIUM_PORT}
```

`environment` に記載を入れると、イメージからコンテナを作成するときに、環境変数をセットすることができます。
ちなみに `docker-compose.yml` でホスト側で使っている環境変数を使いたいときは `$VALUE` か `${VALUE}` でできます。

## まとめ

* `Selenium` + `Ruby Script` + `Jenkins` による E2E自動テストの環境を `Docker` にて構築しました。
* 構築には `docker-compose` を使い一元的に各コンテナを管理するようにしました。
* この記事には `docker-compose` の詳しい書き方を記載しました。

もしこの基盤の詳細やデプロイ方法を知りたいという方は下記をご参照ください。

[Selenium, Jenkins, Ruby(Capybara) の E2E テスト環境基盤を Docker で構築したときの知見のまとめ(環境編)](http://qiita.com/toririn/items/537b0c3896744f334b62)