Docker生命周期
1、Docker三階段
1)Dockerfile
本質(zhì)
Dockerfile是用于構(gòu)建Docker鏡像的腳本文件,其中存放了鏡像構(gòu)建的一系列有先后順序的步驟(指令)。
內(nèi)容
如何構(gòu)建鏡像的一系列步驟:
FROM:基于哪個(gè)基礎(chǔ)鏡像
RUN:安裝哪些依賴
COPY:復(fù)制哪些代碼
CMD:啟動命令
大小
Dockerfile文本文件由于只是指令的文本描述(純文本文件),不包含任何實(shí)際的依賴和文件內(nèi)容,大小只有KB級別。可以類比為C語言的源代碼文件(.cpp,大小幾KB)。
生命周期
由用戶手動創(chuàng)建,獨(dú)立于Docker引擎存在,不占用容器空間,僅作為普通文本文件存在于宿主機(jī)
作用
作為docker build的輸入,定義鏡像的構(gòu)建規(guī)則。
2)Docker build
本質(zhì)
Dockerfile還要經(jīng)過Build(構(gòu)建),構(gòu)建出一個(gè)“可運(yùn)行文件系統(tǒng)集合”,這些文件被永久固化在鏡像的只讀層中,是容器運(yùn)行的“基礎(chǔ)文件集”。
Docker會按照Dockerfile中的指令(如FROM、COPY、RUN)逐步執(zhí)行,每一步生成一個(gè)鏡像層Layer,最終疊加生成一個(gè)完整的鏡像。
內(nèi)容
①包含了基礎(chǔ)鏡像的
編譯產(chǎn)物(如RUN gcc app.c -o app生成的可執(zhí)行文件)
通過COPY/ADD復(fù)制的代碼(如index.html)
RUN安裝的依賴(如Nginx二進(jìn)制文件)等,
②記錄鏡像ID、標(biāo)簽、構(gòu)建歷史、默認(rèn)啟動命令(CMD/ENTRYPOINT)、工作目錄(WORKDIR)等信息。
大小
體積一般為MB~GB級別(取決于應(yīng)用類型)。可以類比cpp文件編譯后產(chǎn)生的.obj文件(.obj,大小KB~MB級別)。
生命周期
鏡像通過build構(gòu)建完成后,其只讀層、元數(shù)據(jù)就不會被修改(除非重新構(gòu)建),可被多次用于創(chuàng)建容器(docker run)。鏡像中的文件是“靜態(tài)的”,不隨容器運(yùn)行而變化(容器本身運(yùn)行過程中對于文件的修改,僅會被記錄在容器自身的可寫層,不影響鏡像,因此重啟后這些修改就會消失,恢復(fù)最初的狀態(tài))。
build構(gòu)建完成后,除非通過docker rmi手動刪除,否則生成的文件系統(tǒng)會持久存在于宿主機(jī)中(存儲在Docker數(shù)據(jù)目錄中,如/var/lib/docker/)。
特點(diǎn)
靜態(tài)只讀,無法修改。除非通過修改dockerfile再build之后重新構(gòu)建,將其作為模板后可重新創(chuàng)建出初始狀態(tài)一模一樣的容器。
用途
作為docker run的輸入,提供容器運(yùn)行所需的基礎(chǔ)文件、配置。
3)docker run
docker run基于鏡像(即2中build后產(chǎn)生的文件系統(tǒng)集合+鏡像屬性(如ID、標(biāo)簽等))創(chuàng)建并啟動容器,(如果說build是編譯,那么run就是運(yùn)行可執(zhí)行程序,真正生成進(jìn)程)容器運(yùn)行中會產(chǎn)生如下文件:
①容器的可寫層文件
容器啟動時(shí),Docker會在鏡像只讀層上添加一個(gè)可寫層,容器內(nèi)的文件修改(如新建文件、修改配置)都會被記錄到這里(不影響鏡像本身,所以容器重啟后一切做的臨時(shí)修改都會被抹除);
②掛載卷(Volumes)中的文件
若通過了docker run -v掛載了宿主機(jī)目錄、命名卷,容器內(nèi)對于掛載路徑上的文件操作(如寫入日志、數(shù)據(jù)庫文件)會直接存儲在宿主機(jī),實(shí)現(xiàn)持久化修改。
③容器元數(shù)據(jù)
記錄容器ID、名稱、端口映射、資源限制等信息,用于Docker引擎管理容器生命周期。
生命周期
與容器綁定,容器刪除、重啟后,可寫層的文件會被清理,但是掛載卷中的文件會保留在宿主機(jī)上。
2、鏡像、容器
鏡像的本質(zhì)是docker build之后生成的文件系統(tǒng)集合+鏡像屬性元數(shù)據(jù)構(gòu)成的一個(gè)“只讀模板”;
容器的本質(zhì)是docker run鏡像啟動后生成的可運(yùn)行實(shí)例(進(jìn)程)。
3、關(guān)于docker build生成的鏡像
1)文件系統(tǒng)
docker build生成的只讀文件系統(tǒng)層(存儲在/var/lib/docker/overlay2等目錄,如果是K8S會是其他的目錄,如/var/lib/containerd),這些層疊加起來形成一個(gè)完整的可運(yùn)行的文件系統(tǒng):
a)包含了OS核心文件(如/bin/lib)、應(yīng)用依賴(如Nginx二進(jìn)制文件)、代碼(如app.py)等運(yùn)行所需的靜態(tài)文件;
b)這個(gè)文件系統(tǒng)時(shí)docker run啟動容器的“基礎(chǔ)模板”,容器啟動時(shí)會直接掛載這些只讀層,并在其上添加一個(gè)可寫層(用于運(yùn)行時(shí)修改)。
2)鏡像屬性元數(shù)據(jù)
docker build除了生成文件系統(tǒng)外,還會生成元數(shù)據(jù)(如鏡像ID、CMD/ENTRYPOINT啟動命令、WORKDIR工作目錄),這些數(shù)據(jù)告訴docker run如何激活文件系統(tǒng)。
這個(gè)元數(shù)據(jù)位于/var/lib/docker/image/overlay2/imagedb/content/sha256/目錄下,是個(gè)以該鏡像的SHA256值為名的文件,每次build之后都會在該目錄下新生成一個(gè)文件。
docker run正是基于以上包含了完整文件系統(tǒng)和運(yùn)行規(guī)則(元數(shù)據(jù))的鏡像,創(chuàng)建出可讀寫的容器實(shí)例——本質(zhì)上是對鏡像文件系統(tǒng)的動態(tài)使用(復(fù)用只讀層+隔離可寫層)。
可運(yùn)行文件系統(tǒng)集合
上述1、2中的分層文件系統(tǒng)、元數(shù)據(jù)共同構(gòu)成了一個(gè)可運(yùn)行的文件系統(tǒng)集合,這就是Docker鏡像的本質(zhì)。
這個(gè)集合不僅包含了應(yīng)用運(yùn)行的所需的所有文件(如代碼、依賴、二進(jìn)制程序),還包含了讓Docker引擎能夠正確啟動、管理容器的元數(shù)據(jù),是容器運(yùn)行的“基礎(chǔ)模板”。
完整性
1中所說的分層文件系統(tǒng),包含了最小化可運(yùn)行環(huán)境的全部文件,在overlay2目錄下的diff/文件中,包含了從基礎(chǔ)鏡像到應(yīng)用代碼的完整文件集:
- 基礎(chǔ)層:OS核心文件(如/bin、/lib下的系統(tǒng)庫);
- 中間層:通過RUN安裝的依賴(如Nginx、Python解釋器);
- 頂層:通過COPY/ADD復(fù)制的應(yīng)用代碼(如app.py、配置文件)
這些文件(層)組合起來,形成了一個(gè)類似精簡操作系統(tǒng)的文件系統(tǒng),足以支撐應(yīng)用運(yùn)行。
可操作性
2中所說的元數(shù)據(jù)(config.json),定義了如何“激活”這個(gè)文件系統(tǒng):
- 啟動命令(CMD、ENTRYPOINT):告訴Docker啟動容器時(shí)需要執(zhí)行哪個(gè)程序(如nginx -g )
- 工作目錄(WORKDIR):定義程序運(yùn)行的默認(rèn)路徑;
- 環(huán)境變量(ENV):提供程序運(yùn)行所需的配置參數(shù)
這些元數(shù)據(jù)讓靜態(tài)的文件系統(tǒng)集合能夠被docker引擎激活為動態(tài)的運(yùn)行環(huán)境。
隔離性
- 多個(gè)容器共享同一個(gè)鏡像的只讀層;
- 每個(gè)容器對文件系統(tǒng)的修改僅保存在自身的可寫層(該可寫層在容器啟動時(shí)會為每個(gè)容器分一個(gè),互不影響),不影響鏡像和其他容器。
3)鏡像標(biāo)簽
build時(shí)通常會用-t參數(shù)給鏡像打上標(biāo)簽,例如docker build -t app:latest .,這里就給最新構(gòu)建的鏡像打上了標(biāo)簽app:latest。
這個(gè)標(biāo)簽存在于文件repositories.json中,位置為:
/var/lib/docker/image/overlay2/repositories.json
打開repositories.json后,可以看到類似以下的結(jié)構(gòu)
{ "Repositories": { "app": { // 鏡像倉庫名(如 "app") "latest": "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b" // 標(biāo)簽 "latest" 對應(yīng)的鏡像 ID }, "ubuntu": { // 基礎(chǔ)鏡像倉庫 "22.04": "sha256:f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5" } } }
該文件在Docker中,全局唯一,且每次build都會修改其值(如果是新標(biāo)簽,就在其中新增一個(gè)值),因此就算你習(xí)慣不好,連續(xù)build了很多次,打的標(biāo)簽都是app:latest;Docker也會正確識別出該鏡像是哪個(gè),因?yàn)榫退銟?biāo)簽一樣,但是repositories.json中這個(gè)標(biāo)簽對應(yīng)的鏡像的SHA-256的值都是不一樣的(除非你沒改dockerfile,重復(fù)build,此時(shí)鏡像本身沒變化,它的SHA-256值也不會變化)。
repositories.json
該文件的核心作用是記錄當(dāng)前Docker環(huán)境中所有鏡像倉庫的標(biāo)簽(tag)和鏡像ID的映射關(guān)系(如nginx:latest、my-app:v1.0等),它有如下特性:
- 對于單Docker守護(hù)進(jìn)程管理的環(huán)境,該文件是全局唯一的;
- 無論有多少個(gè)鏡像、多少個(gè)倉庫(如nginx、ubuntu、自定義應(yīng)用倉庫),所有標(biāo)簽的映射關(guān)系都會集中存儲在該文件中(而非按照倉庫拆分為多個(gè));
- 每次執(zhí)行docker tag、docker rmi、docker build -t等操作,Docker都會直接修改這一個(gè)文件,確保所有標(biāo)簽映射的一致性、唯一性。
4)關(guān)于路徑
在docker學(xué)習(xí)的過程中,經(jīng)常會看到如下路徑(或類似路徑):
/var/lib/docker/image/overlay2/repositories.json
解析:
- /var/lib/docker:Docker默認(rèn)的數(shù)據(jù)根目錄(可通過docker info | grep "Docker Root Dir"確認(rèn)實(shí)際路徑)
- image/overlay2:overlay2存儲驅(qū)動的元數(shù)據(jù)目錄(若使用其他驅(qū)動,如devicemapper,則路徑會變?yōu)閕mage/devicemapper
WORKDIR
容器內(nèi)部的工作目錄
WORKDIR指令可以用來設(shè)置容器內(nèi)部的默認(rèn)工作目錄,不涉及宿主機(jī)的文件系統(tǒng)。
若Dockerfile中包含了WORKDIR /u01/app/rules,那么相當(dāng)于把后續(xù)容器中的工作目錄都置為了/u01/app/rules,如果用了指令ls,則會列出/u01/app/rules中的文件信息。
實(shí)際位置
容器內(nèi)的/u01/app/rules目錄,物理上存儲在宿主機(jī)的Docker數(shù)據(jù)目錄的對應(yīng)層中:
/var/lib/docker/overlay2/<層ID>/diff/u01/app/rules。
不過這是Docker聯(lián)合文件系統(tǒng)的內(nèi)部映射,對于容器內(nèi)的進(jìn)程而言,它看到的就是邏輯上的/u01/app/rules路徑,完全感知不到宿主機(jī)內(nèi)的/var/lib/docker。
4、文件系統(tǒng)集合中的layer
之前一直提到層這個(gè)概念,即layer。Dockerfile中每多一行可生成層的指令(如RUN、COPY、ADD等),就會生成一個(gè)新層,這些層在真實(shí)的宿主機(jī)中,確實(shí)是一個(gè)個(gè)獨(dú)立的目錄,且通過每層的元數(shù)據(jù)info.json,找出該層的父層(即上一條指令生成的層)。層與層的核心就體現(xiàn)在存儲的文件內(nèi)容和層間關(guān)聯(lián)關(guān)系上,具體如下:
1)層在真實(shí)文件系統(tǒng)中的物理形態(tài)
在主流的overlay2存儲驅(qū)動下,每層都對應(yīng)宿主機(jī)數(shù)據(jù)目錄中的一個(gè)獨(dú)立子目錄。
以containerd為例,路徑為/var/lib/containerd/overlayfs/snapshots/,之后dockerfile建立而成的一層層,就在這個(gè)snapshots目錄下,作為它的子目錄:
/snapshots/ ├── 1/ # 層1(基礎(chǔ)層,如 FROM 指令對應(yīng)的基礎(chǔ)鏡像層) │ ├── diff/ # 該層新增/修改的文件(真實(shí)目錄結(jié)構(gòu)) │ │ ├── bin/ │ │ ├── etc/ │ │ └── ... │ └── info.json # 層元數(shù)據(jù)(記錄父層ID、創(chuàng)建時(shí)間等) ├── 2/ # 層2(基于層1的新層,如 RUN 指令生成) │ ├── diff/ # 該層新增的文件(如安裝的依賴) │ │ ├── usr/ │ │ └── ... │ └── info.json # 元數(shù)據(jù)中記錄父層為1 └── 3/ # 層3(基于層2的新層,如 COPY 指令生成) ├── diff/ # 該層新增的應(yīng)用代碼 │ ├── app/ │ └── ... └── info.json # 元數(shù)據(jù)中記錄父層為2
從上表可以看出,不同指令構(gòu)成的上下層之間,并不是通過嵌套子目錄的方式形成結(jié)構(gòu)的,而是作為同一級目錄中的子目錄。目錄間的關(guān)系,通過各層的info.json來關(guān)聯(lián),這個(gè)元數(shù)據(jù)中記錄了該層的父層(本質(zhì)上是生成該層的指令的上一條指令所形成的層)。
雖然物理上是并列目錄,但是Docker/containerd會通過聯(lián)合文件系統(tǒng)(如overlay2),根據(jù)info.json中記錄的依賴關(guān)系,將這些并列的層邏輯上堆疊起來,形成一個(gè)統(tǒng)一的文件系統(tǒng)視圖:
- 先生成的層的內(nèi)容會被后生成的層繼承,如有同名文件,則后生成的層會覆蓋掉上一層;
- 容器中看到的 /(即根目錄),就是這些并列層被合并后的結(jié)果,在容器使用過程中完全感知不到物理上的并列存儲。
注意:以上1、2、3只是舉例,實(shí)際中名稱本身并不代表順序,僅做唯一標(biāo)識。
2)層與層之間的核心區(qū)別
①diff/目錄的不同
注意:diff/和diff(是否帶斜杠)本質(zhì)上是同一個(gè)目錄,斜杠僅用于明確表示“這是一個(gè)目錄路徑”,而不改變目錄本身的身份和內(nèi)容。
每層的diff/目錄僅包含該層新增、修改的文件/目錄,這是層與層之間最直觀的區(qū)別:
例如:
RUN apt install nginx:該層的diff/會包含usr/sbin/nginx、etc/nginx/等nginx相關(guān)文件;
COPY app.py /app/:該層的diff/僅包含app/app.py文件,不會包含前一層的nginx文件。
這種“只記錄差異”的特性,使層與層之間的內(nèi)容天然隔離,避免重復(fù)存儲。
②元數(shù)據(jù)文件info.json記錄的依賴關(guān)系不同
每層都有一個(gè)元數(shù)據(jù)文件info.json,其中會記錄父層ID,形成清晰的依賴鏈:
- 基礎(chǔ)層(FROM層)沒有父層;
- 后續(xù)每層都以上一層為父層,例如層2的父層為1,層3的父層為2(如果多次build,且沒有清除之前的層,可能出現(xiàn)層4的父層為2這種情況)。
注:層1的parent為空。
③對最終文件系統(tǒng)的貢獻(xiàn)不同
若多層通過聯(lián)合掛載合并,則后生成的層會覆蓋前一層的同名文件:
- 若層2的diff/etc/nginx.conf與層1的diff/etc/nginx.conf同名,則合并后顯示層2的文件;
- 若層3刪除了層2的某個(gè)文件(通過RUN rm),則層3的diff/目錄中會生成一個(gè)“刪除標(biāo)記”,合并后該文件會被隱藏。
3)總結(jié)
層是獨(dú)立目錄 + 差異內(nèi)容 + 依賴關(guān)系的組合:
獨(dú)立目錄:每多一層就會在diff下多一個(gè)子目錄,代表該層,不同層在物理結(jié)構(gòu)上平級,不存在嵌套關(guān)系。
差異內(nèi)容:diff目錄中僅記錄該層的差異文件和元數(shù)據(jù)文件
依賴關(guān)系:層通過info.json中記錄的父層ID形成依賴鏈,最終被合并為一個(gè)完整的文件系統(tǒng)。
4)疑問
①我首次build后,形成了1、2、3三層,如果我對第三層進(jìn)行了修改,是會生成一個(gè)3.1層嗎?
修改了第三層對應(yīng)的dockerfile指令,如將COPY app.pu /app/改為COPY app_v2.py /app/,重新build后,會:
- Docker發(fā)現(xiàn)層3指令被修改,因此不會復(fù)用之前的層3;
- 基于層2新建一個(gè)層4(層4的父層為層2),層4的diff目錄包含了修改后的文件app_v2.py;
- 新鏡像的依賴鏈變?yōu)椋?→2→4
對于原來的層3:
- 層3仍會保留(除非手動清理),仍以獨(dú)立目錄形式存在于宿主機(jī)的存儲路徑中;
- 但是新鏡像不會和層3再產(chǎn)生關(guān)聯(lián)了。
這涉及到我們之前說的:Docker鏡像的層一旦創(chuàng)建就是只讀的,任何修改都必須通過創(chuàng)建新層來實(shí)現(xiàn)。
②雖然可以通過每一層的info.json來上溯它的父層,但是Docker是如何從全局層面知道哪個(gè)是最終層呢?如果不知道又該怎么知道如何從1正向構(gòu)建到最后一層呢?
先看之前說過的——每個(gè)層建立完畢后,都會在該層的元數(shù)據(jù)文件中(如info.json或類似結(jié)構(gòu)),通過parent(父層ID)字段記錄該層是基于哪個(gè)層構(gòu)建的:
- 層1的parent為空;
- 層2的parent為1;
- 層3的parent為2;
- 當(dāng)層3被修改并生成層4后,層4的parent仍為2。
當(dāng)鏡像構(gòu)建完成后,會生成一份鏡像配置文件config.json,其中就有一個(gè)關(guān)鍵配置rootfs.layers,它記錄了該鏡像的所有層的哈希號,使Docker可以從頭到尾找到正確的鏡像。
因此Docker并非依靠之前說的info.json來找到全部層的。info.json是層級別的元數(shù)據(jù)文件,而config.json是鏡像的配置文件,它倆的區(qū)別在于:
-
位置
-
- info.json位于/var/lib/docker/overlay2/<層ID>目錄下
- config.json為/var/lib/docker/image/overlay2/imagedb/content/sha256/<鏡像ID>下(標(biāo)紅的位置即為二者路徑分道揚(yáng)鑣之處)。
-
內(nèi)容
- info.json記錄的是該層的信息:id、parent、created(創(chuàng)建時(shí)間)、overlay(層類型);
- config.json記錄鏡像的全局配置:
- rootfs.layers:數(shù)組類型,按順序列出該鏡像所有層的SHA-256哈希(就是這個(gè)配置項(xiàng),使得Docker可以從頭到尾找到關(guān)聯(lián)的所有層)
- 容器的啟動配置(config,如CMD、WORKDIR、ENV);
- 鏡像創(chuàng)建時(shí)間(created)
- parent:該鏡像的基礎(chǔ)鏡像ID。
-
例子
假設(shè)一個(gè)鏡像由層1、2、3構(gòu)成,那么在各層info.json中,就會出現(xiàn)如下寫法:
-
- 層1的info.json,會有parent:""(空,代表無父層)
- 層2的info.json,會有parent:"層1的ID;
- 層3的info.json,會有parent:"層2的ID"
而在鏡像的config.json中,則會有:
"rootfs":{ "type":"layers", "layers":[ "層1的sha256值", "層2的sha256值", "層3的sha256值" ] }
查找與驗(yàn)證
rootfs.layers定義鏡像的層順序,Docker只需按照該數(shù)組順序讀取層,并根據(jù)層的info.json中的parent驗(yàn)證層之間的依賴關(guān)系,共同保證鏡像文件系統(tǒng)的正確構(gòu)建。
實(shí)際過程為:
- Docker讀取config.json中的rootfs.layers數(shù)組,得到層ID的有序列表;
- 逐個(gè)讀取每層的info.json,并驗(yàn)證parent字段是否與前一層的ID匹配:
- 層2的parent必須為1的ID;
- 層3的parent必須為2的ID。
- 若所有層的parent都驗(yàn)證通過,則按照rootfs.layers的順序堆疊這些層,形成完整的文件系統(tǒng)。
③build重新構(gòu)建鏡像,會對鏡像所在目錄、鏡像各層所在的目錄有啥影響?
首先明確一個(gè)前提:
鏡像和鏡像的各層是保存在不同的目錄下的:
- info.json位于/var/lib/docker/overlay2/<層ID>目錄下
- config.json為/var/lib/docker/image/overlay2/imagedb/content/sha256/<鏡像ID>下(標(biāo)紅的位置即為二者路徑分道揚(yáng)鑣之處)。
這倆文件分別代表層元數(shù)據(jù)、鏡像元數(shù)據(jù)所在的目錄。
每次build,都會:
鏡像
- 在/var/lib/docker/image/overlay2/imagedb/content/sha256/目錄下生成一個(gè)新的文件(文件、非目錄),文件名是新鏡像完整的SHA-256哈希值(即鏡像ID),該文件包含了新鏡像的config.json配置信息(包含了rootfs.layers等核心元數(shù)據(jù))。
- 如果未修改Dockerfile,重復(fù)build,由于前后兩次鏡像的SHA-256哈希值沒發(fā)生變化,因此Docker會復(fù)用現(xiàn)有層和鏡像配置,不會重復(fù)生成新文件。
- 如果修改了Dockerfile,即使構(gòu)建時(shí)的標(biāo)簽相同,

浙公網(wǎng)安備 33010602011771號