• Docker scratch 无法正常运行golang二进制程序的问题


    使用Docker构建容器能够极大的降低运维成本,提高部署效率,同时非常方便对服务的平行扩展。然而在构建容器镜像过程中的,存在着一个难以避免的问题,就是如果使用常见的发行版本作为程序运行的基础环境,那么即使一个服务本身的运行文件非常小,最终构建的镜像也可能会有会在运行环境的镜像的基础上变得更大,动不动就是数百M的体积。

    以最常用于微服务开发的golang为例,golang的二进制程序可以一次开发跨平台编译,到处运行,因此其本身的程序自洽性其实非常完善,也很少会依赖复杂的外部环境,因此常用的发行版本镜像硕大的体积其实很没必要。因此,alpine成为了一个非常好的选择。最终alpine也不负众望,将最终的镜像体积减小到了10M+左右,已经压缩了非常大的空间了,结果还算理想。

    然而,能不能更小呢?

    docker自带的scratch镜像给了我一个思路:没有任何镜像会比空镜像更小。那使用scratch制作出来的镜像也必然是最小的。那么scratch是不是一个好的选择呢?scratch镜像是docker自带的空镜像,我也曾见过数篇文章推荐使用其作为golang的运行镜像,然而,在最终实践的时候,遇到的bug却会让人倍感疑惑。

    # 1. 使用官方的golang镜像构建运行的容器

    golang程序非常简单:

    package main

    import "fmt"

    func main(){

        fmt.Println("你好,世界!")

    }

    Dockerfile也不难:

    FROM library/golang:alpine as build

    MAINTAINER fanxiaoqiang <fan_xq@live.com>

    ADD . /data/

    WORKDIR /data/

    RUN export GO111MODULE=on

    RUN export GOSUMDB=off

    RUN unset GOPATH

    RUN go env -w GOPROXY=https://goproxy.cn

    RUN go build -o server helloworld.go

    # deploy-image

    FROM scratch

    #FROM alpine

    COPY --from=build /data/server /data/server

    #COPY --from=build /data/entrypoint.sh /data/entrypoint.sh

    WORKDIR /data/

    EXPOSE 9090

    CMD ["/data/server"]

    构建,运行!

    docker build -t hello .

    && docker run

    -p 9090:9090

    hello


    容器成功的输出了helloword,最终的镜像大小只有2.068MB,效果非常理想。然后scratch是否真的能够满足golang容器化的所有需求呢?下面我们继续看。

    # 2. 使用scratch构建稍复杂的golang的运行容器

    这一次我们构建一个golang实现的http服务器。在开始之前,首先强调一下scratch是一个空镜像,意味这Docker内部不存在任何环境和依赖库。机智的读者可能已经想到我想说什么,接下来开始进行实验。

    golang程序:

    package main

    import (

        "log"

        "net/http"

        "os"

        "os/signal"

        "time"

    )

    func main() {

        server := http.Server{

            Addr:        ":9090",

            ReadTimeout: 10 * time.Second,

        }

        //log.Println("start running")

        log.Println("start running")

        server.ListenAndServe()

        //合建chan

        c := make(chan os.Signal)

        //监听指定信号 ctrl+c kill

        signal.Notify(c, os.Interrupt, os.Kill)

        //阻塞直到有信号传入

        //阻塞直至有信号传入

        s := <-c

        log.Println("exit!", s)

    }

    Docker文件第一节相同,这里就不放了。

    现在让我们尝试 运行,

    docker build -t hello .

    && docker run

    -p 9090:9090

    hello

    构建镜像的过程依旧轻松愉快:

    Building image...

    Preparing build context archive...

    [==================================================>]9/9 files

    Done

    Sending build context to Docker daemon...

    [==================================================>] 2.859kB

    Done

    Step 1/14 : FROM library/golang:alpine as build

     ---> dda4232b2bd5

    Step 2/14 : MAINTAINER fanxiaoqiang <fan_xq@live.com>

     ---> Using cache

     ---> 546d5bcb606b

    Step 3/14 : ADD . /data/

     ---> d6bcfc3f9976

    Step 4/14 : WORKDIR /data/

     ---> Running in 4a8f0fa4c9c4

    Removing intermediate container 4a8f0fa4c9c4

     ---> 6f6092bc91a8

    Step 5/14 : RUN export GO111MODULE=on

     ---> Running in 44a83bb9c9a9

    Removing intermediate container 44a83bb9c9a9

     ---> ea199d64e9d9

    Step 6/14 : RUN export GOSUMDB=off

     ---> Running in df368787ddd7

    Removing intermediate container df368787ddd7

     ---> c338c09c4980

    Step 7/14 : RUN unset GOPATH

     ---> Running in c6016dd29cd8

    Removing intermediate container c6016dd29cd8

     ---> 8f7004cb8ed5

    Step 8/14 : RUN go env -w GOPROXY=https://goproxy.cn

     ---> Running in 237a89c7a644

    Removing intermediate container 237a89c7a644

     ---> 5b5b9b8efb43

    Step 9/14 : RUN go build -o server http_server.go

     ---> Running in 27a5afb6b775

    Removing intermediate container 27a5afb6b775

     ---> 8e0771380586

    Step 10/14 : FROM scratch

     --->

    Step 11/14 : COPY --from=build /data/server /data/server

     ---> 76dc69f34774

    Step 12/14 : WORKDIR /data/

     ---> Running in 8550a1a7b8ee

    Removing intermediate container 8550a1a7b8ee

     ---> 269d3ee7bb29

    Step 13/14 : EXPOSE 9090

     ---> Running in 2a3f21f67f90

    Removing intermediate container 2a3f21f67f90

     ---> 79640d9e743a

    Step 14/14 : CMD ["/data/server"]

     ---> Running in 39581ed1d208

    Removing intermediate container 39581ed1d208

     ---> e30b2238a606

    Successfully built e30b2238a606

    Successfully tagged hello:latest

    Existing container found: 8b31d39f149117566da56be2796418089c47509018857427559600f1ba7c7982, removing...

    Creating container...

    Container Id: 20d38a265fe3496b5a4b6c3742740c6c517b7d449250ab0be246688973212079

    Container name: '/vibrant_hodgkin'

    Attaching to container '/vibrant_hodgkin'...

    Starting container '/vibrant_hodgkin'

    '<unknown> Dockerfile: Dockerfile' has been deployed successfully.

    然而查看容器的日志输出:

    standard_init_linux.go:211: exec user process caused "no such file or directory"

    ???

    是我的二进制程序没有编译成功吗?其实不是。从构建日志中,我们可以清楚的看到,程序其实是编译成功的,也成功的COPY到了最终的运行镜像中,然后启动的时候就是出错了。所以首先我们就排除了代码和Dockerfile的问题。

    此处我曾经疑惑了很久,因为容器运行报上述错误是让人非常摸不着头脑的。我尝试用搜索引擎进行搜索,确实搜到了结果:

    docker启动报错:standard_init_linux.go:211: exec user process caused "no such file or directory"

         如题所示,根据自己构建的镜像启动docker容器,直接退出,查看容器日志报错信息,没有任何别的信息。网上搜索这个问题,发现很多人都遇到过,解决办法也各不相同,最后发现一篇文章。受到启发,我的项目是java项目,通过ENTRYPOINT命令启动脚本docker-entrypoint.sh来构建一个在后台运行的服务。而我的docker-entrypoint.sh是在windows下编辑的,自然fileformat是dos,这里需要修改为unix,修改办法也很简答,无需再在linux下操作,我们一般机器上安装了git工具,自带了git bash命令行工具,进入git bash,找到该文件docker-entrypoint.sh,然后使用vi编辑,修改fileformat=unix,如下所示。

    ————————————————

    原文链接:https://blog.csdn.net/feinifi/java/article/details/102910715

    然而这个问题却与我们无关,因为我们根本没有用到entrypoint的功能。

    现在我们回到本节开始的地方:scratch是一个空镜像,意味这Docker内部不存在任何环境和依赖库。这就意味着即使是最常见的依赖,在scratch中也是不存在的,那么我们检查一下helloworld和httpserver两个二进制文件的依赖,看看是不是能看出一些端倪。

    $ go build http_server.go

    $ ldd http_server

            linux-vdso.so.1 (0x00007fffc4eaf000)

            libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fecea090000)

            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fece9c90000)

            /lib64/ld-linux-x86-64.so.2 (0x00007fecea400000)

    真相大白,即使golang宣传了二进制文件不需要依赖任何外部文件,但是即使是程序运行最基础的libc,scratch也是不包含的。这直接导致了编译完成的httpserver无法运行,但是容器的报错却是找不到文件,报错让人摸不着头脑,希望这篇文章能提供一些小小的帮助。

    理论上来说,如果在scratch中添加需要的动态库,最终是可以让程序正常运行的,但这违背了简化开发流程的原则,同时会在代码中增加不必要的负担。因此,常见的golang程序使用alpine作为最终的运行环境的基础镜像已经是一个非常折衷和合适的方案,不建议再去scratch上折腾。

  • 相关阅读:
    C# 实现将 PDF 转文本的功能
    推荐大家使用的CSS书写规范、顺序
    Android 应用开发者必看的 9 个 Tips
    网站统计中的数据收集原理及实现
    JSON劫持漏洞攻防原理及演练
    巧妙使用 CSS3 的褪色和动画效果制作消息提醒框
    20条常见的编码陷阱
    11 个最佳 jQuery 滚动条插件
    JavaScript客户端MVC 框架综述
    20个初学者实用的CSS技巧
  • 原文地址:https://www.cnblogs.com/svji/p/12983598.html
Copyright © 2020-2023  润新知