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_zone
がSYSTEM
(最初の状態)だと、system_time_zone
を参照するらしい。
↓最初の状態
mysql > show variables like '%time_zone%'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | JST | | time_zone | SYSTEM | +------------------+--------+
system_time_zone
はJST
になっているので、MySQL自体のタイムゾーンは最初から東京時間になっていた模様。
system_time_zone
とtime_zone
の違いについては公式参照
MySQL :: MySQL 5.6 リファレンスマニュアル :: 10.6 MySQL Server でのタイムゾーンのサポート
ここまできて、やっと「Railsのタイムゾーンはどうなっているんだろう」という脳に切り替わった。
Railsのタイムゾーンを確認
こちらの記事に大変お世話になりました。
本当に↑の記事の通り、順番に各タイムゾーンを確認して行ったのだけど、
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 でのタイムゾーンのサポート