DevContainerでGo×Air×Delveの開発環境を構築してみる（+MySQL）

# はじめに
どうも、水無月せきなです。 
Goの勉強のために小さなアプリを作ろうと思い立ったので、そのための環境構築に至る手順を備忘録的に記します。

# 環境
Windows 10　22H2
WSL2
Docker Desktop 4.41.2
Cursor 0.51.2

# 事前準備
* WSLの設定・Dockerはインストール済み
* gitignoreは作成済み

# 環境の構築
## 設定ファイルなどの準備
（１）`Dockerfile`と`compose.yml`を作成する

```dockerfile:Dockerfile
FROM golang:latest

ARG UID=20000
ARG GID=20000
RUN groupadd -g $GID dev \
    && useradd -m -u $UID -g $GID dev
# bashに変更
RUN chsh -s /bin/bash dev
USER dev

WORKDIR /app
COPY --chown=dev:dev . .

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

```yml:compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        UID: ${UID:-20000}
        GID: ${GID:-20000}
    tty: true
    volumes:
      - .:/app
    ports:
      - 8080:8080
      - 2345:2345
```

（２）自身のUIDとGIDを調べる
環境を作るだけなら不要だと思いますが、パーミッション周りで面倒が起きないようにやります。

```bash:bash
$ whoami
user
$ id user
uid=1000(user) gid=1000(user)……（後略）
```

（３）.envファイルを作成する
`.env`ファイルを作成し、（２）で調べたUIDとGIDを記載します。
この時、.envファイルはgitignoreで管理から外してください。
```text:.env
UID=1000
GID=1000
```

（４）`launch.json`を追加する
デバッグ用の設定を記載します。

```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"
        }
    ]
}
```

## DevContainerの設定ファイルを作成
（１）エディター左下の><をクリックする
![go-todo-0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/61bb32ba-e785-4943-9ae3-7ccc47741b1a.png)

（２）ポップアップするコマンドパレットからメニューを選択する
`Add Dev Container Configuration Files…`を選択してください。

![go-todo-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/e65ad02b-3de6-49a8-8bf5-8696c3d06c7b.png)

（３）`From 'compose.yml'`を選択する
今回はcompose.ymlを作成しているため、こちらを選択します。
![go-todo-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/1c86bf90-535f-4136-81a3-2afd453cd641.png)

（４）Featureを選択する
任意ですが、GoのFeatureを一つ入れました。
![go-todo-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/3a85b7be-9fe4-4bb7-8c70-01e87aa591a3.png)

（５）Featureの設定をする
今回はデフォルトのままで進行します。
![go-todo-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/2b267afe-8e82-42e9-b0fc-365b2f416ab9.png)

（６）オプションの選択
任意ですが、一応`dependabot`を入れました。
![go-todo-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/7401d868-0285-4114-b1f2-b010c067f391.png)

（７）作成された`devcontainer.json`の名前と`workspacefolder`を変更する

変更前
```json:devcontainer.json
{
	"name": "Existing Docker Compose (Extend)",
    
    ……中略……
    
	"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

    ……後略……
}

```

変更後

```json:devcontainer.json
{
	"name": "to-do-go-app-dev-container",

    ……中略……
    
	"workspaceFolder": "/app",

    ……後略……
}

```

（８）devcontainer用のcompose.ymlでボリュームのマウントを変更する
変更前
```yml:.devcontainer/docker-compose.yml
version: '3.8'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  app:
    ……中略……

    volumes:
      # Update this to wherever you want VS Code to mount the folder of your project
      - ..:/workspaces:cached

    ……後略……
 

```

変更後
```yml:.devcontainer/docker-compose.yml
version: '3.8'
services:
  # Update this to the name of the service you want to work with in your docker-compose.yml file
  app:
    ……中略……

    volumes:
      # Update this to wherever you want VS Code to mount the folder of your project
      - .:/app:cached
　　
  ……後略……
 
```

## コンテナでの作業
（１）一度コンテナを起動する
エディタ左下の><をクリックし、ポップアップから`Reopen Container`を選ぶ
![go-todo-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/6b431200-650a-4be7-8b83-22192ab00bec.png)

（２）`air init`を実行する
立ち上がったコンテナで、`air init`を実行する（場所はルートディレクトリ）。
.air.tomlが生成されるので、`cmd`と`full_bin`を修正する
```toml
[build]
  cmd = "go build -gcflags \"all=-N -l\" -o ./tmp/main"
  full_bin = "dlv exec --headless --listen=:2345 --accept-multiclient --continue ./tmp/main"
```

（３）`go mod init`を実行する
`go.mod`作成のため、`go mod init {モジュール名}`を実行します。

（４）`devconatainer.json`の`postcreatecommand`を修正
コンテナ起動時にAirも起動するように設定します。
```json:devconatainer.json
	"postStartCommand": "cd /app && air -c .air.toml"
```

（５）`Dockerfile`も修正する
```docker:Dockerfile
FROM golang:latest

ARG UID=20000
ARG GID=20000
RUN groupadd -g $GID dev \
    && useradd -m -u $UID -g $GID dev
# bashに変更
RUN chsh -s /bin/bash dev
USER dev

WORKDIR /app
COPY --chown=dev:dev . .

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
```

## 環境構築の終了
左下の><押下→ポップアップで`Rebuild Container`を選択します。
![go-todo-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/bdd7a1f1-a3a0-4bd2-bb64-b58b23e86e56.png)
![go-todo-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1843472/9e96c91f-27a6-4e34-8ce6-5f18e36d25e5.png)

## DBの追加
ここまででGoの開発・実行ができる環境は整いましたが、DBが無いので追加します。

（１）`compose.yml`の修正
`db`のセクションをまるっと追記し、`app`に`depends_on`も追記します。
また、DBへの接続情報を持つファイルを見れるようにします。

```yml:compose.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      args:
        UID: ${UID:-20000}
        GID: ${GID:-20000}
    depends_on:
      - db
    env_file:
      - ./db/db.env
    tty: true
    volumes:
      - .:/app
    ports:
      - 8080:8080
      - 2345:2345
  db:
    build:
      context: ./db
      dockerfile: db.Dockerfile
    env_file:
      - ./db/db.env
    ports:
      - 3306:3306
    volumes:
      - ./db/data:/var/lib/mysql
```

（２）DB用`Dockerfile`の追加
`compose.yml`で`image`を指定しても良いと思いますが、今回はDockerfileで作ってみます。

```dockerfile:db.Dockerfile
FROM mysql:8.0-debian

RUN apt-get update && \
    apt-get install -y locales

RUN locale-gen ja_JP.UTF-8

RUN localedef -f UTF-8 -i ja_JP ja_JP.UTF-8

ENV LANG=ja_JP.UTF-8
ENV TZ=Asia/Tokyo
```

（３）`db.env`に接続情報を追記する
DB接続のための情報を`db.env`ファイルに記載します。

```text:db/db.env
MYSQL_ROOT_PASSWORD=mysql
MYSQL_DATABASE=mysql_study
MYSQL_USER=mysql
MYSQL_PASSWORD=mysql
```

（４）`.gitignore`の修正
gitのコミットにDB関連が含まれてしまうため、含まれないようにします。

```text:.gitignore
……前略……
db/data/
db/db.env
```

（５）Airの設定ファイル`.air.toml`を修正する
デフォルトのままだと、DBのデータ保存先としてマウントしたフォルダも監視するため、権限エラーになってしまいます。
そのため、`build`の項目の`exclude_dir`にDBのデータ保存先のフォルダを指定します。
```toml:.air.toml
[build]
  # ……中略……
  exclude_dir = ["assets", "tmp", "vendor", "testdata", "db"]
  # ……後略……
```

（６）コンテナをリビルドする
左下の><押下→ポップアップで`Rebuild Container`を選択します。
DevContainerの外で作業していた場合は、`Reopen Container`でリビルドが走ると思います。

# 参考文献
## Dev Container での環境構築
https://plot-hub.com/blog/software/connect-go-docker-to-vscode-devcontainer/

https://zenn.dev/ponko22/articles/084_go_2_hotreload_debug

https://qiita.com/bigbridge/items/71a491848069a6dc40d6

https://zenn.dev/otot_dev/articles/f5492f30f323f5

## DevContainer起動後のコマンド実行
https://code.visualstudio.com/remote/advancedcontainers/start-processes

https://qiita.com/1mono2/items/5bbf91f588ab9d5cd444

## Dockerのパーミッション問題について
https://zenn.dev/forrep/articles/8c0304ad420c8e

https://ken5scal.hatenablog.com/entry/2017/10/13/Dockerfile%E3%81%AEADD/COPY%E3%81%AB--chown%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%9F

## DB（MySQL）の構築について
https://zenn.dev/bloomer/articles/7c879ab2a67abc

https://docs.docker.com/compose/how-tos/startup-order/

https://gotohayato.com/content/533/