箱のプログラミング日記。

えんじにあ奮闘記

Dockerコンテナのタイムゾーン設定を変更する

f:id:y_hakoiri:20191102134313j:plain

Dockerコンテナのタイムゾーン変更でハマったのでメモ。

結論から言うと、docker-compose.ymlに環境変数TZを設定すればうまくいった。

環境

  • Ruby 2.4.3
  • Rails...5.1.5
  • MySQL 5.7
  • Docker 19.03.12

Dockerコンテナ...app(Rails)/ db(MySQL)/ nginx

流れ

そもそもタイムゾーン全般に対しての知識が甘く、最初から「Dockerコンテナのタイムゾーンを変更すれば良い」と分かっていたわけではなかった。

DBの特定のレコードについて、Rails側から確認できる日時は日本時間なのに、MySQLで確認できる日時はUTC時間になっていた。

(なので最初はMySQLのタイムゾーンを変更しようと一生懸命になっていた。)

とりあえず、調べたこと&試したこと全てまとめる。

MySQLのタイムゾーンを確認する

mysqlにログインした状態で確認。

mysql > show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+

今まで意識したことがなかったけど、mysqlというデータベースがあって

mysql> show databases;
+----------------------+
| Database             |
+----------------------+
| information_schema   |
| hakochan_development |
| hakochan_test        |
| mysql                |
| performance_schema   |
| sys                  |
+----------------------+

その中でタイムゾーンを保存しているテーブルがあるらしい。

mysql> show tables;
+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| columns_priv              |
| db                        |
| engine_cost               |
| event                     |
| func                      |
| general_log               |
| gtid_executed             |
| help_category             |
| help_keyword              |
| help_relation             |
| help_topic                |
| innodb_index_stats        |
| innodb_table_stats        |
| ndb_binlog_index          |
| plugin                    |
| proc                      |
| procs_priv                |
| proxies_priv              |
| server_cost               |
| servers                   |
| slave_master_info         |
| slave_relay_log_info      |
| slave_worker_info         |
| slow_log                  |
| tables_priv               |
| time_zone                 |
| time_zone_leap_second     |
| time_zone_name            |
| time_zone_transition      |
| time_zone_transition_type |
| user                      |
+---------------------------+

先ほどのタイムゾーンを確認するコマンドは、mysqlデータベースのtime_zoneテーブルを確認するものなので、どこのデータベースにいる状態でも参照可能。

MySQLのタイムゾーンを変更する

で、先ほどのこれだけど

+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+

time_zoneの方は

コマンド行で --default-time-zone=timezone オプションを使用すると、初期グローバルサーバータイムゾーン値を起動時に明示的に指定できます。

ということだったので、docker-compose.ymlを以下の通り修正

修正前

db:
    image: mysql:5.7
    build:
      context: .
      dockerfile: ./dockerfiles/db/Dockerfile
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci

修正後

db:
    image: mysql:5.7
    build:
      context: .
      dockerfile: ./dockerfiles/db/Dockerfile
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --default-time-zone=Asia/Tokyo

で、dockerコンテナを一度停止→削除→キャッシュ無しでビルド→起動

  • コンテナを停止
docker-compose stop db
  • コンテナを削除
docker rm db
  • キャッシュ無しでビルド
docker-compose build --no-cache db

--no-cacheオプションを付けないと、以前buildした時のキャッシュを使用して再ビルドしてしまい、変更した内容が反映されないので注意

  • 起動
docker-compose up -d

そうすると、確かに以下のようにタイムゾーンが変わっているのが確認できた

mysql > show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | Asia/Tokyo |
+------------------+--------+

ただ、解決したかったRailsとDBの日時齟齬は解決できていない。。

MYSQLのタイムゾーンについて

後々ちゃんと調べて理解したのだけど、time_zoneSYSTEM(最初の状態)だと、system_time_zoneを参照するらしい。

↓最初の状態

mysql > show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+

system_time_zoneJSTになっているので、MySQL自体のタイムゾーンは最初から東京時間になっていた模様。

system_time_zonetime_zoneの違いについては公式参照

MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.6 MySQL Server でのタイムゾーンのサポート

ここまできて、やっと「Railsのタイムゾーンはどうなっているんだろう」という脳に切り替わった。

Railsのタイムゾーンを確認

こちらの記事に大変お世話になりました。

Railsタイムゾーンまとめ - Qiita

本当に↑の記事の通り、順番に各タイムゾーンを確認して行ったのだけど、

Rubyのタイムゾーン

[1] pry(main)> Time.now
=> 2020-10-22 11:15:37 +0000

UTCになっている。これが原因か?

Railsのconfigのタイムゾーン

# config/application.rb
config.time_zone = 'Tokyo'
[1] pry(main)> Time.zone.now
=> Thu, 22 Oct 2020 20:31:13 JST +09:00

これは問題なさそう。

ActiveRecordのタイムゾーン

ActiveRecordのタイムゾーンはDBに読み書きする時刻に影響するらしい。(考えてみれば当然か)

# config/application.rb
config.active_record.default_timezone = :local

:localの場合はrubyプロセスのタイムゾーンであると解釈し、

ということなので、Rubyのタイムゾーン(UTC)を参照した状態でDBに書き込んでしまっている様子。やはり犯人は最初に確認したRubyのタイムゾーンだった。

いくらMySQLのタイムゾーンが日本時間になっていても、書き込み時点でUTCに変換してしまっていたら意味がないよな。。

DBのタイムゾーン

こちらは先ほど確認した通り、JSTになっていたのでスルー

各タイムゾーンの設定まとめ

ということで、まとめると

  • Rubyのタイムゾーン...UTC
  • Railsのconfigのタイムゾーン...JST
  • ActiveRecordのタイムゾーン...UTC(Rubyのタイムゾーンを参照)
  • MySQLのタイムゾーン...JST

になっていた。

DBへの書き込みに直接起因しているのはActiveRecordのタイムゾーンで、ActiveRecordはRubyのタイムゾーンを参照しているので、Rubyのタイムゾーンを変更すれば解決しそう。

さらにRubyのタイムゾーンはデフォルトではOSに依存するようなので、OSのタイムゾーンを変更すれば解決しそう。(ふぅ)

解決:Dockerのアプリケーションコンテナのタイムゾーンを変更する

DBコンテナのタイムゾーンを変更するとばかり思っていたけど、Rubyが入っているアプリケーションコンテナのタイムゾーンを変更すれば解決した。

OSのタイムゾーンはdateコマンドで確認できるので

$ docker-compose exec app date
Thu Oct 22 11:51:11 UTC 2020

やはりUTC時間になっている。

docker-compose.ymlを修正

修正前

app:
    build:
      context: ../
      dockerfile: ./docker/dockerfiles/app/Dockerfile
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ../:/app_name
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
    tty: true
    stdin_open: true

修正後

app:
    build:
      context: ../
      dockerfile: ./docker/dockerfiles/app/Dockerfile
    command: rails s -p 3000 -b '0.0.0.0'
    environment:       # ここ
      TZ: Asia/Tokyo  # ここ
    volumes:
      - ../:/app_name
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
    tty: true
    stdin_open: true

これで、コンテナ停止→削除→キャッシュ無しでbuild→起動して

もう一度タイムゾーンを確認すると

$ docker-compose exec app date
Thu Oct 22 20:51:11 JST 2020

日本時間になった!

そして無事にDBに保存されるTime型の値も日本時間になりました。良かった〜。

まとめ

  • ActiveRecordのタイムゾーンとconfigのタイムゾーンは別
  • DBへの読み書きはActiveRecordのタイムゾーンが影響する
  • RubyのタイムゾーンはデフォルトではOSのタイムゾーンに依存する
  • Linuxの場合、OSのタイムゾーンは環境変数TZで設定可能

今回の一番の収穫は「タイムゾーンはいろいろな環境で気にしなきゃいけない」という気付きでした。笑

参考

MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.6 MySQL Server でのタイムゾーンのサポート

Railsタイムゾーンまとめ - Qiita

Dockerコンテナのタイムゾーン変更方法 - Qiita