【Golang】 Airを用いてWebアプリケーション開発中に変更を即座に反映する

github.com

Golang で Webサイトを作ろうと gin を触っていると、ライブリロードの機能がなく、変更が即座に反映されなくて、面倒くさく思っていました。 Air というツールがよく使われていそうだったので、使ってみました。

このエントリはそのときのメモです。

gin アプリケーションの準備

まず、Air と関係のない部分で最小のアプリケーションを用意します。

Golang バージョンは以下の通りで、環境は macOS Catalina (10.15.7) です。

$ go version
go version go1.15.5 darwin/amd64

目指すフォルダ構成は以下です。

.
├── go.mod
├── go.sum
└── main.go

フォルダを作成し、go modules で環境を構築します。

$ mkdir gin-air-example && cd $_
$ go mod init gin-air-example
go: creating new go.mod: module gin-air-example
$ touch main.go

main.go の内容は以下とします。

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "OK",
        })
    })

    r.Run(":3000")
}

localhost:3000 にアクセスしたら、 {"message": "OK"}というJSONを返します。

$ go run main.go
go: finding module for package github.com/gin-gonic/gin
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.6.3
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :3000
$ curl localhost:3000
{"message":"OK"}

f:id:zuckey_17:20210104004434p:plain

このように、デバッグのログにもアクセスが来たことが表示されます。

main.go を編集する

ここで、main.goを以下のように変更します。

// (省略)

func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
-          "message": "OK",
+           "message": "Hello World",
        })
    })

    r.Run(":3000")
}

main.go を保存して、再度 localhost:3000にアクセスしても結果は変わりません。

この際、go run main.goを一旦止め、再度同じコマンドを実行すると出力結果は変わります。

$ curl localhost:3000
{"message":"Hello World"}

しかし、普段開発していて毎回 コマンドを叩き直すのはやめたいです。ここで Air というツールを導入します。

Air を導入する

Air を installします。

$ go get -u github.com/cosmtrek/air

.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.
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 unchanged files.
exclude_unchanged = 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

そして、gin のプロセスを止め、airコマンドからWebアプリケーションを起動します。

$ air -c .air.toml

すると、main.go への 変更が即座に反映されるようになったのがわかるかと思います。

これで Air が導入できました。

gin アプリケーションの構築以外でやったことは、以下のみなので、簡単です。

  1. Abr の インストール
  2. 設定ファイルの追加

必要に応じて、.air.toml を修正して利用します。

最終的にファイル構成はこうなっています。

.
├── .air.toml
├── go.mod
├── go.sum
└── main.go

Docker で利用

Docker で利用する場合のメモも載せておきます。

Dockerfile を作成

FROM golang:latest

COPY ./ /go/app

WORKDIR /go/app/

RUN go get -u github.com/gin-gonic/gin
RUN go get -u github.com/cosmtrek/air

CMD ["air", "-c", ".air.toml"]**

docker-compose.yml を作成

version: '3'
services:
  app:
    container_name: app
    build:
      context: ./
      dockerfile: ./Dockerfile
    ports:
      - 3000:3000
    tty:
      true
    volumes:
      - ./:/go/app

アプリケーションを立ち上げます。

$ docker-compose up —build 
Building app
Step 1/6 : FROM golang:latest

# 省略

Successfully built 6e9649de9e6b
Successfully tagged gin-air-example_app:latest
Recreating app ... done
Attaching to app
app    | 
app    |   __    _   ___  
app    |  / /\  | | | |_) 
app    | /_/--\ |_| |_| \_ v1.12.1 // live reload for Go apps, with Go1.14.0
app    | 
app    | mkdir /go/app/tmp
app    | watching .
app    | !exclude tmp
app    | building...
app    | running...
app    | [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
app    | 
app    | [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
app    |  - using env:  export GIN_MODE=release
app    |  - using code: gin.SetMode(gin.ReleaseMode)
app    | 
app    | [GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
app    | [GIN-debug] Listening and serving HTTP on :3000

2回目移行は --build オプションは不要です。

これで同様にmain.goの変更が即座に反映されるようになります。

最終のファイル構成はこちらです。

.
├── .air.toml
├── Dockerfile
├── docker-compose.yml
├── go.mod
├── go.sum
└── main.go

終わりです。