docker-composeを使ってMySQL + webフレームワークrevelの開発環境を作る

## 概要
記事の題名そのままです。
[前の記事](https://qiita.com/____easy/items/95076c087a88e7f5aaab)でrevelでapiサーバー作ったので、docker-composeにまとめてみました。

途中ハマったところなどをメインにまとめてみます。

## 動作環境
Mac OS Mojave 10.14
docker Version:           18.09.0
docker-compose version 1.23.2, build 1110ad01
Revel Framework	:	0.21.0

## Dockerfileの設定
```dockerfile
FROM golang

COPY . /go/src
WORKDIR /go/src
# 必要なパッケージなどをインストールする
RUN apt-get update -qq && \
    apt-get install -y mysql-client vim && \
    # install revel 
    go get -u github.com/revel/revel && \
    go get -u github.com/revel/cmd/revel && \
    # install gorm
    go get -u github.com/jinzhu/gorm 

EXPOSE 9000
```

## docker-compose.ymlの設定

```docker-compose.yml
version: '3'
services:
  mysql:
    image: mysql:5.7 
    container_name: mysql
    hostname: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_USER: root
      MYSQL_ROOT_PASSWORD: password
    # MySQLの文字コードを指定する
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --skip-character-set-client-handshake
    volumes:
      # DBデータの永続化
      - "db-data:/var/lib/mysql"
      # MySQL起動時にDBを作成し、初期データの投入する
      - ./initdb.d:/docker-entrypoint-initdb.d 
  sample_docker_compose:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: sample_docker_compose
    ports:
      - "9000:9000"
    command: bash ./scripts/start.sh
volumes:
  db-data:
    driver: local
```

通常、コンテナの中身は一度コンテナが停止するとまっさらな状態になりますが、`docker-compose.yml`ファイルのトップに`volumes`を設定することによって、停止した後に再起動してもデータがそのまま残っている状態になります。

ちなみに、db-dataのvolumesは`docker volume rm `のコマンドで消去することもできます。


初期データを投入する際は、docker-compose.ymlと同階層にある「initdb.d」という名前のディレクトリ配下に「.sql」ファイルを入れておきます。そのディレクトリに入っている「.sql」「.sh」「.sql.gz」ファイルが実行されます。
参考：https://hub.docker.com/_/mysql/

`command`は、一つのコンテナにつき、一つしか書けないので、余分な分はシェルスクリプトを実行するようにします。シェルスクリプトでは、MySQLの処理が完了してから、アプリケーションを起動するようにします。

```shell
# !/bin/bash

# MySQLサーバーが起動してからGolangのアプリケーションを起動させる
until mysqladmin ping -h mysql --silent; do
  echo 'waiting for mysqld to be connectable...'
  sleep 2
done
	
echo 'app is starting...'
exec revel run api-server
```

参考：https://qiita.com/yu-croco/items/0c0d5083298f7db9e1bb

`docker-compose.yml`で指定したコンテナは、同じネットワーク上に存在することになるので、相互に作用することが可能です。その際に、コンテナのIPを指定するのではなく、コンテナのホスト名を指定することができます。今回の場合はGoアプリケーション側からMySQLへアクセスするので、`mysql`がホスト名になります。


## 試しにhostのMacからMySQLに接続してみる
```bash
$ docker-compose up -d
```
`-d`オプションはバックグランドでの実行。

```bash
$ mysql -h mysql --port 3306 -u root -p
```
`-h`または、`--host`で、IPを指定する。`-h 127.0.0.1`も可能。
`--port`でport番号を指定する。デフォルトは、3306。
`-u`でユーザーを指定する。
`-p`は、パスワードの有無。

### トラブルシューティング
ここで、`ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)`のようなエラーがでた場合、もしかすると別のプロセスでローカルのMySQLが動いている可能性があるので、そちらを完全に止める（自分の場合、MacのローカルでもMySQLを動かしていたため、dockerの方ではなく、ローカルの方を参照しようとしていて、アクセスが拒否されていました）。

具体的には、`mysql.server stop`で停止できるはずですが、これも機能しない場合、プロセスから切ってしまいます。

```
$ mysql.server stop
 ERROR! MySQL server process #5470 is not running!
```

```bash
$ ps ax | grep mysql
14594   ??  S      0:00.02 /bin/sh /usr/local/Cellar/mysql/8.0.12/bin/mysqld_safe --datadir=/usr/local/var/mysql --pid-file=/usr/local/var/mysql/MacBook-puro.local.pid
14693   ??  S     10:32.73 /usr/local/Cellar/mysql/8.0.12/bin/mysqld --basedir=/usr/local/Cellar/mysql/8.0.12 --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/Cellar/mysql/8.0.12/lib/plugin --log-error=MacBook-puro.local.err --pid-file=/usr/local/var/mysql/MacBook-puro.local.pid
31608 s001  S+     0:00.00 grep mysql
```

で出てきたプロセスを`kill`します。

```bash
$ sudo kill -TERM 14594
$ sudo kill -TERM 14693
```

```
$ ps ax | grep mysql
31674 s001  S+     0:00.01 grep mysql
```
MacのMySQLサーバーが停止したら、コンテナの方のMySQLとの接続もうまくいきました。
MySQLのプロセスの確認。
きちんと動いていたら、`mysqld is alive`が表示されます。

```bash
$  mysqladmin ping -h 127.0.0.1 --port 3306 -u root -p
Enter password:
mysqld is alive
```


## golangのコンテナからMySQLコンテナに接続する
```
ERROR 2005 (HY000): Unknown MySQL server host 'api-server_db_1' (-2)
```
これにはいくつか原因が考えられます。

### アプリケーション側の問題

今回は、Goの`Revel`と呼ばれるフレームワーク内で、`gorm`を使って接続をしたのですが、tableの指定をしていないかったために、接続ができないことがありました。

アプリケーションロジックの中で`DB.Table(TABLE_NAME).Find(&User)`のように、きちんとtable名を指定してあげれば、問題なく動きました。

### docker-composeの設定の問題
`docker-compose up`で、一度立ち上げたものの、両方のコンテナが立ち上がらないことがあります。そのときに、コンテナの中で、作業して、もう一度アプリケーションを起動させても、MySQLとの接続がうまくいきません。これは、docker-composeの性質上、同じタイミングで立ち上がったコンテナは、同じネットワーク内の者として認識されますが、逐一立ち上げた場合はそれには当てはまらないという仕様になっていることがあげられます。
逐一立ち上げてコンテナ同士の接続をする場合は、`--link`を使う必要があります。


### MySQL側の問題
`mysql:latest`で立ち上げたときに、エラーが出ました。
これは、バージョンの問題で、最新版のMySQL8.0系だとログインの仕方が違うため、同じやり方ではうまくいかないみたいです。

参考：https://employment.en-japan.com/engineerhub/entry/2018/09/18/110000


## dockerのvolumesとdockerfileのcopyについて
volumesでマウントするタイミングと、シェルスクリプト実行のタイミングが合わずにエラーがでました。
これについては、次の記事が同じ状況を書いていたので参考になりました。

参考:https://kuroeveryday.blogspot.com/2016/11/docker-compose-volumes.html

## おまけーコンテナ内部に入って修正作業ー
docker-composeで立ち上げたものの、何らかのエラーで`exited`してしまったコンテナをもう一回立ち上げるには、`docker commit`を使うと良いです。

`Dockerfile`や`docker-compose.yml`の編集をするときに結構使いました。

次のコマンドでimageが生成されます。

```bash
$ docker commit {コンテナID} {任意の名前}
```

そのimageを立ち上げて、コンテナの中に入ります。

```bash
$ docker run --rm -it {任意の名前} /bin/bash
```

参考：https://qiita.com/mom0tomo/items/35dfacb628df1bd3651e


長々書きましたが、結構色々なエラーが出てその度に調べていたので、docker周り詳しくなった気がします。docker奥深いなあ（小並感）