Goland で Docker を使った開発環境を作成する #golang #goland

概要

ネットに十分な情報はあるのだが、なんやかんや毎回詰まったりちょこちょこ調べたりしているので自分用にまとめておく。

  • Goland を使う
  • Docker で動かす Web アプリケーション
  • air でホットリロード
  • delve でリモートデバッグ
  • テストも Docker 経由でデバッグ

今回のサンプルは GitHub に置いてある。

github.com

雛形の作成

mkdir go-dev-starter
cd go-dev-starter
# 初期化
go mod init

Hello World を表示するだけの Web アプリケーションをサンプルとして作成する。

ポートだけ環境変数で変えられるようにしてある。

package main

import (
    "fmt"
    "net/http"
    "os"
)

func main() {
    err := http.ListenAndServe(
        fmt.Sprintf(":%s", getEnv("PORT", "18080")),
        http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintln(w, "Hello, world!")
        }))

    if err != nil {
        fmt.Printf("failed to terminate server: %v", err)
        os.Exit(1)
    }
}

// getEnv は指定した key の環境変数を取得します
// 環境変数が未設定の場合 fallback の値を返します
func getEnv(key, fallback string) string {
    if value, ok := os.LookupEnv(key); ok {
        return value
    }
    return fallback
}

Dockerfile の作成

delve と air を入れた Dockerfile を作成する。

今回は分かりやすくするために、開発用のみ記載している。

FROM golang:1.19  
  
WORKDIR /app  
RUN go install github.com/cosmtrek/air@latest  
RUN go install github.com/go-delve/delve/cmd/dlv@latest  
  
CMD ["air"]

docker-compose.yml の作成

docker-compose.yml を用意する。

security_opt と cap_add は delve のリモートデバッグ用に設定してある。

40000 番のポートはリモートデバッグで Listen する用。

version: "3.9"  
services:  
  app:  
    container_name: dev-server  
    build:  
      context: .  
    environment:  
      PORT: 8080  
    volumes:  
      - .:/app  
    ports:  
      - "18000:8080"  
      - "40000:40000"  
    security_opt:  
      - "apparmor=unconfined"  
    cap_add:  
      - SYS_PTRACE

air の設定ファイルの作成

コンテナ内で air init すると設定ファイルの雛形が作成される。

docker compose up -d
docker compose exec app air init

ビルドコマンドと full_bin を delve を使うように書き換える。

--- a/.air.toml
+++ b/.air.toml
@@ -5,14 +5,14 @@ tmp_dir = "tmp"
 [build]
   args_bin = []
   bin = "./tmp/main"
-  cmd = "go build -o ./tmp/main ."
+  cmd = "go build -gcflags=\"all=-N -l\" -v -o ./tmp/main ."
   delay = 1000
   exclude_dir = ["assets", "tmp", "vendor", "testdata"]
   exclude_file = []
   exclude_regex = ["_test.go"]
   exclude_unchanged = false
   follow_symlink = false
-  full_bin = ""
+  full_bin = "dlv --listen=:40000 --headless=true --api-version=2 --accept-multiclient exec ./tmp/main"
   include_dir = []
   include_ext = ["go", "tpl", "tmpl", "html"]
   kill_delay = "0s"

Docker の実行構成を作成

Goland 側から実行できるようにしておく。

リモートデバッグの実行構成を作成

ここでリモートデバッグ用のポートを設定しておく。

今回の場合だと 40000 番。

デバッグ実行を試す

作成した Docker のリモートデバッグの構成を実行し、ブレイクポイントを貼る。

エンドポイントにアクセスするとブレイクポイントで止まってくれる。

curl localhost:18000

ホットリロードを試す

適当に書き換えると、air が自動でリビルドしてアプリケーションを起動し直してくれる。

ただリモートデバッグが一度切れてしまうので再実行する必要がある。

少し面倒だが、 Ctrl + D のショートカットを使えるので許容範囲かな。。(うまい方法あったら教えてほしい)

テストの追加

とりあえず試すだけなので、環境変数設定のテストを書く。

package main  
  
import (  
   "os"  
   "testing")  
  
func Test_getEnv(t *testing.T) {  
   want := "9999"  
   key := "FOO"  
   err := os.Setenv(key, want)  
   if err != nil {  
      t.Fatalf("failed to set env: %v", err)  
   }  
  
   if got := getEnv(key, "8080"); got != want {  
      t.Errorf("getEnv() = %v, want %v", got, want)  
   }  
}

Run Target に Docker を追加

docker compose 経由で実行できるターゲットを追加しておく。

デフォルトターゲットも変更しておく。

テストのデバッグ

テストのデバッグ実行も Docker 経由で実行できることを確認する。

IDE からテストを実行してみる。

テスト実行後のログを見ると、Docker 経由で実行されていることが分かる(go や delve のパスで分かる)。

ブレイクポイントを認識してくれないことがあるが、一度止めずに実行すると認識してくれる。

参考