Docker Image layer

在此篇文章中: Docker Certified Associate(DCA)認證考試學習-Docker可讀寫層

我們來看一次文章中的的圖片 container-layers

可以看到最上方的Thin R/W layer也就是我們昨天說的可讀寫層也稱作為Container layer
下方部分我們昨天也有提到是唯讀的Image layer(R/Oo),這邊Docker定義為Image layer
那麼Image layer裡面一個一個疊起來的長方形是代表什麼意思呢?
這就要說到Docker image的設計了
首先我們先做幾個測試看看

mkdir test && cd $_
touch hello.txt
vi Dockerfile

貼上以下內容

FROM ubuntu:latest
RUN mkdir -p /hello/hello
COPY hello.txt /hello/hello
RUN chmod 600 /hello/hello/hello.txt

之後建立一個先的image叫做testimage

docker build -t testimage .
[node1] (local) root@192.168.0.8 ~/test
$ docker build -t testimage .
Sending build context to Docker daemon   2.56kB
Step 1/4 : FROM ubuntu:latest
latest: Pulling from library/ubuntu
677076032cca: Pull complete 
Digest: sha256:9a0bdde4188b896a372804be2384015e90e3f84906b750c1a53539b585fbbe7f
Status: Downloaded newer image for ubuntu:latest
 ---> 58db3edaf2be
Step 2/4 : RUN mkdir -p /hello/hello
 ---> Running in 5ec6babe9e21
Removing intermediate container 5ec6babe9e21
 ---> 99ee378954f5
Step 3/4 : COPY hello.txt /hello/hello
 ---> 6b157189e2fb
Step 4/4 : RUN chmod 600 /hello/hello/hello.txt
 ---> Running in 697285183d6c
Removing intermediate container 697285183d6c
 ---> 3b8c55772597
Successfully built 3b8c55772597
Successfully tagged testimage:latest

我們來解讀一下輸出的結果,首先這個Dockerfile有四個步驟

  1. 選擇ubuntu作為底層
  2. 建立資料夾
  3. 將hello.txt複製到第二步驟建立的資料夾
  4. 修改hello.txt權限

這邊需要注意箭頭符號的部分--->後面接了一個ID,這邊把它們列出來

1. Step 1/4  ---> 58db3edaf2be
2. Step 2/4  
   1. ---> Running in 5ec6babe9e21
   2. Removing intermediate container 5ec6babe9e21
   3. ---> 99ee378954f5
3. Step 3/4  ---> 6b157189e2fb
4. Step 4/4
   1. ---> Running in 697285183d6c
   2. Removing intermediate container 697285183d6c
   3. ---> 3b8c55772597

這樣就清楚多了
首先看一下第一步驟的ID 58db3edaf2be,很明顯這個一定是ubuntu image的ID,可以使用命令docker images列出image

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
testimage    latest    3b8c55772597   13 minutes ago   77.8MB
ubuntu       latest    58db3edaf2be   3 weeks ago      77.8MB

接下來看看第四步驟最後一個ID 3b8c55772597,在對應一下上方輸出的image列表,可以發現這個ID就是我們建構出來成品image的ID 那麼照一般沒使用過Docker的人的邏輯來說我們的成品image 3b8c55772597 將ubuntu image 58db3edaf2be的內容全部複製過來並且打包成一份新的,那現在我們的image理論上應該跟ubuntu image沒有關係了
但其實這樣是錯的
Docker其實是一層一層將image疊出來的就是一開始圖片畫的那樣 所以就是說image 3b8c55772597裡面最底下那一層就是ubuntu image 58db3edaf2be

那麼步驟二跟步驟三為什麼也有ID呢?

根據Docker最佳實踐的說法

  • Only the instructions RUN, COPY, ADD create layers. Other instructions create temporary intermediate images, and don’t increase the size of the build.

可以得知當使用RUN, COPY, ADD任意一個指令,Docker就會幫我們創建一個layers
也就是說步驟二三四都幫我們新創建了layer
我們這邊可以使用docker history來查看image建構的歷史

[node1] (local) root@192.168.0.8 ~/test
$ docker history testimage:latest 
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
3b8c55772597   3 hours ago   /bin/sh -c chmod 600 /hello/hello/hello.txt     0B        
6b157189e2fb   3 hours ago   /bin/sh -c #(nop) COPY file:6cc3233ff43b14ff…   0B        
99ee378954f5   3 hours ago   /bin/sh -c mkdir -p /hello/hello                0B        
58db3edaf2be   3 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      3 weeks ago   /bin/sh -c #(nop) ADD file:18e71f049606f6339…   77.8MB    
<missing>      3 weeks ago   /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      3 weeks ago   /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      3 weeks ago   /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B        
<missing>      3 weeks ago   /bin/sh -c #(nop)  ARG RELEASE                  0B        

確實是每個步驟都建立一個層級,並且真的根據建構的順序層層往上堆疊

3b8c55772597 修改hello.txt權限
6b157189e2fb 將hello.txt複製到第二步驟建立的資料夾
99ee378954f5 建立資料夾
58db3edaf2be ubuntu

為什麼要這樣分層呢,我們這邊在下一次docker build的命令

[node1] (local) root@192.168.0.8 ~/test
$ docker build -t testimage .
Sending build context to Docker daemon   29.7MB
Step 1/4 : FROM ubuntu:latest
 ---> 58db3edaf2be
Step 2/4 : RUN mkdir -p /hello/hello
 ---> Using cache
 ---> 99ee378954f5
Step 3/4 : COPY hello.txt /hello/hello
 ---> Using cache
 ---> 6b157189e2fb
Step 4/4 : RUN chmod 600 /hello/hello/hello.txt
 ---> Using cache
 ---> 3b8c55772597
Successfully built 3b8c55772597
Successfully tagged testimage:latest

能很明顯的發現建構的速度比一開始要快很多,幾乎是瞬間就完成了,並且看一下輸出的內容
可以發現每個步驟的ID都好熟悉,並且根據說明可以知道使用了快取,也就是說雖然使用了這三個指令RUN, COPY, ADD
但是Docker會去幫你檢查文件是否有變更過,如果有變更過那麼才需要建新建一個layer出來
我們這邊試試看修改hello.txt的內容,之後再build一次

[node1] (local) root@192.168.0.8 ~/test
$ docker build -t testimage .
Sending build context to Docker daemon   29.7MB
Step 1/4 : FROM ubuntu:latest
 ---> 58db3edaf2be
Step 2/4 : RUN mkdir -p /hello/hello
 ---> Using cache
 ---> 99ee378954f5
Step 3/4 : COPY hello.txt /hello/hello
 ---> 6aac2b9339da
Step 4/4 : RUN chmod 600 /hello/hello/hello.txt
 ---> Running in 4b9a386b873b
Removing intermediate container 4b9a386b873b
 ---> 3c90e3a67392
Successfully built 3c90e3a67392
Successfully tagged testimage:latest

發現第三步驟的層級ID改變了就代表,Docker知道我們有修改過文件內容,那麼就沒辦法使用快取了只能選擇建立一個新的layer

這邊還有一個很大的重點就是第四步驟,怎麼第四步驟的ID也變了,這邊只有第三層的文件被修改,理論上第四層應該可以使用快取才對

但事實上第四層是根據第三層的內容建構出來的所以只要第三層變動過那麼第四層就必須要重新建構

所以當我們撰寫Dockerfile時,步驟的順序是非常重要的

我們可以修改一下之前的範例改成以下內容

FROM ubuntu:latest
RUN mkdir -p /hello/hello
COPY hello.txt /hello/hello
RUN chmod 600 /hello/hello/hello.txt
RUN apt-get update -y
RUN apt-get upgrade -y
$ docker build -t testimage .
$ docker history testimage:latest 
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
e1dd14ea218b   8 seconds ago    /bin/sh -c apt-get upgrade -y                   8.7MB     
204f9e8ea9d8   13 seconds ago   /bin/sh -c apt-get update -y                    41.7MB    
38c2c6215474   18 seconds ago   /bin/sh -c chmod 600 /hello/hello/hello.txt     0B        
f4667314ff5a   19 seconds ago   /bin/sh -c #(nop) COPY file:6cc3233ff43b14ff…   0B        
ded837dd468e   20 seconds ago   /bin/sh -c mkdir -p /hello/hello                0B        
58db3edaf2be   3 weeks ago      /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      3 weeks ago      /bin/sh -c #(nop) ADD file:18e71f049606f6339…   77.8MB    
<missing>      3 weeks ago      /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      3 weeks ago      /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      3 weeks ago      /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B        
<missing>      3 weeks ago      /bin/sh -c #(nop)  ARG RELEASE                  0B        

這邊符合預期多出了兩個layer
我們修改一下hello.txt的內容,在build試試
執行build命令後,會發現怎麼又開始在更新了,所以這個Dockerfile不是好的範例
比較好的作法很簡單就是要調整一下順序

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get upgrade -y
RUN mkdir -p /hello/hello
COPY hello.txt /hello/hello
RUN chmod 600 /hello/hello/hello.txt

現在再修改一次hello.txt的內容,然後build 可以察覺速度變得很快並且那段更新的指令不會一直被執行了 這個就是正確的指令順序帶來的好處,可以完整利用image的快取機制


Summary

今天我們學習了image內部其實就是一層一層image堆疊起來的,就像一座大樓一樣每一樓層都密不可分
並且了解了RUN, COPY, ADD這三個指令都會建立一個新的layer 還有Dockerfile順序的重要性,正確的順序才能最大限度的利用快取機制