創建超小的Golang docker 鏡像

jopen 9年前發布 | 26K 次閱讀 Docker


Docker
是PaaS供應商dotCloud開源的一個基于LXC 的高級容器引擎,源代碼托管在 GitHub 上, 基于Go語言開發并遵從Apache 2.0協議開源。正如DockerPool在免費Docker電子書 Docker —— 從入門到實踐 中這樣提到的:

作為一種新興的虛擬化方式,Docker 跟傳統的虛擬化方式相比具有眾多的優勢。

首先,Docker 容器的啟動可以在秒級實現,這相比傳統的虛擬機方式要快得多。 其次,Docker 對系統資源的利用率很高,一臺主機上可以同時運行數千個 Docker 容器。

容器除了運行其中應用外,基本不消耗額外的系統資源,使得應用的性能很高,同時系統的開銷盡量小。傳統虛擬機方式運行 10 個不同的應用就要起 10 個虛擬機,而Docker 只需要啟動 10 個隔離的應用即可。

Docker讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然后發布到任何流行的 Linux 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何接口(類似 iPhone 的 app)。幾乎沒有性能開銷,可以很容易地在機器和數據中心中運行。最重要的是,他們不依賴于任何語言、框架包括系統。

本文不會介紹Docker原理和操作,而是介紹如何使用Docker創建一個Golang應用程序的鏡像,這樣我們就可以在其它機器上運行這個鏡像。本文參考了很多的文章,這些文章列在了本文的底部。

編寫一個Golang服務器

這里我在研究 endless 庫的時候寫了一個測試程序,就用它來測試一下docker鏡像的創建。

endless可以允許我們在重啟網絡服務器的時候零時間宕機, 英語是graceful restart,我稱之為無縫重啟。

服務器監聽4242端口,順便使用raymond模版引擎替換golang自帶的模版引擎,采用bone這個高性能的mux庫。

代碼如下:

package main

import (

"flag"

"log"

"net/http"

"os"

"syscall"

"github.com/aymerick/raymond"

"github.com/fvbock/endless"

"github.com/go-zoo/bone"

)

var (

//homeTpl, _ = raymond.ParseFile("home.hbs")

homeTpl = raymond.MustParse( `<html>

<head>

<title>test</title>

</head>

</body>

<div class="entry">

<h1></h1>

<div class="body">

</div>

</div>

</body>

</html>

`)

)

func homeHandler(rw http.ResponseWriter, req *http.Request) {

ctx := map [ string ] string { "greet" : "hello" , "name" : "world" }

result := homeTpl.MustExec(ctx)

rw.Write([] byte (result))

}

func varHandler(rw http.ResponseWriter, req *http.Request) {

varr := bone.GetValue(req, "var" )

test := bone.GetValue(req, "test" )

rw.Write([] byte (varr + " " + test))

}

func Handler404(rw http.ResponseWriter, req *http.Request) {

rw.Write([] byte ( "These are not resources you're looking for ..." ))

}

func restartHandler(rw http.ResponseWriter, req *http.Request) {

syscall.Kill(syscall.Getppid(), syscall.SIGHUP)

rw.Write([] byte ( "restarted" ))

}

func main() {

flag.Parse()

mux := bone.New()

// Custom 404

mux.NotFoundFunc(Handler404)

mux.Handle( "/index" , http.HandlerFunc(homeHandler))

mux.Handle( "/index/:var/info/:test" , http.HandlerFunc(varHandler))

// Get, Post etc... takes http.HandlerFunc as argument.

mux.Post( "/home" , http.HandlerFunc(homeHandler))

mux.Get( "/home/:var" , http.HandlerFunc(varHandler))

mux.GetFunc( "/test/*" , func (rw http.ResponseWriter, req *http.Request) {

rw.Write([] byte (req.RequestURI))

})

mux.Get( "/restart" , http.HandlerFunc(restartHandler))

err := endless.ListenAndServe( ":4242" , mux)

if err != nil {

log.Fatalln(err)

}

log.Println( "Server on 4242 stopped" )

os.Exit (0 )

}

Golang鏡像

Docker官方提供了Golang各版本的鏡像: Official Repository - golang .

它包含了Golang的編譯和運行時環境。最簡單的使用方法就是在你的Dockerfile文件中加入

FROM golan g:1 . 3 -onbuild

這個鏡像包含了多個ONBUILD觸發器。你可以編譯和運行你的鏡像:

$ docker build -t my-golang-app .

$ docker run -it --rm --name my-running-app my-golang-app

為編譯好的Golang應用創建小的鏡像

上面的Golang容器相當的大,因為它包含了Golang的編譯和運行環境。官方網站上列出了鏡像的大小:

golang:1.5.1-onbuild

$ docker pull library/golang@sha256:f938465579d1cde302a447fef237a5a45d7e96609b97c83b9144446615ad9e72

Total Virtual Size: 709.5 MB (709470237 bytes)Total v2 Content-Length: 247.0 MB (246986021 bytes)

實際上我們并不需要那么多的軟件,因為我們的Golang應用程序是預先編譯好的,而不是在Golang容器中現場編譯運行,因此我們不需要 Golang的編譯環境等。如果你查看golang:1.5的Dockerfile,會發現它基于buildpack-deps:jessie-scm,會安裝GCC及一堆的build工具,下載Go的發布文件并安裝。基本上這些對于我們來說并不需要。我們需要的是:

一個可以運行我們編譯好的Golang應用的鏡像。

我們可以從scratch鏡像創建。

scratch鏡像是一個空的鏡像文件,特別適合創建超級小的鏡像。

Dockerfile文件如下:

FROM scratch
ADD main /
CMD [ "/main" ]

運行輸出如下

# docker build -t example-scratch .

Sending build context to Docker daemon 8.054 MB

Step 0 : FROM scratch

- -->

Step 1 : ADD main /

- --> 4 ad02fa47a7d

Removing intermediate container d64080c4b42f

Step 2 : CMD /main

- --> Running in 5 d9a08c3a20e

- --> 5 c29c8249678

Removing intermediate container 5d9a08c3a20e

Successfully built 5c29c8249678

這樣鏡像就創建成功了,查看一下:

[root @localhost work] # docker images

REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE

example-scratch latest 5 c29c8249678 3 minutes ago 8.052 MB

只有8M左右,非常的小。

但是運行這個鏡像,容器無法創建:

# docker run -it -p 4242:4242 example-scratch

no such file or directory

Error response from daemon: Cannot start container 79 bb9fb62788b4a8c1487695a3219ddf3aa85bde2bc44473838f6f4d1583a204: [ 8 ] System error: no such file or directory

原因是我們的main文件生成的時候依賴的一些庫如libc還是動態鏈接的,但是scratch 鏡像完全是空的,什么東西也不包含,所以生成main時候要按照下面的方式生成,使生成的main靜態鏈接所有的庫:

CGO_ENABLED= 0 GOOS= linux go build -a -installsuffix cgo -o main .

然后重新生成鏡像并運行:

# docker build -t example-scratch .

# docker run -it -p 4242:4242 example-scratch

容器運行成功,在瀏覽器中訪問 http://宿主IP:4242/index成功返回結果

發布

可以方便的將剛才的鏡像發布到docker.io上。首先將剛才的鏡像打tag:

# docker images

REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE

example-scratch latest 2 ea4bbfd67dc 10 minutes ago 8.01 MB

# docker tag 2ea4bbfd67dc smallnest/example-scratch

# docker images

REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE

smallnest/example-scratch latest 2 ea4bbfd67dc 10 minutes ago 8.01 MB

example-scratch latest 2 ea4bbfd67dc 10 minutes ago 8.01 MB

運行docker login登錄,然后運行下面的命令push到docker.io上。

docker push smallnest/example-scratch

訪問 https://hub.docker.com/r/smallnest/example-scratch/ 可以看到剛剛push的這個鏡像,這樣我們就可以pull到其它機器上運行了。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!