1. 程式人生 > >Docker | 映象 | (一)

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。