VSCodeでGO+air+delveのDocker環境をリモートデバッグしよう！

## 概要
VSCodeを使ってGO、air、delveのDocker環境をリモートデバッグする方法について解説します

## 前提
- WebフレームワークはGinを使用
- ORMはPrismaを使用
- GO 1.21を使用

## ディレクトリ構成
```
tree
.
├── .env
├── .gitignore
├── Makefile
├── README.md
├── application
│   ├── .air.toml
│   ├── .vscode
│   │   └── launch.json
│   ├── config
│   ├── controllers
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   ├── prisma
│   ├── routes
│   ├── serializers
│   └── services
├── containers
│   ├── go
│   │   ├── Dockerfile
│   │   └── Dockerfile.debug
│   └── postgres
│       └── Dockerfile
└── docker-compose.yml
```

## Dockerfileの作成
GOのDockerfileを作成します
airとdelveをインストールしたいのでDockerfile内に記述します
今回はORMにPrismaを使用していますが使わない方はprisma-client-goの記述を削除していただいて問題ないです

```Dockerfile:containers/go/Dockerfile
FROM golang:1.21

WORKDIR /usr/local/go/src/gin-crm/

RUN go install github.com/cosmtrek/air@v1.42.0 && \
    go install github.com/go-delve/delve/cmd/dlv@latest

COPY application/go.mod application/go.sum ./
RUN go mod download

RUN go run github.com/steebchen/prisma-client-go prefetch

COPY application/. /usr/local/go/src/gin-crm/
RUN go run github.com/steebchen/prisma-client-go generate

```

PostgresのDockerfileを作成します
```Dockerfile:containers/postgres/Dockerfile
FROM postgres:16.2

```

## docker-compose.ymlの作成
docker-compose.ymlを作成します
delve用に2345番ポートを開放します

```docker-compose.yml
version: '3.9'

services:
  app:
    container_name: app
    build:
      context: .
      dockerfile: containers/go/Dockerfile
    volumes:
      - ./application:/usr/local/go/src/gin-crm
    ports:
      - '2345:2345'
      - '8000:8000'
    command: air -c .air.toml
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
    networks:
      - proxynet
  db:
    container_name: db
    build:
      context: .
      dockerfile: containers/postgres/Dockerfile
    volumes:
      - db_data:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready -U "${POSTGRES_USER:-postgres}" || exit 1
      interval: 10s
      timeout: 5s
      retries: 5
    environment:
      - POSTGRES_NAME
      - POSTGRES_USER
      - POSTGRES_PASSWORD
    ports:
      - '5432:5432' # デバッグ用
    networks:
      - proxynet
  mail:
    container_name: mail
    image: schickling/mailcatcher
    ports:
      - '1080:1080'
      - '1025:1025'
    networks:
      - proxynet
networks:
  proxynet:
    name: testnet
    external: false
volumes:
  db_data:

```

## air.tomlの作成
delveを使ってリモートデバッグをする際は
```toml
# GO　1.10以降ではgcflags="all=-N -l"を使う
cmd = "go build -gcflags \"all=-N -l\" -o tmp/main ."
# docker-compose.ymlで指定した2345ポートをlistenする必要がある
full_bin = "dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./tmp/main"
```

にする必要があります

> dlv exec
Execute a precompiled binary, and begin a debug session.
Synopsis
Execute a precompiled binary and begin a debug session.
This command will cause Delve to exec the binary and immediately attach to it to begin a new debug session. Please note that if the binary was not compiled with optimizations disabled, it may be difficult to properly debug it. Please consider compiling debugging binaries with -gcflags="all=-N -l" on Go 1.10 or later, -gcflags="-N -l" on earlier versions of Go.

https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_exec.md

```air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -gcflags \"all=-N -l\" -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 = "dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./tmp/main"
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false

[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"

[log]
main_only = false
time = true

[misc]
clean_on_exit = false

[screen]
clear_on_rebuild = false
keep_scroll = true

```

## launch.jsonの作成
launch.jsonに以下のように記載します
remotePathにはvolumeのマウント先、ポートはdelveの2345を指定します

```launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Remote",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "remotePath": "/usr/local/go/src/gin-crm",
            "port": 2345,
            "host": "localhost",
            "cwd": "${workspaceRoot}",
        }
    ]
}

```

## 実際に実行してみよう!
```
docker-compose up -d --build
```
でコンテナのビルドと起動をした後にブレークポイントを設定し、APIを実行します

![スクリーンショット 2024-05-31 11.32.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/d49f503d-c335-f079-1271-c35aea3c4581.png)

以下のように設定したブレークポイントで処理が止まったら成功です

![スクリーンショット 2024-05-31 11.33.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/634ab23f-18cd-40a7-b6cf-b5ec936a1cb8.png)

ライブラリのコードも以下のようにデバッグできます
![スクリーンショット 2024-05-31 11.34.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/4cf93908-2a45-b1ac-fabd-a6cda246262c.png)

デバッグコンソールから変数の中身を確認することもできます
![スクリーンショット 2024-05-31 11.51.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/abe47261-75a1-559d-784b-7ebb4ea50ea3.png)

## 参考
https://github.com/golang/vscode-go/wiki/debugging

https://github.com/jackyzha0/go-remote-debug

https://qiita.com/masataka715/items/f87afa3e7f2c4e640ba7

https://zenn.dev/kngnkg/articles/d223b58235a39c