理解Docker映象分層
關於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" CMD ["/bin/bash"]
ADD命令將本地的centos7的tar包新增到映象,並解壓到根目錄/下。生成/dev,/proc/,/bin等。
我們可以自己構建docker base映象,也可以直接使用已有的base映象。比如centos。我們可以直接從docker hub上拉取。
拉取
docker pull centos
檢視
# docker images centos REPOSITORYTAGIMAGE IDCREATEDSIZE centoslatest1e1148e4cc2c2 months ago202MB
可以看到最新的centos映象只有200mb,是不是覺得太小了?這是因為docker映象在執行的時候直接使用docker宿主機器的kernel。
Linux作業系統由核心空間和使用者空間組成。
核心空間是kernel,使用者空間是rootfs, 不同Linux發行版的區別主要是rootfs.比如 Ubuntu 14.04 使用 upstart 管理服務,apt 管理軟體包;而 CentOS 7 使用 systemd 和 yum。這些都是使用者空間上的區別,Linux kernel 差別不大。
所以 Docker 可以同時支援多種 Linux 映象,模擬出多種作業系統環境。
需要注意的是:
-
base映象只是使用者空間和發行版一致。kernel使用的是docker宿主機器的kernel。例如 CentOS 7 使用 3.x.x 的 kernel,如果 Docker Host 是 Ubuntu 16.04(比如我們的實驗環境),那麼在 CentOS 容器中使用的實際是是 Host 4.x.x 的 kernel。
- ① 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 daemon6.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
組織/id:version .
然後我們新增一個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/httpdlatestf2b58b1192deAbout an hour ago444MB ryan/httpdv1.0f2b58b1192deAbout an hour ago444MB centos71e1148e4cc2c2 months ago202MB centoslatest1e1148e4cc2c2 months ago202MB
可以執行我們建立的映象:
# docker run -d--privileged=true -it ryan/httpd:v1.0 /usr/sbin/init 48a4a128cd7b6924149cd97670919d4e2af6cb96c73c901af60d05fe4478225a # docker ps | grep ryan 48a4a128cd7bryan/httpd:v1.0"/usr/sbin/init"8 seconds agoUp 8 seconds
現在我們的基於原生base centos7的httpd伺服器已經啟動了。可以通過 docker exec -it zealous_kirch /bin/bash
來進入容器內部,檢視啟動httpd。
docker映象的分層結構
我們可以檢視映象的歷史,用上一步的映象id f2b58b1192de
# docker history f2b58b1192de IMAGECREATEDCREATED BYSIZECOMMENT f2b58b1192deAbout an hour ago/bin/sh -c #(nop)CMD ["/bin/bash"]0B 17d8c4095dc4About an hour ago/bin/sh -c yum install -y httpd110MB 74bdbea98f73About an hour ago/bin/sh -c yum install -y vim133MB 1e1148e4cc2c2 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,只允許讀取。
(上圖來自官方文件,和本次實驗內容略有不同,但原理一樣)
第一列是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)' 4b0df4bc3e70centos"/bin/bash"23 seconds agoUp 23 secondsvigorous_elion0B (virtual 202MB) b36421d05005ryan/httpd:v1.0"/usr/sbin/init"32 minutes agoUp 32 minutesgracious_swirles61.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 testaae673aa7db489 minutes ago497MB testb21b2bc49f0bd12 minutes ago444MB
顯然,分層刪除操作並沒有真正刪除掉檔案。
來源
- https://www.cnblogs.com/CloudMan6/p/6799197.html
- https://www.cnblogs.com/CloudMan6/p/6806193.html
- https://docs.docker.com/storage/storagedriver/