Dockerfile與Docker構建流程解讀

摘要

本文主要討論了對docker build的源碼流程進行了梳理和解讀,並分享了在製做Dockerfile過程當中的一些實踐經驗,包括如何調試、優化和build中的一些要點。另外,還針對現有Dockerfile的不足進行了簡要說明,並分享了對於Dockerfile的一些理解。這是2015年初第一次在社區的微信分享,原文刊載在dockone社區git

聽衆

此次的分享主要面向有必定Docker基礎的。我但願你已經:docker

  • 用過Docker,熟悉docker commit命令
  • 本身動手編寫過Dockerfile
  • 本身動手build過一個鏡像,有親身的體驗

我主要分享一些如今網上或者文檔中沒有的東西,包括個人理解和一些實踐,有誤之處也請你們指正。好了,正文開始。apache

Dockerfile

Dockerfile其實能夠看作一個命令集。每行均爲一條命令。每行的第一個單詞,就是命令command。後面的字符串是該命令所要接收的參數。好比ENTRYPOINT /bin/bash。ENTRYPOINT命令的做用就是將後面的參數設置爲鏡像的entrypoint。至於現有命令的含義,這裏再也不詳述。DockOne上有不少的介紹。json

Docker構建(docker build)

docker build的流程

docker build的流程(這部分代碼基本都在docker/builder中)bash

  1. 提取Dockerfile(evaluator.go/RUN)。
  2. 將Dockerfile按行進行分析(parser/parser.go/Parse) Dockerfile,每行第一個單詞,如CMD、FROM等,這個叫作command。根據command,將以後的字符串用對應的數據結構進行接收。
  3. 根據分析的command,在dispatchers.go中選擇對應的函數進行處理(dispatchers.go)。
  4. 處理完全部的命令,若是須要打標籤,則給最後的鏡像打上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):數據結構

  1. 根據Config,create一個container出來。
  2. 而後將這個container經過commit(這個commit是指的docker的commit,與docker commit的命令是相同的)獲得一個新的鏡像。

不只僅是CMD命令,幾乎全部的命令(除了FROM外),在最後都是使用b.commit來產生一個新的鏡像的。app

因此這會致使的結果就是,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來完成。

具體的作法就是:

  1. 把你的Dockerfile上傳到GitHub上。
  2. 進入到registry.hub.docker.com的本身的帳戶中,選擇Automated Build。
  3. 而後就能夠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的子層上,不是也能夠獲得目標鏡像麼?

相關文章
相關標籤/搜索