作者:kevinyan
用Go
开发的应用程序的一个优势在于,可以从"零"开始构建应用的Docker
镜像,镜像中仅需要包含Go
应用程序编译后的二进制文件,不需要额外安装其他执行环境。这样一来Go
应用镜像占用的空间确实很小(通常是几MB
),而且也会更安全些。常用的alpine
镜像(alpine
是专门为容器设计的小型Linux
发行版)中存在一个安全漏洞,该漏洞为大量生产容器留下了空的root
用户密码,所以如果你的的Go
应用程序在没有alpine
(或任何其他操作系统)的容器中运行,黑客就不能利用操作系统的漏洞去攻击容器里的应用。
使用Docker
的多阶段构建,从头开始构建映像非常简单,上一期的文章《线上Go项目的Docker镜像应该怎么构建?》里已经对此进行了详细描述。怎么从"scratch"
基础镜像,使用多阶段构建制作Go
应用程序的镜像。今天接着上期的话题继续说一个从零构建的应用镜像的容器时区设置的问题。
如果你的应用程序在初始化函数init
里有设置时区的操作,那么在启动应用容器时会遇到下面的运行时panic
:
unknown time zone Asia/Shanghai
复制代码
如果你在应用程序里不显示地设置时区,应用容器确实是能正常启动的,只不过这样time
包里的函数统一用的是UTC
时区,等你发现问题时再在程序里去显示设置时区仍然会遇到上面的运行时错误。
下面我们来做个试验,看看上面说的问题的现象。
首先写一个简单的Go
应用程序
package main
import (
"fmt"
"time"
)
func main() {
// 输出当前的时区
fmt.Print("Local time zone ")
fmt.Println(time.Now().Zone())
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
}
复制代码
然后写一个用来构建应用镜像的Dockerfile
,使用的就是之前介绍的多阶段构建。
FROM golang:alpine as build
RUN apk --no-cache add tzdata
WORKDIR /app
ADD . /app
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp
FROM scratch as final
COPY --from=build /app/myapp .
ENV TZ=Asia/Shanghai
CMD ["/myapp"]
复制代码
Dockerfile
里,我们用ENV
指令设置了TZ
这个环境变量。Go
运行时会查找TZ
这个环境变量来设置自己的时区,上面我们把TZ
设置成了Asia/Shanghai
,接下来我们看看在容器里应用是不是能如期运行,输出正确的时区和时间。
➜ docker build -t go_timezone .
➜ docker run --rm go_timezone
Local time zone UTC 0
2020-07-17 04:47:37
复制代码
根据运行结果发现时区的设置并没生效。
在Linux
系统下Go
运行时会从多个来源读取时区信息,在$GOROOT/src/time/zoneinfo.unix
文件里能够找到Go
运行时是从哪些地方读取时区信息的。
// 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",
}
复制代码
于是我就进到刚才镜像的容器里看了看,上面列的几个目录都没有找到。到这里算是定位到问题了,scratch
镜像里并不包含这些时区文件。那么解决办法就是从build
阶段的镜像里拷贝时区文件到最终的应用镜像。
FROM golang:alpine as build
RUN apk --no-cache add tzdata
WORKDIR /app
ADD . /app
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp
FROM scratch as final
COPY --from=build /app/myapp .
### 下面这行是新加的
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo
ENV TZ=Asia/Shanghai
CMD ["/myapp"]
复制代码
重新构建镜像、运行容器后就能发现时区设置已经正常了,Go运行时按照环境变量TZ
里指定的时区打印了当前时间。
➜ docker image rm go_timezone
➜ docker run --rm go_timezone
Local time zone CST 28800
2020-07-17 13:12:18.206 CST