💭

DockerコンテナでgolangをホットリロードするAirを導入

2022/03/09に公開

golangをDockerコンテナで扱う時、ローカルでのコードの変更をコンテナ側に反映するためにリロードをする必要があります。
つまり更新コードの確認のたびにdocker restart や docker-compose restartを実行する必要があります。
Airはこのリロード作業を自動化してくれます。

普通のリロード

ディレクトリ構成
.
├── Dockerfile
├── docker-compose.yml
├── go.mod
└── main.go
docker-compose.yml
version: "3.8"

services:
    reload_test:
      image: reload_test
      container_name: reload_test
      build: .
      ports:
        - 8080:8080
      volumes:
        - .:/app

volumesでローカルのrootディレクトリとコンテナの/appディレクトリをバインドマウントしているので、変更は即時反映されます。

Dockerfile
FROM golang:1.17.7-alpine

WORKDIR /app
CMD ["go","run","main.go"]
main.go
package main

import (
	"fmt"
	"net/http"
)

func helloHander(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "<h1>Hello Normal</h1>")
}

func main() {
	http.HandleFunc("/", helloHander)
	http.ListenAndServe(":8080", nil)
}

実行してブラウザのlocalhost:8080にアクセスするとHello Normalと表示されます。

% docker-compose up reload_test -d


ローカルのソースコードを更新します。

main.go
package main

import (
	"fmt"
	"net/http"
)

func helloHander(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "<h1>Hello Normal Update</h1>")
}

func main() {
	http.HandleFunc("/", helloHander)
	http.ListenAndServe(":8080", nil)
}

ブラウザを更新してもHello Normalのままです。

Dockerをリロードしてブラウザを更新するとHello Normal Updateに変わります。

% docker-compose restart reload_test

golangのホットリロードツールAir導入

それではgolangのホットリロードツールAirを導入します。
Dockerfileを編集しgitからAirをインストールしairコマンドを実行します。

FROM golang:1.17.7-alpine

RUN apk update &&  apk add git
RUN go get github.com/cosmtrek/air@v1.29.0
WORKDIR /app

# air -c [tomlファイル名] // 設定ファイルを指定してair実行(WORKDIRに.air.tomlを配置しておくこと)
CMD ["air", "-c", ".air.toml"]

続いてDockerfileと同じ階層にAirの設定ファイル.air.tomlを作成します。

.air.toml

tomlは軽量な設定ファイルです。2021年1月に1.0.0がリリースされました。
https://github.com/toml-lang/toml
公式に使い方が載っています。
table(ハッシュ)とarray of tablesだけ理解すればすぐに使えそうでした。
https://toml.io/en/v1.0.0
https://qiita.com/b4b4r07/items/77c327742fc2256d6cbe

.air.toml
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary, can setup environment variables when run your app.
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms

[log]
# Show log time
time = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true

Airのgithubにあったair_example.tomlファイルです。
デフォルトのままだとWORKDIRに./tmp/mainという実行ファイルが作成され、ホットリロードされる度に実行ファイルが更新されます。(WORKDIRにもmain実行ファイルが作成されますがこちらは更新されませんでした。)
それではAirを実行してみます。

やってみる

docker-compose upで実行します。

% docker-compose up reload_test   
[+] Running 0/1
 ⠿ reload_test Error                                                                                                    4.2s
[+] Building 2.1s (9/9) FINISHED                                                                                             
 => [internal] load build definition from Dockerfile                                                                    0.0s
 => => transferring dockerfile: 32B                                                                                     0.0s
 => [internal] load .dockerignore                                                                                       0.0s
 => => transferring context: 2B                                                                                         0.0s
 => [internal] load metadata for docker.io/library/golang:1.17.7-alpine                                                 2.0s
 => [auth] library/golang:pull token for registry-1.docker.io                                                           0.0s
 => [1/4] FROM docker.io/library/golang:1.17.7-alpine@sha256:d030a987c28ca403007a69af28ba419fca00fc15f08e7801fc8edee77  0.0s
 => CACHED [2/4] WORKDIR /app                                                                                           0.0s
 => CACHED [3/4] RUN apk update &&  apk add git                                                                         0.0s
 => CACHED [4/4] RUN go get github.com/cosmtrek/air@v1.29.0                                                             0.0s
 => exporting to image                                                                                                  0.0s
 => => exporting layers                                                                                                 0.0s
 => => writing image sha256:7d623f3c5f70ae48bceb1d54947472f1a3c416ec47439a1b9ee147e0de1e0031                            0.0s
 => => naming to docker.io/library/reload_test                                                                          0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 2/1
 ⠿ Network reload_test_default  Created                                                                                 0.0s
 ⠿ Container reload_test        Created                                                                                 0.1s
Attaching to reload_test
reload_test  | 
reload_test  |   __    _   ___  
reload_test  |  / /\  | | | |_) 
reload_test  | /_/--\ |_| |_| \_ , built with Go 
reload_test  | 
reload_test  | mkdir /app/tmp
reload_test  | watching .
reload_test  | !exclude tmp
reload_test  | building...
reload_test  | running...

Airが実行され"running..."で終わっていたら正常に動作しています。
main.goの表示文字を変更しローカルで保存するだけでホットリロードがかかります。

reload_test  | main.go has changed
reload_test  | building...
reload_test  | running...

.air.tomlのrootやtmp_dirやcmdを変更してみたりしたのですが想定通りにホットリロードが機能しなかったので、ここらへんも調べていきたいです。

参考

https://qiita.com/b4b4r07/items/77c327742fc2256d6cbe
https://hisa-web.net/archives/1070
https://blog.zuckey17.org/entry/golang-gin-air

Discussion