摘要
本文主要讨論了對docker build的源碼流程進行了梳理和解讀,并分享了在制作Dockerfile過程中的一些實踐經驗,包括如何調試、優化和build中的一些要點。另外,還針對現有Dockerfile的不足進行了簡要說明,并分享了對于Dockerfile的一些了解。這是2015年初第一次在社群的微信分享,原文刊載在dockone社群
聽衆
這次的分享主要面向有一定Docker基礎的。我希望你已經:
- 用過Docker,熟悉docker commit指令
- 自己動手編寫過Dockerfile
- 自己動手build過一個鏡像,有親身的體驗
我主要分享一些現在網上或者文檔中沒有的東西,包括我的了解和一些實踐,有誤之處也請大家指正。好了,正文開始。
Dockerfile
Dockerfile其實可以看做一個指令集。每行均為一條指令。每行的第一個單詞,就是指令command。後面的字元串是該指令所要接收的參數。比如ENTRYPOINT /bin/bash。ENTRYPOINT指令的作用就是将後面的參數設定為鏡像的entrypoint。至于現有指令的含義,這裡不再詳述。DockOne上有很多的介紹。
Docker建構(docker build)
docker build的流程
docker build的流程(這部分代碼基本都在docker/builder中)
- 提取Dockerfile(evaluator.go/RUN)。
- 将Dockerfile按行進行分析(parser/parser.go/Parse) Dockerfile,每行第一個單詞,如CMD、FROM等,這個叫做command。根據command,将之後的字元串用對應的資料結構進行接收。
- 根據分析的command,在dispatchers.go中選擇對應的函數進行處理(dispatchers.go)。
- 處理完所有的指令,如果需要打标簽,則給最後的鏡像打上tag,結束。
在這裡,我舉一個例子來說明一下在第4步指令的執行過程。以CMD指令為例:
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
cmdSlice := handleJsonArgs(args, attributes)
if !attributes["json"] {
cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
}
b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
return err
}
if len(args) != 0 {
b.cmdSet = true
}
return nil
}
可以看到,b.Config.Cmd = runconfig.NewCommand(cmdSlice...)就是根據傳入的CMD,更新了Builder裡面的Config。然後進行b.commit。Builder這裡的commit大緻含義其實與docker/daemon的commit功能大同小異。不過這裡commit是包含了以下的一個完整過程(參見internals.go/commit):
- 根據Config,create一個container出來。
- 然後将這個container通過commit(這個commit是指的docker的commit,與docker commit的指令是相同的)得到一個新的鏡像。
不僅僅是CMD指令,幾乎所有的指令(除了FROM外),在最後都是使用b.commit來産生一個新的鏡像的。
是以這會導緻的結果就是,Dockerfile裡每一行,最後都會變為鏡像中的一層。幾乎是有多少有效行,就有多少層。
Dockerfile逆向
通過docker history image可以看到該鏡像的曆史來源。即使沒有Dockerfile,也可以通過history來逆向産生Dockerfile。
[root@jd ~]# docker history 2d8
IMAGE CREATED CREATED BY SIZE
2d80e15fcfdb 8 days ago /bin/sh -c #(nop) COPY dir:86faa820e8bf5dcc06 16.29 MB
0f601e909d72 8 days ago /bin/sh -c #(nop) ENTRYPOINT [hack/dind] 0 B
68aed19c5994 8 days ago /bin/sh -c set -x && git clone https://githu 3.693 MB
ebc6ef15552b 8 days ago /bin/sh -c #(nop) ENV TOMLV_COMMIT=9baf8a8a9f 0 B
fe22e308201a 8 days ago /bin/sh -c set -x && git clone -b v1.0.1 htt 5.834 MB
f514c504c9b1 8 days ago /bin/sh -c #(nop) COPY dir:d9a19910e57f47cb3b 3.114 MB
e4e3ec8edf1a 8 days ago /bin/sh -c ./contrib/download-frozen-image.sh 1.155 MB
6250561532fa 8 days ago /bin/sh -c #(nop) COPY file:9679abce578bcaa2c 3.73 kB
...
例如0f601e909d72就是由ENTRYPOINT [hack/dind]産生。這裡的資訊展示的不完全,可以通過docker inspect -f {{.ContainerConfig.Cmd}} layer來看某一層産生的具體資訊。
如何做Dockerfile
Dockerfile調試
Dockerfile更多的像一個腳本,類似于安裝腳本。特别是大篇幅的腳本,想一次寫成是比較有難度的。免不了進行一些調試。調試時最好利用Dockerfile的cache功能,可以大幅度節約調試的時間。
舉個例子,如果我現在有一個Dockerfile。但是我發現。我還需要再開幾個端口,或者再安裝其他的軟體。這個時候最好不要直接修改已經有的Dockerfile的内容。而是在後面追加指令。這樣再build的時候,可以利用已有的cache。
Dockerfile優化
調試過後的Dockerfile當然可以作為最終的Dockerfile,提供給使用者。但是調試的Dockerfile的缺點就是層數可能過多,而且不易越多。是以最好進行一定的優化和整理。經過整理的Dockerfile生成出來的鏡像可以使得層數更少,條理更清晰,也可以更好的複用。
DockerOne裡有一篇文章寫得很好,可以參考。
這裡有兩點要強調:
- 盡量生成一個base:這樣便于版本的疊代和作為公用鏡像。
- 清晰的注釋:有一些注釋會幫助别人了解這些指令的目的
Dockerfile自動build
有了Dockerfile,很多人都是在本地build。其實這個是相當耗時的。這個工作其實完全可以交給registry.hub.docker.com來完成。
具體的做法就是:
- 把你的Dockerfile上傳到GitHub上。
- 進入到registry.hub.docker.com的自己的賬戶中,選擇Automated Build。
- 然後就可以build了。
根據你的Dockerfile内容大小,build時長不确定。但是應該算是比較快了。docker源碼的Dockerfile在我本地build了一個多小時。但是registry.hub.docker.com隻用了半小時左右。大約是因為外國的月亮比較圓吧。
build完成後,可以線上檢視版本資訊等。本地需要的話,可以直接pull下來。
國内有多家公司提供了registry.hub.docker.com的Mirror服務,可以直接從國内的源中pull下來。速度快很多。
Dockerfile的不足
- 層數過多:過多行的Dockerfile
- 不能清理volume等配置:volume、expose等多個參數隻能單向增加。不能删除。比如在某個鏡像層加入了VOLUME /var/lib/docker。那麼在該鏡像之後的所有層将繼承這一屬性。
- IMPORT功能
其他
現在我們回過頭來看Docker的分層的另一個可能的用途。
Docker的鏡像可以看做是一個軟體棧。那麼其中有多個軟體組成。好了,那麼我們是不是可以考慮讓軟體進行自由疊加呢?
比如:從CentOS鏡像上安裝了Python形成鏡像A,從CentOS鏡像上安裝了Apache形成鏡像B。如果使用者想從CentOS上形成一個既有Python又有Apache的鏡像,如何做呢?
我想有兩種方式,一種是dockerfile的import。我們可以基于鏡像A,然後import安裝Apache的Dockerfile,進而得到目标鏡像。
另外一種是可以直接引入,就是基于鏡像A,然後我們直接把B的最後一層(假設B安裝apache隻形成了一層),搬到鏡像A的子層上,不是也可以得到目标鏡像麼?
作者:xuxinkun 出處:xinkun的部落格 連結:https://www.cnblogs.com/xuxinkun/ 本文版權歸作者所有,歡迎轉載。 未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。 歡迎掃描右側二維碼關注微信公衆号 xinkun的部落格 進行訂閱。也可以通過微信公衆号留言同作者進行交流。 | ![]() |