• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    构建Golang应用最小Docker镜像的实现

    我通常使用docker运行我的 golang 程序,在这里分享一下我构建 docker 镜像的经验。我构建 docker 镜像不仅优化构建后的体积,还要优化构建速度。

    示例应用

    首先贴出代码例子,我们假设要构建一个 http 服务

    package main
    
    import (
     "fmt"
     "net/http"
     "time"
    
     "github.com/gin-gonic/gin"
    )
    
    func main() {
     fmt.Println("Server Ready")
     router := gin.Default()
     router.GET("/", func(c *gin.Context) {
     c.String(200, "hello world, this time is: "+time.Now().Format(time.RFC1123Z))
     })
     router.GET("/github", func(c *gin.Context) {
     _, err := http.Get("https://api.github.com/")
     if err != nil {
      c.String(500, err.Error())
      return
     }
     c.String(200, "access github api ok")
     })
    
     if err := router.Run(":9900"); err != nil {
     panic(err)
     }
    }

    说明:

    这里我们可以先试一试构建后包的体积

    $ go build -o server
    $ ls -alh | grep server
    -rwxrwxrwx 1 eyas eyas 14.6M May 29 10:26 server
    

    14.6MB,这是一个http服务的 hello world,当然这是因为使用了 gin ,所以有些大,如果用标准包 net/http 写的 hello world,体积大概是接近 7 MB

    Dockerfile 的进化

    版本一,初步优化

    先看看第一个版本

    FROM golang:1.14-alpine as builder
    WORKDIR /usr/src/app
    ENV GOPROXY=https://goproxy.cn
    COPY ./go.mod ./
    COPY ./go.sum ./
    RUN go mod download
    COPY . .
    RUN go build -ldflags "-s -w" -o server
    
    FROM scratch as runner
    COPY --from=builder /usr/src/app/server /opt/app/
    CMD ["/opt/app/server"]

    说明:

    好了,下面开始构建镜像

    $ docker build -t server .
    ...
    Successfully built 8d3b91210721
    Successfully tagged server:latest
    

    到了这一步,构建成功,看看镜像大小

    $ docker images
    server     latest     8d3b91210721   1 minutes ago    11MB
    

    11MB,还行,现在运行一下

    $ docker run -p 9900:9900 server
    standard_init_linux.go:211: exec user process caused "no such file or directory"

    发现启动报错了,而且main函数的第一行打印语句都没有出现,所以整个程序完全没有运行。错误原因是缺少库依赖文件。这其实是构建的 go 程序还依赖底层的 so 库文件,不信可以在物理机编译后看看它的依赖

    $ go build -o server
    $ ldd server
        linux-vdso.so.1 (0x00007ffcfb775000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f9a8dc47000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9a8d856000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f9a8de66000)
    

    这是不是跟我们的认知有点出入呢,说好无依赖的呢,结果还是有几个依赖库文件呢,虽然这几个依赖都是最底层的,一般操作系统都会有,可谁叫我们选了 scratch,这个镜像里面除了linux内核以外真的什么都没了。

    这是因为go build 是默认启用 CGO 的,不信你可以试试这个命令 go env CGO_ENABLED,在 CGO 开启情况下,无论代码有没有用CGO,都会有库依赖文件,解决方法也很简单,手动指定关闭CGO就行,而且包体积并不会增加哦,还会减少呢

    $ CGO_ENABLED=0 go build -o server
    $ ldd server
        not a dynamic executable
    

    版本二,解决运行时报错

    FROM golang:1.14-alpine as builder
    WORKDIR /usr/src/app
    ENV GOPROXY=https://goproxy.cn
    COPY ./go.mod ./
    COPY ./go.sum ./
    RUN go mod download
    COPY . .
    -RUN go build -ldflags "-s -w" -o server
    +RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server
    
    FROM scratch as runner
    COPY --from=builder /usr/src/app/server /opt/app/
    CMD ["/opt/app/server"]

    改动点: go build 前加了 CGO_ENABLED=0

    $ docker build -t server .
    ...
    Successfully built a81385160e25
    Successfully tagged server:latest
    $ docker run -p 9900:9900 server
    [GIN-debug] GET  /             --> main.main.func1 (3 handlers)
    [GIN-debug] GET  /github          --> main.main.func2 (3 handlers)
    [GIN-debug] Listening and serving HTTP on :9900
    

    正常启动了,我们访问一下试试,访问之前看看当前时间

    $ date
    Fri May 29 13:11:28 CST 2020
    
    $ curl http://localhost:9900    
    hello world, this time is: Fri, 29 May 2020 05:18:28 +0000
    
    $ curl http://localhost:9900/github
    Get "https://api.github.com/": x509: certificate signed by unknown authority

    发现有问题

    解决问题

    版本三,解决运行环境时区与证书问题

    FROM golang:1.14-alpine as builder
    WORKDIR /usr/src/app
    ENV GOPROXY=https://goproxy.cn
    +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories  \
    
    + apk add --no-cache ca-certificates tzdata
    COPY ./go.mod ./
    COPY ./go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server
    
    FROM scratch as runner
    +COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
    COPY --from=builder /usr/src/app/server /opt/app/
    CMD ["/opt/app/server"]

    在 builder 阶段,安装了 ca-certificates tzdata 两个库,在runner阶段,将时区配置和根证书复制了一份

    $ docker build -t server .
    ...
    Successfully built e0825838043d
    Successfully tagged server:latest
    $ docker run -p 9900:9900 server
    [GIN-debug] GET  /             --> main.main.func1 (3 handlers)
    [GIN-debug] GET  /github          --> main.main.func2 (3 handlers)
    [GIN-debug] Listening and serving HTTP on :9900
    

    访问一下试试

    $ date
    Fri May 29 13:27:16 CST 2020
    
    $ curl http://localhost:9900    
    hello world, this time is: Fri, 29 May 2020 13:27:16 +0800
    
    $ curl http://localhost:9900/github
    access github api ok
    
    

    一切正常了,看看当前镜像大小

    $ docker images
    server     latest     e0825838043d   9 minutes ago    11.3MB
    

    才 11.3MB,已经很小了,但是,还可以更小,就是把构建后的包再压缩一次

    版本四,进一步减小体积

    FROM golang:1.14-alpine as builder
    WORKDIR /usr/src/app
    ENV GOPROXY=https://goproxy.cn
    RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories  \
    
    - apk add --no-cache ca-certificates tzdata
    + apk add --no-cache upx ca-certificates tzdata
    COPY ./go.mod ./
    COPY ./go.sum ./
    RUN go mod download
    COPY . .
    -RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server
    +RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server \
    
    + upx --best server -o _upx_server  \
    
    + mv -f _upx_server server
    
    FROM scratch as runner
    COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
    COPY --from=builder /usr/src/app/server /opt/app/
    CMD ["/opt/app/server"]
    
    

    在 builder 阶段,安装了 upx ,并且go build 完成后,使用 upx 压缩了一下,执行一下构建,你会发现这个构建时间变长了,这是因为我给 upx 设置的参数是 --best ,也就是最大压缩级别,这样压缩出来的后会尽可能的小,如果嫌慢,可以降低压缩级别从 -1 到 -9 ,数字越大压缩级别越高,也越慢。我使用 --best 构建完成后看看镜像体积。

    $ docker build -t server .
    ...
    Successfully built 80c3f3cde1f7
    Successfully tagged server:latest
    $ docker images
    server     latest     80c3f3cde1f7   1 minutes ago    4.26MB
    

    这下子可小了,才 4.26MB,再去试试那两个接口,一切正常。优化到此结束。

    最终的Dockerfile

    FROM golang:1.14-alpine as builder
    WORKDIR /usr/src/app
    ENV GOPROXY=https://goproxy.cn
    RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories  \
    
     apk add --no-cache upx ca-certificates tzdata
    COPY ./go.mod ./
    COPY ./go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server \
    
     upx --best server -o _upx_server  \
    
     mv -f _upx_server server
    
    FROM scratch as runner
    COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
    COPY --from=builder /usr/src/app/server /opt/app/
    CMD ["/opt/app/server"]

    总结

    要减小镜像体积,首先多阶段构建这很重要,这样就可以把编译环境和运行环境分开。

    另外,选择 scratch 这个镜像其实很不明智,它虽然很小,但是它太原始了,里面什么工具都没有,程序启动后,连容器都进不去,就算进去了什么都做不了。所以就算一昧的追求尽可能小的镜像体积,也不建议选择 scratch 作为运行环境,我暂时只踩到小部分的坑,后面还有更多坑没踩,我也没有兴趣继续踩 scratch 的坑。

    建议选择 alpine ,alpine 的镜像大小是 5.61MB 这个大小其实还是镜像解压后的大小,实际上下载镜像的时候,只需要下载 2.68 MB 。还有,上文所有我说的镜像体积,全都是指解压后的镜像体积,和实际上传下载时的体积是不一样的,docker自己会压缩一次再传输镜像

    还有个很小的镜像是 busybox,它的体积是 1.22MB,下载 705.6 KB ,有大部分的linux命令可用,但是运行环境还是很原始,有兴趣可以去尝试

    无论是 alpine 还是 busybox ,他们都会上述时区和证书问题,同样按照上面方法就能解决,切换到 alpine 或者 busybox 也很简单,只需要修改 runner 基础镜像就行

    -FROM scratch as runner
    +FROM alpine as runner
    

    或者

    -FROM scratch as runner
    +FROM busybox as runne
    

    到此这篇关于构建Golang应用最小Docker镜像的实现的文章就介绍到这了,更多相关Golang构建最小Docker镜像内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

    您可能感兴趣的文章:
    • 基于Docker镜像部署go项目的方法步骤
    • Docker 部署Go的两种基础镜像的实现
    上一篇:Golang中的Unicode与字符串示例详解
    下一篇:Go语言HTTP请求流式写入body的示例代码
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    构建Golang应用最小Docker镜像的实现 构建,Golang,应用,最小,Docker,