DockerのMySQLコンテナに外部からアクセスする方法まとめ改

以前「コンテナ上のMariaDBに外部から接続する方法まとめ」というタイトルで、アプリやSQLクライアントから、DockerのMariaDBコンテナにアクセスすべく悪戦苦闘した時の備忘録を記事にしていました。
[コンテナ上のMariaDBに外部から接続する方法まとめ - Qiita](https://qiita.com/saken649/items/72f31ee658ce6737009a)

ですが、Macに移ってきたのを機に改めてやってみたら **こんな面倒なことをする必要は全く無かった** ので、改めて備忘録を残しておきます。

※2018/05/27 コメント頂いた内容を適宜反映致しました。

- IPではなく、コンテナ名でそのままアクセス出来ること
- `MYSQL_DATABASE`に記載したスキーマを文字コードを指定しながら作成出来ること

# 試行環境

* macOS 10.13 High Sierra
* Docker for Mac

アプリコンテナとDBコンテナが別になっている環境を想定しています。
なお、今回はMariaDBではなく、MySQL5.7なので、そこだけ前提が違うことをご了承ください。

# Docker立ち上げるまで

## docker-compose.yml

※2018/05/27 内容を更新しました。

```
version: '2'
services:
  db:
    build: ./db/
    environment:
      MYSQL_ROOT_PASSWORD: your_password
      MYSQL_DATABASE: your_schema
    ports:
      - "3306:3306"
  app:
    build: ./app/
    ports:
      - "8080:80"
    links:
      - db
    volumes:
      - ./src:/var/www/html
    stdin_open: true
    tty: true
```

## db/Dockerfile

```
FROM mysql:5.7
COPY ./mysql.conf.d /etc/mysql/mysql.conf.d
```

## db/mysql.conf.d/mysqld.cnf

```
[mysqld]
〜中略〜
character-set-server=utf8mb4 // mysqldの最後に追加

[client]
default-character-set=utf8mb4 // clientのセクションそのものを追加
```

ポイントは、

* `environment`として最低限`MYSQL_ROOT_PASSWORD`と`MYSQL_DATABASE`を設定しておく
    * 文字コードは`mysqld.cnf`の中で定義すれば、`MYSQL_DATABASE`で作成されるスキーマに反映される
* コンテナ側の`3306`番ポートに繋がるようにする(ローカル側は何番でも良いが、同じく`3306`にするのが吉)
    * `expose`ではなく、`ports`で。

※appコンテナ側は今回の話題と直接は関係しないので、説明を省きます。

# 立ち上げ

何も難しいことはありません。普通に立ち上げます。

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

---

※2018/05/27 必ずしもコンテナの中に入る必要はなくなりました。入り方のメモとして残しておきます。

立ち上がったら、`docker exec`してコンテナの中に一度入ります。
入る前には、`docker ps`でidを調べておきましょう。
コンテナが特定出来れば良いので、 **上3桁ぐらいで十分** です。

```bash
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                    NAMES
97c4d1095fab        dirname_app         "/bin/bash"              14 minutes ago      Up 14 minutes       0.0.0.0:8080->80/tcp     dirname_app_1
d47f748e436c        dirname_db          "docker-entrypoint.s…"   14 minutes ago      Up 14 minutes       0.0.0.0:3306->3306/tcp   dirname_db_1
```

DBコンテナ`d47f748e436c`に`docker exec`します。

```bash
$ docker exec -it d47 /bin/bash
```

# ホストを調べる

実は、アプリコンテナからDBコンテナには`localhost`ではアクセス出来ません(当たり前)
じゃあどうするかというと、DBコンテナのIPが分かっている必要があります。(※)

---

#### ※2018/05/27追記
kazuyoshikitahara様、otolab様からのご指摘コメントの内容を反映してなかったので追記。

IP分かっている必要はありません。 **`docker-compose.yml` に記載したコンテナ名でアクセス出来ます。**
`/etc/hosts` に記載されている、ということは、 **そもそもそのホスト名でアクセス出来る**　ということ。
なので、下記の様に `/etc/hosts` をわざわざ見る必要はありません。

---

~~Dockerの場合はホストを調べれば分かるので、先程`docker exec`したDBコンテナの中の`/etc/hosts`を調べます。~~

~~※`ifconfig`を使わないのは、コンテナの中に`ifconfig`コマンドがインストールされていないため。~~

```bash
root@d47f748e436c:/# ifconfig
bash: ifconfig: command not found
```

~~`/etc/hosts`を`cat`します。~~

```bash
root@d47f748e436c:/# cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.18.0.2	d47f748e436c
```

~~先程調べたDBコンテナのid`d47f748e436c`が、`172.18.0.2`のホスト名になっています。~~
~~ここから`172.18.0.2`がこのDBコンテナのIPアドレスである、と判断することが出来ます。~~

これで準備完了です。

# SQLクライアントからのアクセス

MacならSequel Pro、WindowsならA5:SQL mk2などのSQLクライアントからアクセスするのは、以下のように設定すればOKです。

* Host: `127.0.0.1`
* User: `root`
* Password: your_password
* Port: `3306`
    * ローカル側のポートを変更した方は、ここをその番号に

Sequel Proの場合は「標準」接続でOKです。

**先に調べたIPアドレスではなく、`127.0.0.1`であるというのがミソです。**
**`localhost`でもありません。**

## `127.0.0.1`にしないといけない理由(推測)

* `172.18.0.2`というのはDockerのネットワーク上でのローカルIPであるため、その外にあるSQLクライアントからは参照出来ない
    * おそらくではありますが、あながち間違いでも無いかと思ってます。
* `localhost`の場合、ソケットの指定が必要になり、どれ使ったら良いのか分からない。
* `docker-compose.yml`で`ports`の設定をしており、ローカルの`3306`番ポートがコンテナの`3306`番ポートとリンクしているため、`127.0.0.1`を指定した時、そのままコンテナの中までリンク出来る。

# アプリからのアクセス

アプリからアクセスするには、~~先程調べたDBコンテナのIPを使用します。~~ `docker-compose.yml`に記載したコンテナ名でアクセスします。

* Host: ~~`172.18.0.2`~~ `db`
    * ~~上記方法で調べたIPを使用してください。今回はこれ。~~
    * 2018/05/27 上述追記の通り、 `docker-compose.yml` に記載のコンテナ名で。今回は`db`という名前を付けていたので、それを使います。
* User: `root`
* Password: your_password
* Port: `3306`

先述の通り、SQLクライアントの時とは逆で、`127.0.0.1`や`localhost`ではNGです。
当然ながら、アプリコンテナの中にMySQLがあるわけではありませんからね。

例えばPHPで、PDO使って接続するなら、こんな感じで書けばOKです。

```php
$pdo = new PDO('mysql:dbname=your_schema;host=db;', 'root', 'your_password');
```

# TODO

現状試せていないものを一応控えておきます。

## root以外のユーザーでも同様に出来るようにする

実はここまでのやり方では、`root`以外のユーザーでうまくいきません。
`docker-compose.yml`の`environment`で、`MYSQL_USER`を指定した時、などです。
理由は、`mysql.user`テーブルを覗いてみると、`Select_priv`すら与えられてないため。
`MYSQL_USER`で`app`というユーザーを追加した時の、`mysql.user`を見るとこうなってます。

```mysql
mysql> select host, user, Select_priv from mysql.user;
+-----------+---------------+-------------+
| host      | user          | Select_priv |
+-----------+---------------+-------------+
| localhost | root          | Y           |
| localhost | mysql.session | N           |
| localhost | mysql.sys     | N           |
| %         | root          | Y           |
| %         | app           | N           |
+-----------+---------------+-------------+
5 rows in set (0.00 sec)
```

どこかで、`MYSQL_USER`に対しても権限を与えないといけません。
これはTODOとして、改めて調べないといけませんね。。

# 文字コードを指定しながらスキーマを作成する

2018/05/27追記。

`docker-compose.yml`にて、`MYSQL_DATABASE`を定義するとコンテナ作成時にスキーマを作成してくれます。
ただ問題は、設定をしてあげないと文字コードが`latin1`になってしまうこと。
`utf8mb4`などにするには、コンテナ内`/etc/mysql/mysql.conf.d`に下記の内容を含んだ`mysqld.cnf`を配置すればOKです。

```
[mysqld]
〜中略〜
character-set-server=utf8mb4 // mysqldの最後に追加

[client]
default-character-set=utf8mb4 // clientのセクションそのものを追加
```

# おわりに

以前Docker for Windowsでやっていた時にはドハマリしていたところが、なぜか楽に出来ていたのは謎ですが。。。
Sequel ProなどのSQLクライアントからアクセス出来ないのは地味にダルいので、あまり手間掛けずアクセス出来るようになってよかったです。