GO(フレームワーク : Gin) + Docker + Docker-compose + Nginx + Mysqlの環境構築

# 前書き
本記事では、表題の技術を組み合わせて、Webアプリケーション開発における基本的な開発環境の構築方法を順を追って説明します。自身の学習やプロジェクトの雛形としても活用できる内容になっています。構成ファイルやセットアップ手順も含め、初学者でも再現できる様、丁寧にまとめています。

# Go言語の概要
[Go言語（Golang）は、Googleが開発した静的型付きコンパイル言語である。シンプルで高速なコンパイルとガベージコレクションを備え、並行処理（Goroutine）が軽量で強力である。C言語に似た構文を持ちつつも、安全性と開発生産性を両立している。バイナリ単体で動作するため、DevOpsやクラウド環境での運用に適している。標準パッケージが充実しており、Web開発やCLI、ネットワークアプリケーションまで幅広く対応できる。

# Ginの概要
GinはGo言語で書かれた高速なWebフレームワークである。ルーティング、ミドルウェア、`JSON`バインディング、バリデーションなどWeb API開発に必要な機能を備えつつ、驚くほど軽量である。`net/http`をラップし、直感的なルーティングとミドルウェアのチェーン処理が可能。大量のリクエストを効率良く処理できるため、マイクロサービスやREST API構築に最適。開発・テスト・運用を効率化する設計が特徴である。

# 前提条件
- [Homebrew](https://brew.sh/ja/)がインストールされている
- [Docker Desktop](https://formulae.brew.sh/cask/docker)がインストールされている
- [Go言語（Golang）](https://formulae.brew.sh/formula/go)がインストールされている

# ディレクトリ構成
- 最終的に、下記のディレクトリー構成になる
```sh
.
├── backend/
│   └── web1/
│       ├── tmp/ # ⇦ 自動生成
│       │   ├── build-errors.log # ⇦ 自動生成
│       │   └── main # ⇦ 自動生成
│       ├── Dockerfile
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── nginx/
│   ├── conf/
│   │   └── web1.conf
│   └── Dockerfile
├── db/
│   ├── conf/
│   │   └── .env
│   └── mysql_volume # ⇦ 自動生成
├── phpmyadmin/
│   └── conf/
│       └── .env
├── compose.yml
├── .gitignore
└── README.md
```

# 構築準備

## .gitignore
- `.gitignore`に、`git`へ含めたくないファイルの情報を記載する

```sh
# DB関連
mysql_volume
/db/conf/*
/phpmyadmin/conf/*
!/db/conf/.keep
!/phpmyadmin/conf/.keep

# Go関連
backend/web1/tmp/
!backend/web1/tmp/.keep

# すべてのログファイルと一時ファイルを無視する
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# macOS Finderが生成するファイルを無視
.DS_Store

```

## Docker-compose
- ルートディレクトリに`compose.yml`を作成し、下記の内容を記述する
```compose.yml
services:
  nginx:
    container_name: nginx
    build: ./nginx
    image: nginx:latest
    ports:
      - "80:80"
    depends_on:
      - web1
 
  web1:
    container_name: web1
    build: ./backend/web1
    image: web1:latest
    working_dir: /web1
    depends_on:
      - db
    ports:
      - "8080:8080"
      - "2345:2345"
    volumes:
      - ./backend/web1:/web1
    command: sh -c "go mod tidy && air"  
    tty: true
    stdin_open: true

  db:
    container_name: mysql
    image: mysql:latest
    env_file: ./db/conf/.env
    volumes:
      - ./db/mysql_volume:/var/lib/mysql
    ports:
      - "3306:3306"
    restart: always

  phpmyadmin:
    container_name: phpmyadmin
    image: phpmyadmin:latest
    restart: always
    depends_on:
      - db
    env_file:
      - ./phpmyadmin/conf/.env  
    ports:
      - "3001:80"

```


## Go環境
### Dockerfile
- `backend/web1`に`Dockerfile`を作成し、下記の内容を記述する

```Dockerfile
FROM golang:latest

ENV LANG=C.UTF-8 \
    TZ=Asia/Tokyo \
    WEB1_ROOT=/web1 \
    APP_ENV=development

WORKDIR ${WEB1_ROOT}

# 開発支援ツールをインストール（AirとDelve）
RUN go install github.com/air-verse/air@latest && \
    go install github.com/go-delve/delve/cmd/dlv@latest

# 依存関係のみ先にコピーしてダウンロード
COPY go.mod go.sum ./
RUN go mod download 

# アプリケーションソースをコピー
COPY . .

# go.mod と go.sumを整理して、不要な依存を削除・不足を追加する
RUN go mod tidy

# ホットリロードをデフォルトコマンドに設定
CMD ["air", "-c", ".air.toml"]

```

## Nginx環境
- nginのディレクトリーに、`conf`ディレクトリーと`Dockerfile`を作成する
- `Dockerfile`には、下記の内容を記述する

```Dockerfile
FROM nginx:latest

RUN apt-get update && \ 
    apt-get upgrade -y && \ 
    apt-get install -y \ 
    curl && \ 
    rm -rf /var/lib/apt/lists/*
    
# default.confを削除
RUN rm -f /etc/nginx/conf.d/default.conf

# Nginxの設定ファイルをコピー
COPY ./conf/web1.conf /etc/nginx/conf.d/web1.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```


- `conf`ディレクトリーに、`web1.conf`を作成し下記の内容を記述する
```nginx
server {
    listen 80;
    server_name localhost;

    location / {
        proxy_pass http://web1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

---------------------------------------
### メソッド（ディレクティブ）内容の説明
| メソッド（ディレクティブ） | 説明 |
| :-: | - |
| `listen 80;` | このサーバーは、ポート`80`（HTTP） で接続を受け付ける |
| `server_name localhost;` | このサーバーは、`localhost`というドメイン名に対して反応するよう指定している |
| `location / { ... }` | すべてのアクセス（`http://ドメイン/` 以下のURL）に対して、この中の設定内容を適用する場所（ルール）を定義する<br> |
| `proxy_pass http://web1:8080;` | ブラウザから届いたリクエストを、`Go`アプリ（web1コンテナ）の `8080` 番ポートにそのまま渡す設定（リバースプロキシ）<br> |
| `proxy_set_header Host $host;` | 元の`Host`ヘッダー（例: `localhost`）をバックエンドにそのまま渡す |
| `proxy_set_header X-Real-IP $remote_addr;` | クライアントの 実際のIPアドレスをヘッダーに含めて渡す |
| `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` | ユーザーがアクセスしてきた時に、どの経路（IPアドレス）を通ってきたかの一覧をヘッダーに追加する設定。例えば、プロキシやロードバランサーを経由してきた場合でも、元のクライアントのIPアドレスまで把握できるようにする。追加されるヘッダー名は、 `X-Forwarded-For`。複数のIPがカンマ区切りで並ぶ |
| `proxy_set_header X-Forwarded-Proto $scheme;` | リクエストが「`http`」か「`https`」かの情報を、Goアプリ側に伝えるための設定。  例えば、ユーザーが `https://example.com` にアクセスした場合、「これは`HTTPS`で来たよ（スキーム = `https`）」という情報がバックエンド（Goアプリ）に届くようになる |



### 補足
**リバースプロキシ**
- ユーザー（ブラウザなど）からのリクエストを一度Nginxが受け取り、裏側で動いているアプリケーションサーバー（ここではGoアプリ）に転送する仕組み。これにより、セキュリティの強化、負荷分散、ログの集中管理などが可能になる

## DB
- dbのディレクトリーに、`conf`ディレクトリーを作成し、その中に`.env`ファイルを作成する
- `.env`には下記を記述をする
```sh
MYSQL_DATABASE=example_db
MYSQL_ROOT_PASSWORD=password
TZ=Asia/Tokyo
```

---------------------------------------
### 設定内容の説明

| 項目名 | 説明 |
| :-: | - |
| MYSQL_DATABASE | コンテナ起動時に作成されるデフォルトのMySQLデータベース名を指定する |
| MYSQL_ROOT_PASSWORD | MySQLのrootユーザーのパスワードを設定する |
| TZ | コンテナ内で使用するタイムゾーンを指定する（例：Asia/Tokyoは日本時間） |


## phpMyAdmi
- phpmyadminのディレクトリーに、`conf`ディレクトリーを作成し、その中に`.env`を作成する
- `.env`には下記を記述をする

```sh
PMA_ARBITRARY=1
PMA_HOSTS=db
PMA_USER=root
PMA_PASSWORD=password
```

---------------------------------------
### 設定内容の説明

| 項目名 | 説明 |
| :-: | - |
| PMA_ARBITRARY | 任意のサーバーへの接続を許可する（1で有効）。複数のDB管理が可能になる |
| PMA_HOSTS | 接続先のMySQLホスト名を指定（例：サービス名が `db` のコンテナを指す） |
| PMA_USER | 初期ログイン時に使用されるMySQLのユーザー名（例：root） |
| PMA_PASSWORD | 上記ユーザーのパスワード（例：root に対応するパスワード password） |


# 構築手順
- `/backend/web1`に移動して、下記コマンドを実行する
```sh
# go.modの作成
go mod init web1

# ginのインストールとgo.sumの作成
go get github.com/gin-gonic/gin
```

- `.air.toml`を作成する
```sh
docker compose run --rm web1 air init

# compose.ymlにworking_dirの設定がない場合、下記のコマンドを実行する
docker compose run --rm -w /web1 web1 air init
```

- `.air.toml`を下記の内容に修正する
- 修正する部分は、矢印にて記載
```.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 exec --accept-multiclient --log --headless --continue --listen :2345 --api-version 2 ./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
  silent = false
  time = false

[misc]
  clean_on_exit = false

[proxy]
  app_port = 0
  enabled = false
  proxy_port = 0

[screen]
  clear_on_rebuild = false
  keep_scroll = true
```


- `path/backend/web1`に移動して、`main.go`を作成する
```sh
touch main.go
```

- `main.go`に下記の内容を記述する
```main.go
package main

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

func main() {
    r := gin.Default()

    // ブラウザで http://localhost/ にアクセスしたとき "Hello, World!" を返す
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello, World!")
    })

		// ここでWebサーバーを立ち上げる
    r.Run(":8080")
}
```

- `go.mod`の更新が必要である為、コンテナーの再起動する
```sh
# コンテナーの立ち下げ
docker compose down

# コンテナーの起動
docker compose up -d
```

# GitHub

https://github.com/kengo-sk8/go_nginx_app

# 参考資料
## 公式
- [Gin Web Framework](https://gin-gonic.com/ja/)
- [air(日本語の説明)](https://github.com/air-verse/air/blob/master/README-ja.md)
- [[docker hub] golang](https://hub.docker.com/_/golang)

## 非公式
- [とりあえずgolang+nginx+mysql+docker-composeの環境構築](https://qiita.com/greenteabiscuit/items/ef4ed1dfda0d396d9d0f)
- [go + nginx でリバースプロキシを体験する](https://zenn.dev/userkazun/articles/ecbcadd8754129)
- [go_debug_test2](https://github.com/kengo-sk8/go_debug_test2)
- [VSCodeでGO+air+delveのDocker環境をリモートデバッグしよう！](https://qiita.com/shun198/items/78fc41f2ad3ed86c0c9f)
- [2024年版 Go + Docker + VSCode + air + delveの環境でサクサクローカル開発](https://zenn.dev/iwashi623/articles/b09149369fe06f)


# 感想
今回は、Goの環境を構築しました。最近はGoの開発を本格的に行っている為、学んだ事は積極的にアウトプットしていきたいと考えています。