Docker scratch 沒法正常運行golang二進制程序的問題

使用Docker構建容器可以極大的下降運維成本,提升部署效率,同時很是方便對服務的平行擴展。然而在構建容器鏡像過程當中的,存在着一個難以免的問題,就是若是使用常見的發行版本做爲程序運行的基礎環境,那麼即便一個服務自己的運行文件很是小,最終構建的鏡像也可能會有會在運行環境的鏡像的基礎上變得更大,動不動就是數百M的體積。java

以最經常使用於微服務開發的golang爲例,golang的二進制程序能夠一次開發跨平臺編譯,處處運行,所以其自己的程序自洽性其實很是完善,也不多會依賴複雜的外部環境,所以經常使用的發行版本鏡像碩大的體積其實很不必。所以,alpine成爲了一個很是好的選擇。最終alpine也不負衆望,將最終的鏡像體積減少到了10M+左右,已經壓縮了很是大的空間了,結果還算理想。linux

 

然而,能不能更小呢?git

 

docker自帶的scratch鏡像給了我一個思路:沒有任何鏡像會比空鏡像更小。那使用scratch製做出來的鏡像也必然是最小的。那麼scratch是否是一個好的選擇呢?scratch鏡像是docker自帶的空鏡像,我也曾見過數篇文章推薦使用其做爲golang的運行鏡像,然而,在最終實踐的時候,遇到的bug卻會讓人倍感疑惑。golang

 

# 1. 使用官方的golang鏡像構建運行的容器docker

golang程序很是簡單:windows

 

package mainbash

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上折騰。

相關文章
相關標籤/搜索