[中文翻译] Kubernetes 中的时区和 k8tz
Kubernetes 中的时区和 k8tz
Kubernetes 已将自己确立为容器编排的事实标准。 它允许我们在节点集群上执行我们的容器并控制它们周围的所有配置,例如安装存储卷、Secret、网络管理等等。
容器在主机的内核上运行,并从那里获得时钟,但时区不是来自内核,而是来自用户空间。 因此,在大多数情况下,它们默认使用协调世界时 (UTC)。
即使代码与时区无关,即使将容器日志与系统日志相关联之类的事情也会让人头疼。一些应用程序使用机器的时区作为默认时区,并希望用户设置时区。当集群中容器的时区不一致时,问题会变得更大。只是没有标准。幸运的是,有几种方法可以解决这个问题并确定我们正在运行的容器的时区。但在我们找到解决方案之前,让我们先了解一下这个问题。
如何设定时区
在大多数 UNIX 系统中,不同的时区由时区信息格式 (TZif) 定义。它是 1980 年代引入的二进制文件格式。可以在 IANA 时区数据库中找到这些文件的可靠且准确的来源。这些文件通常位于 /usr/share/zoneinfo
。在大多数发行版中,这些文件是发行版的一部分并且默认安装。当我们谈论 Docker 基础镜像时,它们中的大多数默认情况下都不包含此包,它们需要使用包管理器手动安装或从源代码编译,例如:
1 | debian/ubuntu |
一些基础镜像(例如 CentOS 和 Fedora)附带了开箱即用的 tzdata
包。
但是,拥有这些文件是不够的,我们仍然需要在机器(或容器)的级别上定义所需的时区是什么。 /etc/localtime
文件配置本地系统的系统范围内的时区。它通常是指向 /usr/share/zoneinfo
的符号链接,其次是时区数据库名称,例如“Europe/Amsterdam”(即 /usr/share/zoneinfo/Europe/Amsterdam
)。您可以在 Wikipedia - https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List上找到可用时区的列表。
指定时区的另一种方法是使用 TZ
环境变量。它可以设置为时区标识符(即 TZ=Europe/Amsterdam
),但习惯仅在所需时区与机器时区不同的情况下设置它。设置时,它是在 /etc/localtime
之前。
Docker / Podman 中设置时区
现在,我们了解了时区的来源以及如何设置机器(或容器)的时区,让我们更深入地研究,并尝试证明Docker / Podman 的问题。为此,我们将在一个空的Ubuntu容器中执行命令 date
:
1 | docker run -i --rm ubuntu:21.04 date |
从输出来看,我们可以看到时区是 UTC
。 Ubuntu基本映像不包含 /usr/share/zoneinfo
或 /etc/etc/localtime
。默认情况下,正如我们以前所学,需要设置容器时区。 如果我们有选择,我们可以创建将安装 tzdata
软件包并将链接 /etc/localtime
设置到我们所需的时区(Europe/Amsterdam
)的链接的 Dockerfile
,例如:
1 | FROM ubuntu:21.04 |
如果我们使用 docker build -t ubuntu:amsterdam
构建新镜像并在其中运行 date
命令:
1 | docker run -i --rm ubuntu-amsterdam:0 date |
现在的输出有所不同,时间偏移为 +0200
,时区缩写在本例中为 CEST
(中欧夏令时)。
但这并不总是需要构建镜像,有几个原因不应该将其视为最佳实践:
并非所有镜像都有包管理器,有些镜像只是从
FROM scratch
开始为我们依赖的任何镜像维护
Dockerfile
将是一项艰巨的任务我们必须将这些新镜像上传到我们的集群可以到达的地方
我们可能现在才知道或决定构建时间中的时区应该是什么
我们稍后将讨论更多问题,但让我们首先解决这些问题。
我们可以从主机挂载所需的文件,而不是在构建时安装 tzdata
包,例如:
1 | docker run -v /usr/share/zoneinfo/Europe/Amsterdam:/etc/localtime -i --rm ubuntu:21.04 date |
所以这要容易得多,我们使用 -v
将 TZif 文件从主机直接挂载到容器的 /etc/localtime
并解决了上述所有问题,我们现在几乎可以运行任何具有所需时区的镜像。
此外,建议挂载整个
/usr/share/zoneinfo
目录,因为应用程序在运行时可能需要有关其他时区的信息,即-v /usr/share/zoneinfo:/usr/share/zoneinfo:ro
。
所以现在我们知道如何解决这个问题,让我们继续讨论 Kubernetes。 为此,我们将创建一个 date-pod.yaml
文件,在 Ubuntu 镜像上运行命令 date
,类似于我们的 docker run
命令:
1 | # date-pod.yaml |
让我们用 kubectl apply -f date-pod.yaml
执行这个 pod 并用 kubectl logs date-pod
打印日志,我们会看到打印的日期类似于:
1 | Tue Sep 29 00:00:00 UTC 2021 |
再说一次,时区是 UTC。 为了解决这个问题,我们将使用 hostPath
卷和 volumeMounts
来挂载时区,这类似于 docker run
命令中的 -v
参数。 创建 date-pod-amsterdam.yaml
文件:
1 | apiVersion: v1 |
使用 kubectl apply -f date-pod-amsterdam.yaml
应用它并使用 kubectl logs date-pod-amsterdam
查看日志:
1 | Tue Sep 29 02:00:00 CEST 2021 |
此外,建议挂载整个
/usr/share/zoneinfo
目录,因为应用程序在运行时可能需要有关其他时区的信息。
那么,问题解决了吗? 不完全是…
如果 pod 已经定义了
TZ
环境变量,我们的/etc/localtime
挂载将被忽略这是非常手动的工作,对于每个 pod,我们需要添加
hostPath
卷并具有 1 或 2 个volumeMounts
无法保证集群中的所有节点都安装了
tzdata
我们并不总是有权使用
hostPath
也没有访问节点的权限如果一些 pod 有时区,而另一些则没有——这只会造成更多混乱
大多数 Helm 包不支持通过
values.yaml
添加这些补丁
所以是时候见见 k8tz
了……
清理创建的资源:
kubectl delete pod date-pod date-pod-amsterdam
k8tz
设定时区
k8tz 是一个 Kubernetes 准入控制器和一个将时区注入 Pod 的 CLI 工具。 它可以用作手动工具,在本地自动转换我们的 Deployment 和 Pod,也可以作为准入控制器安装,并使用注释来完全自动化任何创建的 Pod 的过程。 但是,不止于此。 为了解决前面提到的所有问题,k8tz 使用了稍微不同的策略。
它不分配 hostPath
,而是分配 emptyDir
并注入引导程序 initContainer
以用 TZif 文件填充卷。 然后使用 emptyDir
将 /etc/localtime
和 /usr/share/zoneinfo
挂载到 Pod 中的每个容器。 并且为了确保所需的时区有效,它会将 TZ
环境变量添加到所有容器中。
k8tz准入控制器可以与Helm一起安装,很简单:
1 | helm repo add k8tz https://k8tz.github.io/k8tz/ |
现在可以创建 Pod,无需更多配置
1 | kubectl run -i -t ubuntu --image=ubuntu:21.04 --restart=OnFailure --rm=true --command date |
您还可以使用 k8tz.io/timezone 等注解来选择 pod 的时区:
1 | kubectl run -i -t ubuntu --image=ubuntu:21.04 --restart=OnFailure --rm=true --command date --annotations k8tz.io/timezone=Europe/London |
或者使用 k8tz.io/inject
为 Pod 禁用时区注入:
1 | kubectl run -i -t ubuntu --image=ubuntu:21.04 --restart=OnFailure --rm=true --command date --annotations k8tz.io/inject=false |
如果出于某种原因你想使用 hostPath
而不是 initContainer
,你可以使用 k8tz.io/strategy
注解:
1 | kubectl run -i -t ubuntu --image=ubuntu:21.04 --restart=OnFailure --rm=true --command date --annotations k8tz.io/strategy=hostPath |
这些注释也可以在命名空间中指定,并影响在命名空间中创建的所有 pod。
结论
Kubernetes 中的时区问题有多种解决方案,这些解决方案可以手动实现,但在此过程中存在一些挑战和限制。使用 k8tz
,您可以自动执行该过程,并且无需额外设置或更改现有资源即可工作,即使节点上没有所需文件也是如此。确保系统中所有组件的时区一致,并且所有组件都可以访问有关不同时区的信息。
k8tz 是免费的开源项目,请查看:github.com/k8tz/k8tz。
免责声明:我是 k8tz 的作者。