目錄
- Docker 鏡像的構建原理和方式
- 通過docker commit命令,基于一個已存在的容器構建出鏡像
- 編寫 Dockerfile 文件,并使用docker build命令來構建鏡像
- 通過docker save和docker load命令構建
- 通過docker export和docker import命令構建
Docker 鏡像的構建原理和方式
Docker構建鏡像的方式有多種,先介紹下最常用的兩種
- 通過
docker commit
命令,基于一個已存在的容器構建出鏡像。 - 編寫
Dockerfile
文件,并使用docker build
命令來構建鏡像。
上面這兩種方法中,鏡像構建的底層原理是相同的,都是通過下面 3 個步驟來構建鏡像:
- 基于原鏡像,啟動一個 Docker 容器。在容器中進行一些操作,例如執行命令、安裝文件等。
- 由這些操作產生的文件變更都會被記錄在容器的存儲層中。
- 將容器存儲層的變更 commit 到新的鏡像層中,并添加到原鏡像上。
下面,具體了解這兩種構建 Docker 鏡像的方式。
通過docker commit命令,基于一個已存在的容器構建出鏡像
通過docker commit來構建一個鏡像,命令的格式為docker commit [選項] [<倉庫名>[:<標簽>]]。
具體步驟如下:
- 執行
docker ps
獲取需要構建鏡像的容器 ID08cd43c7e50d
。 - 執行
docker pause 08cd43c7e50d
暫停08cd43c7e50d
容器的運行。 - 執行
docker commit 08cd43c7e50d redis:test
,基于容器 ID08cd43c7e50d
構建 Docker 鏡像。 - 執行
docker images redis:test
,查看鏡像是否成功構建。
這種鏡像構建方式通常用在下面兩個場景中:
- 構建臨時的測試鏡像;
- 容器被入侵后,使用docker commit,基于被入侵的容器構建鏡像,從而保留現場,方便以后追溯。
除了這兩種場景,不建議你使用docker commit來構建生產現網環境的鏡像。
主要原因有兩個:
- 使用docker commit構建的鏡像包含了編譯構建、安裝軟件,以及程序運行產生的大量無用文件,這會導致鏡像體積很大,非常臃腫。
- 使用docker commit構建的鏡像會丟失掉所有對該鏡像的操作歷史,無法還原鏡像的構建過程,不利于鏡像的維護。
編寫 Dockerfile 文件,并使用docker build命令來構建鏡像
docker build命令會讀取Dockerfile的內容,并將Dockerfile的內容發送給 Docker 引擎,最終 Docker 引擎會解析Dockerfile中的每一條指令,構建出需要的鏡像。
docker build的命令格式為docker build [OPTIONS] PATH | URL | -
。PATH、URL、-
指出了構建鏡像的上下文(context),context 中包含了構建鏡像需要的Dockerfile文件和其他文件。默認情況下,Docker 構建引擎會查找 context 中名為Dockerfile的文件,但你可以通過-f
, --file
選項,手動指定Dockerfile文件。例如:
$ docker build -f Dockerfile -t redis:test .
使用 Dockerfile 構建鏡像,本質上也是通過鏡像創建容器,并在容器中執行相應的指令,然后停止容器,提交存儲層的文件變更。和用docker commit
構建鏡像的方式相比,它有三個好處:
- Dockerfile 包含了鏡像制作的完整操作流程,其他開發者可以通過 Dockerfile 了解并復現制作過程。
- Dockerfile 中的每一條指令都會創建新的鏡像層,這些鏡像可以被 Docker Daemnon 緩存。再次制作鏡像時,Docker 會盡量復用緩存的鏡像層(using cache),而不是重新逐層構建,這樣可以節省時間和磁盤空間。
- Dockerfile 的操作流程可以通過docker image history [鏡像名稱]查詢,方便開發者查看變更記錄。
執行docker build后的構建流程為:
第一步,docker build會將 context 中的文件打包傳給 Docker daemon。如果 context 中有.dockerignore
文件,則會從上傳列表中刪除滿足.dockerignore
規則的文件。
- 這里有個例外,如果
.dockerignore
文件中有.dockerignore
或者Dockerfile
,docker build
命令在排除文件時會忽略掉這兩個文件。如果指定了鏡像的 tag,還會對 repository 和 tag 進行驗證。
第二步,docker build
命令向 Docker server
發送 HTTP 請求,請求 Docker server
構建鏡像,請求中包含了需要的 context 信息。
第三步,Docker server
接收到構建請求之后,會執行以下流程來構建鏡像:
- 創建一個臨時目錄,并將 context 中的文件解壓到該目錄下。
- 讀取并解析 Dockerfile,遍歷其中的指令,根據命令類型分發到不同的模塊去執行。
- Docker 構建引擎為每一條指令創建一個臨時容器,在臨時容器中執行指令,然后 commit 容器,生成一個新的鏡像層。
- 最后,將所有指令構建出的鏡像層合并,形成 build 的最后結果。最后一次 commit 生成的鏡像 ID 就是最終的鏡像 ID。
為了提高構建效率,docker build
默認會緩存已有的鏡像層。如果構建鏡像時發現某個鏡像層已經被緩存,就會直接使用該緩存鏡像,而不用重新構建。如果不希望使用緩存的鏡像,可以在執行docker build
命令時,指定--no-cache=true
參數。
Docker 匹配緩存鏡像的規則為:遍歷緩存中的基礎鏡像及其子鏡像,檢查這些鏡像的構建指令是否和當前指令完全一致,如果不一樣,則說明緩存不匹配。對于ADD
、COPY
指令,還會根據文件的校驗和(checksum)來判斷添加到鏡像中的文件是否相同,如果不相同,則說明緩存不匹配。
這里要注意,緩存匹配檢查不會檢查容器中的文件。比如,當使用RUN apt-get -y update
命令更新了容器中的文件時,緩存策略并不會檢查這些文件,來判斷緩存是否匹配。
最后,可以通過docker history
命令來查看鏡像的構建歷史,如下圖所示:
通過docker save和docker load命令構建
docker save用來將鏡像保存為一個 tar 文件,docker load用來將 tar 格式的鏡像文件加載到當前機器上,例如:
# 在 A 機器上執行,并將 nginx-v1.0.0.tar.gz 復制到 B 機器 $ docker save nginx | gzip > nginx-v1.0.0.tar.gz # 在 B 機器上執行 $ docker load -i nginx-v1.0.0.tar.gz
通過上面的命令,我們就在機器 B 上創建了nginx鏡像。
通過docker export和docker import命令構建
通過docker export 保存容器的鏡像,再通過docker import 加載鏡像,具體命令如下:
# 在 A 機器上執行,并將 nginx-v1.0.0.tar.gz 復制到 B 機器 $ docker export nginx > nginx-v1.0.0.tar.gz # 在 B 機器上執行 $ docker import - nginx:v1.0.0 nginx-v1.0.0.tar.gz
通過docker export
導出的鏡像和通過docker save
保存的鏡像相比,會丟失掉所有的鏡像構建歷史。在實際生產環境中,我不建議你通過docker save和docker export這兩種方式來創建鏡像。
較推薦的方式是:在 A 機器上將鏡像 push 到鏡像倉庫,在 B 機器上從鏡像倉庫 pull 該鏡像。