最简单的 Go Dockerfile 编写姿势,没有之一! - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kevinwan
V2EX    推广

最简单的 Go Dockerfile 编写姿势,没有之一!

  •  8
     
  •   kevinwan 2020-12-10 14:46:40 +08:00 9340 次点击
    这是一个创建于 1774 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1. Dockerfile 一些额外注意点

    • 选择最简单的镜像

      比如 alpine,整个镜像 5M 左右

    • 设置镜像时区

      RUN apk add --no-cache tzdata ENV TZ Asia/Shanghai 

    2. 多阶段构建

    • 第一阶段构建否则构建出可执行文件,确保构建过程独立于宿主机
    • 第二阶段将第一阶段的输出作为输入,构建出最终的极简镜像

    3. 完整 Dockerfile 编写过程

    • 首先安装 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":""} 

    4. 总结

    goctl 工具极大简化了 Dockerfile 文件的编写,提供了开箱即用的最佳实践,并且支持了模板自定义。

    如果觉得工具有帮助,欢迎 star

    5. 项目地址

    https://github.com/tal-tech/go-zero

    64 条回复    2023-11-01 18:20:43 +08:00
    yzbythesea
        1
    yzbythesea  
       2020-12-10 14:48:19 +08:00   1
    为什么不在本地编译好 binary 然后传进 docker 里呢?
    Vegetable
        2
    Vegetable  
       2020-12-10 14:51:07 +08:00
    @yzbythesea 可能应上 CI 环境呗,本地能做的只有 git push
    feelinglucky
        3
    feelinglucky  
       2020-12-10 14:54:44 +08:00   1
    @yzbythesea 本地编译的二进制文件再打包到 Docker 镜像中不符合 CI 的规范,同时也不方便交叉编译等情况,所以正确规范的做法是 docker golang 镜像 build 通过后,将产出二进制文件 cp 到第二 stage 的 base 镜像。

    不过楼主的 Dockerfile 我建议使用 make 等构建工具去构建( golang 镜像自带的),因为如果涉及到更改配置、参数什么的,就又要该 Dockerfile 文件了,会很麻烦而且容易出错。
    lwch
        4
    lwch  
       2020-12-10 14:59:49 +08:00
    alpine 镜像里的 glibc 是阉割过的版本,拿来跑 go 程序需要专门编译,而且线上实测过性能下降特别明显,只能拿来做个玩具
    kevinwan
        5
    kevinwan  
    OP
       2020-12-10 15:14:33 +08:00
    @lwch 有实测数据嘛?我们千万级日活都是用 `alpine` 的,没遇到问题
    kevinwan
        6
    kevinwan  
    OP
       2020-12-10 15:15:11 +08:00
    @feelinglucky 愿闻其详
    xin053
        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
    knight0zh
        8
    knight0zh  
       2020-12-10 15:25:18 +08:00
    更新真快啊,上周问的功能这周就出了。
    kevinwan
        9
    kevinwan  
    OP
       2020-12-10 15:32:26 +08:00 via iPhone
    @xin053 对的,这个很快就改掉,
    12101111
        10
    12101111  
       2020-12-10 15:43:36 +08:00   1
    @lwch alpine 用的是 musl, 根本没有 glibc
    CGO_ENABLED=0 的情况下 go 编译器压根不需要 libc, 性能和什么 libc 没有任何关系, go 的标准库在 Linux 上是直接调 syscall 的
    GopherDaily
        11
    GopherDaily  
       2020-12-10 16:07:07 +08:00
    哪来的♀
    kevinwan
        12
    kevinwan  
    OP
       2020-12-10 16:21:26 +08:00
    @xin053 已修改,代码已提交
    kevinwan
        13
    kevinwan  
    OP
       2020-12-10 16:22:36 +08:00
    下一篇文章写个一键生成 k8s 部署文件的
    lyi4ng
        14
    lyi4ng  
       2020-12-10 16:31:57 +08:00
    alpine 啊,我记得他的 lfs 是通过 minilibc 和 busybox 构建的,那得要求服务相关代码和脚本高度规范的,就 busybox 这玩意的 shll 规范就能折磨死无数用 bash 写 shell 脚本的人
    feelinglucky
        15
    feelinglucky  
       2020-12-10 16:44:24 +08:00
    @kevinwan 如果是一间部署 K8s 的脚本工具集的话,就不要重复造轮子了,兄弟见笑可以参考我原先写的

    https://github.com/mingcheng/deploy-k8s-within-aliyun-mirror
    kevinwan
        16
    kevinwan  
    OP
       2020-12-10 16:52:12 +08:00
    @feelinglucky 学习下,不过我轮子已造好,只是写个文章介绍下哈
    renzhe8102
        17
    renzhe8102  
       2020-12-10 17:14:19 +08:00
    yzbythesea
        18
    yzbythesea  
       2020-12-10 17:42:31 +08:00
    @feelinglucky 如果说 CI/CD,我也没见过。都是 builder 的机器在编译,当然你可以把 builder 容器化,但是 builder 的 docker image 是和生产环境不一样的。
    beginor
        19
    beginor  
       2020-12-10 21:22:08 +08:00 via Android
    把所有的语句都写到一个 install.sh 里面,Dockerfile 一个指令 Run install.sh
    kevinwan
        20
    kevinwan  
    OP
       2020-12-10 22:16:03 +08:00 via iPhone
    @beginor 这样有何好处?
    kieoo
        21
    kieoo  
       2020-12-10 22:47:32 +08:00
    @feelinglucky 使用 docker golang build 有个问题, go mod download 等于要重新拉所有的依赖包, 打包时间会变长; 在 builder 机器上可以有缓存, 减少包时间
    cs419
        22
    cs419  
       2020-12-10 23:00:24 +08:00
    最简单!! 没有之一!!

    分明是钓鱼呀
    集思广益 让大伙提供优化思路
    joesonw
        23
    joesonw  
       2020-12-10 23:14:35 +08:00 via iPhone
    不要用 alpine 用 google 的 distroless
    chazyu1996
        24
    chazyu1996  
       2020-12-10 23:49:18 +08:00
    这样每次 build 都会去拉 mod 依赖项,建议在本地编译,或者把 GOPATH COPY 进去,不然会很慢
    Lemeng
        25
    Lemeng  
       2020-12-10 23:57:21 +08:00
    集思广益
    dreamusername
        26
    dreamusername  
       2020-12-11 00:33:55 +08:00   1
    不够简单,应该要尽量减少分层,一个 apk add 你写了 3 行
    wellsc
        27
    wellsc  
       2020-12-11 00:36:31 +08:00
    @dreamusername 说到点子上了,写 docker file 最需要注意的就是 layer 了
    xuzhzzz
        28
    xuzhzzz  
       2020-12-11 01:52:32 +08:00
    go build 总要拉私有的包吧,这个权限怎么处理的呢?看到你的 dockerfile 里没有处理相关的
    我的做法是 COPY ~/.netrc,感觉比较蠢,大家都是咋做的
    dayeye2006199
        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"]
    ---
    dayeye2006199
        30
    dayeye2006199  
       2020-12-11 03:04:51 +08:00
    再推荐个 Dockerfile 的 linter,可以找到一些比较有问题的 Dockerfile 写法问题,例如不恰当分层,权限过高,标签不恰当啊之类的。
    https://github.com/hadolint/hadolint
    kevinwan
        31
    kevinwan  
    OP
       2020-12-11 03:17:17 +08:00 via iPhone
    @dreamusername master 已经改掉了,只是文章不能编辑
    kevinwan
        32
    kevinwan  
    OP
       2020-12-11 03:19:44 +08:00 via iPhone
    @dayeye2006199 感觉跟我写的版本相差不大,有劳指出差别哈,btw: 层已合并
    kevinwan
        33
    kevinwan  
    OP
       2020-12-11 03:20:08 +08:00 via iPhone
    @dayeye2006199 谢谢,明天试试
    kevinwan
        34
    kevinwan  
    OP
       2020-12-11 03:22:31 +08:00 via iPhone
    @cs419 开源的本质就是集思广益,再回馈社会嘛
    beginor
        35
    beginor  
       2020-12-11 07:55:05 +08:00
    @lwch alpine 自带的不是 glibc 而是 musl c, 如果不能迁移到 musl 的话,alpine 并没有太大的体积优势。
    beginor
        36
    beginor  
       2020-12-11 07:57:12 +08:00
    @kevinwan shell 比 dockerfile 容易调试, 至少可以一行一行的输入进行测试; 而且这样做镜像只有一层, 编译出来的镜像有体积优势
    beginor
        37
    beginor  
       2020-12-11 07:59:57 +08:00
    @kieoo golang 还好了, 要是 node 的话, 每次都要执行 npm ci 才真是折磨
    kaka6
        38
    kaka6  
       2020-12-11 08:45:27 +08:00
    go 现在还用 GO111MODULE 吗,1.4 以后好像升级了,包管理更方便了,一直还没去试
    kevinwan
        39
    kevinwan  
    OP
       2020-12-11 08:54:48 +08:00 via iPhone
    @beginor 这样吃不上缓存层
    kevinwan
        40
    kevinwan  
    OP
       2020-12-11 08:55:27 +08:00 via iPhone
    @kaka6 1.14 默认 auto,我记得 1.15 默认 on 了
    feelinglucky
        41
    feelinglucky  
       2020-12-11 08:59:05 +08:00
    @kieoo 干净的编译环境是比较重要的,相比之下打包时间变长以及缓存等对比不是很关键
    kiddingU
        42
    kiddingU  
       2020-12-11 09:33:22 +08:00
    多条 run 指令为何不合并成一条
    zunceng
        43
    zunceng  
       2020-12-11 09:33:37 +08:00
    @kieoo 我碰到过这个问题 后来为了加快速度编译和打包分了两步
    编译在一个编译用的容器内运行 可以把 GOPATH GOROOT 等挂载进去 做增量编译
    vZexc0m
        44
    vZexc0m  
       2020-12-11 09:48:06 +08:00
    @kieoo #21 不是每次都会重新拉所有的依赖包,因为先复制的 go.mod 和 go.sum ,这两个文件没有变动的时候会使用缓存。
    lwch
        45
    lwch  
       2020-12-11 09:55:18 +08:00
    @kevinwan 没有,我们当初用 docker 的时候还是 go1.10~1.11 左右的时代,监控做的也不全面
    f6x
        46
    f6x  
       2020-12-11 10:05:46 +08:00
    @dayeye2006199 的版本更标准. @kevinwan
    build 和 pack 分离.
    然后把 go 环境的 builder 镜像本地缓存, 没有模块变化时达到最快构建速度.
    kevinwan
        47
    kevinwan  
    OP
       2020-12-11 10:19:32 +08:00 via iPhone
    @kiddingU 合了,只是文章不能改
    fy
        48
    fy  
       2020-12-11 10:47:51 +08:00   3
    看了第一条:

    选择最简单的镜像
    比如 alpine,整个镜像 5M 左右

    告辞。
    mritd
        49
    mritd  
       2020-12-11 11:30:20 +08:00 via iPhone
    把那两个 ADD 指令删掉
    kevinwan
        50
    kevinwan  
    OP
       2020-12-11 12:05:25 +08:00
    @mritd 这个是优化技巧,吃缓存的
    tozp
        51
    tozp  
       2020-12-11 12:11:38 +08:00 via iPhone
    不错,赞一下
    beginor
        52
    beginor  
       2020-12-11 13:11:18 +08:00
    @kevinwan 缓存的副作用是导致最终编译出来的镜像体积增大
    kevinwan
        53
    kevinwan  
    OP
       2020-12-11 13:22:40 +08:00 via iPhone
    @beginor 两阶段构建,不存在变大的
    fuis
        54
    fuis  
       2020-12-11 13:54:44 +08:00   1
    @fy 哈哈,我也是看到第一点就不想看了
    kevinwan
        55
    kevinwan  
    OP
       2020-12-11 14:00:02 +08:00 via iPhone
    @fuis 支持模板完全可定制,按需自定义
    beginor
        56
    beginor  
       2020-12-11 19:23:32 +08:00 via Android
    @kevinwan run 语句越多, 镜像越大
    kevinwan
        57
    kevinwan  
    OP
       2020-12-12 12:31:46 +08:00
    周一我会发一篇文章讲解如何一键生成 K8S 部署文件
    kevinwan
        58
    kevinwan  
    OP
       2020-12-13 19:24:02 +08:00
    @fy scratch 没有 sh,无法登记去查问题
    phx13ye
        59
    phx13ye  
       2020-12-14 01:37:02 +08:00 via Android
    alpine 有什么问题呀?
    @fuis
    @fy
    kevinwan
        60
    kevinwan  
    OP
       2020-12-14 07:31:07 +08:00 via iPhone   1
    @phx13ye 他们意思这不是最简单的镜像,scratch 才是,而我觉得 scratch 没有 sh 不方便
    phx13ye
        61
    phx13ye  
       2020-12-14 10:04:57 +08:00 via Android
    @kevinwan ok, 我们部署的需要不同国家用不同时区,不需要 cgo,一直是 alpine 加个 tzdata,没什么问题就不换了
    fy
        62
    fy  
       2020-12-14 10:24:41 +08:00   1
    @phx13ye

    1. slim 镜像体积不大,而且只用拉一遍,alpine 没有很大优势。
    2. 项目兼容性问题比较严重,直接或间接使用 cgo (你也不能总是把你的依赖都查一遍),可能会出未知 BUG
    3. 有些老哥提到的性能问题(个人没测,但一直听说)
    4. 外围工具(如监测等)的兼容性问题,甚至有可能安装到比 slim 镜像还大几倍的包
    kevinwan
        63
    kevinwan  
    OP
       2020-12-14 12:32:10 +08:00
    @fy 要想镜像小,其它都不考虑可以用 scratch,不过我们线上都是用的 alpine,承载着海量日活没有问题
    join
        64
    join  
       2023-11-01 18:20:43 +08:00
    谢谢,23 年了,对我还是有帮助。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2749 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 13:04 PVG 21:04 LAX 06:04 JFK 09:04
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86