
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 でのタイムゾーンのサポート