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` を記述

```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` を手に入れる

```console
$ 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: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"]
```

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

```console
$ 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](https://github.com/oxequa/realize) を使用して実現します

## Dockerfileの編集
realizeのインストール

```diff: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を使用して起動するようにする

```diff: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` を編集してホットリロードされるかの確認です

```diff: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.
```