问题描述

  • java 程序迁移进容器环境后,获取当前时间与系统时间差 8h,在容器中使用 data 查询时间正确,但 java 程序读取时间及时区存在问题
  • 镜像基于 ubuntu:20.04 镜像构建,时区配置由 k8s 的 volume 将宿主机的 /etc/localtime 映射到容器中,宿主机的时区为东八区
  • tomcat image Dockerfile 主要配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
FROM ubuntu:20.04

LABEL name="{{ IMG_NAME }}"
LABEL version="{{ IMG_VERSION }}"

RUN set -eux && apt-get update && apt-get install -y --no-install-recommends ca-certificates curl netbase unzip inetutils-ping && apt-get clean && rm -rf /var/lib/apt/lists/*

COPY --from=jrebuilder /usr/local/jdk/jre /usr/local/jre
COPY --from=tomcatbuilder /tomcat /tomcat

ENV JAVA_HOME=/usr/local/jre
ENV PATH=$JAVA_HOME/bin:$PATH

ENV JVM_XMS={{ JVM_XMS }}
ENV JVM_XMX={{ JVM_XMX }}

ENV LC_ALL C.UTF-8

EXPOSE 8080
WORKDIR /tomcat
  • Deployment 主要配置
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
spec:
  replicas: 1
  ...
  template:
    spec:
      containers:
        - name: ......
          image: ......
          imagePullPolicy: IfNotPresent
		  ......
          volumeMounts:
            - name: timezone
              mountPath: /etc/localtime
              readOnly: true
		  ......
      volumes:
        - name: timezone
          hostPath:
            path: /etc/localtime
            type: File

问题定位

  • 进入容器中,使用 data 命令查看时间信息
  • data 确定时间及时区信息无误
1
2
3
$ k exec -it XXX-XXXXX-5fcfbc4d86-dpnms -n XXXXX-XXXX -c XX-XXXXX -- /bin/bash
root@XXX-XXX-5fcfbc4d86-dpnms:/tomcat# date
Thu Oct 20 09:10:36 CST 2022
  • 使用其他语言构建镜像查看时间及时区信息
  • 使用 go 语言构建测试程序
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package main

import (
    "fmt"
    "time"
)

func main() {
    for {
        fmt.Println(time.Now())
        time.Sleep(1 * time.Second)
    }

}
  • 使用与 java 程序相同的镜像构建 image
1
2
3
4
5
6
7
8
FROM harbor.XXX.com.cn:XXXX/XXXX-XXX/XXXX-XXXXX:latest


WORKDIR /tomcat
COPY echo_time ./


CMD [ "./echo_time" ]
  • 部署 Deployment 增加一个 container
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
......
    spec:
      containers:
        - name: echo-time
          image: harbor.XXX.com.cn:XXXXX/XXX-XXXX/test_time:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: timezone
              mountPath: /etc/localtime
              readOnly: true
        - name: XXX-XXXX
          image: harbor.XXXX.com.cn:XXXX/XXXX/XXXX-XXXX:XXXX-v1666141733
  • apply 后查看日志
    • 发现 go 获取时间、时区信息没有问题
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ k logs -f  XXX-XXX-6547f694-vjptb -n XXX-XXX -c echo-time
2022-10-20 09:37:15.656060805 +0800 CST m=+1275.238289027
2022-10-20 09:37:16.656204228 +0800 CST m=+1276.238432450
2022-10-20 09:37:17.657352461 +0800 CST m=+1277.239580684
2022-10-20 09:37:18.657451984 +0800 CST m=+1278.239680206
2022-10-20 09:37:19.658515657 +0800 CST m=+1279.240743889
2022-10-20 09:37:20.658649461 +0800 CST m=+1280.240877688
2022-10-20 09:37:21.658792749 +0800 CST m=+1281.241020986
2022-10-20 09:37:22.658942055 +0800 CST m=+1282.241170283
2022-10-20 09:37:23.659087912 +0800 CST m=+1283.241316136
  • 由此确定,大概率是 java 获取时间机制的原因
  • 了解 java 获取时区的机制:
    • 查找系统 TZ 变量,如果没有,进入下一步
    • 读/etc/timezone,如果没有,进入下一步
    • 比较 /etc/localtime 文件与 /usr/share/zoneinfo 目录下所有时区文件,如果有一致的,就为该时区,如果没有,进入下一步
    • 使用标准 GMT 时区
  • Pod 时区通过挂载宿主机 /etc/localtime 文件实现,因此排查镜像内是否有 /usr/share/zoneinfo 目录,如果没有,java 程序无法正确获取时区,排查后发现镜像内没有 /usr/share/zoneinfo 目录,需要安装 tzdata 包。

问题处理

  • 调整 tomcat Dockerfile,额外增加安装 tzdata 包
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
FROM ubuntu:20.04

LABEL name="{{ IMG_NAME }}"
LABEL version="{{ IMG_VERSION }}"

RUN set -eux && apt-get update && apt-get install -y --no-install-recommends ca-certificates curl netbase unzip inetutils-ping tzdata && apt-get clean && rm -rf /var/lib/apt/lists/*

COPY --from=jrebuilder /usr/local/jdk/jre /usr/local/jre
COPY --from=tomcatbuilder /tomcat /tomcat

ENV JAVA_HOME=/usr/local/jre
ENV PATH=$JAVA_HOME/bin:$PATH

ENV JVM_XMS={{ JVM_XMS }}
ENV JVM_XMX={{ JVM_XMX }}

ENV LC_ALL C.UTF-8

EXPOSE 8080
WORKDIR /tomcat

参考