一、问题描述

在 K8s 环境下部署应用后,发现数据库中记录的时间比北京时间晚了 8 小时,导致按时间查询数据时无法查到正确结果。这是典型的时区不一致问题。

涩及的时区配置点:

  • MySQL 数据库服务器的时区设置
  • MySQL 数据库时区配置
  • JDBC 数据库连接的时区配置
  • 应用服务器时区配置
  • 应用容器的时区配置

二、问题排查

2.1 服务器时区检查

首先确认各个服务器的时区,使用命令 datedate -R

[root@host-172 ~]$ date
20231115日 星期三 21:19:02 CST
[root@host-172 ~]$ date -R
Wed, 15 Nov 2023 21:19:06 +0800

结果显示服务器时区为 CST(东八区)。

2.2 数据库时区检查

在确认服务器时区后,进一步确认数据库的时区,在数据库执行下面命令:

show variables like '%time_zone%'

查询结果如下:

variable_name value
system_time_zone CST
time_zone SYSTEM

上述查询结果表明,数据库服务器的时区为CST,数据默认使用服务器的时区则也是CST。

2.3 Java 获取时区机制

在Java代码中我们可以通过一下方式获取系统时区:

// 获取默认时区
TimeZone timeZone = TimeZone.getDefault();

运行结果如下:

sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]

2.4 Java 默认时区获取逻辑

Java 获取默认时区的步骤:

  1. 查找 TZ 环境变量
  2. 若未设置,读取 /etc/timezone 文件
  3. 若文件不存在,比较 /etc/localtime/usr/share/zoneinfo 目录下的时区文件
  4. 都未找到,默认使用 GMT 时区

参考:Java读取系统默认时区

三、解决方案

3.1 挂载时区文件

将宿主机的/etc/localtime路径挂载到容器,可以保持容器与宿主机时间同步,避免由于时间同步引起的应用层的问题。

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  namespace: default
  labels: 
    app: my-pod
     
spec:
  containers:
  - name: my-pod
    image: nginx
    volumeMounts:
      - name: host-time
        mountPath: /etc/localtime
        readOnly: true
  volumes:
    - name: host-time
      hostPath: 
        path: /etc/localtime

**作用:**将宿主机的 /etc/localtime 路径挂载到容器,保持容器与宿主机时间同步。

3.2 设置环境变量 TZ

即使挂载了时区文件,如果没有设置 TZ 环境变量,Java 应用可能仍然使用容器的默认时区。

apiVersion: v1
kind: Pod
metadata:
  name: pod-env-tz
spec:
  containers:
  - name: ngx-time
    image: nginx:latest
    env:
      - name: TZ
        value: Asia/Shanghai

**作用:**设置 TZ 环境变量,确保 Java 应用能获取正确的时区。

3.3 完整配置示例

推荐的容器时区配置方案(同时配置环境变量和挂载时区文件):

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
  - name: app
    image: my-app:latest
    env:
      - name: TZ
        value: Asia/Shanghai
    volumeMounts:
      - name: host-time
        mountPath: /etc/localtime
        readOnly: true
  volumes:
    - name: host-time
      hostPath:
        path: /etc/localtime

四、验证方法

# 进入容器
kubectl exec -it <pod-name> -- /bin/bash

# 检查容器时区
date
date -R

# 检查 TZ 环境变量
echo $TZ

# 检查 Java 应用获取的时区(在应用代码中)
TimeZone.getDefault()

五、注意事项

  1. 双重配置:建议同时配置 TZ 环境变量和挂载 /etc/localtime,确保时区一致
  2. 数据库连接:JDBC 连接串也可以配置时区,如 serverTimezone=Asia/Shanghai
  3. 镜像构建:如果在 Dockerfile 中设置时区,可以避免部署时配置
  4. 时区文件路径:不同的 Linux 发行版时区文件路径可能不同,注意适配

六、参考文章