air によるライブリロード
air は Go のアプリケーションをライブリロードするツール。
$ go install github.com/cosmtrek/air@latest
air init すると設定ファイルが作成される。
$ air init
$ cat .air.toml
root = "."
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
...
次のようなサーバーのコードに対して
package main
import (
"context"
"log"
"net/http"
"os/signal"
"syscall"
)
func pingHandler(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("pong"))
if err != nil {
log.Fatal(err)
}
}
func main() {
http.HandleFunc("/ping", pingHandler)
go func() {
log.Print("Listening on port 5051")
log.Fatal(http.ListenAndServe("0.0.0.0:5051", nil))
}()
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
<-ctx.Done()
}
air を実行すると fsnotify でファイルの変更を検知してビルドし再起動してくれる。 inotify のサポートが完全でない WSL や Docker for Mac では、動かないツールや ENTR_INOTIFY_WORKAROUND のような設定が必要だったりすることもあるが、air は特に何かすることなく動いた。
$ air
__ _ ___
/ /\ | | | |_)
/_/--\ |_| |_| \_ v1.49.0, built with Go go1.20.3
watching .
!exclude tmp
building...
running...
main.go has changed
building...
running...
コンテナで動かす場合はコードをマウントしてコンテナ内でairを実行すればよく、そのためのイメージも提供されている。
$ docker run -it --rm \
-w "/app" \
-e "air_wd=/app" \
-v $(pwd):/app \
-p 5051:5051 \
cosmtrek/air
delve によるリモートデバッグ
最適化されないように -gcflags “all=-N -l” を渡してビルドしたバイナリを dlv exec で実行し listen のポートで待ち受ける。
$ go install -v github.com/go-delve/delve/cmd/dlv@latest
$ cat .air.toml
...
[build]
cmd = "go build -gcflags \"all=-N -l\" -o tmp/main ."
full_bin = "dlv exec --headless --listen=:12345 --accept-multiclient --continue ./tmp/main"
...
これに対して dlv connect で接続し break でブレークポイントを設定したりできる。
$ dlv connect 127.0.0.1:12345
(dlv) break /app/main.go:12
Breakpoint 1 set at 0x308068 for main.pingHandler() /app/main.go:12
(dlv) breakpoints
Breakpoint runtime-fatal-throw (enabled) at 0x4d2a0,0x6473c,0x4d360 for (multiple functions)() <multiple locations>:0 (0)
Breakpoint unrecovered-panic (enabled) at 0x4d780 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1217 (0)
print runtime.curg._panic.arg
Breakpoint 1 (enabled) at 0x308068 for main.pingHandler() /app/main.go:12 (3)
(dlv) continue
> main.pingHandler() /app/main.go:12 (hits goroutine(82):2 total:2) (PC: 0x308068)
(dlv) args
w = net/http.ResponseWriter(*net/http.response) 0xbeef000000000008
r = ("*net/http.Request")(0x4000148b40)
Kubernetes での実行
air のイメージに delve をインストールする。
$ cat Dockerfile_dev
FROM cosmtrek/air:v1.50.0
RUN go install github.com/go-delve/delve/cmd/dlv@latest
$ docker build -t dev_testapp -f Dockerfile_dev .
ライブリロードできるようにカレントディレクトリをマウントする。コンテナ内でビルド時に埋め込まれるパスがワークスペースのパスと異なると VSCodeのエディタ上からブレークポイントを設定したときにファイルが見つからずエラーになるためマウント先を合わせている。起動設定の substitutePath を設定してもよい。
$ cat manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: testapp-deployment
labels:
app: testapp
spec:
replicas: 1
selector:
matchLabels:
app: testapp
template:
metadata:
labels:
app: testapp
spec:
containers:
- name: testapp
image: dev_testapp
imagePullPolicy: Never
workingDir: PATH_PLACEHOLDER
volumeMounts:
- name: host-volume
mountPath: PATH_PLACEHOLDER
ports:
- containerPort: 5051
- containerPort: 12345 # delve
volumes:
- name: host-volume
hostPath:
path: PATH_PLACEHOLDER
type: Directory
---
apiVersion: v1
kind: Service
metadata:
name: testapp-service
spec:
type: NodePort
selector:
app: testapp
ports:
- name: app
port: 5051
targetPort: 5051
nodePort: 30010
- name: delve
port: 12345
targetPort: 12345
nodePort: 30011
$ sed "s|PATH_PLACEHOLDER|${PWD}|g" manifest.yaml | kubectl apply -f -
VSCode でのデバッグ
起動設定はデバッグ実行時に自動で作られた。
$ cat .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Connect to server",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "${workspaceFolder}",
"port": 30011,
"host": "127.0.0.1"
}
]
}