Docker+AlpineLinux3.4+Go1.7+Nginxでfreshを使って自動ビルドする開発環境を作る

# はじめに
久しぶりにDockerさわったらboot2dockerも無くなってて浦島太郎状態でした。
再学習も兼ねて色々と触ってみたメモです。
何となくGoとAlpineLinux使ったらモテそうな気がしたんで、やってみました。

OSはMac前提でのお話しです。

## ゴール
* タイトルの通りの開発用インフラ構成をローカルマシンに構築
* freshを使ってGoのファイルを更新したら自動でビルドが走る
* supervisorで``go run`` して``Hello World!``が表示出来る

## 読むのが面倒な方 (Docker入ってる前提)
[github](https://github.com/s-noguchi/docker_alpine_go_nginx)に一式アップしました。
https://github.com/s-noguchi/docker_alpine_go_nginx

* ①一式プルするなりダウンロードする

* ②イメージビルド``docker build --no-cache . -t alpine-go-nginx``

* ③コンテナ起動``docker run -d -v /ローカルのファイルパス/hello:/home/go/hello -p 1000:80 -i -t alpine-go-nginx /bin/bash``

* ④コンテナに入って``nginx``でNginx起動して、``cd /home/go/hello && fresh``

* ⑤ブラウザから``http://localhost:1000``をたたけば動くはずです。

## 何でAlpineLinuxを使うか？
驚くほど軽量です。
**OS単体だと5MBいかないです。**😵
![スクリーンショット 2016-09-13 17.38.45.png](https://qiita-image-store.s3.amazonaws.com/0/65968/ea891576-94d4-0904-f2c3-75b0946d100d.png)

Dockerで動かす前提なので、CentOSとかUbuntuのようなフルフルでのLinuxOSは必要無いので、まさにコンテナ時代に求められる軽量OSです！

オフィシャルDockerイメージのOSもAlpineLinuxに移行する計画もあるようです。
http://www.publickey1.jp/blog/16/docker_alpine_linux.html

Alpine Linuxの説明とかは他にも色々あるので詳細はここでは割愛します。

# Docker for Macのインストール
何はともあれDockerが無いとはじまりません。
ここからダウンロードしてインストールしてください。
https://docs.docker.com/docker-for-mac/

便利な時代になりました。
dmgでインストールして起動すればDockerが使えるようになります。


# 各種ファイルの説明
## ファイル構成
<img width="144" alt="スクリーンショット 2016-09-13 17.54.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/65968/c7347a5a-5d15-5366-e5d5-eeff0d9e7456.png">

goのHello Worldのスクリプトとnginxの設定ファイルを事前に設定しておいて、Dockerのビルド時にコピーしたり、コンテナ起動時にボリュームマウントを行います。

[github](https://github.com/s-noguchi/docker_alpine_go_nginx)見てもらったほうが分かりやすいかもです。

## Dockerfile

AlpineLinuxではパッケージ管理が``apk``と呼ばれるパッケージ管理システムなのですが、今回は最新版が欲しかったのですが``apk``だと無いので、GoとNginxはビルドしています。

```vim:Dockerfile
# alpine Linux 3.4(latest)を指定
FROM alpine:3.4

# apk本体のupdate
RUN apk update

# 必要なパッケージのインストール
RUN apk add --no-cache wget bash git supervisor gcc build-base openssl-dev pcre-dev

# glibcのインストール
RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub --no-check-certificate -P /tmp
RUN wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk --no-check-certificate -P /tmp
RUN apk add --no-cache /tmp/glibc-2.23-r3.apk

# Go1.7.1のインストール
RUN wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz --no-check-certificate -P /tmp
RUN tar -C /usr/local -xzf /tmp/go1.7.1.linux-amd64.tar.gz

# Goのパス設定
ENV GOPATH /home/go
ENV PATH $PATH:/usr/local/go/bin:$GOPATH/bin

# Goの自動ビルドツールのインストール
# https://github.com/pilu/fresh
RUN go get github.com/pilu/fresh

# インストールした/tmpのファイルを削除
RUN rm /tmp/go1.7.1.linux-amd64.tar.gz
RUN rm /tmp/glibc-2.23-r3.apk

# TimeZoneをJSTに
RUN apk --update add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata

## nginx
# パッケージのダウンロードと展開
WORKDIR /tmp
RUN wget https://nginx.org/download/nginx-1.11.3.tar.gz
RUN tar zxvf /tmp/nginx-1.11.3.tar.gz

# ビルドとインストール
WORKDIR /tmp/nginx-1.11.3
RUN ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_ssl_module
RUN make
RUN make install

# nginxユーザーの追加
RUN adduser -D nginx
RUN passwd nginx -d nginx
RUN addgroup nginx nginx

# nginxのパスを通す
ENV PATH $PATH:/usr/local/nginx/sbin

# default.confの設定
WORKDIR /usr/local/nginx/conf
COPY ./nginx/nginx.conf nginx.conf

# virtual.confの設定
WORKDIR /usr/local/nginx
RUN mkdir conf.d
WORKDIR /usr/local/nginx/conf.d
COPY ./nginx/virtual.conf virtual.conf

# supervisor登録
RUN touch /etc/supervisord.conf
RUN echo '[supervisord]'  >> /etc/supervisord.conf
RUN echo 'nodaemon=true'  >> /etc/supervisord.conf
RUN echo '[program:nginx]' >> /etc/supervisord.conf
RUN echo 'command=/usr/local/nginx/sbin/nginx -g "daemon off;"'   >> /etc/supervisord.conf
RUN echo '[program:hello]' >> /etc/supervisord.conf
RUN echo 'command=go run /home/go/hello/hello.go'   >> /etc/supervisord.conf

# ポートの開放
EXPOSE 80 443 22

# supervisor 起動
CMD ["/usr/bin/supervisord"]

```

ハマったポイントとしては、Goのインストールは成功するものの一向に動かなくて困っていたら、``glibc``が必要な事に気づき、``# glibcのインストール``のコメントのところの処理をいれました。

ちゃんと http://golang-jp.org/doc/install には``Linux 2.6.23以降（glibcが必要)``と書いてありました😅ドキュメントはちゃんと読まないとダメですね。

## nginx
ログすら吐かない最低限のヤバめの設定です。
使う時は必要に応じて設定をしてください。

80番で待ち受けて、9000番のfastcgiにフォワードする設定がしてあるだけです。
後述のGoのスクリプトでも9000番で待ってあげるようにしてあげます。

```vim:/usr/local/nginx/conf/nginx.conf
user  nginx;
worker_processes  1;

events {
    worker_connections  1024;

}

http {
    include /usr/local/nginx/conf.d/*.conf;
}
```


```vim:/usr/local/nginx/conf.d/virtual.conf
server {
    listen       80;
    server_name  localhost;

    location / {
        fastcgi_pass  127.0.0.1:9000;
        include       fastcgi_params;
    }
}
```

## GoのHello Worldスクリプト
```go:hello.go
package main

import (
    "fmt"
    "net"
    "net/http"
    "net/http/fcgi"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World!")
}

func main() {
    l, err := net.Listen("tcp", ":9000")
    if err != nil {
        return
    }
    http.HandleFunc("/", handler)
    fcgi.Serve(l, nil)
}

```

# 実行します

## ビルド
``Dockerfile``のあるディレクトリで以下コマンドを実行します。

```
docker build --no-cache . -t alpine-go-nginx
```

``-t`` のパラメータは任意で設定できます。今回は``alpine-go-nginx``としました。

## コンテナ起動

-v でマウントするボリュームの設定をします。
-p でポートの指定をします。今回の場合、1000番で受け付けたリクエストを80番に転送します。

```
docker run -v /ローカルのパス/hello:/home/go/hello -p 1000:80 -i -t alpine-go-nginx /bin/bash
```

実行すると

```
bash-4.3#
```

のように表示され、コンテナにログイン出来るているはずです。

## nginx 起動

ここで、``nginx``とコマンドを打つとnginxがスタートします。
![スクリーンショット 2016-09-14 10.20.22.png](https://qiita-image-store.s3.amazonaws.com/0/65968/3292b71f-7393-ec8b-fa62-7a6c84fd9c4d.png)


Dockerfileのnginxのビルドでは起動コマンドは``/usr/local/nginx/sbin/nginx``なのですが、毎度打つのも面倒なので、Dockerfileで以下のようにパスを通してあります。

```
# nginxのパスを通す
ENV PATH $PATH:/usr/local/nginx/sbin
```

# fresh 起動
いよいよGoの実行です。
freshはGoのコードの変更を検知して自動でビルドしてくれるツールです。
Dockerfileでは以下の箇所で``go get``しています。

```
# Goの自動ビルドツールのインストール
# https://github.com/pilu/fresh
RUN go get github.com/pilu/fresh
```

使い方ですが、まず``hello.go``のファイルがあるディレクトリに移動します。

```
cd /home/go/hello
```

ここで

```
fresh
```

と実行するとGoの自動ビルドがはじまります。
変更があると検知して自動でビルドしてくれます。フロントエンド開発の``gulp``とか``Grunt``みたいなやつです。

こんな感じになってればOKです。
![スクリーンショット 2016-09-14 10.21.29.png](https://qiita-image-store.s3.amazonaws.com/0/65968/a7bdb80e-f326-4b58-50db-3cdf7112268a.png)


## freshの設定
freshは設定ファイルを渡して起動する事も出来ます。
デフォルトの設定だけでも十分動きますがご興味ある方はやってみてください。

今回は``/home/go/hello``で``vi hello_runner.conf``で新たにファイルを作ります。
中身はこんな感じです。

```
root:              .
tmp_path:          ./tmp
build_name:        runner-build
build_log:         runner-build-errors.log
valid_ext:         .go, .tpl, .tmpl, .html
ignored:           assets, tmp
build_delay:       600
colors:            1
log_color_main:    cyan
log_color_build:   yellow
log_color_runner:  green
log_color_watcher: magenta
log_color_app:
```

``log_color_``の色を変えたり、``build_delay``の長さを変えたり、生成される``tmp_path``の名前を変えてみたりと色々とカスタマイズすることが出来ます。

設定したら、

```
fresh -c hello_runner.conf
```

で起動すると、独自に定義した設定で動作します。

詳細はこちらを見てみてください。
https://github.com/pilu/fresh

### freshを使わないでgoを実行する場合
```
go run ./hello.go
```
すれば動きます。

## Hello Worldの表示
ブラウザから``http://localhost:1000``
と入力すると以下のように表示されると思います。

<img width="222" alt="スクリーンショット 2016-09-14 10.24.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/65968/f7a45367-5b7a-b853-2f63-407b8e8ba9e9.png">

ブラウザから1000番ポートで来たアクセスが８０番に転送されて、さらにgoのアプリケーションが待っている9000番にNginxのfastcgiが転送しているイメージです。

## freshがちゃんと自動ビルドしているか確認
``Hello World! ``を``Hello``に変更して保存してみます。

すると、watcherが動いてちゃんとビルドしてくれます。
![スクリーンショット 2016-09-14 10.24.31.png](https://qiita-image-store.s3.amazonaws.com/0/65968/1c152c72-a6d4-ae1e-ef08-61c0565ff031.png)

変更も変わってます

<img width="225" alt="スクリーンショット 2016-09-14 10.24.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/65968/a60796bb-d93a-1a84-5db6-3b7db802946b.png">

## ビルド時にわざと怒られてみる
```
http.HandleFunc("/", handler)
```

を

```
http.HandleFunc("/", handler1234)
```
と存在しない``func``にするとちゃんと怒ってくれました。

![スクリーンショット 2016-09-14 10.27.19.png](https://qiita-image-store.s3.amazonaws.com/0/65968/a2c0d7f5-5ade-8efa-e85f-cccd7655cf00.png)


## SupervisorでGoを実行してみたい
開発環境とはちょっと趣旨が変わりますが、Supervisorで実行してみたいと思いやってみました。

こちらの記事参考にさせて頂きました。
http://qiita.com/taka4sato/items/1f59371ead748d88635a
要はコンテナで起動しているサービスを永続化させたいので使います。

既にDockerfileには設定を書いているので、ビルドしたイメージで以下のようにコンテナ実行すればOKです。

```
docker run -v /ローカルのパス/hello:/home/go/hello -p 1000:80 -i -t alpine-nginx-go /usr/bin/supervisord
```

起動するとこんな感じでsuccessの表示がされているはずです。

```
INFO spawned: 'nginx' with pid 7
INFO spawned: 'hello' with pid 8
INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
INFO success: hello entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
```

Dockerfileの``# supervisor登録``の設定が動作しています。
``nginx``が起動されて、``go run /home/go/hello/hello.go``が実行された状態です。

これまでと同様に``http://localhost:1000``でアクセスすると``Hello World!``が表示されるはずです。

#### バックグラウンドで実行したい時
``-d`` オプションでバックグラウンド実行できます。

```
docker run -d -v /ローカルのパス/hello:/home/go/hello -p 1000:80 -i -t alpine-nginx-go /usr/bin/supervisord
```

**注意点**
ポートがかぶってるとコンテナ起動出来ないので、既に1000:80で作ったコンテナが残っている場合は破棄するか、ポート``2000:80``とかにポートを変えてあげて実行してください。

参考までに

* 動いてるコンテナの確認 ``docker ps``
* 全てのコンテナの確認 ``docker ps -a``
* コンテナ削除 ``docker rm コンテナid``
* 全部のコンテナ削除 docker rm  -f `docker ps -a -q`

# おわりに
## Goについて
Goをやりはじめたばかりなので、まだまだGoについては初心者レベルなので、皆様がどんな開発環境でやっているのかが知りたいので日々情報収集しております。良かったら教えてください。

## AlpineLinuxについて
とにかく不要なものを削ぎ落とした硬派なLinuxで、僕は大好きになりました。

驚くほど軽量なので、色んなものが入っていないです。自分で必要なパッケージを見極めて作り上げていく楽しさがあるなと思います。