Golang 运行时镜像时区问题

本文主要介绍 Go 程序设置时间戳计算时间时需要注意的问题,以及如何在产出的 Go 服务镜像中设置正确的文件。

在修复一个时间相关的 bug 后,部署新服务时出现了如下的错误

1
could not load time location: unknown time zone Asia/Shanghai

系统报错显示无法加载 time zone,但是这段程序在本地运行是正常的。于是开始了排查之旅。

为了方便说明,下面是一个简单的程序,获取当前的时间,并且转化为指定时区的时间并输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"os"
"time"
)

func main() {
t := time.Now()

loc, err := time.LoadLocation("UTC")
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
fmt.Println(t.String())
fmt.Println(t.In(loc).String())

// Output:
// 2021-09-27 14:00:42.181169 +0800 CST m=+0.000100571
// 2021-09-27 06:00:42.181169 +0000 UTC

}

可以看到,在没有设置时区的时候,输出了当前系统时区的时间,而在设置 Location 之后,就将时间转化为了对应时区的时间。

但是在这段程序在打包成镜像后,出现了相同的错误。

原因在于 time.LoadLocation 这个函数。在用户传入合法的时区后, 函数会从下列文件中查找 timezone 信息:

1
2
3
4
5
6
7
8
// Many systems use /usr/share/zoneinfo, Solaris 2 has
// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
var zoneSources = []string{
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
runtime.GOROOT() + "/lib/time/zoneinfo.zip",
}

源文件

正常情况下,在本地的开发机上运行是没有问题的,但是如果最终的镜像的基础镜像是 alpine 或者是 busybox,那么就会出现问题,因为在这两个镜像为了体积原因是不会包含这些文件的。

在明确问题产生的原因后就可以修复了,对于多步构建的镜像来说,只需从上游镜像中拷贝一份时区文件即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Builder
FROM golang:latest AS builder

WORKDIR /server

COPY . .

ENV GOPROXY="https://goproxy.cn"

RUN go build

# Runtime
FROM busybox:glibc
WORKDIR /server

COPY --from=builder server .
# Copy zoneinfo from builder
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/

ENTRYPOINT ["server"]

这样修改之后就可以正常部署了。


Golang 运行时镜像时区问题
https://blog.zhangliangliang.cc/post/go-runtime-image-timezone.html
作者
Bobby Zhang
发布于
2023年3月27日
许可协议