Docker映象中有什麼?
你可能會熟悉Dockerfile,這是讓Docker為你構建映像的說明。這裡有一個簡單的例子。
FROM ubuntu:15.04 COPY app.py /app CMD python /app/app.py
每一行都是Docker關於如何構建映象的說明。它將ubuntu:15.04用作基礎,然後複製python指令碼。CMD指令是讓容器執行操作指令(將映像轉換為正在執行的程序)。
讓我們執行docker build .並檢查輸出。
$ docker build -t my_test_image . Sending build context to Docker daemon364.2MB Step 1/3 : FROM ubuntu:15.04 ---> d1b55fd07600 Step 2/3 : COPY app.py /app/ ---> 44ab3f1d4cd6 Step 3/3 : CMD python /app/app.py ---> Running in c037c981012e Removing intermediate container c037c981012e ---> 174b1e992617 Successfully built 174b1e992617 Successfully tagged my_test_image:latest
看看最後兩行,我們已經成功構建了一個Docker映象,我們可以通過識別符號174b1e992617來引用它(這個值是影象內容的SHA256雜湊摘要)。
我們有最了終的影象,但各個步驟的ID如d1b55fd07600和44ab3f1d4cd6是什麼意思?他們也是映象嗎?,是的。
想象一下,如果我們從Dockerfile中刪除第二步:COPY app.py /app Docker仍然會成功地將其構建為一個映象,因此,在映象構建過程的每一步,我們都有一個映象。
這告訴我們映象可以建立在彼此之上!當您認為Dockerfile中的FROM 指令的意思是:指定要在其上構建哪個映象,這是有道理的。
匯出映象並解壓縮
為了便於使用,可以將影象匯出到單個檔案中,使我們可以輕鬆檢視內部。
docker save my_test_image > my_test_image
匯出的檔案是..:
$ file my_test_image
my_test_image: POSIX tar archive
一個tarball!壓縮檔案或目錄。我們開啟它。
$ mkdir unpacked_image $ tar -xvf my_test_image -C unpacked_image x 174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/ x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/VERSION x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/json x 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/layer.tar x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/ x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/VERSION x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/json x 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/layer.tar x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/ x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/VERSION x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/json x 6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/layer.tar x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/ x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/VERSION x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/json x c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/layer.tar x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/ x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/VERSION x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/json x cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/layer.tar x manifest.json x repositories
我們將開始研究 manifest.json:
[ { <font>"Config"</font><font>: </font><font>"174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json"</font><font>, </font><font>"RepoTags"</font><font>: [ </font><font>"my_test_image:latest"</font><font> ], </font><font>"Layers"</font><font>: [ </font><font>"cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb/layer.tar"</font><font>, </font><font>"28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114/layer.tar"</font><font>, </font><font>"4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e/layer.tar"</font><font>, </font><font>"c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c/layer.tar"</font><font>, </font><font>"6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373/layer.tar"</font><font> ] } ] </font>
清單檔案是一段元資料,它準確描述了這個映象中的內容。我們可以看到映象有一個標籤my_test_image,它有一個叫做Layers的東西,另一個叫做Config。
配置JSON檔案的前12個字元與我們從docker build中看到的映象ID相同,巧合 - 我想不是!
$ cat 174b1e9926177b5dfd22981ddfab78629a9ce2f05412ccb1a4fa72f0db21197b.json
{ <font>"architecture"</font><font>: </font><font>"amd64"</font><font>, </font><font>"config"</font><font>: { </font><font>"Hostname"</font><font>: </font><font>"d2d404286fc4"</font><font>, </font><font>"Domainname"</font><font>: </font><font>""</font><font>, </font><font>"User"</font><font>: </font><font>""</font><font>, </font><font>"AttachStdin"</font><font>: false, </font><font>"AttachStdout"</font><font>: false, </font><font>"AttachStderr"</font><font>: false, </font><font>"Tty"</font><font>: false, </font><font>"OpenStdin"</font><font>: false, </font><font>"StdinOnce"</font><font>: false, </font><font>"Env"</font><font>: [ </font><font>"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"</font><font> ], </font><font>"Cmd"</font><font>: [ </font><font>"/bin/sh"</font><font>, </font><font>"-c"</font><font>, </font><font>"python /app/app.py"</font><font> ], </font><font>"ArgsEscaped"</font><font>: <b>true</b>, </font><font>"Image"</font><font>: </font><font>"sha256:44ab3f1d4cd69d84c9c67187b378b1d1322b5fddf4068c11e8b11856ced7efc0"</font><font>, </font><font>"Volumes"</font><font>: <b>null</b>, </font><font>"WorkingDir"</font><font>: </font><font>""</font><font>, </font><font>"Entrypoint"</font><font>: <b>null</b>, </font><font>"OnBuild"</font><font>: <b>null</b>, </font><font>"Labels"</font><font>: <b>null</b> }, </font><font>"container"</font><font>: </font><font>"c037c981012e8f03ac5466fcdda8f78a14fb9bb5ee517028c66915624a5616fa"</font><font>, </font><font>"container_config"</font><font>: { </font><font>"Hostname"</font><font>: </font><font>"d2d404286fc4"</font><font>, </font><font>"Domainname"</font><font>: </font><font>""</font><font>, </font><font>"User"</font><font>: </font><font>""</font><font>, </font><font>"AttachStdin"</font><font>: false, </font><font>"AttachStdout"</font><font>: false, </font><font>"AttachStderr"</font><font>: false, </font><font>"Tty"</font><font>: false, </font><font>"OpenStdin"</font><font>: false, </font><font>"StdinOnce"</font><font>: false, </font><font>"Env"</font><font>: [ </font><font>"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"</font><font> ], </font><font>"Cmd"</font><font>: [ </font><font>"/bin/sh"</font><font>, </font><font>"-c"</font><font>, </font><font>"#(nop) "</font><font>, </font><font>"CMD [\"/bin/sh\" \"-c\" \"python /app/app.py\"]"</font><font> ], </font><font>"ArgsEscaped"</font><font>: <b>true</b>, </font><font>"Image"</font><font>: </font><font>"sha256:44ab3f1d4cd69d84c9c67187b378b1d1322b5fddf4068c11e8b11856ced7efc0"</font><font>, </font><font>"Volumes"</font><font>: <b>null</b>, </font><font>"WorkingDir"</font><font>: </font><font>""</font><font>, </font><font>"Entrypoint"</font><font>: <b>null</b>, </font><font>"OnBuild"</font><font>: <b>null</b>, </font><font>"Labels"</font><font>: {} }, </font><font>"created"</font><font>: </font><font>"2018-11-01T03:19:16.8517953Z"</font><font>, </font><font>"docker_version"</font><font>: </font><font>"18.09.0-ce-beta1"</font><font>, </font><font>"history"</font><font>: [ { </font><font>"created"</font><font>: </font><font>"2016-01-26T17:48:17.324409116Z"</font><font>, </font><font>"created_by"</font><font>: </font><font>"/bin/sh -c #(nop) ADD file:3f4708cf445dc1b537b8e9f400cb02bef84660811ecdb7c98930f68fee876ec4 in /"</font><font> }, { </font><font>"created"</font><font>: </font><font>"2016-01-26T17:48:31.377192721Z"</font><font>, </font><font>"created_by"</font><font>: </font><font>"/bin/sh -c echo '#!/bin/sh' > /usr/sbin/policy-rc.d \t&& echo 'exit 101' >> /usr/sbin/policy-rc.d \t&& chmod +x /usr/sbin/policy-rc.d \t\t&& dpkg-divert --local --rename --add /sbin/initctl \t&& cp -a /usr/sbin/policy-rc.d /sbin/initctl \t&& sed -i 's/^exit.*/exit 0/' /sbin/initctl \t\t&& echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \t\t&& echo 'DPkg::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\" };' > /etc/apt/apt.conf.d/docker-clean \t&& echo 'APT::Update::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\" };' >> /etc/apt/apt.conf.d/docker-clean \t&& echo 'Dir::Cache::pkgcache \"\" Dir::Cache::srcpkgcache \"\"' >> /etc/apt/apt.conf.d/docker-clean \t\t&& echo 'Acquire::Languages \"none\"' > /etc/apt/apt.conf.d/docker-no-languages \t\t&& echo 'Acquire::GzipIndexes \"true\" Acquire::CompressionTypes::Order:: \"gz\"' > /etc/apt/apt.conf.d/docker-gzip-indexes"</font><font> }, { </font><font>"created"</font><font>: </font><font>"2016-01-26T17:48:33.59869621Z"</font><font>, </font><font>"created_by"</font><font>: </font><font>"/bin/sh -c sed -i 's/^#\\s*\\(deb.*universe\\)$/\\1/g' /etc/apt/sources.list"</font><font> }, { </font><font>"created"</font><font>: </font><font>"2016-01-26T17:48:34.465253028Z"</font><font>, </font><font>"created_by"</font><font>: </font><font>"/bin/sh -c #(nop) CMD [\"/bin/bash\"]"</font><font> }, { </font><font>"created"</font><font>: </font><font>"2018-11-01T03:19:16.4562755Z"</font><font>, </font><font>"created_by"</font><font>: </font><font>"/bin/sh -c #(nop) COPY file:8069dbb6bfc301562a8581e7bbe2b7675c2f96108903c0889d258cd1e11a12f6 in /app/ "</font><font> }, { </font><font>"created"</font><font>: </font><font>"2018-11-01T03:19:16.8517953Z"</font><font>, </font><font>"created_by"</font><font>: </font><font>"/bin/sh -c #(nop)CMD [\"/bin/sh\" \"-c\" \"python /app/app.py\"]"</font><font>, </font><font>"empty_layer"</font><font>: <b>true</b> } ], </font><font>"os"</font><font>: </font><font>"linux"</font><font>, </font><font>"rootfs"</font><font>: { </font><font>"type"</font><font>: </font><font>"layers"</font><font>, </font><font>"diff_ids"</font><font>: [ </font><font>"sha256:3cbe18655eb617bf6a146dbd75a63f33c191bf8c7761bd6a8d68d53549af334b"</font><font>, </font><font>"sha256:84cc3d400b0d610447fbdea63436bad60fb8361493a32db380bd5c5a79f92ef4"</font><font>, </font><font>"sha256:ed58a6b8d8d6a4e2ecb4da7d1bf17ae8006dac65917c6a050109ef0a5d7199e6"</font><font>, </font><font>"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"</font><font>, </font><font>"sha256:9720cebfd814895bf5dc4c1c55d54146719e2aaa06a458fece786bf590cea9d4"</font><font> ] } } </font>
這是一個非常大的JSON檔案,但通過它你可以看到有很多不同的元資料。特別是,有關於如何將此映象轉換為正在執行的容器的元資料 - 要執行的命令和要新增的環境變數。
映象就像洋蔥
它們都有層次。但是什麼代表一層?
$ ls cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb VERSIONjsonlayer.tar
這是另一個tarfile,讓我們開啟壓縮包看一看。
$ tree -L 1 . ├── bin ├── boot ├── dev ├── etc ├── home ├── lib ├── lib64 ├── media ├── mnt ├── opt ├── proc ├── root ├── run ├── sbin ├── srv ├── sys ├── tmp ├── usr └── <b>var</b>
這是Docker映象的大祕密,它由檔案系統組成不同的檢視!幾乎所有你在標準的Ubuntu檔案系統中看到的這裡面都有。
那麼每個圖層究竟包含什麼?那麼它將有助於知道哪些層來自基本映象,以及哪些層是由我們新增的。
使用我們之前做過的相同過程,但在ubuntu:15.04我可以看到這些層:
cac0b96b79417d5163fbd402369f74e3fe4ff8223b655e0b603a8b570bcc76eb 28441336175b9374d04ee75fdb974539e9b8cad8fec5bf0ff8cea6f8571d0114 4631663ba627c9724cd701eff98381cb500d2c09ec78a8c58213f3225877198e c4f8838502da6456ebfcb3f755f8600d79552d1e30beea0ccc62c13a2556da9c
都屬於ubuntu基礎映象,來自FROM ubuntu:15.04命令,知道這一點,我預測我們my_test_image映象的最頂層6c91b695f2ed98362f511f2490c16dae0dcf8119bcfe2fe9af50305e2173f373應該來自命令COPY app.py /app/。
$ tree . └── app └── app.py
確實,而且內部的所有內容都是我們對檔案系統所做的更改,它只是添加了app.py檔案。
工具
如果您希望將來分析映象,可以使用開源工具ofollow,noindex" target="_blank">Dive !
這怎麼變成一個正在執行的容器?
現在我們瞭解了Docker映象是什麼,Docker如何將其轉換為正在執行的容器?
每個容器都有自己的檔案系統檢視,Docker將獲取影象中的所有層,並將它們放在彼此的頂部,以呈現檔案系統的一個檢視。這種技術稱為Union Mounting ,Docker支援Linux上的幾個Union Mount Filesystems,主要是OverlayFS 和AUFS 。
但這並非全部,容器意味著短暫,容器執行時對檔案系統的更改不應在容器停止後儲存。一種方法是將整個映象複製到其他位置,這樣更改不會影響原始檔案。這不是非常有效,替代方案(和Docker所做的)是在容器中的檔案系統的最頂部新增一個瘦讀/寫層,進行更改。如果您需要對下面某個圖層中的檔案進行更改,則需要將該檔案複製到進行更改的頂層。這稱為Copy-On-Write 。當容器停止執行時,將丟棄最頂層的檔案系統層。
在檔案系統之後,除了配置一些後續步驟的元資料之外,映象不會用於其他許多其他操作。為了完整起見,要建立一個正在執行的容器,我們需要使用名稱空間 來控制程序可以看到的內容(檔案系統,程序,網路,使用者等); 使用Cgroups" rel="nofollow,noindex" target="_blank">cgroups 來控制程序可以使用的資源(記憶體,CPU,網路等); 和安全功能來控制程序可以執行的操作(功能,AppArmor,SELinux,Seccomp)。