Docker 入门(4)镜像与容器
1. 鏡像與容器
1.1 鏡像
Docker鏡像類似于未運行的exe應(yīng)用程序,或者停止運行的VM。當(dāng)使用docker run命令基于鏡像啟動容器時,容器應(yīng)用便能為外部提供服務(wù)。
鏡像實際上就是這個用來為容器進程提供隔離后執(zhí)行環(huán)境的文件系統(tǒng)。我們也稱之為根文件系統(tǒng)(Rootfs)。(注意,rootfs 只是一個操作系統(tǒng)所包含的文件、配置和目錄,并不包括操作系統(tǒng)內(nèi)核。同一臺機器上的所有容器,都共享宿主機操作系統(tǒng)的內(nèi)核。)
由于 rootfs 里封裝的不僅僅是應(yīng)用,還包括它運行所需要的所有依賴。這就賦予了容器的強一致性:無論在本地、云端,還是其他任何地方,用戶只需要解壓打包好的容器鏡像,這個應(yīng)用運行所需要的完整的執(zhí)行環(huán)境就被重現(xiàn)出來了。
我們可以將 Docker 鏡像理解為包含應(yīng)用程序以及其相關(guān)依賴的一個基礎(chǔ)文件系統(tǒng),在 Docker 容器啟動的過程中,它以只讀的方式被用于創(chuàng)建容器的運行環(huán)境。
1.1.1 鏡像分層
但還有一個不容忽視的問題,例如如果我需要一個在CentOS環(huán)境中跑的apache應(yīng)用,我可以將它打包成一個apache鏡像;如果我還需要一個在CentOS環(huán)境中跑的mysql應(yīng)用,我又將它打包成一個mysql鏡像……
這幾個鏡像中都有全部的CentOS的全部環(huán)境,將造成大量空間占用問題及碎片化問題。
Docker的解決方法是: 在鏡像的設(shè)計中,引入了層(layer)的概念。即: 用戶制作鏡像的每一步操作,都生成一個層,也就是一個增量 rootfs。
Docker 鏡像其實是由基于 UnionFS 文件系統(tǒng)的一組鏡像層依次掛載而得,而每個鏡像層包含的其實是對上一鏡像層的修改,這些修改其實是發(fā)生在容器運行的過程中的。所以,我們也可以反過來理解,鏡像是對容器運行環(huán)境進行持久化存儲的結(jié)果。
1.1.1 鏡像的實現(xiàn)
1.1.1.1 Docker 是如何構(gòu)建并且存儲鏡像的
Docker 中的每一個鏡像都是由一系列只讀的層組成的,Dockerfile 中的每一個命令都會在已有的只讀層上創(chuàng)建一個新的層,容器中的每一層都只對當(dāng)前容器進行了非常小的修改
當(dāng)鏡像被 docker run 命令創(chuàng)建時就會在鏡像的最上層添加一個可寫的層,也就是容器層,所有對于運行時容器的修改其實都是對這個容器讀寫層的修改。
上面的這張圖片非常好的展示了組裝的過程,每一個鏡像層都是建立在另一個鏡像層之上的,同時所有的鏡像層都是只讀的,只有每個容器最頂層的容器層才可以被用戶直接讀寫,所有的容器都建立在一些底層服務(wù)(Kernel)上,包括命名空間、控制組、rootfs 等等,這種容器的組裝方式提供了非常大的靈活性,只讀的鏡像層通過共享也能夠減少磁盤的占用。
1.1.1.2鏡像概述
所有的 Docker 鏡像都是按照 Docker 所設(shè)定的邏輯打包的,也是受到 Docker Engine 所控制的。
我們常見的虛擬機鏡像,通常是由熱心的提供者以他們自己熟悉的方式打包成鏡像文件,被我們從網(wǎng)上下載或是其他方式獲得后,恢復(fù)到虛擬機中的文件系統(tǒng)里的。而 Docker 的鏡像我們必須通過 Docker 來打包,也必須通過 Docker 下載或?qū)牒笫褂?#xff0c;不能單獨直接恢復(fù)成容器中的文件系統(tǒng)。
雖然這么做失去了很多靈活性,但固定的格式意味著我們可以很輕松的在不同的服務(wù)器間傳遞 Docker 鏡像,配合 Docker 自身對鏡像的管理功能,讓我們在不同的機器中傳遞和共享 Docker 變得非常方便。
對于每一個記錄文件系統(tǒng)修改的鏡像層來說,Docker 都會根據(jù)它們的信息生成了一個 Hash 碼,這是一個 64 長度的字符串,足以保證全球唯一性。
由于鏡像層都有唯一的編碼,我們就能夠區(qū)分不同的鏡像層并能保證它們的內(nèi)容與編碼是一致的,這帶來了另一項好處,就是允許我們在鏡像之間共享鏡像層。
舉一個實際的例子,由 Docker 官方提供的兩個鏡像 elasticsearch 鏡像和 jenkins 鏡像都是在 openjdk 鏡像之上修改而得,那么在我們實際使用的時候,這兩個鏡像是可以共用 openjdk 鏡像內(nèi)部的鏡像層的。
1.1.2 查看鏡像
如果要查看當(dāng)前連接的 docker daemon 中存放和管理了哪些鏡像,我們可以使用 docker images 這個命令 ( Linux、macOS 還是 Windows 上都是一致的 )。
在 docker images 命令的結(jié)果中,我們可以看到鏡像的 ID ( IMAGE ID)、構(gòu)建時間 ( CREATED )、占用空間 ( SIZE ) 等數(shù)據(jù)。
1.1.3 鏡像命名
鏡像層的 ID 既可以識別每個鏡像層,也可以用來直接識別鏡像 ( 因為根據(jù)最上層鏡像能夠找出所有依賴的下層鏡像,所以最上層進行的鏡像層 ID 就能表示鏡像的 ID ),但是使用這種無意義的超長哈希碼顯然是違背人性的,通過鏡像名我們能夠更容易的識別鏡像。
準(zhǔn)確的來說,鏡像的命名我們可以分成三個部分:username、repository 和 tag。
- username: 主要用于識別上傳鏡像的不同用戶,與 GitHub 中的用戶空間類似。
對于 username 來說,在上面我們展示的 docker images 結(jié)果中,有的鏡像有 username 這個部分,而有的鏡像是沒有的。沒有 username 這個部分的鏡像,表示鏡像是由 Docker 官方所維護和提供的,所以就不單獨標(biāo)記用戶了。
-
repository:主要用于識別進行的內(nèi)容,形成對鏡像的表意描述。
Docker 中鏡像的 repository 部分通常采用的是軟件名。我們推崇一個容器運行一個程序的做法,那么自然容器的鏡像也會僅包含程序以及與它運行有關(guān)的一些依賴包,所以我們使用程序的名字直接套用在鏡像之上,既祛除了鏡像取名的麻煩,又能直接表達鏡像中的內(nèi)容。 -
tag:主要用戶表示鏡像的版本,方便區(qū)分進行內(nèi)容的不同細節(jié)
在鏡像命名中,還有一個非常重要的部分,也就是鏡像的標(biāo)簽 ( tag )。鏡像的標(biāo)簽是對同一種鏡像進行更細層次區(qū)分的方法,也是最終識別鏡像的關(guān)鍵部分。
通常來說,鏡像的標(biāo)簽主要是為了區(qū)分同類鏡像不同構(gòu)建過程所產(chǎn)生的不同結(jié)果的。由于時間、空間等因素的不同,Docker 每次構(gòu)建鏡像的內(nèi)容也就有所不同,具體體現(xiàn)就是鏡像層以及它們的 ID 都會產(chǎn)生變化。而標(biāo)簽就是在鏡像命名這個層面上區(qū)分這些鏡像的方法。
與鏡像的 repository 類似,鏡像 tag 的命名方法也通常參考鏡像所關(guān)聯(lián)的應(yīng)用程序。更確切的來說,我們通常會采用鏡像內(nèi)應(yīng)用程序的版本號以及一些環(huán)境、構(gòu)建方式等信息來作為鏡像的 tag。
1.2 容器
容器就是將軟件打包成標(biāo)準(zhǔn)化單元,以用于開發(fā)、交付和部署。
- 容器鏡像是輕量的、可執(zhí)行的獨立軟件包 ,包含軟件運行所需的所有內(nèi)容:代碼、運行時環(huán)境、系統(tǒng)工具、系統(tǒng)庫和設(shè)置。
- 容器化軟件適用于基于Linux和Windows的應(yīng)用,在任何環(huán)境中都能夠始終如一地運行。
- 容器賦予了軟件獨立性,使其免受外在環(huán)境差異(例如,開發(fā)和預(yù)演環(huán)境的差異)的影響,從而有助于減少團隊間在相同基礎(chǔ)設(shè)施上運行不同軟件時的沖突。
1.2.1 容器的生命周期
由于 Docker 攬下了大部分對容器管理的活,只提供給我們非常簡單的操作接口,這就意味著 Docker 里對容器的一些運行細節(jié)會被更加嚴格的定義,這其中就包括了容器的生命周期。
這里有一張容器運行的狀態(tài)流轉(zhuǎn)圖:
1.2.2 主進程
當(dāng)我們啟動容器時,Docker 其實會按照鏡像中的定義,啟動對應(yīng)的程序,并將這個程序的主進程作為容器的主進程 ( 也就是 PID 為 1 的進程 )。而當(dāng)我們控制容器停止時,Docker 會向主進程發(fā)送結(jié)束信號,通知程序退出。
而當(dāng)容器中的主進程主動關(guān)閉時 ( 正常結(jié)束或出錯停止 ),也會讓容器隨之停止。
1.2.3 寫時復(fù)制
Docker 的寫時復(fù)制與編程中的相類似,也就是在通過鏡像運行容器時,并不是馬上就把鏡像里的所有內(nèi)容拷貝到容器所運行的沙盒文件系統(tǒng)中,而是利用 UnionFS 將鏡像以只讀的方式掛載到沙盒文件系統(tǒng)中。只有在容器中發(fā)生對文件的修改時,修改才會體現(xiàn)到沙盒環(huán)境上。
也就是說,容器在創(chuàng)建和啟動的過程中,不需要進行任何的文件系統(tǒng)復(fù)制操作,也不需要為容器單獨開辟大量的硬盤空間,與其他虛擬化方式對這個過程的操作進行對比,Docker 啟動的速度可見一斑。
Docker的容器是一個多層的結(jié)構(gòu)。如果對鏡像做history操作,我們可以看見他里面每一次dockerfile的命令都會創(chuàng)建一個新的層次。
[root@ip-172-16-1-4 ec2-user]# docker image history nginx IMAGE CREATED CREATED BY SIZE COMMENT 8cf1bfb43ff5 6 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B <missing> 6 days ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B <missing> 6 days ago /bin/sh -c #(nop) EXPOSE 80 0B <missing> 6 days ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B <missing> 6 days ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB <missing> 6 days ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1… 1.96kB <missing> 6 days ago /bin/sh -c #(nop) COPY file:e7e183879c35719c… 1.2kB <missing> 6 days ago /bin/sh -c set -x && addgroup --system -… 63.3MB <missing> 6 days ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B <missing> 6 days ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.2 0B <missing> 6 days ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.1 0B <missing> 6 days ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B <missing> 6 days ago /bin/sh -c #(nop) CMD ["bash"] 0B <missing> 6 days ago /bin/sh -c #(nop) ADD file:6ccb3bbcc69b0d44c… 69.2MBDocker里面有一個重要的概念叫做 Storage driver,他可以幫助我們實現(xiàn)對容器的分層和讀寫。目前,docker的默認storage driver 是overlay2。所有的容器相關(guān)的文件都保存在/var/lib/docker這個目錄下。我們可以看見在overlay2里面有很多不同的文件
[root@ip-172-16-1-4 ec2-user]# ls /var/lib/docker/overlay2/ 12597d435b78d470bed7cf3a4cc7d60691432e74f12c00fd44def7ecf6ab659f 6945f4530a5212bc3a8aa598dc88839d39b763799ccdfbd18a5f04180e3b676e d1f1bcc388595272f11f4ace02f9e6976dc96fd80cce8216d3626484ba114aad 145e76991ae57691ed94de5dd2ea950ff7b50dc26729605e711cd0f35f275e84 7c4d732c22b84e0df8c0d317df825623958d4eff59055827e6b2c76cbe8b0350 d1f1bcc388595272f11f4ace02f9e6976dc96fd80cce8216d3626484ba114aad-init 16da856b3acb71eea396f529a80cf728d664a4bf3eb042f828cb9651d82e90bf 9b6749a9a76ad9f76d7795ebbf8e47595dada7a06b9126f5d9c6e1084f1d0c02 f089b3fd763c560e1c398c9b432100cea56ad4dae3a6b760de42b45e856ce693 16da856b3acb71eea396f529a80cf728d664a4bf3eb042f828cb9651d82e90bf-init a60b187ea615a59402ad2fe6687a4dc87f1c037eafea6880167795c6e1b7f900 f089b3fd763c560e1c398c9b432100cea56ad4dae3a6b760de42b45e856ce693-init 658b1eab364b4523a8c7274e9b8a2bdc55095e9bd56dcb697f6456b4064abe66 a60b187ea615a59402ad2fe6687a4dc87f1c037eafea6880167795c6e1b7f900-init l 658b1eab364b4523a8c7274e9b8a2bdc55095e9bd56dcb697f6456b4064abe66-init backingFsBlockDev一個典型的場景如下所示,一個鏡像文件里面,里面分了多層,最下面的是基礎(chǔ)鏡像,這個基礎(chǔ)鏡像不包括內(nèi)核文件,執(zhí)行的時候他會直接調(diào)用宿主機的內(nèi)核,因此他的空間并不大。在基礎(chǔ)鏡像上面,又分了很多層,每一層代表在dockerfile里面執(zhí)行的一行命令。這整個鏡像文件都是只讀的。每個容器通過鏡像創(chuàng)建自己的容器層,而容器層是可以讀寫的,修改的內(nèi)容他們會保存在自己的目錄下面。因此每個容器對自己的修改 不會影響到其他容器。
在docker里面,我們通過storage driver來進行所謂的copy on write ( 寫時復(fù)制)的操作。storage driver有很多種,目前默認的是overlay2
overlay的基本工作原理如下
我們通過鏡像創(chuàng)建的容器包括了三層。最下面的是一個只讀的鏡像層,第二層是容器層,在他上面最上面的容器掛載層。最上層顯示的是我們在容器上直接看見的內(nèi)容,他通過UnionFS,或者說類似軟連接的方式,文件的路徑指向了容器層或者是鏡像層。當(dāng)我們嘗試讀取,修改或者創(chuàng)建一個新的文件時,我們總是從上到下進行搜索,如果在容器層找到了,那么就直接打開;如果容器層沒有,那就從鏡像層打開。如果是一個新建的文檔,那么就從鏡像層拷貝到容器層,再打開操作。
參考資料
《開發(fā)者必備的 Docker 實踐指南》
總結(jié)
以上是生活随笔為你收集整理的Docker 入门(4)镜像与容器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker 入门(3)Docke的安装
- 下一篇: leetcode 1011. 在 D 天