Dockerを使ったGolang開発環境
しばらくiOSアプリのクライアントサイドばかり開発していてサーバサイドプログラムにご無沙汰だったのだけど、 最近またGoでアプリのサーバサイドを書くようになった。
ちょうど xhyve が話題になっているのもあって、OS X の仮想環境がアツい感じだったので、 ひさしぶりに Docker で開発してみよう、と思いたち、Dockerを使ったアプリ開発をやってみている。
docker-compose を使って依存ミドルウェアも一緒に立ち上げる
docker-compose [1] というのを使うと、複数のコンテナを同時に立てられ、それぞれにリンクも良い感じにやってくれる。 開発環境を作るにはもってこいのツールだ。
GoのWebアプリ
サンプルとして以下のようなアプリを考える。
// main.go
package main
import (
"log"
"fmt"
"net/http"
"github.com/garyburd/redigo/redis"
)
func main() {
redi, err := redis.Dial("tcp", "redis:6379")
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
res, err := redi.Do("incr", "counter")
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
return
}
if res, ok := res.(int64); ok {
w.Write([]byte(fmt.Sprintf("counter: %d", res)))
} else {
w.WriteHeader(500)
w.Write([]byte("unexpected value"))
}
})
log.Fatal(http.ListenAndServe(":5000", nil))
}
HTTPでリクエストを受けて、Redisに保存してるカウントを +1 して返す、というアプリだ。
まず、このアプリ自体のコンテナを作る。これは以下のようなDockerfileを書くだけで良い。
# Dockerfile
FROM golang:onbuild
EXPOSE 5000
こいつを docker-compose から使うには、さらに以下のように docker-compose.yml を書く
# docker-compose.yml
app:
build: .
ports:
- "5000:5000"
links:
- redis
redis:
image: redis
内容としては、 app
と、 redis
という二つのコンテナを定義し、 app
はさきほど作った Dockerfile を指定、5000ポートでアクセスできるようにして、redisコンテナにリンク、という感じだ。
これで、 docker-compose up
とすると、コンテナたちが立ち上がり、 127.0.0.1:5000[2] でGoのwebアプリにアクセスできる。
何回かリロードすればredisに保存された値が増えていくのが分かるはずだ。
docker-compose がよしなに設定してくれるので、アプリからは redis
というホストでredisコンテナにアクセスできているのも楽で良い。[3]
Goのコードを変更した時の対応
このままだと、Goのコードを書き換えた時にアプリのDockerイメージ自体をrebuildしないと変更が反映されない。 開発時に毎回ビルドするのは面倒だし、時間もかかるので、手元のコードをコンテナにマウントし、それを起動するようにする。
そうするには以下のように docker-compose.yml を書き換えれば良い。
app:
build: .
ports:
- "5000:5000"
links:
- redis
volumes:
- ".:/go/src/app"
command: go run main.go
redis:
image: redis
こうすればコードを書き換えたら docker-compose up
しなおせば手元のコードがrunされるので、イメージをrebuildしなくても変更に追従できる
依存ライブラリーが変わった時の対応
それだけだと、 go get
するライブラリが増えた時に結局rebuildする必要があってめんどくさい。また、その場合 go get を全部やりなおすことになって時間もかかる。
なので、開発時には $GOPATH を格納する専用のデータコンテナを作ってやると良い。
まず、
$ docker run -itd --name myapp-gopath -v /go busybox
のようにして、 myapp-gopath
という名前[4]で、 /go
以下[5]を共有するデータコンテナを作る。[6]
そして、 docker-compose-yml を以下のようにする
app:
build: .
ports:
- "5000:5000"
links:
- redis
volumes:
- ".:/go/src/app"
volumes_from:
- myapp-gopath
command: go run main.go
redis:
image: redis
これで、appのGOPATHはmyapp-gopathコンテナのデータが使われるようになる。
go get
をこのデータコンテナに対しておこなうのは、わかりにくいが、
$ docker run --rm --volumes-from myapp-gopath -v $PWD:/go/src/app golang:onbuild go-wrapper download
こんな感じにすればappイメージをrebuildすることなく、GOPATHを更新できる。
まとめ
以上で、go getするコマンドが長いのを除けば、わりと不満ない開発環境になった。
以前からもVMを使った開発環境というのは作っていたが、それに比べ、Dockerは
- ミドルウェア単体でシンプルなコンテナがあるので分かりやすい
- docker-composeでの簡単にコンテナ連携
- aufsなどのDockerそもそものメリット
と、開発環境としてうれしいメリットがおおい。
ホストマシンにもGoの環境はあるので、Goのアプリは手元で動かし、 ミドルウェアだけをDockerで、というパターンも考えられそうだが、そちらはコンテナへの接続がめんどくさそうで一長一短か。
とりあえず、しばらくこの環境で開発してみようと思う。 プロダクションへの導入も追々考えたい。