Edited at

Golang(Echo) x docker-composeでホットリロード用いた開発


About

Golang(Echo)でDockerfileを開発と本番で同じものを使いつつ、docker-composeを使う。

なおかつホットリロードもする。


前提


  • Golang


    • 1.12



  • docker


    • 18.09.2



  • docker-compose


    • 1.23.2



  • realize


    • 2.0.2




Docker環境を用意する

とりあえず最低限のEchoサーバーを動かすこと前提


main.go

main.go を記述


main.go

package main

import (
"net/http"

"github.com/labstack/echo"
)

func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}



modulesの初期設定

go.mod を手に入れる

$ docker run -v `pwd`:/go/app -w /go/app golang:1.12-alpine go mod init app

go: creating new go.mod: module app
$ ls
go.mod main.go

ここで go.sum が欲しい人はよしなに。


Dockerfile

DockerのMulti-Stage Buildを使ってレイヤーを2つ用意します。

1つ目のレイヤーはdocker-composeで使用するため、

2つ目のレイヤーは本番で使用するために使います。


Dockerfile

FROM golang:1.12-alpine as build

WORKDIR /go/app

COPY . .

RUN apk add --no-cache git \
&& go build -o app

FROM alpine

WORKDIR /app

COPY --from=build /go/app/app .

RUN addgroup go \
&& adduser -D -G go go \
&& chown -R go:go /app/app

CMD ["./app"]


動作確認をしておきましょう。

$ docker build -t myapp .

$ docker run -p 1323:1323 -d --name myapp myapp
$ curl localhost:1323
Hello, World!

動いたのを確認できたらコンテナを落とします。

$ docker stop down myapp

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$


docker-compose

docker-composeを動かすために docker-compose.yml を記述します。

この時に指定する target: build がポイントです。

Multi-Stage Buildのレイヤーを使用することができます。


docker-compose.yml

version: '3.5'

services:
app:
build:
context: .
target: build
volumes:
- ./:/go/app
command: go run main.go
ports:
- 1323:1323


さて、動作確認です。

$ docker-compose up

Recreating echo_app_1 ... done
Attaching to echo_app_1
app_1 | go: finding github.com/labstack/gommon/color latest
app_1 | go: finding github.com/labstack/gommon/log latest
app_1 | go: finding golang.org/x/crypto/acme/autocert latest
app_1 | go: finding golang.org/x/crypto/acme latest
app_1 | go: finding golang.org/x/crypto latest
app_1 | go: finding github.com/valyala/fasttemplate latest
app_1 |
app_1 | ____ __
app_1 | / __/___/ / ___
app_1 | / _// __/ _ \/ _ \
app_1 | /___/\__/_//_/\___/ v3.3.10-dev
app_1 | High performance, minimalist Go web framework
app_1 | https://echo.labstack.com
app_1 | ____________________________________O/_______
app_1 | O\
app_1 | ? http server started on [::]:1323

別のターミナルを起動してcurlを打ってみましょう

$ curl localhost:1323

Hello, World!

動いてますね。


ホットリロードを導入する

oxequa/realize を使用して実現します


Dockerfileの編集

realizeのインストール


Dockerfile

FROM golang:1.12-alpine as build


WORKDIR /go/app

COPY . .

RUN apk add --no-cache git \
- && go build -o app
+ && go build -o app \
+ && go get github.com/oxequa/realize

FROM alpine

WORKDIR /app

COPY --from=build /go/app/app .

RUN addgroup go \
&& adduser -D -G go go \
&& chown -R go:go /app/app

CMD ["./app"]



docker-composeの編集

realizeを使用して起動するようにする


docker-compose.yml

version: '3.5'


services:
app:
build:
context: .
target: build
volumes:
- ./:/go/app
- command: go run main.go
+ command: realize start --run --no-config
ports:
- 1323:1323


動作確認

$ docker-compose up

Recreating echo_app_1 ... done
Attaching to echo_app_1
app_1 | len [0/0]0x0
app_1 | [10:25:29][APP] : Watching 1 file/s 1 folder/s
app_1 | [10:25:29][APP] : Install started
app_1 | [10:25:30][APP] : Install completed in 0.805 s
app_1 | [10:25:30][APP] : Running..
app_1 | [10:25:30][APP] : ____ __
app_1 | [10:25:30][APP] : / __/___/ / ___
app_1 | [10:25:30][APP] : / _// __/ _ \/ _ \
app_1 | [10:25:30][APP] : /___/\__/_//_/\___/ v3.3.10-dev
app_1 | [10:25:30][APP] : High performance, minimalist Go web framework
app_1 | [10:25:30][APP] : https://echo.labstack.com
app_1 | [10:25:30][APP] : ____________________________________O/_______
app_1 | [10:25:30][APP] : O\
app_1 | [10:25:30][APP] : ? http server started on [::]:1323

動いてますね。

curlをして動作確認してみましょう。

$ curl localhost:1323

Hello, World!

最後に main.go を編集してホットリロードされるかの確認です


main.go

package main


import (
"net/http"

"github.com/labstack/echo"
)

func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!")
+ return c.String(http.StatusOK, "Good Bye.")
})
e.Logger.Fatal(e.Start(":1323"))
}


$ curl localhost:1323

Good Bye.