本次分享主要是從我的實踐的角度,講述本人對於Docker鏡像的一些玩法和體會。本文中大部分的內容都還處於實驗的階段,未通過大規模生產的實踐。特此說明。思慮不全或者偏頗之處,還請你們指正。git
鏡像應該算是Docker的核心價值之一。鏡像由多層組成。那麼對於一個層來講,就有了兩個角度來看待。一個角度是把這層當作一個獨立的單位來看,那麼這一個層其實主要是包含了文件和配置兩個部分。另外一個角度則是把這一層和它的全部父層結合起來看,那麼這個總體則是表明了一個完整的鏡像。github
本文所述的Docker鏡像,主要是指的從Dockerfile構建出來的鏡像。docker
如今已經有了Docker Hub等多家公有容器服務供應商,爲咱們提供了很是便捷的鏡像構建服務。咱們再也不須要在本地運行docker build而是能夠借用他們的服務實現方便的鏡像構建。下文中以Docker Hub爲例,介紹一些很是規的用法。各位在實踐中可使用國內的多家容器服務提供商,如DaoCloud等。json
衆所周知,Docker鏡像能夠用來描述一個APP的runtime。好比咱們構建一個Tomcat的鏡像,鏡像裏包含了運行Tomcat的環境以及依賴。可是咱們再細看,其實Docker鏡像不只僅是一個runtime,而是提供了一個環境,一個軟件棧。從這個角度上來講,鏡像不只僅能夠用來提供APP進行運行,還能夠提供諸如編譯的環境。ubuntu
用Docker來進行編譯,這個應該來講不是什麼新奇玩法。由於Docker源碼的編譯就是經過這種方式來得到的。Docker有對應的Dockerfile。能夠利用這個來完成代碼的編譯。小程序
這裏我舉個例子。這裏有一個寫的Dockerfile。test.c是一個輸出hello world的c語言源文件。centos
FROM centos:centos6 RUN yum install -y gcc ADD test.c / RUN gcc /test.c
構建這個鏡像,因爲最後一步是編譯命令gcc/test.c,因此編譯過程會在Docker Hub上進行執行。bash
咱們能夠經過編寫Dockerfile,使得整個編譯過程都託管在Docker Hub上。若是咱們提交了新的代碼,須要從新編譯,那麼只須要從新構建鏡像便可。服務器
在v1版本中,Docker Client是串行下載鏡像的各層。對於docker pull的過程進行分析,能夠看到Docker Client總共有這樣幾個步驟:網絡
/v1/repositories/{repository}/tags/{tag} 獲取tag的id
/v1/images/{tag_id}/ancestry 獲取tag的各層的id
/v1/images/{layer_id}/json 依次獲取各層對應的配置文件json
/v1/images/{layer_id}/layer 依次獲取各層對應的鏡像數據layer
Docker Hub的鏡像數據,並非在本身的服務器中存儲,而是使用的亞馬遜的s3服務。所以在調用/v1/images/{layer_id}/layer接口,拉取鏡像的layer數據時,會返回302,將請求重定向到亞馬遜的s3服務上進行下載。
爲了方便下載,我本身寫了個小程序,使用HTTP協議便可徹底模擬Docker Client的整個過程。本身寫的好處在於你能夠依次獲取tag的ID,各層的ID,以及全部層的配置,進而一次性將全部層對應的鏡像數據存儲在亞馬遜的s3地址獲取到,而後能夠進行並行下載。若是單層下載失敗,只須要從新下載這一層便可。當全部的層在本地下載完畢後。而後打成tar包,再使用Docker Client進行load便可。
對於上文中所說的在線編譯,那麼咱們其實只關心編譯出來的相關文件。如剛剛的舉例,咱們其實只須要獲取鏡像的最後一層就能夠了。那麼使用本身寫的工具,能夠僅僅把最後一層下載下來。下載下來的tar包進行解包,就能夠直接獲取出編譯結果,即編譯過程生成的相關文件了。Docker Hub就成爲了咱們的一個強大的在線編譯器。
注:這裏說的鏡像下載過程是針對的Registry v1版本。Docker Hub在不久以後即將全面結束v1的服務。目前國內的幾家容器服務提供商還能夠支持v1。該方法一樣有效。v2的協議和代碼我還沒學習,後面研究以後再同你們分享。
鏡像層合併這個話題一直是一個有爭議的話題。過長的Dockefile會致使一個冗長的鏡像層數。而由於鏡像層數過多(好比十幾層,幾十層),可能會帶來的性能和穩定性上的擔心也不無道理,可是彷佛Docker社區一直不認爲這是一個重要的問題。因此基本上對於鏡像層合併的PR最後都被拒了。可是這不影響咱們在這裏討論他的實現。
我爲Dockerfile增長了兩個指令。TAG和COMPRESS。
TAG功能相似於docker build -t
的參數。不過build -t
只能給Dockerfile中的最後一層鏡像打上tag。新增長的TAG指令能夠在build生成的中間層也用標籤記錄下來。好比:
FROM centos:centos6 RUN yum install -y sshd TAG sshd:latest ADD test / CMD /bin/bash
這個TAG功能至關於使用下面的Dockerfile生成了這樣的一個鏡像,並打上了sshd:latest的標籤。
FROM centos:centos6
RUN yum install -y sshd
COMPRESS功能實現了一個鏡像多層合併的功能。好比下面這個Dockerfile:
FROM centos:centos6 RUN yum install -y sshd ADD test / CMD /bin/bash COMPRESS centos:centos6
咱們知道這裏假設RUN yum install -y sshd
,ADD test /, CMD /bin/bash生成的鏡像層爲a、b、c。那麼COMPRESS的功能目標就是將新增的a、b、c的文件和配置合併爲一個新的層d,並設置層d的父親爲鏡像centos:centos6。層d的配置文件能夠直接使用層c的配置文件。合併的難點在於如何計算層d的文件。
這裏有兩種作法,一種是把層a、b、c中的文件按照合併的規則合併起來。合併的規則包括子層和父層共有的文件則使用子層的,沒有交叉的文件則所有作爲新添加的。這種方法效率較低,在須要合併的層數過多的時候,會極爲耗時。
另一種思路則較爲簡單,不須要考慮中間總共有多少層。直接比較centos:centos6鏡像和c鏡像(c鏡像是指由c和其全部父層組成的鏡像),將二者的全部文件作比較,二者的diff結果即爲新層d。
最終,我採用了後者做爲COMPRESS的實現。鏡像的合併縮減了層數,可是弊端在於將生成鏡像的Dockerfile信息也消除了(使用Dockerfile生成的鏡像,能夠經過docker history進行回溯)。
dind(Docker in Docker),顧名思義就是在容器裏面啓動一個Docker Daemon。而後使用後者再啓動容器。dind是一種比較高級的玩法,從另外一個角度來講也是一種有必定風險的玩法。dind巧妙的利用了Docker的嵌套的能力,可是使人頗爲擔憂的是底層graph driver在嵌套後的性能和穩定性。因此dind我並不推薦做爲容器的運行環境來使用(RancherOS實際上是使用了這種方式的),可是使用其做爲構建鏡像的環境,能夠進行實踐。畢竟構建失敗的後果沒有運行時崩潰的後果那麼嚴重。
之因此會用到dind,是由於若是用於鏡像構建,那麼直接使用多個物理機,未免比較浪費。由於構建並非隨時都會發生的。而使用dind的方式,只需在須要的時候申請多個容器,而後再在其上進行構建操做。在不須要時候就能夠及時釋放容器資源,更加靈活。
製做dind的鏡像須要一個CentOS的鏡像(其餘暫未實踐過,fedora/ubuntu也均可以作),和一個wrapdocker的文件。wrapdocker的主要做用是容器啓動後爲Docker Daemon運行時準備所需的環境。
由於容器啓動後,Docker還須要一些環境才能啓動daemon。好比在CentOS下,須要wrapdocker把cgroup等準備好。使用CentOS的鏡像建立一個容器後,安裝Docker等Docker須要的組件後,而後把wrapdocker ADD進去。並把wrapdocker添加爲ENTRYPOINT或者CMD。而後將容器commit成爲鏡像,就得到了一個dind的鏡像。使用dind的鏡像時須要使用privileged賦予權限,就可使用了。
熟悉Docker源碼的同窗應該知道,dind其實並不陌生。在Docker項目裏,就有這樣一個dind的文件。這個dind文件其實就是一個wrapdocker文件。在Docker進行集成測試時,須要使用該文件,協助準備環境以便在容器內部啓動一個Daemon來完成集成測試。
若是對於dind有興趣,能夠參考jpetazzo中的Dockerfile和wrapdocker,構建本身的dind鏡像。
dind中Docker的使用跟普通Docker同樣。再也不贅述。
Docker鏡像由若干層組成。而其中的每一層是由文件和配置組成的。若是把層與層之間的父子關係,看作一種時間上的前後關係,那麼Docker鏡像其實與Git十分的相像。那麼從理論上來講,Git的若干功能,好比merge、reset、rebase功能其實咱們均可以在Docker的構建過程當中予以實現。好比上文中的COMPRESS功能,就相似於Git的merge。理論上,Docker鏡像其實也能夠擁有Git般強大的功能。從這點上來講,Docker鏡像的靈活性就遠高於KVM之類的鏡像。
在這裏,不得不抱怨幾句。Docker的維護者們對於dockerfile或者說Docker的構建過程並無給予很是積極的態度,予以改善。固然這也多是因爲他們的更多的關注點集中在了runC、libnetwork、Orchestration上。因此沒有更多的人力來完善Docker構建的工具,而是寄但願於社區能本身增長其餘的tool來豐富Docker的構建過程。
因此不少時候,docker build的功能並不盡如人意。好比一直呼聲很高的Docker鏡像壓縮功能,幾經討論,終於無果而終。又好比在build過程當中,使用–net參數來使得能夠控制build過程當中容器使用的網絡。該討論從今年的一月份開始討論,至今仍未定論結貼。你們能夠去強勢圍觀。地址在這裏(https://github.com/docker/docker/issues/10324)。
這裏特別說一下,在CentOS 6下,dind不能使用網橋(centos7能夠支持),因此在CentOS 6下使用dind,進行docker build,須要指定網絡–net=host的方式。
因此不少功能並不能等待Docker本身去完善,只好本身動手開發。其實熟悉了Docker源碼後,關於docker build這方面的開發難度並非很大。能夠本身去實現。讀一下孫宏亮同窗的《Docker源碼分析》,會很快上手。