Posted at

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

More than 3 years have passed since last update.


はじめに

久しぶりにDockerさわったらboot2dockerも無くなってて浦島太郎状態でした。

再学習も兼ねて色々と触ってみたメモです。

何となくGoとAlpineLinux使ったらモテそうな気がしたんで、やってみました。

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


ゴール


  • タイトルの通りの開発用インフラ構成をローカルマシンに構築

  • freshを使ってGoのファイルを更新したら自動でビルドが走る

  • supervisorでgo run してHello World!が表示出来る


読むのが面倒な方 (Docker入ってる前提)

githubに一式アップしました。

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

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が使えるようになります。


各種ファイルの説明


ファイル構成

スクリーンショット 2016-09-13 17.54.35.png

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

github見てもらったほうが分かりやすいかもです。


Dockerfile

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


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番で待ってあげるようにしてあげます。


/usr/local/nginx/conf/nginx.conf

user  nginx;

worker_processes 1;

events {
worker_connections 1024;

}

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



/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スクリプト


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

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


freshの設定

freshは設定ファイルを渡して起動する事も出来ます。

デフォルトの設定だけでも十分動きますがご興味ある方はやってみてください。

今回は/home/go/hellovi 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

と入力すると以下のように表示されると思います。

スクリーンショット 2016-09-14 10.24.16.png

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


freshがちゃんと自動ビルドしているか確認

Hello World!Helloに変更して保存してみます。

すると、watcherが動いてちゃんとビルドしてくれます。

スクリーンショット 2016-09-14 10.24.31.png

変更も変わってます

スクリーンショット 2016-09-14 10.24.24.png


ビルド時にわざと怒られてみる

http.HandleFunc("/", handler)

http.HandleFunc("/", handler1234)

と存在しないfuncにするとちゃんと怒ってくれました。

スクリーンショット 2016-09-14 10.27.19.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で、僕は大好きになりました。

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