本文主要討論了對docker build的源碼流程進行了梳理和解讀,並分享了在製做Dockerfile過程當中的一些實踐經驗,包括如何調試、優化和build中的一些要點。另外,還針對現有Dockerfile的不足進行了簡要說明,並分享了對於Dockerfile的一些理解。這是2015年初第一次在社區的微信分享,原文刊載在dockone社區git
此次的分享主要面向有必定Docker基礎的。我但願你已經:docker
我主要分享一些如今網上或者文檔中沒有的東西,包括個人理解和一些實踐,有誤之處也請你們指正。好了,正文開始。apache
Dockerfile其實能夠看作一個命令集。每行均爲一條命令。每行的第一個單詞,就是命令command。後面的字符串是該命令所要接收的參數。好比ENTRYPOINT /bin/bash。ENTRYPOINT命令的做用就是將後面的參數設置爲鏡像的entrypoint。至於現有命令的含義,這裏再也不詳述。DockOne上有不少的介紹。json
docker build的流程(這部分代碼基本都在docker/builder中)bash
在這裏,我舉一個例子來講明一下在第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):數據結構
不只僅是CMD命令,幾乎全部的命令(除了FROM外),在最後都是使用b.commit來產生一個新的鏡像的。app
因此這會致使的結果就是,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的cache功能,能夠大幅度節約調試的時間。
舉個例子,若是我如今有一個Dockerfile。可是我發現。我還須要再開幾個端口,或者再安裝其餘的軟件。這個時候最好不要直接修改已經有的Dockerfile的內容。而是在後面追加命令。這樣再build的時候,能夠利用已有的cache。
調試事後的Dockerfile固然能夠做爲最終的Dockerfile,提供給用戶。可是調試的Dockerfile的缺點就是層數可能過多,並且不易越多。因此最好進行必定的優化和整理。通過整理的Dockerfile生成出來的鏡像可使得層數更少,條理更清晰,也能夠更好的複用。
DockerOne裏有一篇文章寫得很好,能夠參考。
這裏有兩點要強調:
有了Dockerfile,不少人都是在本地build。其實這個是至關耗時的。這個工做其實徹底能夠交給registry.hub.docker.com來完成。
具體的作法就是:
根據你的Dockerfile內容大小,build時長不肯定。可是應該算是比較快了。docker源碼的Dockerfile在我本地build了一個多小時。可是registry.hub.docker.com只用了半小時左右。大約是由於外國的月亮比較圓吧。
build完成後,能夠在線查看版本信息等。本地須要的話,能夠直接pull下來。
國內有多家公司提供了registry.hub.docker.com的Mirror服務,能夠直接從國內的源中pull下來。速度快不少。
如今咱們回過頭來看Docker的分層的另外一個可能的用途。
Docker的鏡像能夠看作是一個軟件棧。那麼其中有多個軟件組成。好了,那麼咱們是否是能夠考慮讓軟件進行自由疊加呢?
好比:從CentOS鏡像上安裝了Python造成鏡像A,從CentOS鏡像上安裝了Apache造成鏡像B。若是用戶想從CentOS上造成一個既有Python又有Apache的鏡像,如何作呢?
我想有兩種方式,一種是dockerfile的import。咱們能夠基於鏡像A,而後import安裝Apache的Dockerfile,從而獲得目標鏡像。
另一種是能夠直接引入,就是基於鏡像A,而後咱們直接把B的最後一層(假設B安裝apache只造成了一層),搬到鏡像A的子層上,不是也能夠獲得目標鏡像麼?