ChatOps( Slack / Hubot / Docker )で検証環境をポンポン作って、ポンポン捨てる

#この投稿について

Slack/Hubot/Dockerを用いたChatOpsでアプリの検証環境をポンポン作って、ポンポン捨てる方法をまとめます。
チャットから誰でも手軽に使い捨てのアプリの検証用環境を作成できるので、チーム開発の生産性向上が期待できます。

#システム構成の概要図

![Untitled (7).png](https://qiita-image-store.s3.amazonaws.com/0/47881/24fedae4-0d81-7cc8-1310-6efac6ed3484.png)

#システム構築手順

##SlackとHubotの連携

HubotをPaaSであるHerokuにデプロイさせ、Slackと連携させます。
こちらの具体的な手順については、[「Slack / Hubot / GitHub / CircleCI によるChatOpsなデプロイ方法」](http://qiita.com/s-kiriki/items/26bdf537169891b22653)の前半部分で詳しくまとめていますので、そちらを参照ください。

##Dockerを動かすサーバーの用意(EC2)

今回はAWSのEC2を用います。

###EC2の起動

まずは、Dockerを動かすインスタンスを立ち上げる。
今回はCommunityAMIの「ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20140927」というものを利用。

![zz_スクリーンショット_2014-12-12_14_43_26.png](https://qiita-image-store.s3.amazonaws.com/0/47881/20db7024-ac90-7137-6f7e-d1b1225f9ff3.png)

検証なので、インスタンスタイプは`t2.micro`を選択。

![zz_スクリーンショット_2014-12-12_14_44_17.png](https://qiita-image-store.s3.amazonaws.com/0/47881/f04c21d6-bc26-36ee-d7f0-37694b4c81bb.png)

インスタンスの名前は適当に分かりやすいものを付けておけばOK。

![zz_スクリーンショット_2014-12-12_14_45_27.png](https://qiita-image-store.s3.amazonaws.com/0/47881/3603600f-beaa-4dd9-1241-20a9f7e20df3.png)


Security Groupの設定では、SSH用ポートの他に、追加で`49153〜65535`ポートを解放しておく。
これはDockerのポートフォワーディングで使用されるポートである。

![zz_スクリーンショット_2014-12-12_14_47_29.png](https://qiita-image-store.s3.amazonaws.com/0/47881/1e688bf5-b650-7857-bb9f-33658c5c5a8b.png)

インスタンスが立ち上がれば、IPを確認してちゃんとSSH接続できるか確認。

```
ssh -i .ssh/docker-key.pem ubuntu@54.65.169.132

> ubuntu@ip-172-31-6-189:~ $
```

###ユーザーの作成

Dockerを操作する用のユーザーを作成する。ここでは`wanko`という名前で作成。

```
ubuntu:~$ sudo adduser wanko
Adding user `wanko' ...
Adding new group `wanko' (1001) ...
Adding new user `wanko' (1001) with group `wanko' ...
Creating home directory `/home/wanko' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
Changing the user information for wanko
Enter the new value, or press ENTER for the default
	Full Name []: 
	Room Number []: 
	Work Phone []: 
	Home Phone []: 
	Other []: 
Is the information correct? [Y/n] y
```

sudo権限を付与

```
ubuntu:~$ sudo usermod -G sudo wanko
```

パスワードレスでsudoになれるように設定

```
ubuntu:~$ sudo visudo

#以下を、追記
wanko ALL=(ALL) NOPASSWD: ALL
```

`/etc/ssh/sshd_config`を変更して、パスワードによるログインを許可

```
ubuntu:~$ sudo vi /etc/ssh/sshd_config
```

```:/etc/ssh/sshd_config
#PasswordAuthentication no
PasswordAuthentication yes
```

変更した設定を反映するために、sshdを再起動

```
sudo service ssh restart
```

追加したユーザーでパスワードログインできるか確認

```
ssh wanko@54.65.169.132

wanko@54.65.169.132's password:

```

##GitHubにレポジトリ作成

今回はDockerのコンテナにGitHubからアプリをcloneしてきて動かしたいので、その検証用のレポジトリを作ります。
今回はHTMLだけのシンプルなアプリにしています。

**実際のレポジトリ**
https://github.com/s-kiriki/docker-test-app


また今回はChatOpsでブランチを自由に切り替えてDockerコンテナに任意のブランチをデプロイ出来るかを検証したいので、
以下の２つのブランチを用意しました。

・masterブランチ
シンプルに「Hello from Docker!」とだけ表示される実装


・add_imageブランチ
「Hello from Docker!」の下にDockerのイメージ（クジラのやつ）が表示される実装


![スクリーンショット 2014-12-12 15.52.35.png](https://qiita-image-store.s3.amazonaws.com/0/47881/1fd87f28-39f7-9986-6eac-e54679bc0732.png)






##DockerホストOSの用意

上で立ち上げたインスタンスにDockerをインストールして、各種設定を行っていきます。
以下、このDockerのインストールされたインスタンスをDockerホストOSと書きます。

###Dockerのインストール

[公式サイト](https://docs.docker.com/installation/ubuntulinux/)に従ってインストールする。

```
sudo apt-get update
sudo apt-get install docker.io
```

インストールされたか確認

```
sudo docker --version
Docker version 1.0.1, build 990021a
```

Version1.3より追加された`docker exec`を使いたいので、Dockerを最新版にUPDATE

```
curl -sSL https://get.docker.com/ubuntu/ | sudo sh
```

```
sudo docker --version
Docker version 1.4.0, build 4595d4f
```

###Dockerfileおよび、コンテナに転送するファイルの作成

Dockerfileを以下のように作成します。

```:Dockerfile
FROM centos:centos6

RUN yum update -y
RUN yum install -y httpd
RUN yum install -y git

ADD ./app_init.sh /

RUN mkdir /root/.ssh/
ADD id_rsa /root/.ssh/id_rsa
RUN chmod 700 /root/.ssh/id_rsa

RUN echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config

EXPOSE 80
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
```

`ADD`コマンドで指定している`app_init.sh`と`id_rsa`はコンテナに転送するものですので、予め手元で作成にておく必要があります。
`id_rsa`はGitHubに認証可能な秘密鍵で、`app_init.sh`は以下のような`git clone`で先ほどつくった検証用のレポジトリをcloneしてくるシェルスクリプトです。

```:app_init.sh
#!/bin/bash

if [ $# -ne 1 ]; then
 echo "You must specify branch name"
 exit 1 
fi

BRANCH_NAME=$1

cd /var/www/html && git clone -b $BRANCH_NAME --depth 1 git@github.com:s-kiriki/docker-test-app.git
```

###Dockerイメージの作成

上で作ったDockerfileよりDockerイメージを作成します。
今手元には、`Dockerfile`、`app_init.sh`、`id_rsa`があることを確認してください。

```
wanko@ip-172-31-6-189:~$ ls
app_init.sh  Dockerfile  id_rsa
```

イメージの作成は`docker build`コマンドで行います。`-t`オプションでイメージ名（今回はdocker-appとした）を指定し、最後の`.`は`Dockerfile`のパス（今回はカレントディレクトリ）を指定しています。

```
$ sudo docker build -t docker_app .
```

Dockerイメージが作られたかを`docker image`コマンドで確認します。

```
$ sudo docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker_app          latest              6900f52b1ac5        5 seconds ago       466.5 MB

```

###コンテナの起動

上で作ったDockerイメージを使って、`docker run`コマンドでコンテナを起動させます。
`-d`オプションでApacheをデーモン化させ、`-P`オプションでポートフォワーディングを行います。

```
$ sudo docker run -d -P docker_app
afbf3bd7c6be6e36ca436d34880b49970bacb25a72a9a351c9d3c28d5a8cd0a6

```

コンテナが作られたかは、`docker ps`コマンドで確認できます。

```
$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                   NAMES
afbf3bd7c6be        docker_app:latest   "/usr/sbin/httpd -D    2 minutes ago       Up 2 minutes        0.0.0.0:49157->80/tcp   kickass_brown     
```

80番ポートが49157番ポートにポートフォワーディングされているのがわかります。

この状態で、`http://54.65.169.132:49157/`（54.65.169.132の部分はDockerホストOSのアドレス）にアクセスすると、Apacheのホーム画面が表示されているはずです。

![スクリーンショット 2014-12-12 17.17.05.png](https://qiita-image-store.s3.amazonaws.com/0/47881/d3b3184f-ae8f-94da-3b99-689780946007.png)

###アプリの設置

次に、この`docker run`で立ち上げたコンテナにアプリを設置します（GitHubからclone）。
コンテナに対してのコマンドの実行は`docker exec`コマンドで行えます。
今回はコンテナ側に既にある`app_init.sh`を実行させればいいだけなので、以下のようにします。

```
$ sudo docker exec kickass_brown sh app_init.sh master
```

`kickass_brown`はコンテナ名、`master`はブランチ名


これでコンテナにアプリが設置されたので、`http://54.65.169.132:49157/docker-test-app`にアクセスすると以下のようにアプリが動くはずです。

![スクリーンショット 2014-12-12 17.37.38.png](https://qiita-image-store.s3.amazonaws.com/0/47881/18a6716c-2b16-2ca4-71e8-4377244b0073.png)

Dockerで起動させたコンテナでGitHub上のアプリが動きました！！


###ここまでのまとめ

ここでは、大きく分けて以下のようなことを行ってきました。

1. Dockerのインストール
2. Dockerfileの作成とアプリ設置シェルスクリプトの作成
3. Dockerイメージの作成(`docker build`)
4. Dockerコンテナの起動(`docker run`)
5. アプリの設置(`docker exec`)

今回の最終目的はChatOps（Slackから指令を出す）で、指定のアプリの検証環境をポンポンつくること（コンテナをポンポン起動させること）であったので、この1~5の中の4と5をSlack経由で行う必要があります。
以降の節で、これらの方法を説明していきます。



##HubotからDockerコンテナの起動とアプリの設置を行う

SlackとHubot(Heroku上にデプロイ)の連携は既に済んでいるので、あとはHubotからDockerホストOSに命令を出してコンテナの起動とアプリの設置が行えれば、ChatOpsでアプリの検証環境を作れることになります。

この実装方法として、今回はDockerホストOSにDockerコンテナの起動とアプリの設置を行うシェルスクリプトを用意し、そのスクリプトをHubotからsshで接続して実行するという構成にしました。

###シェルスクリプトの用意（DockerホストOS）

DockerホストOSにDockerコンテナの起動とアプリの設置を行う以下のようなシェルスクリプトを用意します。


```sh:container-up.sh
#!/bin/bash

if [ $# -ne 1 ]; then
 echo "You must specify branch name"
 exit 1
fi

BRANCH_NAME=$1

CONTAINER_ID=`sudo docker run -d -P docker_app` # コンテナの起動
if [ $? -ne 0 ]
then
        echo "Error occured on running container."
        exit 1
fi

echo "Created Container:$CONTAINER_ID"

PORT_FORWARDING=`sudo docker port $CONTAINER_ID`
if [ $? -ne 0 ]
then
        echo "Error occured on checking port forwarding."
        exit 1
fi

sudo docker exec $CONTAINER_ID sh app_init.sh $BRANCH_NAME # アプリの設置
if [ $? -ne 0 ]
then
        echo "Error occured on setting app."
        exit 1
fi

echo ":docker: Your app is ready!! $PORT_FORWARDING" # :docker: というのはSlackのカスタム絵文字
```

ポンポン作るだけではなく、ポンポン捨てることも目的だったので、同様にコンテナを削除するスクリプトも以下のように作ります。

```sh:container-remove.sh
#!/bin/bash

if [ $# -ne 1 ]; then
 echo "You must specify container name or ID"
 exit 1
fi

CONTAINER_ID=$1

REMOVED_CONTAINER=`sudo docker rm -f $CONTAINER_ID`
if [ $? -ne 0 ]
then
        echo "Error occured on removing container $CONTAINER_ID."
        exit 1
fi

echo ":docker: Removed container: $REMOVED_CONTAINER" # :docker: というのはSlackのカスタム絵文字
```

###Hubotスクリプトの作成

次に、DockerホストOSに設置した`container-up.sh`および`container-remove.sh`をssh経由で実行するためのスクリプトをHubotに追加します。

simple-sshというnpmモジュールを使うのでインストールしておきます。

```
npm install simple-ssh --save
```

実際のコードは以下のようにしました。

```hubot-container.coffee
# Description:
#  Handling Docker containers.
#
# Dependencies:
#   "simple-ssh": "^0.8.1"
#	"hubot-slack": "^2.2.0"
#
# Configuration:
#   [Required]
#     DOCKER_HOST
#     DOCKER_USER
#     DOCKER_PASSWD
#
# Author:
#   s-kiriki


module.exports = (robot) ->

	_getSSH = (msg) ->
		if !process.env.DOCKER_HOST
			return msg.send 'You must set ENV: DOCKER_HOST'
		if !process.env.DOCKER_USER
			return msg.send 'You must set ENV: DOCKER_USER'
		if !process.env.DOCKER_PASSWD
			return msg.send 'You must set ENV: DOCKER_PASSWD'
	
		SSH = require('simple-ssh');
		ssh = new SSH({
			host: process.env.DOCKER_HOST,
			user: process.env.DOCKER_USER,
			pass: process.env.DOCKER_PASSWD
		});
		return ssh

	robot.respond /container up( (.+))?/i, (msg) ->
		branch_name = msg.match[2] || "master"
		ssh = _getSSH(msg)
		ssh.exec("sh container-up.sh #{branch_name}", {
		    out: (stdout) ->
		        msg.send stdout
		}).start();
		
	robot.respond /container remove (\w+)/i, (msg) ->
		container_id = msg.match[1]
		ssh = _getSSH(msg)
		ssh.exec("sh container-remove.sh #{container_id}", {
		    out: (stdout) ->
		        msg.send stdout
		}).start();
		
	robot.respond /container list/i, (msg) ->
		ssh = _getSSH(msg)
		ssh.exec("sudo docker ps | awk '{print $1, $(NF-1), $(NF)}'", {
		    out: (stdout) ->
		        msg.send stdout
		}).start();	

```




###Heroku上でHubotスクリプト用の環境変数をセット

`hubot-container.coffee`では`DOCKER_HOST`、`DOCKER_USER`、`DOCKER_PASSWD`を環境変数でセットする必要があるので、これらをHeroku上で設置します。

![zz_スクリーンショット_2014-12-12_18_19_07.png](https://qiita-image-store.s3.amazonaws.com/0/47881/db201da5-394a-61e3-7f59-6db146dff94d.png)


##デモ

ようやく準備が整ったので、SlackからDockerを操作して検証環境をポンポン作ってみます。

###SlackとHubotの連携確認

まずは、ちゃんとSlackとHubotがうまく連携して動いてるか確認してみます。

![zz_スクリーンショット_2014-12-12_18_24_05.png](https://qiita-image-store.s3.amazonaws.com/0/47881/79a1ef20-1b5f-3f49-9142-a5faa324898c.png)


ちゃんと動いてますね。ではいよいよ、コンテナを起動してアプリをデプロイしてみます。

###コンテナの起動とアプリのデプロイ


![スクリーンショット 2014-12-12 18.51.30.png](https://qiita-image-store.s3.amazonaws.com/0/47881/9e342518-6976-43a3-aadf-bd9bdab1cedf.png)


49160番ポートでコンテナが起動したようなので、`http://54.65.169.132:49160/docker-test-app/`にアクセスしてみます。

![スクリーンショット 2014-12-12 18.54.53.png](https://qiita-image-store.s3.amazonaws.com/0/47881/96c9a92e-5ea8-1d36-49cb-bc309d73fa81.png)

うまく、アプリがデプロイされてます（ブランチが未指定の場合はmasterブランチがcloneされるようにしてある）。

では次に、別のブランチをデプロイしてみましょう。

![スクリーンショット 2014-12-12 18.59.32.png](https://qiita-image-store.s3.amazonaws.com/0/47881/03a4ba45-99ae-a590-3741-3857b46e147b.png)

49162番ポートでコンテナが起動したようなので、`http://54.65.169.132:49162/docker-test-app/`にアクセスしてみます。

![スクリーンショット 2014-12-12 18.56.54.png](https://qiita-image-store.s3.amazonaws.com/0/47881/4e9dce71-dd32-31e7-3a09-6d6c7a9b1269.png)

今度は、指定したとおり`add_image`ブランチの内容がデプロイされています。

###コンテナの削除

今度は不要なコンテナを削除してみます。
最初に49160番ポートで起動した方のコンテナを消してみます。

![スクリーンショット 2014-12-12 19.05.02.png](https://qiita-image-store.s3.amazonaws.com/0/47881/22f1cf02-2833-e16b-ad3e-c8ff5ab067be.png)


コンテナが削除され、`http://54.65.169.132:49160/docker-test-app/`にアクセスしても接続できなくなっていることが確認できます。

###コンテナ一覧の確認

コンテナ一覧を確認できるコマンドも実装したので試してみます。

![スクリーンショット 2014-12-12 19.07.40.png](https://qiita-image-store.s3.amazonaws.com/0/47881/75be79b0-cfad-e9df-88e0-4bf40a5ea718.png)

うまく表示されました！

以上長くなりましたが、Slack/Hubot/Dockerを使ったChatOpsでアプリの検証環境をポンポン作って、ポンポン捨てる方法についてまとめました。