Docker | 映象 | (一)
--昨夜西風凋碧樹,獨上高樓,望盡天涯路
-
Docker映象
映象是容器的基石,容器是映象的執行例項。
1.最小映象
首先我們先看一下 ‘最小映象’ hello-wold:
只有不到2KB,執行hello-world:
hello-world映象是通過Dockerfile(映象的描述檔案,定義瞭如何構建Docker映象,語法簡潔可讀性強)建立的。
hello-world映象的Dockerfile如下:
1.FROM scratch表示從零開始構建
2.COPY hello / 表示將檔案hello複製到映象的根目錄
3.CMD ["/hello"] 表示容器啟動時,執行 /hello
映象hello-world中就只有一個可執行檔案“hello”,其功能就是打印出“Hello from Docker”。
2.base映象
base映象就是指 (1)不依賴其他映象,從scratch構建;(2)其他映象可以用base映象為基礎來進行擴充套件。比如:Ubuntu,CentOS,Debian等。
從Docker Hub拉一個centos映象,發現只有200M。why?為什麼這麼小?
Linux作業系統由核心空間和使用者空間組成。核心空間是Kernel,linux剛啟動時會載入bootfs檔案系統,之後bootsfs會被解除安裝掉,使用者空間的檔案系統是rootfs,包含/dev、/proc、/bin等目錄。
對於base映象來說,底層用Host的Kernel,自己只需要提供rootfs就可以了。對於一個精簡的OS,rootfs可以很小,只需要包括基本的命令、工具和程式庫就可以了。
這裡需要說明的是:
(1)base映象只是使用者空間與發行版一致,kernel版本與發行版可能不同(上面說了,base映象採用Host的Kernel,比如CentOS7使用3.X.X的Kernel,Docker Host是Ubuntu18.04,採用的是4.15的Kernel,那麼實際上,CentOS使用的是Host4.15的Kernel)。
首先通過uname -r檢視Ubuntu的核心版本,之後進入CentOS檢視CentOS映象的版本,發現是一樣的。
(2)容器只能用Host的Kernel,並且不能修改。
所有容器都公用Host的Kernel,在容器中無法對Kernel升級。如果容器對Kernel版本有要求(比如應用只能在某個版本的Kernel下執行),則不建議用容器,這張場景虛擬機器更合適。
3.映象的分層結構|Docker的Copy-on-Write特性
Docker支援通過擴充套件現有映象,建立新的映象。
比如通過Dockerfile構建一個新的映象,Dockerfile如下:
新映象直接從ubuntu base映象開始,之後更新apt-get,安裝vim
可以看到,新映象是一層一層疊加上去,每次安裝一個軟體就會在現有映象的基礎上增加一層。生成一個ID
最後成功構建映象生成 Image ID。
Docker映象採用這種分層結構最大的一個好處就是:共享資源。
比如:有多個映象都從相同的base映象構建而來的,那麼Docker Host只需在磁碟上儲存一份base映象;同時記憶體中也只需要載入一份base映象,就可以為所有容器服務了,而且映象的每一層都可以被共享。
多個容器共享一份base映象,當某個容器修改了基礎映象的內容,比如/etc檔案下的內容,其他容器的/etc也會被修改麼?答案是:不會。
Docker提供Copy-on-Write特性使得修改會被限制在單個容器內:
當容器啟動時,一個新的可寫層(這一層通常被稱為“容器層”,“容器層”之下的都被成為”映象層“)載入到映象的頂部。所有對容器的改動,無論新增、刪除、還是修改檔案都只會發生在容器層之中。只有容器層是可寫的,容器層下面的所有映象都是隻讀的。
映象層的數量可能會很多,所有映象層聯合起來組成一個統一的檔案系統。在容器層中,使用者看到的是一個疊加後的檔案系統。
(1)新增檔案。在容器中建立檔案時,新檔案被新增到容器層中
(2)讀取檔案。在容器中讀取某個檔案時,Docker會從上往下依次在各映象層中查詢此檔案。一旦找到,開啟並讀入記憶體。
(3)修改檔案。在容器中修改某個已經存在的檔案時,Docker會從上往下一次在各映象層中查詢此檔案。一旦找到,立刻複製到容器層,然後修改。
(4)刪除檔案。再容器中刪除檔案時,Docker也會從上往下依次再映象層中查詢此檔案。找到後會在容器層記錄下此刪除操作。
容器層儲存的是映象層變化的部分並不會對映象本身進行修改。對於整個層層疊加的檔案系統的訪問,如果不同層有相同路徑的檔案,那麼上面的會覆蓋下面的,使用者訪問的是最上面的;如果路徑不同,那麼都是可以訪問到的。
總結:容器層記錄的是對映象的修改,所有映象層都是隻讀的,不會被容器修改,所以映象可以被多個容器共享。
-
構建映象
如果Docker Hub無法找到符合工作業務的映象,我們就需要自己構建映象了。Docker提供了兩種構建映象的方法:docker commit命令和Dockerfile構建檔案。
1.docker commit
docker commit是構建映象最直觀的方法,包含三個步驟:
(1)執行容器
-it是-i,-t的縮寫,意思是以互動模式進入容器(-i的意思是保證容器種STDIN是開啟的,-t告訴Docker為要建立的容器分配一個偽tty終端)
(2)安裝vim
(3)儲存為新映象
由於上面的操作是再容器中進行操作,關閉容器後操作會丟失,所以需要開啟一個新視窗,進行新映象的構建。
開啟新視窗檢視當前正在執行的容器:
執行docker commit命令將容器儲存為新映象,新映象命名為ubuntu_commit_vi:
通過docker ps查到的映象名(唯一)進行構建映象,構建完畢後生成128位的完整image ID(唯一),當以互動模式進入容器時[email protected]後面的16位ID是完整image ID的前16位,平時我們只需要採用16位ID來確定映象就可以。
docker commit是一種底層的構建映象的方法,不建議使用,原因如下:
(1)手工構建,容易出錯,效率低下,不易重複利用(如果有10個映象中加入vim,同樣的操作要重複10次)
(2)使用者不知道映象是如何創建出來的,並且無法對映象進行審計(只有構建者知道映象如何建立,如果不寫筆記,很可能也會忘記)。
2.Dockerfile
Dockerfile是一個文字檔案,記錄了映象的構建所有步驟。
再/root/dockerfile下建立Dockerfile檔案,內容如下:
在該目錄下通過docker build 命令構建映象(docker build後面的引數,-t 將新映象命名為ubuntu-lala,末尾的 . 表明當前目錄為build context,就是在當前目錄下尋找Dockerfile檔案。也可以通過-f引數指定Dockerfile的位置):
執行命令之後開始構建新映象:
(1)首先會檢查是否本地有基礎映象ubuntu,有就直接構建,沒有會先從Registry拉取一個base image
(2)將ubuntu映象作為base image,生成image ID為735f80812f90
(3)執行RUN命令後面的操作(這個地方的Using cache是由於以前操作過相同的步驟,生成過相同的映象層,所以直接使用了快取中的,這也是Docker的強大之處),之後生成這一層的Image ID。
(4)Successfully build 成功構建映象,映象ID為25557a8fbbe6。由於這個地方是使用了快取,所以沒有生成容器層(在容器層中執行RUN後面的操作),否則成功構建映象的上面會有將容器層儲存為映象層,並且刪除容器層的操作(下面會有演示)。
通過docker images檢視映象,發現ID一致:
通過docker history顯示映象的構建歷史:
發現會在原生ubuntu 映象的基礎上多了一層ID為25557a8fbbe6的映象層。
3.映象的快取特性
Docker會快取已有映象的映象層,構建新映象時,如果映象層已經存在,就直接使用,無須重新建立。
在前面的Dockerfile中新增一些內容,複製Host中的檔案hello到映象中:
可以看到Step3中沒有Using cache,因為之前沒有執行過相同的指令。如果不希望在構建映象時使用快取,那麼可以在docker build命令中加上--no-cache引數。
Dockerfile中每一個指令都會建立一個映象層,上層是依賴於下層的。無論什麼時候,只要某一層發生變化,其上面所有層的快取都會失效。也就是說如果改變Dockerfile指令的執行順序,或者修改或者新增指令,都會使快取失效。
如圖在RUN前面新增一個操作,所有快取都失效:
沒有使用快取,並且直接重新下載一次vim。如果不加操作,只是改變原來RUN和COPY的位置,也是不會使用快取(雖然在邏輯上這種改動對映象的內容沒有影響,但是由於分層的結構特性,Docker必須重建受影響的映象層)
4.除錯Dockerfile
從通過Dockerfile構建映象的過程可以看出,如果Dockerfile由於某種原因執行到某條指令失敗了,前面的映象層是正常的,那麼我們就可以拿前面的映象層ID,啟動映象,進行除錯。
編寫一個錯誤的Dockerfile(映象檔案系統中沒有lll目錄):
構建映象:
發現在Step4的位置出錯(就是剛才Dockerfile中的cd lll),之後我們拿到Step3的image ID編號37f4f4fe1679進行除錯:
發現是我們的操作錯誤,改正Dockerfile。