Goのhot reloadにgo-taskを使ってみる

Goでhot reloading

作っているアプリのサーバサイドをGOで書いているので、Realizeでhot reloadを実現しようと思ったのですが、 GO111MODULE=off にしないとgo getできなかったり、いざdocker-composeで realize start --run しようとすると下記のようなエラーが出たりと色々あれだったので、他に使えそうなパッケージが無いか探してみました。

...
[01:09:01][SRC] : Running..
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4cf2fb]

goroutine 8768 [running]:
os.(*Process).signal(0x0, 0xad7a20, 0xe34878, 0x0, 0x0)
      /usr/local/go/src/os/exec_unix.go:56 +0x3b
os.(*Process).Signal(...)
      /usr/local/go/src/os/exec.go:131
github.com/oxequa/realize/realize.(*Project).run.func1(0xc000175698)
      /go/src/github.com/oxequa/realize/realize/projects.go:581 +0x5c
github.com/oxequa/realize/realize.(*Project).run(0xc0001fa000, 0xc000133ab8, 0x7, 0xc000342300, 0xc000110540, 0xad2c20, 0xc00011c8d0)
      /go/src/github.com/oxequa/realize/realize/projects.go:646 +0xc2d
github.com/oxequa/realize/realize.(*Project).Reload.func3(0xc0001fa000, 0xc000342300, 0xc000110540)
      /go/src/github.com/oxequa/realize/realize/projects.go:262 +0x147
created by github.com/oxequa/realize/realize.(*Project).Reload
      /go/src/github.com/oxequa/realize/realize/projects.go:260 +0x297
Code 1: docker-composeでrealize startした際のエラー

調べたところ、go-task が中々シンプルで良さそうだったので試してみました。

go-taskの使い方

基本的な使い方は下記の通り。

go-taskのインストール

ドキュメント にあるように、MacとLinux(Linuxbrew導入済)は brew で、Windowsの場合は scoop とかでサクッとインストールできるみたいです。

ただ、私の場合はdocker上のdebianでインストールしたかったので dockerでlinuxbrew入れるの地味にめんどうなので バイナリを dpkg でインストールしました。

wget https://github.com/go-task/task/releases/download/v2.8.1/task_linux_amd64.deb
dpkg -i task_linux_amd64.deb
rm task_linux_amd64.deb
Code 2: Debianにおいて、バイナリ(.deb)をdpkgでインストールするコマンド

Taskfile.ymlの作成

go-taskでは諸々の設定をTaskfile.ymlに記述しますので、プロジェクトルートにTaskfile.ymlを作成します。 task init でサクッと作ってくれます。

cd /path/to/project/root
task init

ソースをウォッチして特定のコマンド(go runとか)を実行させたい場合、こんな感じに書けます

version: '2'

tasks:
  run:
    cmds:
      - go run main.go
    sources:
      - ./**/*

実行

あとはTaskfile.ymlがあるディレクトリで実行するだけです。

# runはTaskfile.ymlで指定したタスク名
task run

自分の使い方

プレイベートで開発しているアプリが、Goで書いたローカルサーバでReactを配信する といった構成になってます。Goのサーバはリソース配信用とAPI兼用になっており、開発中はDocker container上で動かします。シンプルな構成なのでついでに記載しておきます。

フォルダ構成(抜粋)は下記の通り。

Dockerfile

npmも入れています。

FROM golang:1.14.4

WORKDIR /go/src

ENV GO111MODULE=on

pCOPY . /go/src

RUN apt-get update \
    && apt-get install -y git python jq curl \
    && curl -sL https://deb.nodesource.com/setup_14.x | bash - \
    && apt-get update && apt-get install -y nodejs \
    && npm install yarn -g \
    && wget https://github.com/go-task/task/releases/download/v2.8.1/task_linux_amd64.deb \
    && dpkg -i task_linux_amd64.deb \
    && rm task_linux_amd64.deb

EXPOSE 8080

CMD ["task", "run"]

Taskfile

version: '2'

tasks:
  run:
    cmds:
      - cmd: kill -TERM `cat pidfile`
        ignore_error: true
      - go run main.go --pid-file=pidfile
    sources:
      - ./**/*

go run main.go だけだとフォルダ変更を検知する度に前に走っていたプロセスを落とさずにまた別プロセスとして起動してしまうので、pidを適当にどこかに吐き出しておいて、起動時は前のプロセスをkillしてから実行するようにしています(go-taskでサーバーのライブリロードを実現する を参考にさせていただきました)。

これでファイル変更を検知してホットリロードしてくれます。

所感

環境構築というプロジェクトの本質に関わらない部分については、なるべくエネルギーを割きたくないのですが、go-taskのおかげで自分が作りたいものに集中できています。

実行済タスクのkillの仕方は若干ゴリっぽい側面があるので、もうちょいスマートにいけないか考え中です。ただ、Taskfile作ってコマンド叩くだけでいいというシンプルなワークフローは気に入ったので、しばらく使ってみたいと思います。


related-posts