情弱だがgit pushするだけで(goの)Webアプリを積んだDockerのコンテナを自動でデプロイしたい


## はじめに

以下は『なんか流行ってるみたいだし、試しにDocker使ってみたい』そんな手段の目的化メモです。

いやいや、カスな情報はいらないよ(=^・^=)
と、いう人は下記のリンクを読むと良さそうです。
http://blog.gopheracademy.com/advent-2014/easy-deployment/
http://deeeet.com/writing/2015/01/08/dockerhub-hook/

## 自動デプロイの流れ

以下のような感じでしょうか。

* ローカルでgoのアプリを書く
* リモートリポジトリにpushする
* リポジトリ側がwebhookでデプロイ先サーバに対してPOSTを投げる
* デプロイ先サーバはPOSTを受けてデプロイ用のシェルスクリプトを実行する
 * リポジトリから`git pull`する
 * Dockerfileに基づいて`docker build`
 * 起動中のコンテナを`docker kill`
 * 新しいコンテナを`docker run`

### ローカルでgoのアプリを書く

書きましょう。

Productionモードで実行したら80ポートをみる、Developmentモードだったら9999番をみる、のような処理は追加しておくとよさそうです。

当方は標準の`http`を使ってサーバを実装してしまいました。
なので、下記のように`os.Getenv()`を使って環境変数を読み込み、リッスンするポートを指定するようにしました。

```go:env
import (
	"net/http"
	"os"
)

func main() {
	http.HandleFunc("/", awesomeHandler)
	port := os.Getenv("AWESOME_PORT")
	if port == "" {
		port = ":9999"
	}
	http.ListenAndServe(port, nil)
}
```

イケてるWAFだったらモード切り替えあるかもしれませんね。
https://github.com/go-martini/martini#martini-env

### リモートリポジトリにpushしたタイミングでwebhookを発行する

今回はbitbucketを使いました。
githubとかでもあるはず。

https://confluence.atlassian.com/display/BITBUCKET/Manage+Bitbucket+hooks
ここにかいてる`POST`ってヤツを使いました。

リポジトリへのPushを検知したら`https://"自分のドメイン"/deploy`を叩くように設定します。

### デプロイ先のサーバでwebhookなPOSTを受け取る

bitbucketから飛んでくるPOSTを捌く必要があります。
どのように捌くのが理想形か分かりませんが、とりあえずデプロイ先のサーバにwebhookなPOSTを受け取るようにしました。

こちらを使います。
https://github.com/bketelsen/captainhook
これはgoで書かれたwebhook処理に特化したサーバです。

１エンドポイントを１つのjsonファイルで表現することができます。
公式のリポジトリに書いてるQuickStartをコピペしただけですが、こんな感じ。

```json:endpoint.json
{
    "scripts": [
        {
            "command": "ls",
            "args": [
                "-l",
                "-a"
            ]
        },
        {
            "command": "echo",
            "args": [
            "hello"
            ]
        }
    ]
}
```

このようなjsonファイルを用意して`captainhook -configdir ~/captainhook`
でwebhook専用のWEBサーバが立ち上がります。
ちなみにポートはデフォルト8080です。

自分はこんな感じで動かしました。`0.0.0.0`指定しないと動かなかったからです。
`captainhook -listen-addr=0.0.0.0:8080 -echo -configdir ~captainhook`

で、このサーバに対して
`curl -X POST http(s)://"自分のドメイン"/endpoint`ってやるとhelloとか表示されるはず。

上の例では`ls`や`echo`を実行しましたが、もちろんシェルスクリプトも実行できます。

```json:awesome.sh
{
    "scripts": [
        {
            "command": "/script/path/awesome.sh"
        }
    ]
}
```

で、実際に実行してるスクリプトは下記のとおり。

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

cd /app/path # アプリのコードが設置されているディレクトリに移動
git pull origin master # リポジトリから最新版のコードを取得（なので鍵登録は事前に必要）

docker build --tag="awesome/base:latest" . 
# アプリのコードにDockerfileを含めて、それをbuildするようにしてます

OLDPORTS=( `docker ps | tail -1 | awk '{print $1}'` ) # 起動中のコンテナのid一覧を取得

for i in ${OLDPORTS[@]}
do
    docker kill $i # 古いコンテナを消す
done

sleep 3s

docker run -d -p 80:80 -t awesome/base:latest # 新しいコンテナを実行する

```

これでwebhookからdockerを再起動するところまでできました。

### Dockerfileのなかみ

コンテナ内で何をするかDockerfileに書きます

```
FROM ubuntu
MAINTAINER tokyotaro

RUN apt-get update
RUN apt-get install -y sqlite golang git gcc

RUN mkdir go
ENV GOPATH /path/go
ENV PATH /path/go/bin:$PATH

ADD . /var/www/awesome # ここでアプリのコードをコンテナ内に配置

# 必要なライブラリを導入
RUN ["go", "get", "github.com/go-gorp/gorp"] 
RUN ["go", "get", "github.com/mattn/go-sqlite3"]

# 80番ポートを開放
EXPOSE 80

WORKDIR /var/www/awesome
ENV AWESOME_PORT :80 # アプリ側でポート指定するために使う環境変数

ENTRYPOINT ["go", "run", "app.go"] 
# docker runしたときに実行される処理
# 今回はgo標準のhttpパッケージを使ったのでgo run app.goです。
```

なかなかカスっぽい感じですが、これで`http(s)://"自分のドメイン"`をブラウザで見ると自分のアプリが表示されるはずです。

# 勉強になりました、シェアさせてください。