问题
为什么要设置时区?
代码处理时间时,同时考虑时区信息,便可避免时区问题带来的显示错误,为什么还要设置时区?
1、容器日志、系统日志信息中的时间显示UTC时间,不直观,影响问题定位与解决。
2、有些应用程序将机器的时区作为默认时区,并希望用户设置时区。
为什么宿主机时区已设置,容器时区还要单独配置?
容器在主机内核上运行,从内核中获取时钟,但时区并非来自内核,而是来自用户空间。因此,在大多数情况下,它们默认使用UTC时间。
1.简介
本文主要介绍Linux如何正确设置时区,然后介绍三种容器设置时区的方法:镜像中修改时区、容器运行时设置时区、K8S集群中设置时区。
2.时区说明
正确设置时区 必须做到以下两点:
- 配置:指定时区 (通过环境变量
$TZ
、或通过配置文件/etc/localtime
)- 文件:被指定的时区时区信息格式文件
/usr/share/zoneinfo/$TZ
存在(通过安装tzdata库、或从linux宿主机目录下拷贝)
2.1 时区信息格式
在大多数 UNIX 系统中,不同的时区由时区信息格式
( Time Zone Information Format)定义。这是 20 世纪 80 年代推出的一种二进制文件格式。这些文件可以在IANA 时区数据库
中找到(通常位于/usr/share/zoneinfo
)。在大多数Linux发行版中,这些文件都是作为发行版的一部分,默认安装。
对于容器基础镜像而言,大部分基础镜像(Ubuntu/Debian/alpine)默认情况下并不包含这个软件包,因此需要手动安装:
|
|
2.2 如何指定时区
但仅有这些文件还不够,我们还需要在主机(或容器)中指定所需的时区。有两种方法(两种方法选一种即可):
2.2.1 修改/etc/localtime
文件
/etc/timezone
文件:只有Ubuntu/debian生效,不具备通用性,不推荐。
/etc/localtime
文件配置本地系统的全系统时区。它通常是一个指向/usr/share/zoneinfo
的软链接,后面跟一个时区数据库名称,如 “Asia/Shanghai”(即 /usr/share/zoneinfo/Asia/Shanghai)。
|
|
2.2.2 声明TZ
环境变量
优先级更高
将TZ
环境变量设置为时区标识符(如 TZ=Asia/Shanghai)来设置时区,通常只在程序所需时区与主机时区不同的情况下进行设置。它的优先级比/etc/localtime
更高。
|
|
3. 容器配置方法
以
alpine:3.19.1
镜像举例说明
通过运行下面命令
|
|
输出为
|
|
可以看出,alpine:3.19.1
镜像时区为UTC。Alpine基础镜像默认不包含/usr/share/zoneinfo
或/etc/localtime
,基于2.时区说明,这些文件是设置容器时区所必需的。
3.1 构建镜像时配置
可以在镜像构建时,安装tzdata
软件包,并设置TZ
环境变量来完成时区设置。具体Dockerfile内容如下:
|
|
如果以后时区都不需要更改,且想精简镜像体积。可以使用下面的Dockerfile
- 安装 tzdata 软件包
- 复制需要的时区信息(例如,/usr/share/zoneinfo/Asia/Shanghai)
- 卸载 tzdata 软件包
- 重新创建文件夹 /usr/share/zoneinfo/Asia/(因为卸载会删除该文件夹)
- 将之前复制的文件放回该目录
- 设置时区。可通过TZ环境变量;也可通过
/etc/localtime
软连接。
|
|
或
|
|
缺点:
- 并非所有镜像都有包管理器,有些镜像只是
FROM scratch
- 要为所有镜像(尤其是一些不重新构建的中间件镜像)维护一个 Dockerfile,增加复杂性
- 需要在构建时就确定时区
3.2 容器运行时配置
除了在构建时安装 tzdata 软件包,还可以在容器运行时,从宿主机上加载所需的时区文件,例如:
|
|
使用 -v 将主机上的TZif
文件直接挂载到容器的/etc/localtime
,从而解决了容器时区问题。
如果容器后续需要修改其他时区,建议挂在整个
/usr/share/zoneinfo
目录
缺点:
- 提高了运维人员的维护成本,容易出现人为失误
- 需要提前确定,主机是否安装tzdata
3.3 Pod中手动设置
使用hostPath
将K8S节点上的时区文件挂载至容器中。创建tz-test.yaml
文件:
|
|
缺点:
- 手动操作,维护成本高
- 无法保证集群中的所有节点都安装了 tzdata
- hostPath权限问题,且有安全隐患
- 如果使用helm chart,则需要定制化修改
3.4 通过k8tz管理
k8tz 可以将时区注入Pod
和CronJobs
,只需极少的工作就能在 pod 和命名空间中自动标准化选定的时区。它可以作为手动工具,在本地自动转换部署yaml和 Pod;也可以作为准入控制器,使用annotations
为创建的任何 Pod 完全自动执行流程。
k8tz不使用hostPath
,而是分配emptyDir
,并注入initContainer
,用 TZif 文件填充卷。然后,使用emptyDir
将/etc/localtime
和/usr/share/zoneinfo
挂载到Pod
中的每个容器。为了确保所需的时区有效,它会在所有容器中添加TZ
环境变量。
3.4.1 安装
|
|
运行下面命令测试:
|
|
3.4.2 配置
- 安装时全局设置
|
|
- 通过Pod注解设置
|
|
- 通过Namespace注解设置(与Pod相同)
3.4.3 注解说明
Controller的行为可通过 Pod、Namespace对象上的注解进行控制。如果在两个对象中指定了相同的注解,则Pod 优先级更高。
Annotation | 描述 | 默认值 |
---|---|---|
k8tz.io/inject | k8tz 是否注入时区 | true |
k8tz.io/timezone | 决定设置哪个时区, e.g: Asia/Shanghai | UTC |
k8tz.io/strategy | 决定使注入策略, i.e: hostPath /initContainer | initContainer |
缺点:
- 如果容器下集群,通过docker运行时,运维人员容易失误,遗漏时区设置
4. 总结
Linux系统中设置东八时区,需要两步:
- 设置时区:
export TZ=Asia/Shanghai
- 获取时区文件:从宿主机拷贝文件
/usr/share/zoneinfo/Asia/Shanghai
, 或者安装tzdata
容器时区设置建议:
- 镜像中设置东八时区
- k8s集群中通过k8tz
由于国内时区不需要频繁修改,建议需要重新构建的镜像在基础镜像中将时区配置正确;k8s集群中部署k8tz,解决中间件时区设置问题;docker-run运行的中间件,单独维护,并将运行脚本版本化控制。