こんにちは、トレタでサーバーサイドエンジニアをしている神山と申します。
こちらはトレタアドベントカレンダー12日目の記事です。
この記事ではdocker-compose環境におけるgolangのリモートデバッグについて記載したいと思います。
概要
docker-composeを使ってgolangの環境を構築し、そこでapplicationを動作させています。
既にairのホットリロードにより差分をすぐ反映することができるため、そこまで気にせず開発を進めていました。
ですがサービスのコード量や複雑な要件が増えるにつれて、デバッグが徐々に辛くなってきました。
そこでデバッグできるようにdelveを導入してみました。
環境
- MacBook Pro(Apple M1)
- go version go1.19.4 linux/arm64
- Docker Compose version v2.13.0
- air v1.40.4
- delve v1.20.1
docker-compose.yml
docker-composeファイルはこのように書きました。
version: '3.7'
services:
app:
image: 'golang:1.19'
working_dir: '/app'
volumes:
- type: bind
source: '.'
target: '/app'
ports:
- '8080:8080'
- '2345:2345'
command: >
sh -c '
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/cosmtrek/air@latest
air -c .air.toml
'
- imageはそのままgolangの1.19を指定しています。
- working_dirにソースコードをbindしています。
- portsは二箇所開けています。
- api用の8080
- リモートデバッグ接続用の2345
- commandでdocker-composeをbuildするタイミングでdelveとairををinstall、airを起動するようにしています。
- 本記事では触れませんが、本番環境用に別でDockerfileを用意しています。
- 本番環境ではdelve, airともにinstallしたくないのでここに記載する形にしました。
.air.toml
airを使っている場合、tomlファイルを作成しているかと思います。
編集したのは下記に記載の部分です。他は変えていません。
[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -gcflags \"all=-N -l\" -o ./.air/main ./cmd/main.go"
# Binary file yields from `cmd`.
bin = "./.air/main"
# Customize binary.
full_bin = "APP_ENV=dev APP_USER=air dlv exec ./.air/main --headless=true --listen=:2345 --api-version=2 --accept-multiclient"
-
cmdでは、
./cmd/main.go
をbuildして、./.air/main
にバイナリを配置しています。 - binでは、あらためてバイナリの位置を記載しています。
- full_binでは、airコマンドを使ってdelve用のserverを起動します。
この状態でdocker-compose up
をするとターミナルに下記のようなログが表示されるはずです。
app | API server listening at: [::]:2345
app | 2022-12-19T10:16:59Z warning layer=rpc Listening for remote connections (connections are not authenticated nor encrypted)
リモートデバッグの接続
- golandの右上から
Edit Configuration
を選択します。 -
+
ボタンからGo Remoteを選択してください。 - Nameは適当な名前を入れてください。
- Hostはlocalhost, Portは2345を指定してください。
-
Leave it runnning
にチェックを入れて保存してください。
- 下記のような表示になっているはずなので、デバッグマークを押してください。
- 問題なければ下記のように
Connected
が表示されるはずです。
デバッグを試してみる
本番のソースコードとは全く違うのですが、こんなmain関数を用意しました。
GET http://localhost:8080/hello
でアクセスするとレスポンスが返ってくるだけです。
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", helloHandler)
http.ListenAndServe(":8080", nil)
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
test := "Hello Remote Debug!!"
fmt.Fprint(w, test)
}
実際にブレイクポイントを置いてAPIを叩いてみます。
下部のデバッグコンソールで、testの変数値などが見えているかと思います。
ソースコードを変更して保存すれば、ホットリロードも有効になっています。
注意点
どうやらM1 Macでplatformにlinux/amd64を指定したdocker環境ではdelveは使えないようです。
どういうことかというと、docker-compose.ymlを下記のように書き換えました。
golang1.19
はlinux/amd64
に対応していないimageのようだったので、golang:1.19-buster
に書き換えています。
version: '3.7'
services:
app:
+ platform: 'linux/amd64'
+ image: 'golang:1.19-buster'
- image: 'golang:1.19'
working_dir: '/app'
volumes:
- type: bind
source: '.'
target: '/app'
ports:
- '8080:8080'
- '2345:2345'
command: >
sh -c '
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/cosmtrek/air@latest
air -c .air.toml
'
他の設定は全てそのままにdocker-compose up
を実行します。
起動はできるものの could not launch process: fork/exec /app/.air/main: function not implemented
と言われてしまいました。
app | API server listening at: [::]:2345
app | 2022-12-19T10:37:43Z warning layer=rpc Listening for remote connections (connections are not authenticated nor encrypted)
app | could not launch process: fork/exec /app/.air/main: function not implemented
delveのissueを調べてみたらありました。
https://github.com/go-delve/delve/issues/2910
Nothing we can do anything about.
コメントのラリーもなくcloseされていました・・。無慈悲。
さいごに
やや中途半端ですが、delveを入れてリモートデバッグを実現しました。
本番環境のDockerfileではlinux/amd64
でgolang:1.19-buster
を使用しているので、このデバッグ環境100%信用して使うべきではないと思っています。
とはいえ、例えばチームメンバーからレビューが上がってきたときや複雑なロジックを書くときなどはデバッグがないと辛い・・・
そんな時は少しだけdocker-compose.ymlと.air.tomlを書き換えるだけでリモートデバッグ環境を構築することができるので、重宝しています。
特にgolangはfor文など愚直に書くことが多い言語なので、開発体験を整備すると効率があがりますね。。
なお、トレタではエンジニアの募集を行なっております。
興味がある方はぜひご連絡ください。