airとdelveを共存させたままコンテナ内で動くGoのWebアプリケーションをデバッグする方法

# 概要
Golandを使ってコーディングしている時に、Dockerで立ち上げたwebアプリケーションをデバッグしたいと思い、リモートデバッグができるdelveを導入することにした。
DockerではAPIサーバとMySQLを動かしている。

# compose_dev.yaml
デバッグ用とデバッグなしで動かす用の２つを使い分けたいので、compose_dev.yamlとしている。
compose_dev.yamlは以下の通りにした。
```yaml
version: '3'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - '8080:8080'
      - '2345:2345'
    volumes:
      - '.:/go/src'
    depends_on:
      - 'db'
    tty:
      true
  db:
    image: 'mysql:8.0'
    ports:
      - '3306:3306'
    volumes:
      - './tmp/mysql:/var/lib/mysql'
```

* portsはAPI用の8080とリモートデバッグ接続用の2345の２つを開けている。
* dockerfileでDockerfile.devを指定している。

# Dockerfile.dev
Dockerfile.devは以下の通りにした。
```dockerfile
FROM golang:1.20 as builder

WORKDIR /go/src

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

ENV GO111MODULE=on

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

ENV DOTENV_PATH=/go/src/.env

COPY .air.toml ./

CMD ["air"]
```
* airとdelveの最新版をインストールしている
* .air.tomlを指定してairを実行する
* 開発で.envファイルを使用しているので`ENV DOTENV_PATH=/go/src/.env
`でパス指定しているが、人によるため必須ではない

# .air.toml
.air.tomlは以下の通りにした。

```toml
root = "."
tmp_dir = "tmp"

[build]
cmd = "dlv debug --headless --listen=:2345 --api-version=2 --log"
bin = "tmp/main"
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = []
include_dir = []
kill_delay = "1000ms"
stop_on_error = true
send_interrupt = true
log = "build-errors.log"

[color]
app = ""
build = "yellow"
runner = "green"

[log]
time = false
level = "debug"
```
この設定では、Airがソースコードの変更を検知すると、`dlv debug`コマンドを使用してアプリケーションをデバッグモードで再起動する。Delveのデバッグサーバーはポート2345でリッスンし、IDEなどの外部ツールから接続可能になっている。

# Golandの設定
* golandの右上から`Edit COnfiguration`を選択する。
* `+`ボタンからGo Remoteを選択する。
* Hostはlocalhost, Portは2345を指定する。
* `Ask`にチェックを入れて保存する。

![スクリーンショット 2023-11-28 11.49.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268421/23fb126b-e418-dee0-2c70-5c5449b43ca3.png)



# デバッグの実行
この設定で`docker-compose -f compose_dev.yaml  up --build
`をするとターミナルに以下のようなログが表示される。
```shell
app | API server listening at: [::]:2345
app | 2023-11-28T02:50:45Z warning layer=rpc Listening for remote connections (connections are not authenticated nor encrypted)
app | 2023-11-28T02:50:46Z debug layer=debugger Adding target 126 "/go/src/__debug_bin2673177628"
```

この状態で、ブレイクポイントを適当に配置し、Golandのデバッグを実行する。以下のような画面になると成功である。

![スクリーンショット 2023-11-28 11.55.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268421/bc9888bd-9edb-48b6-0b7a-7efc5b7029fe.png)

# （補足）.air.tomlを指定しない通常の実行方法を共存させる方法
delveを使わない場合は.air.tomlを使わないこともあると思うので、通常時に.air.tomlを無視する方法について説明する。
まず、compose.yamlを以下のようにする。
```yaml
version: '3'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '8080:8080'
    volumes:
      - '.:/go/src'
    depends_on:
      - 'db'
    tty:
      true
  db:
    image: 'mysql:8.0'
    ports:
      - '3306:3306'
    volumes:
      - './tmp/mysql:/var/lib/mysql'
```
上で解説したcompose_dev.yamlと違う点は指定しているdockerfileだけである。

次にDockerfileの内容を以下のようにする。
```Dockerfile
FROM golang:1.20 as builder

WORKDIR /go/src

RUN go install github.com/cosmtrek/air@latest

ENV GO111MODULE=on

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

ENV DOTENV_PATH=/go/src/.env

CMD ["air", "-c", "empty.air.toml"]
```
コマンドに`air -c empty.air.toml`としている。これで後述する空のtomlファイルを指定している。
そして、empty.air.tomlというファイルを作成し、内容は空で保存する。
これで`docker-compose up --build`とすれば、airだけが効いたDockerを立ち上げることができる。