Docker scratch 無法正常運行golang二進位程式的問題

来源:https://www.cnblogs.com/svji/archive/2020/05/28/12983598.html
-Advertisement-
Play Games

使用Docker構建容器能夠極大的降低運維成本,提高部署效率,同時非常方便對服務的平行擴展。然而在構建容器鏡像過程中的,存在著一個難以避免的問題,就是如果使用常見的發行版本作為程式運行的基礎環境,那麼即使一個服務本身的運行文件非常小,最終構建的鏡像也可能會有會在運行環境的鏡像的基礎上變得更大,動不動 ...


使用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 <[email protected]>

 

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 <[email protected]>

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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • C 數組允許定義可存儲相同類型數據項的變數,結構是 C 編程中另一種用戶自定義的可用的數據類型,它允許您存儲不同類型的數據項。 結構用於表示一條記錄,假設您想要跟蹤圖書館中書本的動態,您可能需要跟蹤每本書的下列屬性: Title Author Subject Book ID 定義結構 為了定義結構, ...
  • 前言 前一篇已經開發了大部分框架,包含視頻上下滑動播放,這次將上次未完成的數據顯示友好顯示,以及底部音樂走馬燈特效,另外優化了載入數據的bug,在dart語言里 & 會自動變成&amp; 也不知道這個bug啥時候修複哈. 本系列會持續更新,將各個模塊及功能持續完善. 修複Dart語言 URL顯示錯誤 ...
  • 編程學習本身就是一個枯燥的過程,面對一個新鮮的東西一定是一開始比較好奇,起初比較有興趣,但是越學越覺得枯燥。學習任何東西都是一樣的,但是一定要堅持下去(如果決定要做這一行)。 電腦語言的學習其實就是學習別人的思想,因為這些東西是別人發明出來的,這些東西不是憑空就來了,而是基於很多理論和為解決具體的 ...
  • 前言 本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。 加企鵝群695185429即可免費獲取,資料全在群文件里。資料可以領取包括不限於Python實戰演練、PDF電子文檔、面試集錦、學習資料等 1、使用代理 適用情況:限制IP地 ...
  • 1.思路 原先圖片匹配一般都是缺口匹配全圖 優化點: 1.缺口圖片匹配缺口所在圖片那一行圖片可以提高他識別率 2.移動後再進行2次匹配計算距離 2.代碼 def get_image_deviation(): ##讀取滑塊圖 block = cv.imread("img.png", -1) #完整圖片 ...
  • from itertools import groupbyresult = [list(g) for k, g in groupby(data, lambda x:x=='') if not k]print(result) ...
  • laravel 安裝jwt-auth及驗證 1、使用composer安裝jwt,cmd到項目文件夾中; composer require tymon/jwt-auth 1.0.*(這裡版本號根據自己的需要寫) 安裝jwt ,參考官方文檔https://jwt-auth.readthedocs.io/ ...
  • 方法句柄 方法句柄(method handle)是JSR 292中引入的一個重要概念,它是對Java中方法、構造方法和域的一個強類型的可執行的引用。這也是句柄這個詞的含義所在。通過方法句柄可以直接調用該句柄所引用的底層方法。從作用上來說,方法句柄的作用類似於2.2節中提到的反射API中的Method ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...