zoukankan      html  css  js  c++  java
  • 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上折腾。

  • 相关阅读:
    最大子数组问题(分治策略实现)
    Solving the Detached Many-to-Many Problem with the Entity Framework
    Working With Entity Framework Detached Objects
    Attaching detached POCO to EF DbContext
    如何获取qq空间最近访问人列表
    Health Monitoring in ASP.NET 2.0
    problem with displaying the markers on Google maps
    WebMatrix Database.Open… Close() and Dispose()
    Accessing and Updating Data in ASP.NET: Retrieving XML Data with XmlDataSource Control
    Create web setup project that has crystal reports and sql script run manually on client system
  • 原文地址:https://www.cnblogs.com/svji/p/12983598.html
Copyright © 2011-2022 走看看