ECS上のJava(JVM)アプリケーションで時刻が9時間ずれる問題の原因と対策
はじめに
この記事では、AWS ECS上で動作するJavaアプリケーション(SpringBoot)で、OSのタイムゾーンは日本時間(JST)なのに、アプリケーションが取得する時刻が9時間ずれる(UTCになる)という問題に遭遇し、解決した際の技術的なノウハウを記録します。
この記事で伝えたいこと
まず最初に伝えたいこと。
- Dockerコンテナ上でJavaアプリケーションを動かす際は、OSのタイムゾーン設定だけでは不十分な場合がある。
- JVM自体のタイムゾーンを明示的に設定する必要がある。
TL;DR
- 問題: DockerコンテナのOSのタイムゾーンをJSTに設定しても、JVMがUTCで動作してしまう。
- 原因: JVMのタイムゾーンは、OSの設定とは別に決定されるため。
- 解決策: Javaの実行時オプション
-Duser.timezone=Asia/Tokyoを追加するか、環境変数TZ=Asia/Tokyoを設定する。
発生した問題
AWS ECSで動作させているJavaアプリケーションにおいて、「データベースに記録される時刻が9時間ずれている」という報告がありました。
具体的には、アプリケーションが LocalDateTime.now() などで現在時刻を取得しDBに保存する処理で、期待される日本時間(JST)ではなく、協定世界時(UTC)で記録されていました。
原因調査と切り分け
9時間のズレから、タイムゾーン設定が原因であると仮説を立て、以下の2段階で調査を行いました。
Step 1: コンテナOSのタイムゾーン確認と設定
まず、問題が発生しているコンテナのOS自体のタイムゾーンを確認しました。ECS Exec を使ってコンテナに入り、dateコマンドを実行したところ、UTC と表示されました。
# ECS Execでコンテナに入る
$ aws ecs execute-command --cluster ... --task ... --command "/bin/bash" --interactive
# コンテナ内でタイムゾーンを確認
$ date
Wed Jul 3 22:00:00 UTC 2024 # ← UTCになっている!
この問題を解決するため、DockerfileにOSのタイムゾーンをJSTに設定する処理を追加しました。
# 環境変数を設定し、tzdataのインストールを非対話的にする
ENV TZ=Asia/Tokyo
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y --no-install-recommends tzdata && \
# 後片付け
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Note: apt-get install tzdata を実行すると対話形式で地域選択を求められることがあるため、ENV DEBIAN_FRONTEND=noninteractive を設定しておくのが定石のようです。また、ENV TZ=Asia/Tokyo を設定しておくと、OSだけでなく多くのプログラムがこのタイムゾーンを参照するようになります。
この修正後、コンテナの date コマンドは正しくJSTを指すようになりました。
Step 2: OS設定だけでは解決せず → JVMのタイムゾーン設定へ
しかし、アプリケーションを再デプロイしても、DBに記録される時刻は依然としてUTCのままでした。
ここで、「OSのタイムゾーンとJVMのタイムゾーンは別物である」という重要なポイントにたどり着きました。JVMは起動時にOSからデフォルトのタイムゾーンを継承しようとしますが、環境によってはそれがうまく機能せず、デフォルトのUTCが使用されることがあります。
そのため、JVMに対して明示的にタイムゾーンを指定する必要があります。
解決策
調べたところ、以下のいずれかの方法でJVMが使用するタイムゾーンをJSTに設定できることがわかりました。
解決策1: Java実行時オプションで指定する(今回採用した方法)
Dockerfile の CMD または ENTRYPOINT で、Javaの実行時オプション -Duser.timezone を追加します。
# ... (省略) ...
CMD ["java", "-Duser.timezone=Asia/Tokyo", "-jar", "/data/app.jar"]
この修正を加えてデプロイしたところ、無事にアプリケーションが取得する時刻がJSTになり、問題は解決しました。
解決策2: 環境変数 JAVA_TOOL_OPTIONS を利用する
コンテナの実行環境(ECSのタスク定義など)で環境変数を設定できる場合、こちらの方法がより柔軟です。JAVA_TOOL_OPTIONS は、JVM起動時に自動的に読み込まれる環境変数です。
"environment": [
{
"name": "JAVA_TOOL_OPTIONS",
"value": "-Duser.timezone=Asia/Tokyo"
}
]
この方法のメリットは、Dockerfileを再ビルドすることなく、環境変数でJVMの挙動を制御できる点です。
まとめ
Dockerコンテナ上でJavaアプリケーションを動作させる際のタイムゾーン問題は、「OSのタイムゾーン」と「JVMのタイムゾーン」の両方を意識する必要があります。
- OSのタイムゾーン:
ENV TZ=Asia/Tokyoなどで設定する。 - JVMのタイムゾーン:
-Duser.timezone=Asia/Tokyoオプションで明示的に指定する。
この記事をまとめる時点で解決策2が存在することがわかりました。当時は本番環境で気づいた事象で、急いで対処する必要がありすぐに解決できる解決策1を採用しました。
ただ、特にECSのような環境では、コンテナの再利用性やポータビリティを考慮し、環境変数(TZやJAVA_TOOL_OPTIONS)でタイムゾーンを外部から注入する設計が望ましいでしょう。(今なら、こちらの解決策2を採用すると思います。)
Discussion