问题背景

在k8s环境下部署应用,在web页面根据时间条件查询数据,无法查询到数据。但是在查询条件的范围内,是有数据插入到数据库的。查看数据库表中的记录发现数据库中的时间比北京时间晚了8小时。很明显这是一个时区问题,关于时区的配置,包括有以下几个部分:

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

问题排查

出现时区不一致的情况主要是由于应用层和数据库层的时区不一致导致,所以问题的排查主要围绕应用和数据的时区配置。

服务器时区

首先我们先确认各个服务器的时区,确认服务器的时区一致命令:date 或者是 date -R,可以确认服务器的时间均为东八区时间CST。

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

数据库时区

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

show variables like '%time_zone%'

查询结果如下:

variable_name value
system_time_zone CST
time_zone SYSTEM

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

Java获取时区

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

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

运行结果如下:

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

那么Java的默认时区是由哪些配置来决定的呢?

Java配置默认时区

首先我们需要先了解清楚Java获取默认时区的逻辑,步骤如下:

  • 1.先找“TZ”环境变量,没有,到2,
  • 2.读/etc/timezone,没有到3,
  • 3.比较/etc/localtime文件与”/usr/share/zoneinfo目录下所有时区文件,如果有一致的,就为该时区,如果没有,到4,
  • 4.默认为标准GMT

参考文章:Java读取系统默认时区
根据上述步骤,Java应用获取默认时区先找“TZ”环境变量,没有“TZ”环境变量再逐步向下查找时区配置文件,如果还未找到默认使用GMT时区。

K8s容器部署时区配置

容器部署时我们默认会将容器的时间和宿主机同步,并通过设置容器的时区的环境变量和宿主机一致。

挂载时区配置到容器

将宿主机的/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

容器时区配置

如果仅通过挂载时区配置路今到容器从而保持宿主机和容器时间同步,没有在容器设置“TZ”环境变量,则Java应用获取的时区为容器的默认时区,如果容器的默认时区与宿主机不一致,则会导致实现出现问题。K8s容器时区配置如下:

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

修改后重启容器即可生效。

注意:即使没有配置宿主机与容器时间同步,也可以通过设置容器环境变量的时区。

参考文章

Java读取系统默认时区
k8s pod时区更改