天天看點

如何了解Docker鏡像分層?且聽百度進階研發工程師細細道來目錄關于base鏡像關于存儲結構(About storage drivers)寫在最後

目錄

  • 關于base鏡像
  • 關于存儲結構(About storage drivers)
    • 先來建立一個自己的鏡像
    • docker鏡像的分層結構
    • 容器的大小
    • 修改時複制政策 copy-on-write (CoW)
    • Copying makes containers efficient

關于base鏡像

base 鏡像有兩層含義:

  • 不依賴其他鏡像,從 scratch 建構。
  • 其他鏡像可以之為基礎進行擴充。

是以,能稱作 base 鏡像的通常都是各種 Linux 發行版的 Docker 鏡像,比如 Ubuntu, Debian, CentOS 等。

base 鏡像提供的是最小安裝的 Linux 發行版。

我們大部分鏡像都将是基于base鏡像建構的。是以,通常使用的是官方釋出的base鏡像。可以在docker hub裡找到。比如centos: https://hub.docker.com/_/centos

點選版本可以看到github裡的Dockerfile

FROM scratch
ADD centos-7-docker.tar.xz /

LABEL org.label-schema.schema-version="1.0" \
    org.label-schema.name="CentOS Base Image" \
    org.label-schema.vendor="CentOS" \
    org.label-schema.license="GPLv2" \
    org.label-schema.build-date="20181205"
    //歡迎加入Java進階架構進階Qqun:963944895;免費分享Java架構學習資料、面試題、程式設計書籍
CMD ["/bin/bash"]           

複制

ADD指令将本地的centos7的tar包添加到鏡像,并解壓到根目錄/下。生成/dev,/proc/,/bin等。

我們可以自己建構docker base鏡像,也可以直接使用已有的base鏡像。比如centos。我們可以直接從docker hub上拉取。

拉取

docker pull centos           

複制

檢視

# docker images centos 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              1e1148e4cc2c        2 months ago        202MB           

複制

可以看到最新的centos鏡像隻有200mb,是不是覺得太小了?這是因為docker鏡像在運作的時候直接使用docker主控端器的kernel。

Linux作業系統由核心空間和使用者空間組成。

如何了解Docker鏡像分層?且聽百度進階研發工程師細細道來目錄關于base鏡像關于存儲結構(About storage drivers)寫在最後

核心空間是kernel,使用者空間是rootfs, 不同Linux發行版的差別主要是rootfs.比如 Ubuntu 14.04 使用 upstart 管理服務,apt 管理軟體包;而 CentOS 7 使用 systemd 和 yum。這些都是使用者空間上的差別,Linux kernel 差别不大。

是以 Docker 可以同時支援多種 Linux 鏡像,模拟出多種作業系統環境。

如何了解Docker鏡像分層?且聽百度進階研發工程師細細道來目錄關于base鏡像關于存儲結構(About storage drivers)寫在最後

需要注意的是:

  • base鏡像隻是使用者空間和發行版一緻。kernel使用的是docker主控端器的kernel。例如 CentOS 7 使用 3.x.x 的 kernel,如果 Docker Host 是 Ubuntu 16.04(比如我們的實驗環境),那麼在 CentOS 容器中使用的實際是是 Host 4.x.x 的 kernel。
如何了解Docker鏡像分層?且聽百度進階研發工程師細細道來目錄關于base鏡像關于存儲結構(About storage drivers)寫在最後
  • ① Host kernel 為 4.4.0-31
  • ② 啟動并進入 CentOS 容器
  • ③ 驗證容器是 CentOS 7
  • ④ 容器的 kernel 版本與 Host 一緻

關于存儲結構(About storage drivers)

上文裡展示了如何下載下傳一個base鏡像。我們通常是基于這份base鏡像來建構我們自己的鏡像。比如,在centos裡添加一個nginx負載均衡。首先,得需要了解鏡像的結構是什麼。

官方文檔: https://docs.docker.com/storage/storagedriver/

先來建立一個自己的鏡像

首先,base鏡像是基于docker主控端器kernel之上的Linux發行版。

現在,我們給這台機器安裝一個vim,一個httpd. 基于Dockerfile來建立一個新的鏡像。

我們的Dockerfile

FROM centos:7
RUN yum install -y vim
RUN yum install -y httpd
CMD ["/bin/bash"]           

複制

含義:

  • 基于centos7的base鏡像建構
  • 安裝vim
  • 安裝httpd
  • 執行bash

在目前目錄下建立一個檔案Dockerfile, 填充上述内容。然後執行

# docker build -t ryan/httpd:v1.0 .
Sending build context to Docker daemon  6.144kB
Step 1/4 : FROM centos:7
 ---> 1e1148e4cc2c
Step 2/4 : RUN yum install -y vim
 ---> Using cache
 ---> 74bdbea98f73
Step 3/4 : RUN yum install -y httpd
 ---> Using cache
 ---> 17d8c4095dc4
Step 4/4 : CMD /bin/bash
 ---> Using cache
 ---> f2b58b1192de
Successfully built f2b58b1192de
Successfully tagged ryan/httpd:latest
//歡迎加入Java進階架構進階Qqun:963944895;免費分享Java架構學習資料、面試題、程式設計書籍           

複制

  • -t 指定我們建立的鏡像名稱,鏡像名稱可以用組織

    /id:version

    的方式标記
  • 最後一個參數是

    Dockerfile

    所在的路徑

    .

    , 表示目前目錄

然後我們添加一個tag

latest

docker tag ryan/httpd:v1.0 ryan/httpd:latest           

複制

  • 即給鏡像

    ryan/httpd:v1.0

    标記為

    ryan/httpd:latest

建構完成之後,檢視

# docker images  | grep -E '(ryan|centos)'
ryan/httpd                                                               latest                     f2b58b1192de        About an hour ago   444MB
ryan/httpd                                                               v1.0                       f2b58b1192de        About an hour ago   444MB
centos                                                                   7                          1e1148e4cc2c        2 months ago        202MB
centos                                                                   latest                     1e1148e4cc2c        2 months ago        202MB           

複制

可以運作我們建立的鏡像:

# docker run -d  --privileged=true -it ryan/httpd:v1.0 /usr/sbin/init
48a4a128cd7b6924149cd97670919d4e2af6cb96c73c901af60d05fe4478225a
# docker ps | grep ryan
48a4a128cd7b        ryan/httpd:v1.0                                                          "/usr/sbin/init"         8 seconds ago       Up 8 seconds                  

複制

現在我們的基于原生base centos7的httpd伺服器已經啟動了。可以通過docker exec -it zealous_kirch /bin/bash來進入容器内部,檢視啟動httpd。

docker鏡像的分層結構

我們可以檢視鏡像的曆史,用上一步的鏡像id f2b58b1192de

# docker history f2b58b1192de
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
f2b58b1192de        About an hour ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
17d8c4095dc4        About an hour ago   /bin/sh -c yum install -y httpd                 110MB               
74bdbea98f73        About an hour ago   /bin/sh -c yum install -y vim                   133MB               
1e1148e4cc2c        2 months ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           2 months ago        /bin/sh -c #(nop)  LABEL org.label-schema....   0B                  
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:6f877549795f479...   202MB              

複制

啟動鏡像的時候,一個新的可寫層會加載到鏡像的頂部。這一層通常稱為“容器層”, 之下是“鏡像層”。

容器層可以讀寫,容器所有發生檔案變更寫都發生在這一層。鏡像層read-only,隻允許讀取。

如何了解Docker鏡像分層?且聽百度進階研發工程師細細道來目錄關于base鏡像關于存儲結構(About storage drivers)寫在最後

(上圖來自官方文檔,和本次實驗内容略有不同,但原理一樣)

第一列是imageid, 最上面的id就是我們新建立ryan/httpd:latest. 下面幾行都是我們dockerfile裡定義的步驟堆棧。由此可以看出,每個步驟都将建立一個imgid, 一直追溯到

1e1148e4cc2c

正好是我們的base鏡像的id。關于<missing>的部分,則不在本機上。

最後一列是每一層的大小。最後一層隻是啟動

bash

,是以沒有檔案變更,大小是0. 我們建立的鏡像是在base鏡像之上的,并不是完全複制一份base,然後修改,而是共享base的内容。這時候,如果我們建立一個新的鏡像,同樣也是共享base鏡像。

那修改了base鏡像,會不會導緻我們建立的鏡像也被修改呢? 不會!因為不允許修改曆史鏡像,隻允許修改容器,而容器隻可以在最上面的容器層進行寫和變更。

容器的大小

建立鏡像的時候,分層可以讓docker隻儲存我們添加和修改的部分内容。其他内容基于base鏡像,不需要存儲,讀取base鏡像即可。如此,當我們建立多個鏡像的時候,所有的鏡像共享base部分。節省了磁盤空間。

對于啟動的容器,檢視所需要的磁盤空間可以通過

docker ps -s

# docker run -d -it centos
4b0df4bc3e705c540144d545441930689124ade087961d01f56c2ac55bfd986d
# docker ps -s | grep -E '(ryan|centos)'
4b0df4bc3e70        centos                                                                   "/bin/bash"              23 seconds ago      Up 23 seconds                           vigorous_elion                                                                                                                           0B (virtual 202MB)
b36421d05005        ryan/httpd:v1.0                                                          "/usr/sbin/init"         32 minutes ago      Up 32 minutes                           gracious_swirles                                                                                                                         61.6kB (virtual 444MB)           

複制

  • 首先啟動一個base鏡像用來對比
  • 可以看到第一行就是base鏡像centos,第2列的size是0和202MB, 0表示容器層可寫層的大小,virtual則是容器層+鏡像層的大小。這裡對比可以看到一共202M,正好是最初centos鏡像的大小。
  • 第二行是我們自己建立的鏡像。virtual達到了444MB。對比前面的history部分,可以發現這個數字是每一層大小之和。同時,由于共享base,其中的202M是和第一行的鏡像共享的。

修改時複制政策 copy-on-write (CoW)

docker通過一個叫做copy-on-write (CoW) 的政策來保證base鏡像的安全性,以及更高的性能和空間使用率。

Copy-on-write is a strategy of sharing and copying files for maximum efficiency. If a file or directory exists in a lower layer within the image, and another layer (including the writable layer) needs read access to it, it just uses the existing file. The first time another layer needs to modify the file (when building the image or running the container), the file is copied into that layer and modified. This minimizes I/O and the size of each of the subsequent layers. These advantages are explained in more depth below.

Copying makes containers efficient

When you start a container, a thin writable container layer is added on top of the other layers. Any changes the container makes to the filesystem are stored here. Any files the container does not change do not get copied to this writable layer. This means that the writable layer is as small as possible.

When an existing file in a container is modified, the storage driver performs a copy-on-write operation. The specifics steps involved depend on the specific storage driver. For the aufs, overlay, and overlay2 drivers, the copy-on-write operation follows this rough sequence:

Search through the image layers for the file to update. The process starts at the newest layer and works down to the base layer one layer at a time. When results are found, they are added to a cache to speed future operations.

Perform a copy_up operation on the first copy of the file that is found, to copy the file to the container’s writable layer.

Any modifications are made to this copy of the file, and the container cannot see the read-only copy of the file that exists in the lower layer.

Btrfs, ZFS, and other drivers handle the copy-on-write differently. You can read more about the methods of these drivers later in their detailed descriptions.

Containers that write a lot of data consume more space than containers that do not. This is because most write operations consume new space in the container’s thin writable top layer.

簡單的說,啟動容器的時候,最上層容器層是可寫層,之下的都是鏡像層,隻讀層。

當容器需要讀取檔案的時候

從最上層鏡像開始查找,往下找,找到檔案後讀取并放入記憶體,若已經在記憶體中了,直接使用。(即,同一台機器上運作的docker容器共享運作時相同的檔案)。

當容器需要添加檔案的時候

直接在最上面的容器層可寫層添加檔案,不會影響鏡像層。

當容器需要修改檔案的時候

從上往下層尋找檔案,找到後,複制到容器可寫層,然後,對容器來說,可以看到的是容器層的這個檔案,看不到鏡像層裡的檔案。容器在容器層修改這個檔案。

當容器需要删除檔案的時候

從上往下層尋找檔案,找到後在容器中記錄删除。即,并不會真正的删除檔案,而是軟删除。這将導緻鏡像體積隻會增加,不會減少。

綜上,Docker鏡像通過分層實作了資源共享,通過copy-on-write實作了檔案隔離。

對于檔案隻增加不減少問題,我們應當在同一層做增删操作,進而減少鏡像體積。比如,如下測試。

Dockerfile.A: 分層删除檔案

FROM centos:7
RUN yum install -y vim
RUN yum install -y httpd
WORKDIR /home
RUN dd if=/dev/zero of=50M.file bs=1M count=50
#建立大小為50M的測試檔案
RUN rm -rf 50M.file
CMD ["/bin/bash"]           

複制

建構

docker build -t test:a -f Dockerfile.A .           

複制

Dockerfile.B: 同層删除

FROM centos:7
RUN yum install -y vim
RUN yum install -y httpd
WORKDIR /home
RUN dd if=/dev/zero of=50M.file bs=1M count=50 && rm -rf 50M.file           

複制

建構

docker build -t test:b -f Dockerfile.B .           

複制

比較二者大小

[root@sh-k8s-001 tmp]# docker images | grep test
test                                                                     a                          ae673aa7db48        9 minutes ago       497MB
test                                                                     b                          21b2bc49f0bd        12 minutes ago      444MB           

複制

顯然,分層删除操作并沒有真正删除掉檔案。

寫在最後

點關注,不迷路;持續更新Java相關技術及資訊文章