見出し画像

Go-Gin の開発とデバッグをDockerで効率化

ローカル環境をコンテナ化することには、実行環境と非常に近い環境でシステムを開発できる利点があります。これにより、「自分のPCでは動作するが開発環境や本番環境では動作しない」といった問題のほとんどが解決されます。

この記事では、Go-Gin 開発におけるデバッグとライブリロードを可能にする方法について説明します。他の開発環境にも同様に適用できると思います。

Go の docker コンテナでデバッグ機能とコードリロード機能を探していましたが、ニーズに合うものが見つかりませんでした。compile daemon と delve(Goデバッガ)を一緒に使用するなど、いくつかの方法を試しましたが、成功しませんでした。最終的に、ファイルの変更を監視するために inotify watchers を使用して、デバッガーの実行と保存後の再コンパイルを実現することができました。

この記事では以下の内容を扱います。

  • compile daemonの代わりにカスタムスクリプトを使用してサーバーを再起動するDockerイメージの設定

  • Goのdelveデバッガーを使用したデバッグ環境の設定

  • 設定した開発環境を使用して、VS Codeで設定を行いGoコードをデバッグ

説明を始める前に、Dockerコンテナの基礎知識があり、PCにDockerエンジンがインストールされていることを前提としています。
まず、docker-composeを使用して基本的なウェブサーバーを実行するための簡単なDocker環境をセットアップしましょう。server.goがサーバーのエントリーポイントとなり、以下のようになります。

package main

import (
	"log"

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

func main() {
	router := gin.Default()

	router.GET("/check", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "server is working",
		})
	})

	log.Print("Listening for requests in :5000")
	router.Run(":5000")
}

上記のコードは、:5000でリッスンする基本的なGinウェブサーバーです。サーバーが実行されウェブリクエストに応答しているかどうかは、curl -X GET localhost:5000で確認できます。
基本的なサーバーをセットアップしたので、次にdocker-compose.ymlファイルを使用してDockerコンテナで実行してみましょう。以下のようになります。

version: "3.3"

services:
  web:
    build:
      context: .
      dockerfile: ./web.Dockerfile
    ports:
      - 5000:5000
      - 5002:2345
    volumes:
      - .:/debug_env

ここでは、ウェブサーバー用に5000ポート、デバッグ目的で2345ポートを公開するwebサービスを実行しています。また、現在のディレクトリにあるすべてのファイルをコンテナ内の/debug_envフォルダにマウントしています。
web.Dockerfileについては、以下のようにgolang:alpineイメージを使用しています:

FROM golang:alpine

# Required because go requires gcc to build
RUN apk add build-base

RUN apk add inotify-tools

COPY . /debug_env

WORKDIR /debug_env

RUN go mod download

RUN go install github.com/go-delve/delve/cmd/dlv

CMD sh /debug_env/run.sh

ここでは、依存関係としてbuild-baseとinotify-toolsをインストールしています。これらは、16行目でコンテナのエントリーポイントとして使用するカスタムスクリプトrun.shでinotify watchersを使用するために必要です。8行目から14行目では、現在のディレクトリをコンテナ内にコピーし、作業ディレクトリを設定し、Go言語用のdelveデバッガーをダウンロードしています。
run.shスクリプトについては、以下のようにファイルの変更を監視し、デバッグサーバーの起動と停止を担当します:

while true; do
    echo "[run.sh] Starting debugging..."
    dlv debug --headless --log --listen=:2345 --api-version=2 --accept-multiclient --continue &

    PID=$!

    inotifywait -e modify -e move -e create -e delete -e attrib --exclude __debug_bin -r .

    echo "[run.sh] Stopping process id: $PID"
    
    kill -9 $PID
    pkill -f __debug_bin
done

ここで、3行目では:2345でリッスンするdelveを実行しており、これはdocker-compose.ymlファイルを通じて公開しています。以下のオプションを使用しています:

  • -headless:delveをヘッドレスモードで実行

  • -log:デバッグログを出力

  • -continue:最初の実行時にデバッグサーバーを継続。これがないと、プログラムは常に実行の開始時に停止し、続行シグナルを待ちます。

7行目では、delveデバッガーによって生成されるデバッグバイナリである__debug_binを除外して、現在のディレクトリ内のファイル変更を監視しています。
11行目と12行目では、以前実行されていたプロセスを終了させ、すべてが無限ループ上にあるため、再起動させています。
このようにして、現在のディレクトリ内のファイルに変更を加えると、現在実行中のデバッグセッションがGoバイナリとともに停止し、その後デバッグセッションが再起動します。
では、docker-compose up -dを実行してサーバーを起動しましょう。初回実行時は、イメージのダウンロードと不足している依存関係の解決に時間がかかりますが、その後のサーバーの起動時間は速くなります。

ウェブサーバーのログは、以下のコマンドで確認できます:docker-compose logs -f --tail=300 web これは、webコンテナの最後の300行のログを表示し、新しいログのために現在実行中のシェルをブロックします。docker-compose logs -f

設定が正しく機能しているかどうかは、localhost:5000/checkでサーバーにアクセスして確認できます。サーバーの出力メッセージを変更して保存し、サーバーが実際に再起動するかどうかを試すことができます。再起動が確認できれば、ライブリロード機能を持つ開発環境のセットアップに成功したことになります。では、アプリケーションをどのようにデバッグすればよいでしょうか?以下のセクションでは、Dockerコンテナ内でのリモートデバッグのためのVS Codeの設定を行います。

リモートデバッグ 

始める前に、docker-compose.ymlで5002ポートを公開していることを確認してください。このポートをデバッグ、つまり実行中のGoサーバーにデバッグセッションを接続するために使用します。ルートディレクトリの.vscodeフォルダ内にlaunch.jsonファイルを作成し、以下のJSON設定を記述します:

{
    "configurations": [
      {
          "name": "Attach to Remote",
          "type": "go",
          "request": "attach",
          "mode": "remote",
          "port": 5002,
          "host": "127.0.0.1",
          "cwd": "${workspaceFolder}",
      }
    ]
}

8行目で、VS Codeが接続すべきポートを指定しています。この設定により、VS Codeには以下の図のように、設定名としてAttach to Remoteオプションが表示されるはずです:

Attach to Remoteをクリックすると、VS CodeはDockerコンテナ内で実行中のデバッグセッションに接続します。これで、ブレークポイントを設定し、/checkルートにリクエストを送信したときにブレークポイントが適切に機能することを確認できます。

上図のように、デバッグセッションで値を継続または検査することができます。ライブリロード後はデバッガーが機能しなくなります。これはrun.shスクリプトで実行中のプロセスが終了されるためです。そのため、特定のファイルが変更された場合は、デバッガーを再接続する必要があるかもしれません。
このようにして、開発環境のデバッグとホットリロードのサポートを成功裏にセットアップしました。
ご清読ありがとうございました。良い一日をお過ごしください。🎉
この記事で使用したサンプルセットアップは、以下のGitHubリポジトリで見つけることができます:
dipeshdulal/docker-go-debugging
ホットリロード、Ginフレームワーク、GORM、依存性注入をクリーンアーキテクチャを念頭に置いて行った完全な開発セットアップについては、こちらのGitHubリポジトリをご覧ください。


この記事は、2020年11月に弊社のエンジニア Dipesh Dulal が執筆した内容を日本語に翻訳したものです。 英語版はこちらをご覧ください。
https://articles.wesionary.team/docker-debug-environment-for-go-and-gin-framework-36df80e061ac


採用情報

私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!


開発パートナーをお探しの企業様へ

弊社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ弊社にご相談ください。