<Golang+Nginx+MySQL>Macローカル上にDocker Composeでアプリを立ち上げてみた

#Dockerアプリケーションのアーキテクチャー
![スクリーンショット 2021-03-15 10.32.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/580193e7-2961-57ea-6239-dce0b7315012.png)

##ざっくりとやること・できることの説明
- docker-composeでNginxとGo言語のAPI, DBとしてMySQLのコンテナを3つ立ち上げる
- Nginxのindex.htmlからGo言語で書いたREST API(go-ginのフレームワーク使用)を経由して、DBに問い合わせ
- DBにCRUD(gormのフレームワーク使用)ができることを確認
- ※DockerとDocker Composeはインストールされているものとする。


##バージョン確認
確認してみたら、以下のバージョンでした。

```
Docker version 20.10.2
docker-compose version 1.27.4
golang:1.14.7-alpine3.12
mysql:5.7
nginx:1.17.3-alpine
```
※厳密にはバージョンの依存関係とかも考えないといけないが、一旦ローカルで動かすので、スルーしますm(__)m


#フォルダ構成
```
docker-local
├── .env
├── api
│   └── src
│       ├── main.go
│       ├── model  ── model.go
│       └── config ── config.go
├── db
│   ├── conf ── my.cnf
│   └── db-data
├── docker
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
├── docker-compose.yml
└── web
    ├── env
    │   └── nginx ── site.conf
    └ src ── index.html
```
###ディレクトリとファイルの用途解説


| 名前 | 種類 | 説明 |
|:--|:-:|:--|
| docker-local | フォルダ | dockerローカルアプリケーションの親フォルダ |
| .env | ファイル | docker-composeで使う環境変数のファイル |
| api | フォルダ | APIのフォルダ |
| src | フォルダ | ソースフォルダ |
| main.go | ファイル | goを起動するときのファイル |
| model/model.go | ファイル | DBとやりとりする時のファイル<br>struct, dao系のメソッドを記載 |
| config/config.go | ファイル | DBの接続情報等を記載 |
| db | フォルダ | DBに関するフォルダ |
| conf/my.cnf | ファイル | 任意のDB設定を記載。マウント用 |
| db-data | フォルダ | DBのデータをsyncするマウント用 |
| docker | フォルダ | Dockerfileたちを格納するフォルダ |
| nginx | フォルダ | NginxのimageをビルドするDockerfileを格納 |
| Dockerfile | ファイル | nginxのimage |
| nginx.conf | ファイル | nginxの設定ファイル |
| docker-compose.yml | ファイル | docker containerを一括でコントロールするyamlファイル |
| web | フォルダ | フロントのファイルを格納 |
| env | フォルダ | 環境設定ファイルを格納 |
| nginx/site.conf | ファイル | nginxのserver設定ファイル<br>IPアドレス、ポート、ルーティングなど設定 |
| src/index.html | ファイル | 画面表示用ファイル |

#Docker Composeの中身
```yaml
version: "3.8"

services:
  nginx:
    image: infra-challenge:nginx-20210312
    # command: 
    build: 
      context: ./docker/nginx
      dockerfile: Dockerfile
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ./web/env/nginx:/etc/nginx/conf.d
      - ./web/src:/var/www/html
    depends_on:
      - api
    logging:
      driver: "none"
    restart: always
    networks:
      - services

  api:
    image: golang:1.14.7-alpine3.12
    command: go run main.go --host 0.0.0.0 --port 8080
    container_name: api
    ports:
      - 8080:8080
    volumes:
      - ./api:/go/src/github.com/infra-challenge/api
      - /Users/username/go:/go
    working_dir: /go/src/github.com/infra-challenge/api/src
    environment:
      - APP_STAGE=local
      - LOG_LEVEL=debug
      - MYSQL_CONNECTION
    depends_on:
      - db
    restart: always
    networks:
      - services

  db:
    image: mysql:5.7
    # build: ./docker/mysql
    container_name: db
    ports:
      - 3306:3306
    volumes:
      - ./db/conf/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./db/db-data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD
      - MYSQL_DATABASE
      - MYSQL_USER
      - MYSQL_PASSWORD
    healthcheck:
      test: ["CMD-SHELL", "mysqlshow -u ${MYSQL_USER} -p${MYSQL_PASSWORD}"]
      interval: "5s"
      retries: 10
    logging:
      driver: "none"
    restart: always
    networks:
      - services
    
networks:
  services:
    external:
      name: local_infra_challenge_networks

```

###主な属性の解説
- `docker compose`の文法バージョンは`3.8`だよ
- `services`配下に、コンテナの定義を書いていく。今回はnginx, api, dbの3つ
- `nginx`は`Dockerfile`を使うから、image名は任意で、`build`の属性を指定。
    - `context`はDockerfileのある相対パス、`dockerfile`はDockerfile名
    - ※ファイル名が`Dockerfile`となっていたら、`build: ./docker/nginx`としてもOK
- `container_name`を指定すると、`docker stop`とかするときに、idじゃなくて名前を指定すれば制御できるので便利
- `ports`は`<ホスト(Macローカル)>:<ゲスト(Dockerコンテナ)>`でポートを接続
- `volumes`は`<ホスト(Macローカル)>:<ゲスト(Dockerコンテナ)>`でマウントフォルダを指定
    - マウントとは、フォルダ同士を共有してsync（同期）すること。
- `depends_on`は他のコンテナが起動成功でき次第〜みたいなやつ
- apiとdbは、自分でDockerfileは用意せずに、`Docker-Hub`公式のimageを使っている
- `environment`に環境変数を指定する。
    - `docker-compose.yml`と同階層に`.env`ファイルを置き指定
- DBでは`healthcheck`を行い、起動しているか随時確認
- ネットワークを組むことで、**コンテナ間通信が可能となり、APIからDBに問い合わせができるようになった**

###ネットワークについて
作ったネットワークの情報を確認してみると、下記のようなObjectが返ってきますね〜
`"Subnet": "172.19.0.0/16",`の中で、Containers(nginx, api, db)がIPアドレスを振られて存在しているのがわかります。
ちなみに、`networks`だけでなく`links`という属性もあり、`links`を使うと、一方的にコンテナ間通信ができるようになる設定もあるので、TPOによって使い分ける◎
[公式docker:links](https://docs.docker.com/compose/compose-file/compose-file-v3/#links)

```shell
$ docker network inspect local_infra_challenge_networks
[
    {
        "Name": "local_infra_challenge_networks",
        "Id": "f6a978e6f3aa803be061418c1c7081d132c9538c207ea08b1d732fc55ba2b917",
        "Created": "2021-03-12T03:57:20.0697997Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7a81d8c6f965a07ce446aa0983180bbb4775187ed6873a9e26dbc9540f623392": {
                "Name": "nginx",
                "EndpointID": "09336c1acc58f9b75ea3ced604a7d82b168a5f0cd132ae4f2fffd267a416b022",
                "MacAddress": "02:42:ac:13:00:04",
                "IPv4Address": "172.19.0.4/16", #<-これ!!
                "IPv6Address": ""
            },
            "842244244bf00593e25317dd1cafb35ae03cfbc5246a87c666c6434a6ad7540d": {
                "Name": "db",
                "EndpointID": "3810c5b1b5f87b3ba4331320fcb2106696f3dcd157a771f9de63aeffc3a14720",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16", #<-これ!!
                "IPv6Address": ""
            },
            "c0906b3fc86d92f4b89474b7647e85a36e489469f83905c94785f2f278807532": {
                "Name": "api",
                "EndpointID": "e291176e7bd08040c2486d0409dab009a724fb7e3f0da3a5a9585cb23344a999",
                "MacAddress": "02:42:ac:13:00:03",
                "IPv4Address": "172.19.0.3/16", #<-これ!!
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

```
####コンテナ間通信をするときは、**コンテナ名でエンドポイントを指定することができるから便利**

```shell
# db:3306 の部分 <コンテナ名:ポート>
MYSQL_CONNECTION=local:local@tcp(db:3306)/infra-challenge 
```


###マウントについて
####nginx
```yml
    volumes:
      - ./web/env/nginx:/etc/nginx/conf.d
      - ./web/src:/var/www/html
```
- nginxには`default.conf`というものがあり、それを自分の設定に書き換えたいので、`./web/env/nginx`
で書き換えてる
- `/var/www/html`以下がdockerコンテナ内のnginxフォルダ構成になる（する）ので、index.htmlがあるディレクトリを同期

####api
```yml
    volumes:
      - ./api:/go/src/github.com/infra-challenge/api
      - /Users/username/go:/go
```
- とりあえずGo Pathが通っているところをdockerコンテナ側にマウントしてる。
- APIのファイル等も同期

####db
```yml
    volumes:
      - ./db/conf/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./db/db-data:/var/lib/mysql
```
- MySQLのデータの格納先が`/var/lib/mysql`になるので、ホストの`./db/db-data`に同期して、コンテナを除去してもデータが消えないようにする
- 自分のDB設定、例えば、`character-set`とか`collation`とかをマウントして上書き

```
# my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[mysql]
default-character-set=utf8mb4
```


#いざ、Docker Composeで起動してみる！！
##コマンド
```bash
$ cd /Users/username/src/github.com/infra-challege/docker-local
$ docker network create --driver bridge local_infra_challenge_networks
[~省略~]
$ docker-compose build --no-cache
[~省略~]
$ docker-compose up -d --remove-orphans
Creating db ... done
Creating api ... done
Creating nginx ... done

$ docker ps -a
CONTAINER ID   IMAGE                                 COMMAND                  CREATED         STATUS                            PORTS                               NAMES
8fxxxb102ed4   infra-challenge:nginx-20210312        "nginx -g 'daemon of…"   4 seconds ago   Up 3 seconds                      0.0.0.0:80->80/tcp                  nginx
8fxxx25640e6   golang:1.14.7-alpine3.12              "go run main.go --ho…"   6 seconds ago   Up 4 seconds                      0.0.0.0:8080->8080/tcp              api
e5xxxe97320e   mysql:5.7                             "docker-entrypoint.s…"   6 seconds ago   Up 5 seconds (health: starting)   0.0.0.0:3306->3306/tcp, 33060/tcp   db

```
- docker-composeファイルがある階層に移動
- `networks`属性を指定したので、そのネットワークを作成する。
- nginxはDockerfileをつかってimageを作成するので、ビルドする必要がある。
    - `--no-cache`オプション：構築時にイメージのキャッシュを使わない。
- docker-compose upで起動
    - `-d`オプション：バックグラウンド実行
    - `--remove-orphans`オプション：Composeファイルで定義されていないサービス用のコンテナを削除


#Webページにアクセスできるか確認
(超しょぼいですが...w)
アクセスできた！
![スクリーンショット 2021-03-15 11.54.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/acdfd366-962c-fb39-d33e-e3dab001c6bf.png)

user1を作成してみよう。
![スクリーンショット 2021-03-15 11.57.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/7aba215b-d925-483b-7a8f-dc4944b42372.png)
作成できたことを確認◎
![スクリーンショット 2021-03-15 11.59.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/dafa6a1f-14c0-e4a2-9e10-08612a311dac.png)

##コンテナ終了コマンド
```shell
$ docker-compose down
Stopping nginx ... done
Stopping api   ... done
Stopping db    ... done
Removing nginx ... done
Removing api   ... done
Removing db    ... done
```
止まりました。

#ハマったポイント
- `go run main.go`した後に、起動はしていたがポート設定がミスっていて、APIにアクセスできなかった。
    - 「なんかうまくいかないな〜〜汗」ってなったので、VSCodeから一旦APIサーバーを止めて再起動して、通信がうまくいったけど、それはコンテナ間通信ではなかった、、、
        - コンテナ上ではなくローカルでAPIサーバーが立ち上がってしまい、通信できていると勘違いしてしまった。
- `index.html`の画面表示するポートと`API`を司るポートそれぞれが別だったので、その調整にハマった。
- nginxには、サーバー設定が必要だった。
    - `nginx.conf`と`default.conf`というファイルが存在しこれのIPアドレス、ポート等を設定しないといけないので、前もってnginxの知識をいれておかないと簡単には動かなかった

```shell
# site.conf (default.confの上書き)
server {
    listen      80;
    listen      8080; # APIで8080ポートも使ってる関係で、これも必要だった
    server_name localhost;
    root        /var/www/html;

    location / {
        index  index.html index.htm;
        root   /var/www/html;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /var/www/html/error;
    }
}

```

#まとめ
docker-compose.ymlでコンテナを起動して、Dockerアプリケーションを作る感覚がつかめたので、楽しかったですわい！

コンテナのコントロールってすごい簡単で、サクサク動くから、せっかちな自分でも全然苦じゃない。
マスターしていきたいなああああ


以上、ありがとうございました！