下面給出 Docker 中 Tomcat 時間少 8 小時的系統性解決方案與核對流程,直接可用 ✅
一、問題本質
容器默認時區多為 UTC,而你期望是 東八區(如 <span style="color:red">Asia/Shanghai</span> 或 <span style="color:red">Asia/Taipei</span>)。Tomcat 進程由 JVM 繼承容器時區,若 DB/JDBC 再做時區轉換,就會產生 少 8 小時偏差。🕗
二、最小可用修復(推薦組合)
方案 A:運行時一次到位
docker run -d --name tomcat \
-e TZ=Asia/Shanghai \
-e "JAVA_OPTS=-Duser.timezone=Asia/Shanghai" \
-v /etc/localtime:/etc/localtime:ro \
-v /etc/timezone:/etc/timezone:ro \
-p 8080:8080 tomcat:9.0
解釋:
-e TZ=Asia/Shanghai:設置容器層時區為 <span style="color:red">東八區</span>。-e "JAVA_OPTS=…":強制 JVM 使用 <span style="color:red">Asia/Shanghai</span>,避免進程級偏差。-v /etc/localtime:/etc/localtime:ro、-v /etc/timezone:/etc/timezone:ro:掛載宿主機時區文件,使容器時間與宿主保持<span style="color:red">一致</span>。tomcat:9.0:示例鏡像標籤,按你實際版本替換。
方案 B:docker-compose 持久化
services:
tomcat:
image: tomcat:9.0
environment:
TZ: "Asia/Shanghai"
JAVA_OPTS: "-Duser.timezone=Asia/Shanghai"
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
ports:
- "8080:8080"
解釋:
environment:固定容器與 JVM 時區為 <span style="color:red">東八區</span>。volumes:複用宿主時區,防止容器內外時間不一致。ports:按需映射服務端口。
三、鏡像層修復(可選,用於自定義鏡像)
Debian/Ubuntu 基礎鏡像:
FROM tomcat:9.0
ENV TZ=Asia/Shanghai
RUN apt-get update && apt-get install -y tzdata && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
dpkg-reconfigure -f noninteractive tzdata
ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
解釋:
- 安裝
<span style="color:red">tzdata</span>並指向 /usr/share/zoneinfo/Asia/Shanghai;鏡像內永久生效。 - 設置
JAVA_OPTS,保證 <span style="color:red">JVM</span> 時區一致。
Alpine 基礎鏡像:
FROM tomcat:9.0-jre17-temurin-alpine
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
解釋:
- Alpine 用
apk add tzdata;複製時區文件並寫入/etc/timezone。 - 同步設置 <span style="color:red">JVM</span> 時區。
四、JDBC 與應用層同步(避免“二次偏移”)
- MySQL JDBC URL:
jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
**解釋:**通過 <span style="color:red">serverTimezone</span> 指明 DB 時區,避免驅動自動猜測造成 +8/-8 偏差。 - Spring/Java(可選):
-Duser.timezone=Asia/Shanghai已覆蓋大多數組件的時間基線。若使用 Jackson,建議spring.jackson.time-zone=Asia/Shanghai,防止序列化展示錯位。
五、驗證與排錯
docker exec -it tomcat date
**解釋:**應顯示東八區本地時間與 <span style="color:red">CST</span>/+0800 偏移。
docker exec -it tomcat bash -lc 'java -XshowSettings:system -version 2>&1 | grep -i timezone'
**解釋:**應看到 user.timezone = Asia/Shanghai,確認 <span style="color:red">JVM</span> 接受了時區參數。
docker logs tomcat | head
**解釋:**檢查 Tomcat 日誌時間戳是否與系統時間一致(無 -8h 偏差)。
六、常見根因與修復點(對比表)
| 場景 | 現象 | 根因 | 修復點 |
|---|---|---|---|
| 容器時間對但 Java 錯 | 日誌少 8h | 未設 <span style="color:red">-Duser.timezone</span> | 在 JAVA_OPTS 增加 -Duser.timezone=Asia/Shanghai |
| 容器與宿主不同步 | date 不同 |
未掛載 <span style="color:red">/etc/localtime</span> | 掛載本地 /etc/localtime 與 /etc/timezone |
| 僅接口時間錯 | 返回 JSON 少 8h | JDBC/序列化時區不一致 | JDBC 加 serverTimezone=Asia/Shanghai,Jackson 指定時區 |
| Alpine 鏡像失效 | 設置 TZ 無效 | 缺少 <span style="color:red">tzdata</span> | apk add tzdata 並複製時區文件 |
七、排障流程圖(vditor/mermaid)
關鍵提示
- 生產環境建議同時設置 容器時區 與 JVM 時區,並統一 JDBC/序列化,避免任何一環“各説各話”。
- 若部署在台灣地區,可將上述 <span style="color:red">Asia/Shanghai</span> 替換為 <span style="color:red">Asia/Taipei</span>,原則與步驟完全一致。📍
按上述步驟執行後,Tomcat 時間應與預期本地時間一致,不再出現 少 8 小時 的偏差。