摘要:我一直在尋找盡可能小的容器入手,并且發現了一個鏡像是完美的,真正的完美它簡潔,小巧以及快速。所以這可能就是最小的鏡像。盡管如此,這樣看起來會很復雜并且導致容器很大。
注:本文由 Adriaan de Jonge 編寫,本文的原文地址為 Create The Smallest Possible Docker Container
當我們在使用 Docker 的時候,你會很快注意到你正在下載很多 MB 作為你的預先配置的容器。一個簡單的 Ubuntu 容器很容易超過 200 MB,并且隨著在上面安裝軟件,尺寸在逐漸增大。在某些情況下,你不需要任何事情都使用 Ubuntu 。例如,如果你只是簡單的想運行一個 web 服務,使用 GO 編寫的,沒有必要圍繞它使用任何工具。
我一直在尋找盡可能小的容器入手,并且發現了一個:
docker pull scratch
scratch 鏡像是完美的,真正的完美!它簡潔,小巧以及快速。它不包含任何 bug,安全泄漏,慢的代碼或是技術債務。這是因為它是一個空的鏡像。除了一點由 Docker 加入的元數據。事實上,你可以使用如下命令按照 Docker 文檔描述的那樣創建一個自己的 scratch 鏡像。
tar cv --files-from /dev/null | docker import - scratch
所以這可能就是最小的 Docker 鏡像。
或者我們可以說說關于這個的更多東西?比如,你怎樣使用 scratch 鏡像。這給自己帶來了一些挑戰。
為 scratch 鏡像創建內容我們可以在一個空鏡像中運行什么?一個沒有依賴的可執行程序。你是否有沒有依賴的可執行程序?
我過去常常使用 Python,Java 和 Javascript 編寫代碼。每一個這樣的語言/平臺都需要一個運行時的安裝。最近,我開始涉及 Go(或是 golang 如果你喜歡)平臺。看起來 Go 是靜態連接的。因此我嘗試編譯一個簡單的 web 服務輸出 Hello World 并且運行在 scratch 容器中。下面是這個 Hello World web 服務的代碼:
package main import ( "fmt" "net/http" ) func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello World from Go in minimal Docker container") } func main() { http.HandleFunc("/", helloHandler) fmt.Println("Started, serving at 8080") err := http.ListenAndServe(":8080", nil) if err != nil { panic("ListenAndServe: " + err.Error()) } }
明顯地,我不能在 scratch 容器中編譯我的 web 服務,因為容器中沒有 Go 編譯器。正如我在 Mac 上工作,我也無法編譯 Linux 的二進制文件一樣(實際上,是可以在不同的平臺上交叉編譯 Go 的源碼的,但這會在另外一篇博客中介紹)。
因此,我首先需要一個有 Go 編譯器的 Docker 容器。讓我們開始:
docker run -ti google/golang /bin/bash
在這個容器里面,我可以構建一個 Web 服務,通過我已經提交到一個 GitHub 倉庫的代碼。
go get github.com/adriaandejonge/helloworld
go get 命令是 go build 命令的變種,運行獲取和構建遠程的依賴。你可以運行可執行的結果:
$GOPATH/bin/helloworld
它工作了,但是這不是我們想要的。我們需要 hello world 容器運行在 scratch 容器里面。因此,實際上,我們需要一個 Dockerfile :
FROM scratch ADD bin/helloworld /helloworld CMD ["/helloworld"]
然后啟動它,不幸的是,我們開始 google/golang 容器的這個方法, 沒有辦法構建這個 Dockerfile 。因此,首先,我們需要一種方法從這個容器內部訪問到 Docker。
從 Docker 內部調用 Docker當你使用 Dokcer 時,你遲早會遇到需要從 Docker 內部訪問 Docker。可以有多種方法實現它。你可以使用遞歸和在 Docker 中運行 Docker。盡管如此,這樣看起來會很復雜并且導致容器很大。你還可以使用一些額外的命令選項在實例外訪問 Docker 服務器:
docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):$(which docker) -ti google/golang /bin/bash
在你繼續前,你重新運行 Go 編譯器,由于在重啟動過程中 Docker 忘記了我們以前編譯過。
go get github.com/adriaandejonge/helloworld
當我們啟動這個容器, -v 參數在 Docker 容器中創建一個卷并且允許你從 Docker 的機器提供一個文件作為輸入。/var/run/docker.sock 是 UNIX socket,通過這個允許你訪問 Docker 服務。 (which docker) 部分是一個非常聰明的方法,它提供了一個在 容器中的 Docker 可執行文件的路徑,而不是硬編碼。盡管如此,當你在 Mac 上通過 boot2docker 使用這個命令的時候需要小心。如果 Docker 的可執行文件與 boot2docker 虛擬機的在不同的位置,將導致不匹配。因此,你或許想使用 /usr/local/bin/docker 硬編碼的方式替換 $(which docker),如果你運行在不同的系統,/var/run/docker.sock 有在不同位置的機會,你需要做相應的調整。
現在你可以在 google/golang 容器的 $GOPATH 目錄使用 Dockerfile ,在這個示例中指向 /gopath。實際上,我已經在 github 上檢查過了這個 Dockerfile,因此,你可以從 Go build 目錄復制它到所需的位置,像這樣:
cp $GOPATH/src/github.com/adriaandejonge/helloworld/Dockerfile $GOPATH
你需要復制這個作為二進制的編譯文件,現在位于 $GOPATH/bin,并且它不可能從父目錄包含文件當構建一個 Dockerfile 的時候。因此復制后,下一步是:
docker build -t adejonge/helloworld $GOPATH
所有的都完成以后, Docker 給出如下響應:
Successfully built 6ff3fd5a381d
允許你運行這個容器:
docker run -ti --name hellobroken adejonge/helloworld
但是不幸的是, Docker 這次響應如下:
2014/07/02 17:06:48 no such file or directory
那么到底是怎么回事?我們在 scratch 容器中有可執行的靜態鏈接。難道我們犯了一個錯誤?
事實證明,Go 不是靜態鏈接庫。或者至少不是所有的庫。在 Linux 下,我們可以使用 ldd 命令來看到動態鏈接庫:
ldd $GOPATH/bin/helloworld
得到如下響應:
linux-vdso.so.1 => (0x00007fff039fe000) libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f61df30f000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f61def84000) /lib64/ld-linux-x86-64.so.2 (0x00007f61df530000)
因此,在我們運行我們的 web 服務之前,我需要告訴 go 編譯器實際的靜態鏈接。
創建在 Go 中的可執行靜態鏈接為了創建可執行的靜態鏈接,我們需要告訴 Go 使用 cgo 編譯器而不是 go 編譯器。命令如下:
CGO_ENABLED=0 go get -a -ldflags "-s" github.com/adriaandejonge/helloworld
CGO_ENABLED 環境變量告訴 Go 使用 cgo 編譯器而不是 go 編譯器。-a 參數告訴 GO 重薪構建所有的依賴。否則的話你將以動態鏈接依賴結束。最后的 -ldflags "-s" 參數是一個非常好的擴展。它大概降低了可執行文件 50% 的文件大小。你也可以不通過 cgo 使用這個。尺寸縮小是去除了調試信息的結果。
為了確定,運行 ldd 命令:
ldd $GOPATH/bin/helloworld
返回是:
not a dynamic executable
你也可以重新運行步驟,圍繞著從 scratch 創建 Docker 容器的可執行文件。
docker build -t adejonge/helloworld $GOPATH
如果一切順利,Docker 將響應如下:
Successfully built 6ff3fd5a381d
允許你運行這個容器:
docker run -ti --name helloworld adejonge/helloworld
響應如下:
Started, serving at 8080
到目前為止,有許多手動的步驟和很多錯誤的地方。讓我們退出 google/golang 容器并且從周邊服務器繼續:
exit
你可以檢查 Docker 容器和鏡像存在不存在:
docker ps -a docker images -a
你可以使用如下命令清理:
docker rm -f helloworld docker rmi -f adejonge/helloworld創建一個 Docker 容器來創建一個 Docker 容器
目前為止,我們花了那么多步驟,我們還可以記錄在 Dockerfile 中并且 Docker 會為我們做這些工作:
FROM google/golang RUN CGO_ENABLED=0 go get -a -ldflags "-s" github.com/adriaandejonge/helloworld RUN cp /gopath/src/github.com/adriaandejonge/helloworld/Dockerfile /gopath CMD docker build -t adejonge/helloworld gopath
我在 一個多帶帶的稱為 adriaandejonge/hellobuild 的 GitHub 倉庫檢查了 Dockerfile。它可以使用下面的命令構建:
docker build -t adejonge/hellobuild github.com/adriaandejonge/hellobuild
提供 -t 參數命名 adejonge/hellobuild 鏡像并且它的最新的隱式的標簽。這些名字讓你以后更容易去除鏡像。下一步,你可以使用就像我們在這篇文章前面看到的那樣提供一個參數從這個鏡像中創建一個容器:
docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):$(which docker) -ti --name hellobuild adejonge/hellobuild
提供 --name hellobuild 參數使得在運行后更容易移除容器。事實上,你可以這樣做,因為運行這個命令后,你已經創建了一個 adejonge/helloworld 鏡像:
docker rm -f hellobuild docker rmi -f adejonge/hellobuild
現在你可以創建一個基于 adejonge/helloworld 鏡像的名為 helloworld 的新容器,就像你以前做的那樣:
docker run -ti --name helloworld adejonge/helloworld
因為所有的這些步驟都是從相同的命令中運行,不需要在 Docker 中打開一個 bash shell 。你可以把這些步驟添加進一個 bash 腳本,自動運行它,為了使你方便,我已經把這些腳本加入了 hellobuild GitHub 倉庫。
另外,如果你想嘗試一個盡可能小的容器,但是又不想遵循博客中的步驟,你可以使用我檢入進 Docker Hub repository 的預先構建好的鏡像。
docker pull adejonge/helloworld
使用 docker images -a ,你可以看到大小是 3.6MB。當然,如果你成功創建一個比我使用 Go 編寫的 web 服務還小的可執行文件,你可以使得它更小。使用 C 語言或者是匯編,你可以這樣做到。盡管如此,你不可能使得它比 scratch 鏡像還小
擴展閱讀
OPTIMIZING DOCKER IMAGES
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28094.html
摘要:從本質上來看,精簡目標實際上使用了兩個存儲設備一個大的是存儲塊池自己,還有一個小的存儲了一些元數據。另外一個非常好的選擇是把你的磁盤或者磁盤陣列放到的物理卷中,并且創建兩個邏輯卷一個是數據,一個是元數據。 問題引出 今日筆者docker下出現詭異問題,在容器中vim編輯文件,保存是,一直提示 E667: Fsync failed,而且在同一個宿主機上的容器均有此問題,遂懷疑環境故障,...
本篇本意是介紹hadoop的部署資源隔離和調度方案yarn。順便介紹了容器和容器集群管理。說回yarn隔離分為cpu和內存,cpu基于cgroups,內存自行實現計算ru_maxrss。還對比了k8n的隔離,它內存和cpu都基于cgroups。在調度方面介紹了yarn的兩種調度機制Capacity Scheduler和Fair Scheduler。整體:https://segmentfault.c...
摘要:執行容器內部運行的執行工作作為容器的執行驅動,負責創建容器運行命名空間,負責容器資源使用的統計與限制,負責容器內部進程的真正運行等。典型的在啟動后,首先將設置為進行一系列檢查然后將其切換為供用戶使用。 在https://segmentfault.com/a/11... 容器,隔離,云的概述。這篇對其中用途廣泛的docker,k8s做詳細介紹,并給出云搭建的生態環境體系。 docker ...
摘要:執行容器內部運行的執行工作作為容器的執行驅動,負責創建容器運行命名空間,負責容器資源使用的統計與限制,負責容器內部進程的真正運行等。典型的在啟動后,首先將設置為進行一系列檢查然后將其切換為供用戶使用。 在https://segmentfault.com/a/11... 容器,隔離,云的概述。這篇對其中用途廣泛的docker,k8s做詳細介紹,并給出云搭建的生態環境體系。 docker ...
摘要:在這三種調度框架做出選擇需要進行驗證根據應用的工作方式,數量以及如何管理數據等基礎,可以幫助縮小選擇范圍。容器安裝和運行時對存儲服務進行特定的請求,以實現如創建刪除檢查列表連接分離掛載卸載等功能。和一樣,它也有相同的功能和限制。 Swarm、Mesos、和Kubernetes都為各種規模的企業提供了全面的支持,如何選擇是好? API ▼ 目前找到符合企業自身需求的調度框架比較困難,Do...
閱讀 2780·2021-09-23 11:44
閱讀 1671·2021-09-13 10:24
閱讀 2619·2021-09-08 09:36
閱讀 1231·2019-08-30 15:54
閱讀 2248·2019-08-30 13:54
閱讀 3308·2019-08-30 10:57
閱讀 1844·2019-08-29 18:43
閱讀 3609·2019-08-29 15:10