😸

【解説】Goでホットリロード+デバッグ実行する【air + delve + VSCode + Dev Container】

2025/03/24に公開

はじめに / この記事で解説すること

  • アプリケーション開発時、コード変更後に毎回再起動するのが面倒なのでホットリロードを導入します。
  • エラー解析のため、デバッグ実行環境も導入。
  • VS Code, Dev Container を連携させることで、ローカル環境を汚さず、チームで共通した環境を作成できます。

なぜ air + delve + VSCode + Dev Container を使うのか? / 構成のメリット

  • air: ホットリロードによる開発効率の向上
    • コードを変更するたびにアプリケーションを再起動する必要がなくなる
    • 開発サイクルが短縮され、生産性が向上する
  • delve: デバッグ機能
    • ブレークポイントの設定、ステップ実行、変数の確認/変更が可能
    • 実行中のアプリケーションの状態を詳細に分析できる
  • VSCode: IDE、デバッグのクライアントとして利用
  • Dev Container: 開発環境の再現性、チーム開発の効率化
    • 開発環境をコードで定義し、チーム全体で共有できる
    • 環境構築の手間を省き、すぐに開発に取りかかれる
    • 異なるOSやバージョンのGoでも、同じ環境で開発できる

デバッグの仕組みとデバッグ実行の流れ

  • デバッグ実行の基礎知識や仕組みについて
    • DelveはGoで開発されたコマンドラインツール。インストールすると基本はサーバー内部の「$HOME/go/bin」に配置される。
      (dlv version で確認可能)
    • 起動中のアプリケーションにアタッチ(接続するイメージ)でデバッグサーバーが起動する。(ポート番号2345)
    • 起動中のデバッグサーバーに対し、デバッグクライアント(VSCode)から操作することでブレークポイントを設定
      ※デバッグクライアントがデバッグサーバーとのセッションを確立(launch.json)し、GUI上でブレークポイントを設定することでデバッグサーバーへ「ブレークポイントを設定してね」という通信が行われ、デバッグサーバーがOSのAPIを利用してアプリケーションに介入しブレークポイントを設定する。
    • デバッグサーバーはデバッグ情報(後述)を利用して実行中のアプリケーションの状況を分析し、ブレークポイントやウォッチ機能をトリガーにアプリケーションへ介入(一時停止、変数の読み書き、ステップ実行)する。(OSのAPIを利用してメモリ空間にアクセスし値を差し替える)
    • 普通にビルドを行うとデバッグ情報が含まれずデバッグ実行ができないため、アプリケーションのコンパイル時にデバッグ情報(ソースコードの行番号、変数名、型情報、メモリ上のアドレスの対応、関数の情報など)を埋め込むように指示してビルドを行う。(go build -gcflags "all=-N -l" )

デバッグ実行のパターン

  1. delveのみ(デバッグのみ)
    • ホットリロードができない
  2. delve+air(デバッグ+ホットリロード)
    1. コマンドラインでデバッグ(非推奨)

      • 起動方法
        • airを起動(.air.tomlにデバッグ情報を含んでビルドする設定追記)→アプリケーションのプロセスIDを特定→delveでアプリケーションにアタッチ(デバッガサーバーを起動)
      • デメリット
        • ホットリロードでアプリケーションが再起動する度にデバッガをアタッチする必要があり手間がかかる
        • ブレークポイントや変数の操作をコマンドライン上で行う必要があり操作性が悪い
      • 手順
        • air.tomlを修正(ビルド)

          .air.toml・・・cmd = "go build -gcflags 'all=-N -l' -o ./tmp/main .”
          
        • コマンド実行

          air
          ps aux
          dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec --continue tmp/main
          dlv attach <PID>
          
    2. VSCodeを使ってデバッグ(今回はこちら)

      • 設定
        • air設定(.air.tomlにデバッグ情報を含んでビルドする設定、同時にdlvを起動する設定を追記)
        • launch.jsonにデバッグ構成を定義
      • 起動方法
        • airを起動(同時にdlvも起動)
          • アプリケーションが8080でlisten
          • デバッガ(デバッグサーバー)も起動し2345がlisten
        • VSCode(デバッガクライアント)でdelveとの接続を確立し、httpでデバッグを指示
      • メリット
        • ブレークポイントや変数の操作をVSCodeのGUI上で行うことができるので操作性がいい
        • ホットリロード後、同時にデバッガも起動してくれる

環境構築

  • 前提
    • ホットリロードはairを利用
    • 環境はdev containerを利用
  • 設定
    • dockerfileの設定

      # ビルドステージ: Go の公式イメージを使用
      FROM golang:1.23-alpine
      
      # モジュールファイルとソースコードのコピー
      WORKDIR /app
      COPY ./ .
      
      RUN go mod tidy
      RUN go install -v github.com/air-verse/air@latest
      RUN go install -v github.com/go-delve/delve/cmd/dlv@latest
      
      # ポート8080を公開
      EXPOSE 8080 2345
      
      WORKDIR /app/cmd
      
      # アプリケーションの起動
      CMD ["tail", "-f", "/dev/null"]
      
      
      • 説明
        • RUN go install -v github.com/air-verse/air@latest・・・air(ホットリロードツール)をインストール
        • RUN go install -v github.com/go-delve/delve/cmd/dlv@latest・・・delve(デバッガサーバー)をインストール
        • CMD ["tail", "-f", "/dev/null"]・・・起動はdevcontainerの「postCreateCommand」で行う(dockerfileでの起動だとバックグラウンドでの起動になるため、ターミナル上でairのログが確認できない)
          そのため、コンテナの維持目的のコマンド
    • .air.tomlの設定

      [build]
        cmd = "go build -gcflags \"all=-N -l\" -o ./tmp/main"
        full_bin = "dlv exec --headless --listen=:2345 --accept-multiclient --continue ./tmp/main"
      
      • 説明
        • cmd = "go build -gcflags "all=-N -l" -o ./tmp/main"・・・airがファイルの変更を検知した際に実行するビルドコマンド。
          • -gcflags "all=-N -l"・・・Goコンパイラに渡す引数。ここでは「all=-N -l」を渡している。all は、すべての Go ファイルにこのフラグを適用することを意味する。
            • -N・・・最適化を無効(有効な場合、デバッグ時に変数が消える)
            • -l・・・インライン展開を無効
        • full_bin = "dlv exec --headless --listen=:2345 --accept-multiclient --continue ./tmp/main"・・・ビルド後にairが実行するコマンド。(空の場合おそらく./tmp/mainを実行するが、ここではデバッグ実行を指示)
          • --headless・・・ヘッドレスモードでデバッガを起動。(デバッガサーバーはAPIを介して操作される)
          • --accept-multiclient・・・複数のデバッガクライアントの接続を受け付け
          • --continue・・・プログラムを即時実行する(このコマンドがないとホットリロード後にアプリケーションが実行されず待機状態になる)
          • ./tmp/main・・・実行するバイナリファイルのパス
    • launch.jsonの設定

      {
          "version": "0.2.0",
          "configurations": [
              {
                  "name": "Attach to Remote Delve",
                  "type": "go",
                  "request": "attach",
                  "mode": "remote",
                  "remotePath": "${workspaceFolder}",
                  "port": 2345,
                  "host": "localhost",
                  "showLog": true,
                  "console": "integratedTerminal",
                  "trace": "verbose"
              }
          ]
      }
      
      • 説明
        • name・・・デバッグ名(プルダウンに表示される名前)
        • type・・・言語のデバッグをサポートする VS Code 拡張機能を設定。(ここではgo)
        • request・・・すでに実行中のプロセスにアタッチしてデバッグを開始
        • remotePath・・・プロジェクトルートを設定
        • 他設定については別記事「【launch.json】VSCodeのlaunch.jsonに関するメモ【air + delve + vscode】」を参照ください。
    • devcontainer.jsonの設定

      	"postCreateCommand": "cd /app/cmd && air -c .air.toml",
      
      • 説明
        • postCreateCommand・・・コンテナ起動後に実行するコマンド

デバッグ実行手順(初回起動)

  1. Dev Container を起動する(自動でair、delveも起動)
  2. VS Code のデバッグビューを開き、"Attach to Remote Delve" 構成を選択
  3. デバッグを開始を選択
  4. VS Code が Delve に接続し、デバッグセッションが確立される
  5. ブレークポイントを設定し、コードを実行する

デバッグ実行手順(コード変更時)

  1. コード変更を変更する
  2. airの機能で自動でビルドしてアプリケーションが再起動、同時にデバッグサーバーも起動する
  3. VS Code のデバッグビューを開き、"Attach to Remote Delve" 構成を選択
  4. デバッグを開始を選択
  5. VS Code が Delve に接続し、デバッグセッションが確立される
  6. ブレークポイントを設定し、コードを実行する

トラブルシューティング / 詰まったところ

  • air実行時にはtomlファイルも指定して実行する(指定しない場合カレントディレクトリのtomlファイルを参照するが、tomlファイルがない場合や設定が正しくない場合はtomlファイルは反映されない)

まとめ

  • この記事で解説した内容のまとめ
    • air, delve, VS Code, Dev Container を連携させることで、Go アプリケーションの効率的な開発環境を構築できる
    • ホットリロードとデバッグ実行を組み合わせることで、開発サイクルを大幅に短縮できる
  • ご意見やご感想、改善点などがあれば、コメント欄にお気軽にご記入ください。

Discussion