Docker社の言う"Container as a Service"をやってみた

本記事は[Docker Advent Calendar 2015](http://qiita.com/advent-calendar/2015/docker)の13日目の記事です。

## 概要
先日の DockerCon Europe 2015において、Docker社は「Container as a Service」という戦略を打ち出しました。詳しくは[publickeyさんの記事](http://www.publickey1.jp/blog/15/docker_container_as_a_service.html)を見ていただければと思いますが、ここで紹介されているデモが非常に興味深かったので、再現してみることにしました。

### 作成するデモの内容
Github > Travis CI > Docker Hub > Tutum のCI/CDパイプラインを作ります。上記記事で紹介されているデモと少し流れが違いますが、やりたいことは同じです。`git push`するだけで、テストが実行され、Dockerイメージがビルドされ、最終的にはアプリがデプロイされるという自動化フローを作ります。


### [Tutum](https://www.tutum.co/)について
Docker社が今年の10月に買収したコンテナのデプロイサービスです。位置付け的にはKubernetesやMesosなどと同じくコンテナのスケジューリング機能を持つサービスですが、Dockerホストクラスタを直感的なGUIで簡単にクラウド上に用意できることから、より開発者フレンドリーなサービスだと言えると思います。VMから下の管理を全てTutumに任せることができるため、ユーザは開発に集中することができます。まだベータ版であることから、誰でも無料で試すことができます[^1]。

Tutumはそれ自体がクラウドを持っているわけではなく、Tutumを通してAWSやDigital Oceanといったクラウドサービス上にDockerホストクラスタを構築し、そのクラスタ上にコンテナを配置していくという形になります。今回はAWSを利用します。


### デモに利用するアプリについて
[AngularJSのチュートリアル](https://docs.angularjs.org/tutorial)で使用されている[angular-phonecat](https://github.com/angular/angular-phonecat)というアプリを利用することにします。これを自分のGithubリポジトリにforkする形で進めていきます。

## 事前準備
以下の準備をしておいてください。なお、Macを前提とします。

* Github上で[angular-phonecat](https://github.com/angular/angular-phonecat)リポジトリをforkする
* Homebrewをインストールする
* Google Chromeをインストールする
* Docker Toolboxをインストールし、dockerコマンドがたたける状態にする
* AWSアカウントのアクセスキーとシークレットアクセスキーを取得しておく
 * TutumがEC2等を操作するため、適切な権限が付与されている必要があります。詳しくは[公式ドキュメント](https://support.tutum.co/support/solutions/articles/5000224910-link-your-amazon-web-services-account)を参照してください

### nodebrewのインストール
angular-phonecatはnode.jsを使用します。以下の手順でnode.jsが使えるようにしておきます。

```
$ brew install nodebrew
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
$ source ~/.bash_profile

$ nodebrew install-binary v0.12.9
$ nodebrew use v0.12.9

$ node -v
v0.12.9
$ npm -v
2.14.9
```

### アプリの実行確認
angular-phonecatがどんなアプリなのか、まずは試しておきましょう。forkしたリポジトリをクローンし、アプリを実行してみます。

```
$ git clone https://github.com/tktk8924/angular-phonecat.git
$ cd angular-phonecat
$ npm install
$ npm start
```

ブラウザで`http://localhost:8000/app`にアクセスすると、アプリが表示されます。

今度はテストを実行してみます。このリポジトリにはユニットテストとE2Eテストが用意されています。E2Eテストは実際に稼働しているアプリに対してテストが行われるので、アプリは起動したままにしておいてください。

```
$ npm test
...
Chrome 47.0.2526 (Mac OS X 10.11.1): Executed 5 of 5 SUCCESS (0.077 secs / 0.073 secs)
Firefox 41.0.0 (Mac OS X 10.11.0): Executed 5 of 5 SUCCESS (0.06 secs / 0.058 secs)
TOTAL: 10 SUCCESS

$ npm run protractor
...
7 tests, 11 assertions, 0 failures
```

上記のように出力されていればテスト成功です。

## Dockerコンテナ化
angular-phonecatは最終的にDockerコンテナとしてTutum上にデプロイされることになりますので、Dockerfileを作成してイメージをビルドできるようにします。

### Dockerfileの作成

以下の内容でDockerfileを作成します。

```Dockerfile
FROM node:0.12

WORKDIR /app
COPY ./ .

RUN npm install
RUN node_modules/.bin/bower --allow-root install

EXPOSE 8000
CMD ["npm", "start"]
```

Dockerfileはクローンしたリポジトリのルートディレクトリに置いてください。

```
$ tree angular-phonecat -L 1
angular-phonecat
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── bower.json
├── node_modules
├── package.json
├── scripts
└── test
```

### コンテナの起動テスト
イメージをビルドして、アプリがコンテナで動作することを確認します。Dockerfileがあるディレクトリで以下のコマンドを実行します。

```
$ docker build -t angular-phonecat .
$ docker images
REPOSITORY                       TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
angular-phonecat                 latest              88fd1db238cb        10 minutes ago      855.8 MB
```

イメージができたらコンテナを起動してみましょう。以下のコマンドを実行してください。

```
$ docker run -d -p 8000:8000 --name angular-phonecat angular-phonecat
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
58b967642168        angular-phonecat    "npm start"         11 minutes ago      Up 10 minutes       0.0.0.0:8000->8000/tcp   angular-phonecat
```

`http://{Your Docker Host IP}:8000/app`にアクセスして、アプリが表示されることを確認してください。


## GithubとTravis CIの連携
まずはGithubとTravis CIを連携させ、`git push`に対して自動でテストが実行されるようにしましょう。

[Travis CI](https://travis-ci.org)にログインし、[プロファイルページ](https://travis-ci.org/profile)で該当のリポジトリを有効にします。下記のようになればOKです。

![Kobito.fWry8b.png](https://qiita-image-store.s3.amazonaws.com/0/81715/415681d2-23a5-a5c3-d4a6-40a7595cb62c.png "Kobito.fWry8b.png")


## Docker Hub Automated Buildの設定
続いて、Docker Hubに自動ビルド（Automated Build）の設定を行います。Travis CIにてテストが成功したら、さきほど作成したDockerfileを使って自動でイメージがビルドされるようにします。

[Docker Hub](https://hub.docker.com/)にログインし、[Linked Accounts & Services](https://hub.docker.com/account/authorized-services/)ページでGithubとリンクしておきます。途中で聞かれるアクセス権限については、Public and Private（Recommended）の方を選択してください。下記のようになればOKです。

![Kobito.DfnPdr.png](https://qiita-image-store.s3.amazonaws.com/0/81715/8a1c0bad-747e-47ee-9cee-3fbcc6d382aa.png "Kobito.DfnPdr.png")

次に、画面右上のCreate > Create Automated Buildをクリックします。するとGithubのリポジトリ一覧が表示されるので、angular-phonecatをクリックします。

ここではShort Descriptionにてきとうな文字列を入れ、Createをクリックします。

![Kobito.teNDSX.png](https://qiita-image-store.s3.amazonaws.com/0/81715/54c87c74-12b5-a094-2be2-91c6efaae0ad.png "Kobito.teNDSX.png")

これでDocker Hub上にangular-phonecatリポジトリが作成されました。

![Kobito.2brQkd.png](https://qiita-image-store.s3.amazonaws.com/0/81715/65339545-4012-988f-cb8f-eedca842c5a0.png "Kobito.2brQkd.png")

Build Settingsに移動して、「When active, builds will happen automatically on pushes.」のチェックを外します。Githubへのプッシュではなく、Travis CIをビルド開始のトリガーにするためです。

![Kobito.IFnkas.png](https://qiita-image-store.s3.amazonaws.com/0/81715/75d4e87e-b68f-4db0-10b1-a406a58fc1c5.png "Kobito.IFnkas.png")

さらに画面下の方にあるBuild TriggerのところのActivate Triggersをクリックします。するとこのイメージのビルドを開始するためのTrigger URLが表示されます。Travis CIからこのURLにHTTP POSTすることで、イメージのビルドが開始されます。

![Kobito.lvYzC3.png](https://qiita-image-store.s3.amazonaws.com/0/81715/657ff0ef-5a5e-cc3b-e6bd-c36c7f017300.png "Kobito.lvYzC3.png")


## Github > Travis CI > Docker Hub のフローを自動化する
それでは、`git push`によってDockerイメージがビルドされるところまでを自動化しましょう。

angular-phonecatリポジトリにはTravis CIのビルド設定ファイルである`.travis.yml`がすでに含まれていますが、これを以下のように変更したいと思います。

```.travis.yml
language: node_js
node_js:
  - 0.12

before_script:
  - export DISPLAY=:99.0
  - sh -e /etc/init.d/xvfb start
  - npm start > /dev/null &
  - npm run update-webdriver
  - sleep 3

script:
  - node_modules/.bin/karma start test/karma.conf.js --no-auto-watch --single-run --reporters=dots --browsers=Firefox
  - node_modules/.bin/protractor test/protractor-conf.js --browser=firefox

after_success: .travis/after_success.sh
```

`after_success`は`script`のテストが成功したら実行されるスクリプトです。テストが成功したらDocker Hubでイメージをビルドするようにします。下記のスクリプトファイルを作成してください。Trigger URLはご自分の環境に合わせて修正してください。

```.travis/after_success.sh
#!/bin/bash

if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
  curl -i -H "Content-Type: application/json" --data '{"source_type": "Branch", "source_name": "master"}' -X POST {Your Trigger URL}
fi
```
※このままではTrigger URLはGithub上で公開されることになってしまいます。Trigger URLが知られてしまうと誰でもイメージのビルドができてしまうので危険です。Travis CIではこのような[秘密にしておきたいデータを暗号化して記述できるようにする機能](https://docs.travis-ci.com/user/encryption-keys/)がありますので、そちらを利用してください。

`$TRAVIS_PULL_REQUEST`はTravis CIによって提供される環境変数で、ビルドするコードのリビジョンがプルリクされたものかどうかを判別するために使います。Dockerイメージのビルドはmasterブランチにマージされたコードを使って行いたいのでこのような書き方をしています。

もともとあったscriptsディレクトリは不要なので削除します。また、`after_success.sh`に実行権限をつけます。

```
$ rm -fr scripts
$ chmod +x .travis/after_success.sh
```

これで準備は完了です。これまでの変更をコミットして`git push`しましょう。Travis CIでテストが実行され、Docker Hubでイメージがビルドされるはずです。

```
$ git add .

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   .travis.yml
        new file:   .travis/after_success.sh
        new file:   Dockerfile
        deleted:    scripts/private/README.md
        deleted:    scripts/private/old/README.md
        deleted:    scripts/private/old/ScrapeData.js
        deleted:    scripts/private/old/format-json.sh
        deleted:    scripts/private/old/goto_step.bat
        deleted:    scripts/private/old/goto_step.sh
        deleted:    scripts/private/old/snapshot.sh
        deleted:    scripts/private/push-to-github.sh
        deleted:    scripts/private/retag.sh
        deleted:    scripts/private/test-all.sh
        deleted:    scripts/private/update-gh-pages.sh
        deleted:    scripts/update-repo.sh

$ git commit -m "Add Dockerfile and integration with Docker Hub"

$ git push -u origin master
```

Travis CIのビルド完了画面

![Kobito.Bk2uM1.png](https://qiita-image-store.s3.amazonaws.com/0/81715/3e732db8-5ab0-e544-2cdb-9877dfc6d527.png "Kobito.Bk2uM1.png")

Docker HubのBuild Details画面

![Kobito.0twOon.png](https://qiita-image-store.s3.amazonaws.com/0/81715/ba7a7912-57e4-14c9-bfad-66967950dbe1.png "Kobito.0twOon.png")


## Tutum利用準備
ようやくTutumを使うところまで来ました。さっそく[ログイン](https://dashboard.tutum.co/accounts/login/)してみましょう。Docker Hubのアカウントでログインできます。

ログインしたら、まずはTutumをAWSとリンクさせます。画面右上のアカウントをクリックし、Account infoページに移動してください。

![Kobito.TN9ZaJ.png](https://qiita-image-store.s3.amazonaws.com/0/81715/fbb3a56f-4a66-0ab8-f2e2-383e1c6237d6.png "Kobito.TN9ZaJ.png")

次に、Amazon Web ServicesのAdd credentialsボタンをクリックして、アクセスキーとシークレットアクセスキーを入力します。これでAWSとのリンクは完了です。

それでは、Dockerホストクラスタを構築します。TutumではDockerホストをNodeと呼んでいます。Launch new node clusterをクリックしてください。

![Kobito.ub7cn2.png](https://qiita-image-store.s3.amazonaws.com/0/81715/9c4aac91-87e5-15d4-b37b-f5d175c250aa.png "Kobito.ub7cn2.png")

とりあえずNode cluster nameだけ入力し、Launch node clusterをクリックします。するとNodeが1台EC2上に構築されます。VPCやセキュリティグループなど、必要な設定はTutumが全てやってくれます。

![Kobito.fPw3oh.png](https://qiita-image-store.s3.amazonaws.com/0/81715/01983991-2120-9150-065b-306a83916489.png "Kobito.fPw3oh.png")

Nodeが構築されました。

![Kobito.EdVZV5.png](https://qiita-image-store.s3.amazonaws.com/0/81715/c6d16a03-4ee6-1403-711a-5f980e4e99b0.png "Kobito.EdVZV5.png")


## アプリのデプロイ
Nodeが構築され、コンテナをデプロイする準備ができました。いよいよangular-phonecatアプリをデプロイします。あんまり面白くありませんが、とりあえずangular-phonecatコンテナを1台デプロイしてみることにします。

Tutumでは同じイメージから起動されたコンテナの集まりをServiceと呼びます。Create serviceをクリックしてください。

![Kobito.IWrtl4.png](https://qiita-image-store.s3.amazonaws.com/0/81715/be7b1248-b1fe-2496-5efd-59794007fb75.png "Kobito.IWrtl4.png")

まずはイメージを選択します。Public repositories > Search Docker hubと進んで、さきほど作成した自分のangular-phonecatイメージリポジトリ名を入力します。そしてSelectをクリックしてください。

![Kobito.79xK2B.png](https://qiita-image-store.s3.amazonaws.com/0/81715/9f2b48e7-9659-7c47-5dea-bfd50ccc60bc.png "Kobito.79xK2B.png")

続いてServiceの設定です。一箇所だけ、Portsの設定を修正します。Publishedにチェックを入れて、Node portに80を指定します。これでangular-phonecatコンテナに対してインターネット上からポート80番でアクセス出来るようになります。`docker run -p 80:8000`と同じ意味ですね。修正できたらCreate and deployをクリックしてください。

![Kobito.6th9w7.png](https://qiita-image-store.s3.amazonaws.com/0/81715/54159eb5-91c9-002a-d9b1-b0565e836663.png "Kobito.6th9w7.png")

これでコンテナのデプロイが始まります。Docker Hubからイメージをpullし、AWS上のNode内にコンテナが起動します。デプロイが完了するとこんな感じの画面が確認できると思います。

![Kobito.hI0h7t.png](https://qiita-image-store.s3.amazonaws.com/0/81715/baea5b6b-d1af-5e7f-a6cc-81ec7243a0ac.png "Kobito.hI0h7t.png")

それではangular-phonecatアプリにアクセスしてみましょう。Endpointsタブを開いてください。

![Kobito.sJ1WSa.png](https://qiita-image-store.s3.amazonaws.com/0/81715/eeaa871a-0279-0bf4-5aa5-b0cf009f009f.png "Kobito.sJ1WSa.png")

するとService endpointsというところにServiceのURLが表示されています。このURL（に`/app`を加えて）をブラウザで開いてください。

![Kobito.A8Znif.png](https://qiita-image-store.s3.amazonaws.com/0/81715/ce8b9843-fa76-ccce-6287-53f0388d7b69.png "Kobito.A8Znif.png")

angular-phonecatアプリが表示されました。これでアプリのデプロイは完了です。


## Docker Hub > Tutum Redeployのフローを自動化する
最後に、Docker HubとTutumを連携させます。Docker Hubにてイメージが更新されたら、さきほどデプロイしたコンテナを自動でRedeploy（コンテナを停止し、最新のイメージをpullして新しいコンテナをデプロイする）します。こうすることですぐに変更が反映されるようになります。

さきほどデプロイしたコンテナ（Service）の画面からTriggersタブに移動します。ここでTriggersというものを登録します。TriggersというのはTutumのAPIの一つであり、外部のプログラムからこのAPIにHTTP POSTすることで、ServiceをRedeployさせることができます。Add trigger欄に任意の文字列を入力し、＋Addをクリックします。

![Kobito.mwmGOc.png](https://qiita-image-store.s3.amazonaws.com/0/81715/1af82d9d-8691-bf9f-843c-629ba7161136.png "Kobito.mwmGOc.png")

Triggerが登録されました。このURLをDocker Hub側からPOSTすることで、Serviceの自動Redeployを可能にします。URLはコピーしておいてください。

今度はDocker Hub側にWebhookの設定を追加します。Docker Hub上のangular-phonecatリポジトリページに行き、その中のWebhooksというタブに移動してください。

![Kobito.Mt9jAg.png](https://qiita-image-store.s3.amazonaws.com/0/81715/bb74424a-2a2a-40b3-cdb2-2fe375c3ceff.png "Kobito.Mt9jAg.png")

Add Webhookをクリックし、さきほどコピーしたURLを登録します。

![Kobito.10iNOv.png](https://qiita-image-store.s3.amazonaws.com/0/81715/37fdf0db-741b-cb07-d7a4-f282ff201b4f.png "Kobito.10iNOv.png")

![Kobito.BV0rfA.png](https://qiita-image-store.s3.amazonaws.com/0/81715/8179e21f-a1b2-08cd-60e2-1f2613195162.png "Kobito.BV0rfA.png")

Webhookが登録されました。これでDockerイメージが更新されるたびにコンテナがRedeployされ、変更が自動で反映されるようになります。

お疲れ様でした。これで全ての設定が完了しました。


## Git Push!!
アプリに変更を加えて`git push`してみましょう。`app/partials/phone-list.html`の6行目に以下のコードを追記してください。検索ボックスの上に見出しをつけます。

```
<h1>Angular Phonecat</h1>
```

コミットして`git push`しましょう。

```
$ git commit -am "Add heading"
$ git push origin master
```

ServiceのURLにアクセスしてみましょう。下記のような画面になっていれば成功です。

![Kobito.v2ftgE.png](https://qiita-image-store.s3.amazonaws.com/0/81715/7828fb0f-0b61-11b1-15b6-8ae84e70e581.png "Kobito.v2ftgE.png")



## おわりに
今回はGithub>Travis CI>Docker Hub>TutumというCI/CDパイプラインを作ってみましたが、このパイプラインの組み合わせは色々なパターンがあると思います。特に企業のプロダクションで利用するということであれば、イメージをprivateにしておきたいはずです。実はTutumにはPrivate Registryの機能があって、直接Tutumにイメージをプッシュすることで、イメージをprivateに利用できるようになっています。

本記事ではDockerコンテナベースの自動化フローの構築を目的としていたので、コンテナ1台立ち上げただけのシンプルな構成でしかTutumを使用しませんでした。しかし、Tutumはすでにプロダクションで利用できるだけの非常に多岐に渡る機能を持っていて、今後注目されていくサービスになることは間違いないでしょう。


[^1]: [2015年Q3にプロダクションレディーになる](https://support.tutum.co/support/solutions/articles/5000512255-why-is-tutum-in-beta-and-for-how-long-)ということなので今すぐ試しましょう！