CircleCIでDockerコンテナのCI環境を整える


自分の備忘録兼、社内ハンズオン用の資料なので、足りないところが多いかもしれません。

記事の最後まで進んだ状態が以下にありますので、参考にどうぞ。

[honeniq/docker-circleci-sample](https://github.com/honeniq/docker-circleci-sample)

## 事前に必要なもの

* ローカルでDockerが使える環境
  * Docker-Toolboxなど
* DockerHubのアカウント
* GitHubのアカウント
  * CircleCIはGitHubアカウントでログインできます
* Ruby 
  * Bundlerも必要


## 大体の手順

1. GitHubでリポジトリを作って、ローカルにクローン
2. ``Gemfile``を作成
3. テストの準備をする
4. テストと実装コードを書く
5. CircleCIの設定
6. GitHubフローでCIを回す
7. DockerHubに自動デプロイ

## GitHubでリポジトリを作って、ローカルにクローン

項目として作ってみたけど、特に書くことがない。


## ``Gemfile``を作成

こんな感じの内容で作成。

```Gemfile
source 'https://rubygems.org/'

gem 'docker-api'

group :test do
  gem 'specinfra', '2.12.7'
  gem 'serverspec'
end
```

specinfraのバージョンを指定しているのは、CircleCIとの相性から。
参照: [CircleCIでDockerコンテナをテストしようとしたらエラーになるとき](http://qiita.com/honeniq/items/00504fecc708f9026bc5)

保存したら、コンソールから``bundle``コマンドを叩く。

```
$ bundle
Fetching gem metadata from https://rubygems.org/.......
Fetching version metadata from https://rubygems.org/..
Resolving dependencies....
Using diff-lcs 1.2.5
Using excon 0.45.4

  ~

Using serverspec 2.8.2
Using bundler 1.9.9
Bundle complete! 3 Gemfile dependencies, 16 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
```


## テストの準備をする

### ``.rspec``を作成する

```.rspec
--color
--format documentation
```

設定しておかないとテストの結果表示が味気ないので。

### ``spec_helper.rb``を作成する

```spec/spec_helper.rb
require "docker"
require "serverspec"

set :backend, :docker
set :docker_url, ENV["DOCKER_HOST"]
set :docker_image, "honeniq/docker-circleci-sample"

Excon.defaults[:ssl_verify_peer] = false
```

``set :backend, :docker``でDockerコンテナに対してテストするように指定してる。

*イメージ名を直接書いてるのがイケてないので、そのうち修正する。*

### テストのテスト

この状態で``rspec``コマンドを叩くと、空のテストが回る。

```
$ bundle exec rspec
No examples found.

Finished in 0.0003 seconds (files took 0.06445 seconds to load)
0 examples, 0 failures
```


## テストと実装コードを書く

とりあえず今回はnginxが入ればOKという要件で。

### ``web_spec.rb`` を作成する

```spec/web/web_spec.rb
require 'spec_helper'

# Workaround needed for circleCI
if ENV['CIRCLECI']
  class Docker::Container
    def remove(options={}); end
    alias_method :delete, :remove
  end
end

describe "nginx" do
  describe package("nginx") do
    it { should be_installed }
  end
end
```

上半分は、これまたCircleCIで動かすためのコード。

この状態で``rspec``すると、Dockerイメージが見つからないと怒られる。

```
Failures:

  1) nginx Package "nginx"
     Failure/Error: Unable to find matching line from backtrace
     Docker::Error::NotFoundError:
       No such image: honeniq/docker-circleci-sample
     # (eval):1:in `backend'
```


### ``Dockerfile``を作成する

```Dockerfile
FROM ubuntu
```

とりあえずイメージがビルドできるだけ。

``docker build``を叩いてイメージを作る。イメージ名は``spec_helper.rb``で指定しているもの。

```
$ docker build -t honeniq/docker-circleci-sample .

Sending build context to Docker daemon 67.58 kB
Step 0 : FROM ubuntu
 ---> d2a0ecffe6fa
```

これで、``rspec``のコケ方が変わる。

```
Failures:

  1) nginx Package "nginx" should be installed
     Failure/Error: it { should be_installed }
       expected Package "nginx" to be installed
     # ./spec/web/web_spec.rb:13:in `block (3 levels) in <top (required)>'
```

## テストを通す

nginxをインストールするように``Dockerfile``を編集する。

```Dockerfile
FROM ubuntu

RUN apt-get update
RUN apt-get install -y nginx
```

再度``docker build``してイメージに反映させる。

```
$ docker build -t honeniq/docker-circleci-sample .
Sending build context to Docker daemon 73.22 kB
Step 0 : FROM ubuntu
 ---> d2a0ecffe6fa
Step 1 : RUN apt-get update
 ---> Using cache
 ---> cb24a07d1bd2
Step 2 : RUN apt-get install -y nginx
 ---> Running in 5fd7387700fa
Reading package lists...

  ~

 ---> 1d87758f46a8
Removing intermediate container 5fd7387700fa
Successfully built 1d87758f46a8
```

これでテストも無事通るようになる。

```
$ bundle exec rspec

nginx
  Package "nginx"
    should be installed

Finished in 12.72 seconds (files took 0.26434 seconds to load)
1 example, 0 failures
```

ローカルでテストが通るようになったので、GitHubにPushしておく。

## CircleCIの設定

### 新規プロジェクト作成

CircleCIのメニューから、 **Add Projects** を選んで、GitHubに作ったリポジトリ名の横にある **Build project** を押す。

しばらくすると1回目のテストが走って・・・コケる。

![kobito.1441379041.240178.png](https://qiita-image-store.s3.amazonaws.com/0/2492/40748591-d10b-eb77-b659-7bde58692c27.png "kobito.1441379041.240178.png")

CircleCIがDockerコンテナを使う設定になっていないのが原因。

### ``circle.yml``を作成する

```circle.yml
machine:
  services:
    - docker

dependencies:
  override:
    - docker info
    - docker build -t honeniq/docker-circleci-sample .

test:
  override:
    - bundle
    - bundle exec rspec
```

大体見たまんまの内容で、CircleCIでテストを走らせるホストでDockerを使えるようにして、テストの前段で``docker build``を実行、あとは``bundle``で必要なgemを入れたあとに``rspec``しているだけ。

作成したらGitHubにPush。
CircleCIがリポジトリの更新を検知して、2回目のテストが走る。

![kobito.1441379700.114022.png](https://qiita-image-store.s3.amazonaws.com/0/2492/40ffe1c9-1b40-1f64-7053-518c782df077.png "kobito.1441379700.114022.png")

通った！


## GitHubフローでCIを回す

参照: [GitHub Flow](https://gist.github.com/Gab-km/3705015)

### Featureブランチを作る

nginxのindexページの中身を変更する、って体で。

### テストコードを書き換える

後半のみ抜粋。

```web_spec.rb
describe "nginx" do
  describe package("nginx") do
    it { should be_installed }
  end

  describe file("/usr/share/nginx/html/index.html") do
    its(:content) { should match /Welcome to test container/ }
    its(:content) { should_not match /Welcome to nginx/ }
  end
end
```

当然、``rspec``はコケる。

### ``Dockerfile``を書き換える

```Dockerfile
FROM ubuntu

RUN apt-get update
RUN apt-get install -y nginx
RUN sed -i 's/Welcome to nginx/Welcome to test container/g' /usr/share/nginx/html/index.html
```

テストが通るように修正。

### Pull Requestを投げる

自分で自分のリポジトリに投げることになるけど気にしない。

CircleCIがPRを検知して、自動的にFeatureブランチのテストを始めてくれる。

![kobito.1441384708.262199.png](https://qiita-image-store.s3.amazonaws.com/0/2492/b09aaa8d-8748-042a-3d96-6f4dbdf184a7.png "kobito.1441384708.262199.png")

こんな感じに、masterと区別してテスト状況が確認できる。

### Pull Requestをマージする

自分で自分のPRをマージすることになるけど気にしない。

![kobito.1441384832.226240.png](https://qiita-image-store.s3.amazonaws.com/0/2492/8edbba42-2de6-58ff-d1cb-d00c96481ad5.png "kobito.1441384832.226240.png")

GitHub上でもCircleCIのテスト結果が確認できるので非常に分かりやすい。

PRがmasterブランチにマージされると、masterブランチの更新を検知してCircleCIがまたテストを走らせてくれる。

![kobito.1441384965.550473.png](https://qiita-image-store.s3.amazonaws.com/0/2492/63fd1252-fca3-5348-524e-771768c43cd3.png "kobito.1441384965.550473.png")


## DockerHubに自動デプロイ

masterでもテストが通ったら、DockerイメージをDockerHubに自動デプロイするように変更する。

### Featureブランチを作る

一応、GitHubフローに沿うように。

ただ、今回の変更内容はmasterブランチ上でしか検証できないので、あんまり意味ないかもしれない。

### ``circle.yml``を修正する

```Dockerfile
machine:
  services:
    - docker

dependencies:
  override:
    - docker info
    - docker build -t honeniq/docker-handson .

test:
  override:
    - bundle
    - bundle exec rspec

deployment:
  hub:
    branch: master
    commands:
      - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
      - docker push honeniq/docker-handson
```

deploymentタスクを追加。
``branch: master``で、masterブランチの時のみ実行するように絞っている。

途中、``docker login``でDockerHubにログインしてもらう必要があるけど、直接コードにユーザー名やパスワードを書くわけにはいかないので、環境変数に入れる。

### CircleCIに環境変数を登録

``circle.yml``に指定した3つの環境変数を返せるように、CircleCI側にも登録してあげる。

![kobito.1441429887.390978.png](https://qiita-image-store.s3.amazonaws.com/0/2492/f0461277-4ffe-8af2-51ab-560384d61368.png "kobito.1441429887.390978.png")


**Project Setting** → **Tweaks** / **Environment Variables** と進んで、環境変数を登録する。

「Name」に環境変数名、「Value」に対応する値を入力して保存。以下3つを登録。

* DOCKER_EMAIL : DockerHubに登録しているメールアドレス
* DOCKER_USER : DockerHubのユーザー名
* DOCKER_PASS : DockerHubのパスワード

### Pull Requestを投げてマージする

先ほどと同じく、自分で自分にPRを投げてマージする。

masterブランチのテストも問題なく通れば、追加したdeploymentタスクが走ってDockerHub上のイメージが更新される。![kobito.1441430777.228240.png](https://qiita-image-store.s3.amazonaws.com/0/2492/12c3c421-98dc-724e-1294-00de48c24b4f.png "kobito.1441430777.228240.png")

これで、デプロイまで一通り流れるようになった。