[VScode] Docker+Go+air+delveでリモートデバッグ

## はじめに
Docker上で動いているGo言語のアプリケーションを、VScodeでリモートデバッグします。
その際に、デバッガツールの [delve](https://github.com/go-delve/delve) と、ホットリロードツールの [air](https://github.com/cosmtrek/air) を使います。


## ディレクトリ構成

```
├── .vscode
│   └── launch.json
├── cmd
│   ├── .air.toml
│   ├── go.mod
│   └── main.go
├── Dockerfile
└── docker-compose.yml
```

## Dockerfile
まず、Dockerで以下の２つをインストールします。

```Dockerfile
FROM golang:1.17.2

RUN go install github.com/go-delve/delve/cmd/dlv@latest
    
RUN go install github.com/cosmtrek/air@latest
```

## docker-compose.yml

```yaml
version: '3.7'
services:
  app:
    build: .
    ports:
      - "2345:2345" #delve用のポート
    volumes:
      - ./:/go/src
    working_dir: /go/src/cmd
    command: air -c .air.toml
```

commandでairを実行しています。
（airの設定ファイル`.air.toml`の中で、delve実行も設定している）

## .air.toml

[air_example.toml](https://github.com/cosmtrek/air/blob/master/air_example.toml)
こちらをコピペして使いました。
変更ポイントは、`cmd`と`full_bin`だけです。

```toml
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format

# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"

[build]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -gcflags \"all=-N -l\" -o tmp/main ."
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary.
full_bin = "dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./tmp/main"
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file places in your tmp_dir.
log = "air.log"
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 1000 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # ms

[log]
# Show log time
time = false

[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# Delete tmp directory on exit
clean_on_exit = true
```

+ `cmd` = `"go build -gcflags \"all=-N -l\" -o tmp/main ."`
`dlv exec` を使うためには、`-gcflags=all='-N -l'`が必要になります。
> [go build](https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies)
> -gcflags '[pattern=]arg list'
>        arguments to pass on each go tool compile invocation.


+ `full_bin` =
`"dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec ./tmp/main"`
> cf.　[dlvコマンドのoption詳細](https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv.md#options)
> --api-version int                  Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
>  --accept-multiclient               Allows a headless server to accept multiple client connections.


## .vscode/launch.json

```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Remote",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "remotePath": "/go/src/cmd",
            "port": 2345,
            "host": "localhost",
            "cwd": "${workspaceRoot}/cmd",
            "env": {},
            "args": [],
        }
    ]
}
```

+ `name`: お好きな名前
+ `type`: デバッガーの種別
+ `request`: デバッグ時にプログラムを起動するか（launch）、  
既に起動しているプログラムにデバッガーをアタッチするか（attach）
+ `remotePath`: コンテナ内のパス
+ `port`: dlvのポート
+ `cwd`: デバッグ実行を行うディレクトリ
+ `${workspaceRoot}`: VSコードで開いたディレクトリのパス

詳細：[launchjson-attributes](https://code.visualstudio.com/Docs/editor/debugging#_launchjson-attributes)