Docker, VSCode Remote Container, Air による Go 開発環境構築

# はじめに

## 概要

**Docker と VSCode Remote Container による Go の Web サーバ開発環境を構築する記事です**。
ローカルに Go をインストールすることなく、

- コード補完
- コード変更があるたびに再ビルド（ライブリロード）

の Go 開発環境を構築します。

## リポジトリ

この記事で作成する内容は GitHub に上げています。

https://github.com/hatahata7757/sample-go-dev-env

## 背景

業務で Go を触ることとなり、勉強するにあたって手軽な Go の開発環境の構築方法を調べていました。
Docker を使った Go の開発環境を構築する方法はいくつか情報があったのですが、コード補完やフォーマッタを効かせるために結局ローカルに Go をインストールする必要があり、少し微妙でした。
また、簡単な Web サーバを書いていたのですが、ソースコードを修正するたびにいちいちビルドし直すのが面倒でした。

色々と試している中で、

- ローカルに Go を入れないといけない問題: [**VSCode の拡張機能 Remote Container**](https://code.visualstudio.com/docs/remote/containers)
- いちいちビルドし直す問題： [**Go のパッケージ Air**](https://github.com/cosmtrek/air)

で解決できることが分かり、本記事はその成果物です。

**できるだけ手軽に、Go の Web サーバ開発環境を整えたい人向けの記事となっています。**

# 開発環境の構築
# 動作環境
<img src="https://img.shields.io/badge/Docker Desktop for Mac-3.3.1-2496ED.svg?logo=docker&style=plastic"> <img src="https://img.shields.io/badge/Docker Engine-20.10.5-2496ED.svg?logo=docker&style=plastic"> <img src="https://img.shields.io/badge/MacOS Catalina-10.15.7-999999.svg?logo=apple&style=plastic">
## ディレクトリ構造

```
.
|
├── .devcontaier/
|      └── devcontainer.json
├── .air.toml
├── docker-compose.yml
└── Dockerfile
```

### コンテナ設定

```Dockerfile:Dockerfile
FROM golang:1.16.3-alpine

ENV GO111MODULE on

WORKDIR /go/src/app

RUN apk update \
  && apk add git \
  && go install github.com/cosmtrek/air@latest \
  && go get -u golang.org/x/tools/gopls \
  github.com/ramya-rao-a/go-outline

```

ここでは、Go 拡張機能で使用する ranguage server に必要な gopls や、ライブリロードに必要な Air のバイナリインストール＆ビルドを行っています。

```yml:docker-compose.yml
version: "3.8"

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: "air"
    tty: true
    stdin_open: true
    command: "air"
    volumes:
      - ./app:/go/src/app
    ports:
      - 8080:8080
    security_opt:
      - apparmor:unconfined
    cap_add:
      - SYS_PTRACE
```

### ライブリロード設定

.air.toml で、ライブリロードの設定を行います。
今回は、公式のサンプルコードをそのまま拝借して使用します。

https://github.com/cosmtrek/air/blob/master/air_example.toml

### コンテナ接続設定

VSCode Remote Container 用の設定をします。

```json:.devcontainer/.devcontainer.json
{
	"name": "Go-Practice",
	"dockerComposeFile": [
		"../docker-compose.yml",
	],
	"service": "web",
	"workspaceFolder": "/go/src/app",
	"settings": {
		"terminal.integrated.shell.linux": "/bin/ash",
		"go.toolsManagement.checkForUpdates": "off",
		"go.gopath": "/go",
		"go.gocodeAutoBuild": true,
		"go.formatTool": "gofmt",
		"go.useLanguageServer": true,
		"editor.formatOnSave": false,
		"[go]": {
			"editor.formatOnSave": true
		}
	},
	"extensions": [
		"golang.go"
	],
}

```

`extensions`の部分で、ワークスペースに任意の VSCode 拡張をインストールすることができます。今回は、最低限の設定として Go の VSCode 拡張を入れています。

他にも、今回は軽量な`alpine`イメージを使用するので、

```json
"terminal.integrated.shell.linux": "/bin/ash",
```

の部分でワークスペースで使用するシェルに`ash`を指定しています。
`alpine`でも`bash`を使いたい方は、イメージビルド時に bash を入れたりするなど、適宜カスタマイズしてください。

# 開発環境の起動

## Docker イメージのビルド

```bash
docker-compoase build
```

## コンテナの立ち上げ

```bash
docker-compose up -d
```

`docker-compose logs -f web `でコンテナのログを見ると、ライブリロードの`air`が立ち上がっていることがわかります。

```
web_1  |   __    _   ___
web_1  |  / /\  | | | |_)
web_1  | /_/--\ |_| |_| \_  // live reload for Go apps, with Go
web_1  |
```

## 開発コンテナ接続

Remote Container でコンテナに接続する方法はいくつかあります。以下のうちどれかを行います。（どれでもいい）

以下、全て VSCode での作業

- [Cmd + Shift + P] > [reopen in container]を選択

- VSCode の左下にある >< を押す > [reopen in container]を選択

- 左側のアイコン(Remote Explerer) > Containers > 該当のコンテナを選択
  ![Screenshot 2021-04-18 10.34.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/2975ce6b-99e1-acd2-498f-eeaa0c38e86e.png)
  Docker のコンテナ内で、VSCode を開くことができました。

# コーディングしてみる

Go コンテナの中に入ることができたので、とりあえず Gin で Web サーバーを立てて動作を確認します。

## パッケージのインストール

```bash:ash
go mod init プロジェクト名
```

で go.mod を作成したら、

```bash:ash
go get -u github.com/gin-gonic/gin
```

で`gin`をインストール。

## サーバー立ち上げ

```bash:ash
touch main.go
```

```go:main.go
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
```

### 動作確認

[localhost:8080/ping](http://localhost:8080/ping)

```json
{ "message": "pong" }
```

ちゃんと動いてそうです。

### ライブリロードの確認

コードを少し変更してみます。

```diff:main.go
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
+    r.GET("/hoge", func(c *gin.Context) {
+   　　 c.JSON(200, gin.H{
+           "message": "fuga",
+       })
+    })
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
```

### 再度動作確認

```bash
web_1  | main.go has changed
web_1  | building...
web_1  | running...
web_1  | [GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
web_1  | [GIN-debug] GET    /hoge                     --> main.main.func2 (3 handlers)
```

[localhost:8080/hoge](http://localhost:8080/hoge)

```json
{ "message": "fuga" }
```

ちゃんと変更が反映されていることがわかります。

# おわりに

**手軽な Go の開発環境構築として、Docker・Air・VSCode Remote Container を使用する方法を紹介させていただきました。**

「**DB コンテナも使いたい**」となったら`docker-compose.yml`に DB コンテナの記述を加えたり、「**もっといろいろワークスペースをカスタマイズしたい**」となったら拡張機能をワークスペースに入れたりなど、色々と柔軟に拡張できると思います。

最後に、メリットデメリットをまとめておきます。

## 本記事で紹介する環境のメリット/デメリット

### メリット

- Docker, VSCode があれば作れる
- ローカルに Go をインストールせずに、コード補完等のサポートが効く
- コードを変更すると、自動でソースコードをリビルドしてくれる
- docker コマンドをいちいち流さなくても良い

### デメリット

- git 管理の構成を考えるのが難しい
- 1 コンテナにつき開ける VSCode のウィンドウが一つ
- 何らかのポートを開けっぱなしにするとき、VSCode のデバッグが使えない

メリットもある一方、デメリットも多くあるなぁと筆者自身感じています。特にデバッグ構成については、Web サーバの開発であればライブリロードと相性が悪いです。

---

（以下、デバッグと Air が共存できない例）

1. 変更があるたびにソースコードがビルドされ、ポート`:8080`で Gin の Web サーバが立ち上がっている。
2. ここで、VSCode でデバッグを行うと、デバッグのためにソースコードがビルドされる。
3. デバッグでのビルド時、同じくポート`:8080`で Gin をサーバを立ち上げようとするため、ポートの競合が発生し、デバッグモードにできない。

こちらについては、未だベストな解決方法が思いついておりません。結局、ライブリロードをやめてデバッグモードでの開発が良いのかなと思っています。

以上のメリット・デメリットを踏まえた上で、今回の環境構築を試していただければと思います。

もっと良い方法があるよ！等ありましたら、是非教えていただきたいです 🙇‍♂️

# 参考資料

https://qiita.com/d0ne1s/items/d2649801c6f804019db7

https://qiita.com/maroKanatani/items/75c38f6ab2f474ef7d37

https://github.com/microsoft/vscode-remote-try-go