在使用Docker時候,針對鏡像的操做通常就是docker pull
,docker build
,docker commit
(剛開始接觸Docker的時候,還不會Dockerfile,常用這個命令,可是經歷了一次血的教訓,我已經放棄這個命令好久)這些操做,大概都知道Images在Docker中是由無數個Layer組成,可是,Image在本地是如何存儲的?上述操做又會對本地存儲帶來怎樣的變化?抱着學習的態度,我從剛剛安裝完docker開始,一步一步研究docker image的目錄結構和含義。
本人也只是docker初學者,寫文章的目的也是但願本身不只僅停留在會使用docker的階段,還可以邊用邊學邊總結,一方面加深本身的理解,另外一方面但願經過這種方式與一塊兒學習Docker的童鞋們交流。若有錯誤,歡迎批評指正,謝謝。python
之前基本都在本地服務器上使用Dockerfile構建鏡像,通常來講磁盤的空間都是足夠的,並且基本不須要docker save,應用場景也不存在頻發啓動容器的狀況,因此無論是空間仍是效率的角度,都沒有刻意去壓縮構建出來的鏡像大小。可是,最近由於須要在VPS上構建,可用的空間嚴重受限,所以,以爲重寫Dockerfile來壓縮鏡像大小。本覺得應該是一件很簡單的事情,果真太年輕。直接從dockerfile提及:mysql
FROM alpine ........ RUN apk -U upgrade && \ apk -v add --no-cache bash curl && \ apk -v add --no-cache --virtual .build-deps gcc make && \ apk -v add --no-cache mysql-client libc-dev mariadb-dev && \ rm -rf /var/cache/apk/* COPY ./startService.sh / ........ RUN make clean && make && make install && \ apk del .build-deps CMD ["/bin/bash", "/startService.sh"]
實驗發現上面的寫法,apk del .build-deps這一句加不加,大小都是同樣的,也就是說徹底沒有像預期的同樣,卸載環境就能夠壓縮大小。一通googole,問題獲得瞭解決,你們給出來的緣由基本能夠總結爲:「Image是由多個Layer組成的,後面的Layer沒辦法修改前面的Layer」,修改一下dockerfile就能解決:linux
FROM alpine ........ RUN apk -U upgrade && \ apk -v add --no-cache bash curl && \ apk -v add --no-cache mysql-client libc-dev mariadb-dev && \ rm -rf /var/cache/apk/* COPY ./startService.sh / ........ RUN apk -v add --no-cache --virtual .build-deps gcc make && \ make clean && make && make install && \ apk del .build-deps CMD ["/bin/bash", "/startService.sh"]
問題確實解決了,也大概能體會到在dockerfile中,最好把中間過程寫在一塊兒,減小Layer,可是,爲何會這樣?很明顯,從最開始的錯誤理解和如今的不理解,都是由於對Image的實現原理不清楚,因此,決定從Image本地目錄結構的角度來分析和理解。若是隻想看結論,直接跳到最後吧。git
由於不一樣的Docker版本,目錄結構有一些差別,下面的操做都是針對V18.09.0,而不一樣的操做系統會影響默認的存儲方式等,這裏使用的是Centos 7.4。
接下來的內容,首先根據最初始的Docker環境,拉去一個alpine鏡像分析本地目錄結構,以及每個目錄或文件的含義;而後基於alpine鏡像,從dockerfile中構建一個簡單的test-image鏡像,完成構建以後進一步分析和驗證目錄或文件的含義,並分析Image和Layer的關聯關係在本地文件系統是如何實現關聯的。github
docker pull alpine
瞭解本地目錄結構通常默認安裝啓動Docker,全部相關的文件都會存儲在/var/lib/docker下面,可使用tree /var/lib/docker
查看目錄結構,而與Image相關的目錄主要包括兩個:image和overlay2,須要注意overlay2,是存儲驅動,不一樣的操做系統和docker版本可能不太一致,因此在查看目錄的時候要結合本身的環境:sql
/var/lib/docker ├── builder │ └── fscache.db ├── buildkit │ ├── cache.db │ ├── content │ │ └── ingest │ ├── executor │ ├── metadata.db │ └── snapshots.db ├── containerd │ └── daemon │ ├── ........ │ └── tmpmounts ├── containers ├── image │ └── overlay2 │ ├── distribution │ ├── imagedb │ │ ├── content │ │ │ └── sha256 │ │ └── metadata │ │ └── sha256 │ ├── layerdb │ └── repositories.json ├── network │ └── files │ └── local-kv.db ├── overlay2 │ ├── backingFsBlockDev │ └── l ├── plugins │ ├── storage │ │ └── blobs │ │ └── tmp │ └── tmp ├── runtimes ├── swarm ├── tmp ├── trust └── volumes └── metadata.db
由於上面是剛安裝完的狀態,並無pull或者build任何鏡像,因此目前image目錄下只有一些默認的文件或者目錄,並且文件和目錄也沒有存什麼有用的信息。如今咱們使用docker pull alpine
獲取一個最簡單的鏡像。docker
[root@docker-learn docker]# docker pull alpine Using default tag: latest latest: Pulling from library/alpine cd784148e348: Pull complete Digest: sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1 Status: Downloaded newer image for alpine:latest
上面拉去過程只會產生一個Layer,咱們能夠經過docker images --digests
命令查看拉取的鏡像,注意Image ID和digest的區別。json
[root@docker-learn docker]# docker images --digests REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE alpine latest sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1 3f53bb00af94 8 days ago 4.41MB
此時,咱們能夠再看文件系統的變化,爲了方便,只展現image目錄:bash
image/ └── overlay2 ├── distribution │ ├── diffid-by-digest │ │ └── sha256 │ │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 │ └── v2metadata-by-diffid │ └── sha256 │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ├── imagedb │ ├── content │ │ └── sha256 │ │ └── 3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991 │ └── metadata │ └── sha256 ├── layerdb │ ├── sha256 │ │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ │ ├── cache-id │ │ ├── diff │ │ ├── size │ │ └── tar-split.json.gz │ └── tmp └── repositories.json
這個文件存儲了本地的全部images列表,裏面目前包含了兩個,"alpine:latest"和"alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1",其實這兩個是同一個鏡像,你能夠在剛剛docker images --digests
看到,前者是tag,後者是digest(docker inspect 3f53bb00af94
也能夠看到相同的效果)。服務器
{ "Repositories": { "alpine": { "alpine:latest": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991", "alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991" } } }
imagedb/ ├── content │ └── sha256 │ └── 3f53bb00af9...... └── metadata └── sha256
該目錄存儲了鏡像的相關信息,每一個鏡像的內容都包含在本身的目錄下,目錄名即爲該鏡像的Image ID。
首先是metadata目錄,該目錄保存每一個鏡像的parent鏡像ID,由於這裏的alpine:lasted鏡像沒有更上層的鏡像,因此目錄爲空,後續咱們使用docker build
構建一個鏡像,再進一步分析。
其次是content目錄,該目錄下存儲了鏡像的JSON格式描述信息:
{ "architecture": "amd64", "config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "", "Image": "sha256:49573004c44f9413c7db63cbab336356e7a8843139fca5e68f92d84a56f0e6df", "Labels": null, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "container": "c44d11fa67899a984d66f5542092b474f11ca95cc9b03b1470546f16ec8ce74f", "container_config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"/bin/sh\"]" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "c44d11fa6789", "Image": "sha256:49573004c44f9413c7db63cbab336356e7a8843139fca5e68f92d84a56f0e6df", "Labels": {}, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "created": "2018-12-21T00:21:30.122610396Z", "docker_version": "18.06.1-ce", "history": [ { "created": "2018-12-21T00:21:29.97055571Z", "created_by": "/bin/sh -c #(nop) ADD file:2ff00caea4e83dfade726ca47e3c795a1e9acb8ac24e392785c474ecf9a621f2 in / " }, { "created": "2018-12-21T00:21:30.122610396Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", "empty_layer": true } ], "os": "linux", "rootfs": { "diff_ids": [ "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8" ], "type": "layers" } }
解釋如下主要的幾個部分:
docker build
構建鏡像時,能夠看見是不斷地生成新的container,而後提交爲新的image,此處的容器ID即生成該鏡像時臨時容器的ID,後面經過docker build
構建鏡像會進一步驗證。和imagedb目錄同樣,根據命名便可理解該目錄主要用來存儲Docker的Layer信息,在只有一個alpine:lasted鏡像的狀況下,目錄結構以下:
layerdb/ ├── sha256 │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ ├── cache-id │ ├── diff │ ├── size │ └── tar-split.json.gz └── tmp
在咱們docker pull alpine:lasted
的時候,能夠發現只pull了一層,而在上面imagedb/content中的鏡像信息中,rootfs中也只有一個diff,所以,與此處的一個Layer層吻合。可是,須要注意此處的 7bff100f35... 與rootfs中的diff_id 7bff100f35...雖然值同樣,可是含義並不相同,此處標識Layer的Chain ID,之因此此處一致,是由於在只有一層Layer,沒有parent時,diff id與chain id相等,後面咱們構建test-image後再來分析便可看出區別。
在改Layer的目錄下,包含四個文件:
diff:該Layer層的diff id
[root@docker-learn overlay2]# cat layerdb/sha256/7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8/diff sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8
如上面所述,最底層的Layer具備相同的chain id 和 diff id
size:該Layer的大小,單位字節
[root@docker-learn overlay2]# cat layerdb/sha256/7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8/size 4413428
在docker images中,咱們能夠看到alpine鏡像的大小爲4.41MB,將此處的大小進行換算 4413428/(1024*1024),發現大小不一致,第一反應是Image相對於Layer還增長了其餘信息,可是理論上彷佛沒法解釋,因而使用docker inspect alpine
查看了鏡像的具體信息,發現其中Size: 4413428,與該處數值一直,那麼4.41M應該是4413428/(1000000)計算得來,後面咱們會使用test-image鏡像進一步驗證。
該文件生成須要依賴tar-split,經過這個文件能夠還原layer的tar包。
cache_id:內容爲一個uuid,指向Layer本地的真正存儲位置。
[root@docker-learn layerdb]# cat sha256/7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8/cache-id 281c53a74496be2bfcf921ceee5ec2d95139c43fec165ab667a77899b3691d56
那麼Layer本地真正的存儲位置又在何處呢?即是上面提到的/var/lib/docker/overlay2
目錄下:
[root@docker-learn overlay2]# ls 281c53a74496be2bfcf921ceee5ec2d95139c43fec165ab667a77899b3691d56 backingFsBlockDev l
須要注意,layerdb目錄下除了diff、size、cache_id和tar-split.json.gz文件,還應該包括一個parent文件,文件存儲了當前Layer層的父層chain_id,由於當前alpine鏡像只有一層,因此也就沒有parent。
該目錄包含了Layer層diif id和digest之間的對應關係。
[root@docker-learn overlay2]# tree distribution/ distribution/ ├── diffid-by-digest │ └── sha256 │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 └── v2metadata-by-diffid └── sha256 └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 4 directories, 2 files
v2metadata-by-diffid目錄下,咱們能夠經過Layer的diff id找到對應的digest,而且包含了生成該digest的源倉庫。
[ { "Digest": "sha256:cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3", "HMAC": "", "SourceRepository": "docker.io/library/alpine" } ]
diffid-by-digest目錄則與v2metadata-by-diffid相反
[root@docker-learn overlay2]# cat distribution/diffid-by-digest/sha256/cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8
到這裏爲止,基於最簡單的alpine鏡像,咱們看到了Image的本地目錄結構,以及每個目錄或文件大概的做用。可是,由於該鏡像只有一層,不少關聯關係並無很好的體現,接下來用一個稍微複雜的鏡像再過一遍上述過程。
docker build test-image
進一步理解目錄結構一個簡單的dockerfile構建test-image
FROM alpine LABEL name="test-image" RUN apk -v add --no-cache bash RUN apk -v add --no-cache curl COPY ./startService.sh / CMD ["/bin/bash", "/startService.sh"]
構建過程輸出以下:
[root@docker-learn docker]# docker build -t test-image . Sending build context to Docker daemon 3.072kB Step 1/6 : FROM alpine ---> 3f53bb00af94 Step 2/6 : LABEL name="test-image" ---> Running in 3bd6320fc291 Removing intermediate container 3bd6320fc291 ---> bb97dd1fb1a1 Step 3/6 : RUN apk -v add --no-cache bash ---> Running in f9987ff57ad7 fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz (1/5) Installing ncurses-terminfo-base (6.1_p20180818-r1) (2/5) Installing ncurses-terminfo (6.1_p20180818-r1) (3/5) Installing ncurses-libs (6.1_p20180818-r1) (4/5) Installing readline (7.0.003-r0) (5/5) Installing bash (4.4.19-r1) Executing bash-4.4.19-r1.post-install Executing busybox-1.28.4-r2.trigger OK: 18 packages, 136 dirs, 2877 files, 13 MiB Removing intermediate container f9987ff57ad7 ---> a5635f1b1d00 Step 4/6 : RUN apk -v add --no-cache curl ---> Running in c49fb2e4b311 fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz (1/5) Installing ca-certificates (20171114-r3) (2/5) Installing nghttp2-libs (1.32.0-r0) (3/5) Installing libssh2 (1.8.0-r3) (4/5) Installing libcurl (7.61.1-r1) (5/5) Installing curl (7.61.1-r1) Executing busybox-1.28.4-r2.trigger Executing ca-certificates-20171114-r3.trigger OK: 23 packages, 141 dirs, 3040 files, 15 MiB Removing intermediate container c49fb2e4b311 ---> 9156d1521a2f Step 5/6 : COPY ./startService.sh / ---> 704626646baf Step 6/6 : CMD ["/bin/bash", "/startService.sh"] ---> Running in 1c5e6e861264 Removing intermediate container 1c5e6e861264 ---> 6cd0a66e83f1 Successfully built 6cd0a66e83f1 Successfully tagged test-image:latest
鏡像build過程能夠理解爲基於一個鏡像啓動一個容器,在容器內執行Dockerfile裏的一條命令,生成一個新的鏡像。根據上述的輸入,test-image的構建過程能夠表示爲:
最終生成的test-image鏡像ID爲 6cd0a66e83f1,咱們從該鏡像開始,再一次分析本地目錄。首先查看鏡像的基本信息:
[root@docker-learn docker]# docker images --digests test-image REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE test-image latest <none> 6cd0a66e83f1 About an hour ago 9.88MB
如前面所述,digest是有docker repository生成,由於本地構建完以後並無推送至遠程倉庫,因此爲None。此時,image目錄發生了以下變化:
image/ └── overlay2 ├── distribution │ ├── diffid-by-digest │ │ └── sha256 │ │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 │ └── v2metadata-by-diffid │ └── sha256 │ └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ├── imagedb │ ├── content │ │ └── sha256 │ │ ├── 3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991 │ │ ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ │ ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ │ ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ │ ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ │ └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c │ └── metadata │ └── sha256 │ ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ │ ├── lastUpdated │ │ └── parent │ ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ │ └── parent │ ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ │ └── parent │ ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ │ └── parent │ └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c │ └── parent ├── layerdb │ ├── mounts │ ├── sha256 │ │ ├── 0e88764cdf90e8a5d6597b2d8e65b8f70e7b62982b0aee934195b54600320d47 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── parent │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ ├── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ ├── 80fe1abae43103e3be54ac2813114d1dea6fc91454a3369104b8dd6e2b1363f5 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── parent │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ └── db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 │ │ ├── cache-id │ │ ├── diff │ │ ├── parent │ │ ├── size │ │ └── tar-split.json.gz │ └── tmp └── repositories.json
能夠看到,相比於只有alpine鏡像的時候,在imagedb的content和metadata下,增長了build過程當中產生的鏡像(鏡像ID可以一一對應),在layerdb下增長了三個Layer,目前還看不出來關聯關係,後續分析。
[root@docker-learn docker]# cat image/overlay2/repositories.json | python -m json.tool { "Repositories": { "alpine": { "alpine:latest": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991", "alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991" }, "test-image": { "test-image:latest": "sha256:6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924" } } }
能夠看見,增長了test-image這一項,包含其tag和id。
[root@docker-learn overlay2]# tree imagedb/ imagedb/ ├── content │ └── sha256 │ ├── 3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991 │ ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c └── metadata └── sha256 ├── 6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924 │ ├── lastUpdated │ └── parent ├── 704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836 │ └── parent ├── 9156d1521a2fd50d972e1e1abc30d37df7c8e8f7825ca5955170f3b5441b3341 │ └── parent ├── a5635f1b1d0078cd926f21ef3ed77b357aa899ac0c8bf80cae51c37129167e3a │ └── parent └── bb97dd1fb1a10b717655594950efb4605ff0d3f2f631feafc4558836c2b34c3c └── parent 9 directories, 12 files
在構建test-image以前,metadata目錄爲空,由於alpine沒有parent,在build以後,新增了5個目錄,分別對應docker build test-image
所產生的5個鏡像,每一層以上一層爲parent,即parent文件中存儲了上一層image id。
content目錄中,相比於構建test-image以前,也多了上述5個鏡像的內容,以test-image爲例(目前的最底層鏡像),查看其描述信息:
{ "architecture": "amd64", "config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/bash", "/startService.sh" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "", "Image": "sha256:704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836", "Labels": { "name": "test-image" }, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "container": "1c5e6e861264654f79a190eba5157dd4dedce59ab3de098a3625fb4e5b6f1d98", "container_config": { "ArgsEscaped": true, "AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"/bin/bash\" \"/startService.sh\"]" ], "Domainname": "", "Entrypoint": null, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Hostname": "1c5e6e861264", "Image": "sha256:704626646baf8bdea82da237819cded076a0852eb97dba2fc731569dd85ae836", "Labels": { "name": "test-image" }, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": "" }, "created": "2019-01-01T02:29:19.701494089Z", "docker_version": "18.09.0", "history": [ { "created": "2018-12-21T00:21:29.97055571Z", "created_by": "/bin/sh -c #(nop) ADD file:2ff00caea4e83dfade726ca47e3c795a1e9acb8ac24e392785c474ecf9a621f2 in / " }, { "created": "2018-12-21T00:21:30.122610396Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", "empty_layer": true }, { "created": "2019-01-01T02:29:06.530296297Z", "created_by": "/bin/sh -c #(nop) LABEL name=test-image", "empty_layer": true }, { "created": "2019-01-01T02:29:14.182236016Z", "created_by": "/bin/sh -c apk -v add --no-cache bash" }, { "created": "2019-01-01T02:29:19.327280058Z", "created_by": "/bin/sh -c apk -v add --no-cache curl" }, { "created": "2019-01-01T02:29:19.549474383Z", "created_by": "/bin/sh -c #(nop) COPY file:fff66db7f2d773b25215edcc9d5697d84813835e3b731e5a6afe9a9b9647ecec in / " }, { "created": "2019-01-01T02:29:19.701494089Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\" \"/startService.sh\"]", "empty_layer": true } ], "os": "linux", "rootfs": { "diff_ids": [ "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8", "sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36", "sha256:9edc93f4dcf640f272ed73f933863dbefae6719745093d09c6c6908f402b1c34", "sha256:a6c8828ba4b58628284f783d3c918ac379ae2aba0830f4c926a330842361ffb6" ], "type": "layers" } }
主要注意一下幾個參數:
至此,解釋完了Image相關的目錄,總結一下,單個Image的配置信息在content目錄中,以image id爲文件名存儲,Image之間關聯信息在metadata中,以parent文件存儲。而後,咱們根據image生成容器的時候,但是生成了一個文件系統,可是上述這些信息並不包含fs的數據。由於真正的fs數據是存儲在Layer中的。如前面所述,Layer的信息存儲在layerdb目錄下,因此咱們轉戰layerdb目錄。
[root@docker-learn overlay2]# tree layerdb/ layerdb/ ├── mounts ├── sha256 │ ├── 0e88764cdf90e8a5d6597b2d8e65b8f70e7b62982b0aee934195b54600320d47 │ │ ├── cache-id │ │ ├── diff │ │ ├── parent │ │ ├── size │ │ └── tar-split.json.gz │ ├── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 │ │ ├── cache-id │ │ ├── diff │ │ ├── size │ │ └── tar-split.json.gz │ ├── 80fe1abae43103e3be54ac2813114d1dea6fc91454a3369104b8dd6e2b1363f5 │ │ ├── cache-id │ │ ├── diff │ │ ├── parent │ │ ├── size │ │ └── tar-split.json.gz │ └── db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 │ ├── cache-id │ ├── diff │ ├── parent │ ├── size │ └── tar-split.json.gz └── tmp
相比於只有alpine鏡像時,首先,layerdb目錄多了一個mounts目錄,簡單來講,當由鏡像生成容器時,該目錄下會生成容器的可讀可寫兩個layer,可讀即爲由鏡像生成,而可寫就是將來對容器的修改都會放在着了,由於本文只討論鏡像,這個目錄就再也不深刻分析。
其次,目前Layer已經增長至4個,這與咱們上一節中看到的test-image的rootfs配置中有4個layer diif id可以對應上,而後,很顯然除了第一層的"7bff100f35cb"能對應上,其餘三個徹底不一樣。進一步研究才知道這裏目錄名實際上是layer的chain id,而非diff id,關於這兩這個的區別,咱們能夠理解爲diff id用來描述單個變化,而chain id用來便於一些列的變化,diff id和chain id之間的計算公式能夠在image-spec中看到。
ChainID(A) = DiffID(A) ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B)) ChainID(A|B|C) = Digest(ChainID(A|B) + " " + DiffID(C))
在這裏以test-image的rootfs驗證分析他們是如何關聯的。
"rootfs": { "diff_ids": [ "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8", "sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36", "sha256:9edc93f4dcf640f272ed73f933863dbefae6719745093d09c6c6908f402b1c34", "sha256:a6c8828ba4b58628284f783d3c918ac379ae2aba0830f4c926a330842361ffb6" ], "type": "layers" }
ChainID(A) = DiffID(A) = sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ChainID(A|B) = Digest(ChainID(A) + " " + DiffID(B)) ChainID(A) = sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 DiffID(B) = sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36 計算: [root@docker-learn overlay2]# echo -n "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 sha256:b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36" | sha256sum - db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 - 結果: ChainID(A|B) = sha256:db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655 chainID(A|B|C) = sha256:0e88764cdf90e8a5d6597b2d8e65b8f70e7b62982b0aee934195b54600320d47 chainID(A|B|C|D) = sha256:80fe1abae43103e3be54ac2813114d1dea6fc91454a3369104b8dd6e2b1363f5
][3]
所以,test-image的第二層Layer對應的目錄爲:layerdb/sha256/db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655,查看該Layer的信息:
[root@docker-learn sha256]# ls db7c15c2f03f63a658285a55edc0a0012ccd0033f4695d4b428b1b464637e655/ cache-id diff parent size tar-split.json.gz
與上一節中相比多了parent,包含了上一層Layer的chain id。
[root@docker-learn overlay2]# tree distribution/ distribution/ ├── diffid-by-digest │ └── sha256 │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 └── v2metadata-by-diffid └── sha256 └── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 4 directories, 2 files
目前來看,destribution目錄和只有alpine鏡像時並無什麼區別,這是由於digest是由鏡像倉庫生成,本地構建的鏡像在沒有push到倉庫以前,天然也就沒有digest。使用docker push
命令將test-image推送至dockerhub。
[root@docker-learn distribution]# docker push backbp/test-image:lasted The push refers to repository [docker.io/backbp/test-image] a6c8828ba4b5: Pushed 9edc93f4dcf6: Pushed b1ddbff02257: Pushed 7bff100f35cb: Mounted from library/alpine lasted: digest: sha256:3dc66a43c28ea3e994e4abf6a2d04c7027a9330e8eeab5c609e4971a8c58f0b0 size: 1156
根據過程輸出,咱們能夠看到雖然test-image鏡像包括四層Layer,可是由於最底層的7bff100f35cb原本就是在docker pull alpine
時,拉取得來,天然也就不須要再push,所以真正push的只有三層。而如今destribution目錄也已經增長了對應Layer的digest,Image的digest能夠在上面的過程輸出中看到。
distribution/ ├── diffid-by-digest │ └── sha256 │ ├── 2826782ee82560ec5f90a8a9da80880d48dd4036763f5250024fab5b3ef8e8cf │ ├── 8e905c02e6908fbb0e591cea285470208920d32408735bd6a8fcaf85ffba9089 │ ├── a5bec9983f6902f4901b38735db9c427190ffcb3734c84ee233ea391da81081b │ └── cd784148e3483c2c86c50a48e535302ab0288bebd587accf40b714fffd0646b3 └── v2metadata-by-diffid └── sha256 ├── 7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8 ├── 9edc93f4dcf640f272ed73f933863dbefae6719745093d09c6c6908f402b1c34 ├── a6c8828ba4b58628284f783d3c918ac379ae2aba0830f4c926a330842361ffb6 └── b1ddbff022577cd249a074285a1a7eb76d7c9139132ba5aa4272fc115dfa9e36
docker tag
命令發生了什麼?咱們經過基於test-image生成一個新的tag,test-image-tag
[root@docker-learn overlay2]# docker tag test-image test-image-tag [root@docker-learn overlay2]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE test-image-tag latest 6cd0a66e83f1 4 hours ago 9.88MB test-image latest 6cd0a66e83f1 4 hours ago 9.88MB alpine latest 3f53bb00af94 11 days ago 4.41MB
查看repositories.json文件會發現兩個tag的鏡像都指向同一個image id,因此這個命令至關於只有修改了repositories.json。
[root@docker-learn overlay2]# cat repositories.json | python -m json.tool { "Repositories": { "alpine": { "alpine:latest": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991", "alpine@sha256:46e71df1e5191ab8b8034c5189e325258ec44ea739bba1e5645cff83c9048ff1": "sha256:3f53bb00af943dfdf815650be70c0fa7b426e56a66f5e3362b47a129d57d5991" }, "test-image": { "test-image:latest": "sha256:6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924" }, "test-image-tag": { "test-image-tag:latest": "sha256:6cd0a66e83f133a2bad37103ed03f6480330fa3c469368eb5871320996d3b924" } } }
上述的alpine鏡像和test-image鏡像大小分別是:4.41M和9.88M。爲了更準確的分析,可使用docker inspect 鏡像
查看詳細大小,分別爲4413428和9876099。
再次查看每層Layer的大小(layerdb/layer chain id/size),分別爲
alpine只有一層,因此image size與Layer size相等;test-image有四層,因此image size = sum(Layer1+Layer2+Layer3+Layer4)=9876099
完成上面的分析,如今再回頭看最初的問題,在apk del .build-deps這一層中,執行完以後生成的Layer只是記錄相對於上一層刪除了安裝包,在計算Image size時(或者說在根據Layer合併Image時),由於安裝.build-deps而帶來的變換依然存在於app add這一層Layer中,因此大小並不會減少。若是將add和del放在同一個命令內,那麼生成的該層Layer記錄的是相對於上一層的變化,.build-deps的安裝和卸載相對於上一層而言,根本就不存在,因此Layer中也就徹底不存在。
說到底,仍是OCI Image的原理所致,最須要記住的是每個Layer記錄的是與上一層Layer相比的變化。