容器化Go应用--基础镜像的未知时区问题
用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
- [编程经验] Pandas中比较好用的几个方法
- [编程经验] Elasticsearch 初识
- 2017.10.26水题大作战部分题解
- 2017.10.27涩会题大乱斗部分题解
- 【 关关的刷题日记50】 Leetcode 345. Reverse Vowels of a String
- Day1上午解题报告
- 【 关关的刷题日记51】 Leetcode 67. Add Binary
- 【 关关的刷题日记53】 Leetcode 100. Same Tree
- Day1下午解题报告
- 【关关的刷题日记54】Leetcode 226. Invert Binary Tree
- Day2上午解题报告
- 【关关的刷题日记55】Leetcode 404. Sum of Left Leaves
- CSS选择器详解
- 前端开发必备之Emmet
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python pathlib模块的基本使用和总结
- Python 爬取前程无忧最新招聘数据 matplotlib数据分析与可视化
- Python opencv图像处理基础总结(一)
- Python opencv图像处理基础总结(二) ROI操作与泛洪填充 模糊操作 边缘保留滤波EPF
- python asyncio+aiohttp异步请求 批量快速验证代理IP是否可用
- python pyecharts数据可视化 玫瑰图、柱形图、饼图、环图
- Python opencv图像处理基础总结(三) 图像直方图 直方图应用 直方图反向投影
- Python opencv图像处理基础总结(四) 模板匹配 图像二值化
- python pyecharts数据可视化 词云图 仪表盘 水球图
- python jupyter notebook配置 更改默认工作目录 更换皮肤主题 代码字体 大小
- 关于直播卖货系统平台在微信浏览器中音视频播放的问题
- python爬虫 scrapy爬虫框架的基本使用
- Python opencv图像处理基础总结(五) 图像金字塔 图像梯度 Canny算法边缘提取
- python scrapy爬虫练习(1) 爬取豆瓣电影top250信息
- python爬虫 senlenium爬取拉勾网招聘数据