V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kevinwan
V2EX  ›  推广

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

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

    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

    63 条回复    2020-12-14 12:32:10 +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,承载着海量日活没有问题
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2993 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 00:57 · PVG 08:57 · LAX 17:57 · JFK 20:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.