选择最简单的镜像
比如 alpine,整个镜像 5M 左右
设置镜像时区
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai
首先安装 goctl
工具
GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl
在 greet
项目下创建一个 hello
服务
goctl api new hello
文件结构如下:
greet
├── go.mod
├── go.sum
└── service
└── hello
├── Dockerfile
├── etc
│ └── hello-api.yaml
├── hello.api
├── hello.go
└── internal
├── config
│ └── config.go
├── handler
│ ├── hellohandler.go
│ └── routes.go
├── logic
│ └── hellologic.go
├── svc
│ └── servicecontext.go
└── types
└── types.go
在 hello
目录下一键生成 Dockerfile
goctl docker -go greet.go
Dockerfile
内容如下:
FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOOS linux
ENV GOPROXY https://goproxy.cn,direct
WORKDIR /build/zero
ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
COPY service/hello/etc /app/etc
RUN go build -ldflags="-s -w" -o /app/hello service/hello/hello.go
FROM alpine
RUN apk update --no-cache
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache tzdata
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/hello /app/hello
COPY --from=builder /app/etc /app/etc
CMD ["./hello", "-f", "etc/hello-api.yaml"]
在 greet
目录下 build
镜像
docker build -t hello:v1 -f service/hello/Dockerfile .
查看镜像
hello v1 5455f2eaea6b 7 minutes ago 18.1MB
可以看出镜像大小约为 18M 。
启动服务
docker run --rm -it -p 8888:8888 hello:v1
测试服务
$ curl -i http://localhost:8888/from/you
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 10 Dec 2020 06:03:02 GMT
Content-Length: 14
{"message":""}
goctl
工具极大简化了 Dockerfile
文件的编写,提供了开箱即用的最佳实践,并且支持了模板自定义。
如果觉得工具有帮助,欢迎 star 🤝
1
yzbythesea 2020-12-10 14:48:19 +08:00 1
为什么不在本地编译好 binary 然后传进 docker 里呢?
|
2
Vegetable 2020-12-10 14:51:07 +08:00
@yzbythesea 可能应上 CI 环境呗,本地能做的只有 git push
|
3
feelinglucky 2020-12-10 14:54:44 +08:00 1
@yzbythesea 本地编译的二进制文件再打包到 Docker 镜像中不符合 CI 的规范,同时也不方便交叉编译等情况,所以正确规范的做法是 docker golang 镜像 build 通过后,将产出二进制文件 cp 到第二 stage 的 base 镜像。
不过楼主的 Dockerfile 我建议使用 make 等构建工具去构建( golang 镜像自带的),因为如果涉及到更改配置、参数什么的,就又要该 Dockerfile 文件了,会很麻烦而且容易出错。 |
4
lwch 2020-12-10 14:59:49 +08:00
alpine 镜像里的 glibc 是阉割过的版本,拿来跑 go 程序需要专门编译,而且线上实测过性能下降特别明显,只能拿来做个玩具
|
6
kevinwan OP @feelinglucky 愿闻其详
|
7
xin053 2020-12-10 15:18:44 +08:00 1
Dockerfile 最佳实践中尽量减少层,比如将多个 RUN 合成一个,例如
RUN apk update --no-cache RUN apk add --no-cache ca-certificates RUN apk add --no-cache tzdata 可以写成 RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata |
8
knight0zh 2020-12-10 15:25:18 +08:00
更新真快啊,上周问的功能这周就出了。
|
10
12101111 2020-12-10 15:43:36 +08:00 1
@lwch alpine 用的是 musl, 根本没有 glibc
CGO_ENABLED=0 的情况下 go 编译器压根不需要 libc, 性能和什么 libc 没有任何关系, go 的标准库在 Linux 上是直接调 syscall 的 |
11
GopherDaily 2020-12-10 16:07:07 +08:00
哪来的🚴♀️
|
13
kevinwan OP 下一篇文章写个一键生成 k8s 部署文件的
|
14
lyi4ng 2020-12-10 16:31:57 +08:00
alpine 啊,我记得他的 lfs 是通过 minilibc 和 busybox 构建的,那得要求服务相关代码和脚本高度规范的,就 busybox 这玩意的 shll 规范就能折磨死无数用 bash 写 shell 脚本的人
|
15
feelinglucky 2020-12-10 16:44:24 +08:00
@kevinwan 如果是一间部署 K8s 的脚本工具集的话,就不要重复造轮子了,兄弟见笑可以参考我原先写的
https://github.com/mingcheng/deploy-k8s-within-aliyun-mirror |
16
kevinwan OP @feelinglucky 学习下,不过我轮子已造好,只是写个文章介绍下哈
|
17
renzhe8102 2020-12-10 17:14:19 +08:00
@12101111 zj
|
18
yzbythesea 2020-12-10 17:42:31 +08:00
@feelinglucky 如果说 CI/CD,我也没见过。都是 builder 的机器在编译,当然你可以把 builder 容器化,但是 builder 的 docker image 是和生产环境不一样的。
|
19
beginor 2020-12-10 21:22:08 +08:00 via Android
把所有的语句都写到一个 install.sh 里面,Dockerfile 一个指令 Run install.sh
|
21
kieoo 2020-12-10 22:47:32 +08:00
@feelinglucky 使用 docker golang build 有个问题, go mod download 等于要重新拉所有的依赖包, 打包时间会变长; 在 builder 机器上可以有缓存, 减少包时间
|
22
cs419 2020-12-10 23:00:24 +08:00
最简单!! 没有之一!!
分明是钓鱼呀 集思广益 让大伙提供优化思路 |
23
joesonw 2020-12-10 23:14:35 +08:00 via iPhone
不要用 alpine 用 google 的 distroless
|
24
chazyu1996 2020-12-10 23:49:18 +08:00
这样每次 build 都会去拉 mod 依赖项,建议在本地编译,或者把 GOPATH COPY 进去,不然会很慢
|
25
Lemeng 2020-12-10 23:57:21 +08:00
集思广益
|
26
dreamusername 2020-12-11 00:33:55 +08:00 1
不够简单,应该要尽量减少分层,一个 apk add 你写了 3 行
|
27
wellsc 2020-12-11 00:36:31 +08:00
@dreamusername 说到点子上了,写 docker file 最需要注意的就是 layer 了
|
28
xuzhzzz 2020-12-11 01:52:32 +08:00
go build 总要拉私有的包吧,这个权限怎么处理的呢?看到你的 dockerfile 里没有处理相关的
我的做法是 COPY ~/.netrc,感觉比较蠢,大家都是咋做的 |
29
dayeye2006199 2020-12-11 03:02:48 +08:00 1
分享个 google 项目的示例写法:
--- # Build the manager binary FROM golang:1.13 as builder WORKDIR /workspace # Copy the Go Modules manifests COPY go.mod go.mod COPY go.sum go.sum # cache deps before building and copying source so that we don't need to re-download as much # and so that source changes don't invalidate our downloaded layer RUN go mod download # Copy the go source COPY main.go main.go COPY api/ api/ COPY controllers/ controllers/ # Build RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . USER nonroot:nonroot ENTRYPOINT ["/manager"] --- |
30
dayeye2006199 2020-12-11 03:04:51 +08:00
再推荐个 Dockerfile 的 linter,可以找到一些比较有问题的 Dockerfile 写法问题,例如不恰当分层,权限过高,标签不恰当啊之类的。
https://github.com/hadolint/hadolint |
31
kevinwan OP @dreamusername master 已经改掉了,只是文章不能编辑👍🏻
|
32
kevinwan OP @dayeye2006199 感觉跟我写的版本相差不大,有劳指出差别哈,btw: 层已合并
|
33
kevinwan OP @dayeye2006199 谢谢,明天试试👍🏻
|
35
beginor 2020-12-11 07:55:05 +08:00
@lwch alpine 自带的不是 glibc 而是 musl c, 如果不能迁移到 musl 的话,alpine 并没有太大的体积优势。
|
36
beginor 2020-12-11 07:57:12 +08:00
@kevinwan shell 比 dockerfile 容易调试, 至少可以一行一行的输入进行测试; 而且这样做镜像只有一层, 编译出来的镜像有体积优势
|
38
kaka6 2020-12-11 08:45:27 +08:00
go 现在还用 GO111MODULE 吗,1.4 以后好像升级了,包管理更方便了,一直还没去试
|
41
feelinglucky 2020-12-11 08:59:05 +08:00
@kieoo 干净的编译环境是比较重要的,相比之下打包时间变长以及缓存等对比不是很关键
|
42
kiddingU 2020-12-11 09:33:22 +08:00
多条 run 指令为何不合并成一条
|
43
zunceng 2020-12-11 09:33:37 +08:00
@kieoo 我碰到过这个问题 后来为了加快速度编译和打包分了两步
编译在一个编译用的容器内运行 可以把 GOPATH GOROOT 等挂载进去 做增量编译 |
44
vZexc0m 2020-12-11 09:48:06 +08:00
@kieoo #21 不是每次都会重新拉所有的依赖包,因为先复制的 go.mod 和 go.sum ,这两个文件没有变动的时候会使用缓存。
|
46
f6x 2020-12-11 10:05:46 +08:00
|
48
fy 2020-12-11 10:47:51 +08:00 3
看了第一条:
选择最简单的镜像 比如 alpine,整个镜像 5M 左右 告辞。 |
49
mritd 2020-12-11 11:30:20 +08:00 via iPhone
把那两个 ADD 指令删掉
|
51
tozp 2020-12-11 12:11:38 +08:00 via iPhone
不错,赞一下
|
57
kevinwan OP 周一我会发一篇文章讲解如何一键生成 K8S 部署文件
|
60
kevinwan OP @phx13ye 他们意思这不是最简单的镜像,scratch 才是,而我觉得 scratch 没有 sh 不方便
|
61
phx13ye 2020-12-14 10:04:57 +08:00 via Android
@kevinwan ok, 我们部署的需要不同国家用不同时区,不需要 cgo,一直是 alpine 加个 tzdata,没什么问题就不换了
|
62
fy 2020-12-14 10:24:41 +08:00 1
@phx13ye
1. slim 镜像体积不大,而且只用拉一遍,alpine 没有很大优势。 2. 项目兼容性问题比较严重,直接或间接使用 cgo (你也不能总是把你的依赖都查一遍),可能会出未知 BUG 3. 有些老哥提到的性能问题(个人没测,但一直听说) 4. 外围工具(如监测等)的兼容性问题,甚至有可能安装到比 slim 镜像还大几倍的包 |
64
join 2023-11-01 18:20:43 +08:00
谢谢,23 年了,对我还是有帮助。
|