DockerのMySQL imageでタイムゾーンを日本時間にする

# はじめに

- ユーザーが指定したコンテンツ表示予約時間の9時間前にコンテンツが表示されてしまう、とユーザーから問合せがありました
- コンテンツ表示予約時間の抽出SQLが以下の状態となっており、環境構築時にMySQLを日本時間にしておけば今回の問題が起きなかったのでは、と思ったので、とりあえず試してみます

    ```sql
    SELECT * FROM contents AS c WHERE c.reservation_time <= NOW();
    ```

# 前提（試した環境）

- ホストOSバージョン: macOS Catalina 10.15.4
  - Windows Proであれば、PowerShellでもイケると思います（たぶん）
- dockerバージョン: 19.03.8
  - docker for mac（windows）必須
- dockerコンテナのMySQLイメージのバージョン: 5.7系（業務で使用中のものと合わせているので）

※ニワカなので、おかしな実装や考え方をしているかもしれませんが、ご指摘くださると嬉しいです！

# 実現方法

1. [MySQLが載ってるOSのタイムゾーンを日本時間にする](#mysqlが載ってるosのタイムゾーンを日本時間にする)
    - MySQLはOSのタイムゾーンをデフォルトで見るため
2. [MySQLのタイムゾーンを日本時間にする](#mysqlのタイムゾーンを日本時間にする)

# 結論

- 「1.[MySQLが載ってるOSのタイムゾーンを日本時間にする](#mysqlが載ってるosのタイムゾーンを日本時間にする)」がDBコンテナ再起動不要な分オススメ
- そもそもSQLの`NOW()`関数を使わずに、アプリ側で現在時刻を埋め込むようにすべきなのかも（アプリ側のタイムゾーンを日本時間にしている前提）
  - 本番環境でDockerを使っていなければ、システムOSを日本時間に変更することを忘れる可能性があるので。。。  
  <details><summary>アプリ側としてCakePHPを利用しており、`NOW()`を使わない例</summary><div>

    ```php
    # sample.php
    use Cake\Datasource\ConnectionManager;
    $now = date('Y-m-d H:i:s'); // アプリ側のタイムゾーンで現在時刻を取得
    $sql = "SELECT * FROM contents AS c WHERE c.reservation_time <= '" . $now . "'";
    $connection = ConnectionManager::get('default');
    $results = $connection->execute($sql)->fetchAll('assoc');
    ```

  - `execute()`ではなく`find()`メソッドなど使うべきですが…いったんわかりやすさ重視で  
  （ちなみに[はじめに](#はじめに)の問合せの修正対応は上記を行いました）

</div></details>

# 実現手順

## MySQLが載ってるOSのタイムゾーンを日本時間にする

1. dockerfileを以下の通り、ホストOSの適当なディレクトリに用意する  
  （ファイル名: dockerfile）

    ```dockerfile
    FROM mysql:5.7
    RUN apt-get update
    RUN apt-get install -y tzdata && \
        cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
    ENV MYSQL_ROOT_PASSWORD root
    ```

2. 以下のコマンドを実行する

    ```bash
    # イメージのビルド（例：`docker build -t mysql57_jst:latest ./`）
    ホストOS% docker build -t ＜好きなイメージ名＞:＜好きなタグ名＞ ＜作成したdockerfileがいるパス＞
    # DBコンテナ作成＆起動（例：`docker run -d -it --name mysql57 mysql57_jst:latest`）
    ホストOS% docker run -d -it --name ＜好きなDBコンテナ名＞ ＜作成したイメージ名＞:＜作成したタグ名＞
    ```

3. [MySQLのタイムゾーンが日本時間になっているか確認する](#mysqlのタイムゾーンが日本時間になっているか確認する)

## MySQLのタイムゾーンを日本時間にする

1. 以下のコマンドを実行する

    ```bash
    # DBコンテナ作成＆起動（DBコンテナがなければ）
    ホストOS% docker run --name ＜好きなDBコンテナ名＞ -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
    # DBコンテナに入る
    ホストOS% docker exec -it ＜作成したDBコンテナ名＞ bash
    # タイムゾーンのテーブルを取得
    DBコンテナ% mysql_tzinfo_to_sql /usr/share/zoneinfo/
    # タイムゾーンを日本時間にする設定ファイルを新規作成（conf.dディレクトリの配下であればファイル名は何でもOK）
    DBコンテナ% echo "" >> /etc/mysql/conf.d/etc-mysql.cnf
    DBコンテナ% sed -i -e "1i [mysqld]\n    default-time-zone='Asia/Tokyo'" /etc/mysql/conf.d/etc-mysql.cnf

    DBコンテナ% exit
    # DBコンテナの再起動（MySQL単体の再起動ができないので）
    ホストOS% docker restart ＜作成したDBコンテナ名＞
    ```

2. [MySQLのタイムゾーンが日本時間になっているか確認する](#mysqlのタイムゾーンが日本時間になっているか確認する)

## MySQLのタイムゾーンが日本時間になっているか確認する

1. 以下のコマンドを実行する

    ```bash
    # DBコンテナに入る
    ホストOS% docker exec -it ＜作成したDBコンテナ名＞ bash
    # MySQLにログイン
    DBコンテナ% mysql -u root -p
    Enter password: root
    # タイムゾーン確認
    mysql> SHOW VARIABLES LIKE '%time_zone%';
    # 実際に日本の現在時刻と一致しているか確認
    mysql> SELECT NOW();
    ```

# おわりに

- コロナのせいで、せっかくの連休なのに外出自粛というのはツラいですが、耐えましょう。。。

# 参考

- 参考というか、本記事はほぼ以下の記事をマネっこしている状況ですが、、、  
  ありがとうございます！
  - [Dockerコンテナのタイムゾーン変更方法](https://qiita.com/rururu_kenken/items/972314402d588e073d40)
  - [MySQLでタイムゾーンを設定する](https://qiita.com/tailak/items/63dce2dd7dfe049b038e)