选择最简单的镜像
比如 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 ![]() 为什么不在本地编译好 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 ![]() @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 ![]() 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 ![]() @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 ![]() 不够简单,应该要尽量减少分层,一个 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 ![]() 分享个 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 ![]() 看了第一条: 选择最简单的镜像 比如 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 ![]() @phx13ye 1. slim 镜像体积不大,而且只用拉一遍,alpine 没有很大优势。 2. 项目兼容性问题比较严重,直接或间接使用 cgo (你也不能总是把你的依赖都查一遍),可能会出未知 BUG 3. 有些老哥提到的性能问题(个人没测,但一直听说) 4. 外围工具(如监测等)的兼容性问题,甚至有可能安装到比 slim 镜像还大几倍的包 |
![]() | 64 join 2023-11-01 18:20:43 +08:00 谢谢,23 年了,对我还是有帮助。 |