本文主要介绍 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 mainimport ( "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()) }
可以看到,在没有设置时区的时候,输出了当前系统时区的时间,而在设置 Location 之后,就将时间转化为了对应时区的时间。
但是在这段程序在打包成镜像后,出现了相同的错误。
原因在于 time.LoadLocation
这个函数。在用户传入合法的时区后, 函数会从下列文件中查找 timezone 信息:
1 2 3 4 5 6 7 8 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 FROM golang:latest AS builder WORKDIR /server COPY . . ENV GOPROXY="https://goproxy.cn" RUN go build FROM busybox:glibc WORKDIR /server COPY --from=builder server . COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/ ENTRYPOINT ["server" ]
这样修改之后就可以正常部署了。