大战熟女丰满人妻av-荡女精品导航-岛国aaaa级午夜福利片-岛国av动作片在线观看-岛国av无码免费无禁网站-岛国大片激情做爰视频

Docker教程
Docker安裝
Docker使用
Docker實例

Docker容器化

Docker 的核心思想就是如何將應(yīng)用整合到容器中,并且能在容器中實際運行。

將應(yīng)用整合到容器中并且運行起來的這個過程,稱為“容器化”(Containerizing),有時也叫作“Docker化”(Dockerizing)。

容器是為應(yīng)用而生的,具體來說,容器能夠簡化應(yīng)用的構(gòu)建、部署和運行過程。

完整的應(yīng)用容器化過程主要分為以下幾個步驟。

? 編寫應(yīng)用代碼。

? 創(chuàng)建一個 Dockerfile,其中包括當(dāng)前應(yīng)用的描述、依賴以及該如何運行這個應(yīng)用。

? 對該 Dockerfile 執(zhí)行 docker image build 命令。

? 等待 Docker 將應(yīng)用程序構(gòu)建到 Docker 鏡像中。

一旦應(yīng)用容器化完成(即應(yīng)用被打包為一個 Docker 鏡像),就能以鏡像的形式交付并以容器的方式運行了。

下圖展示了上述步驟。

單體應(yīng)用容器化

接下來我們會逐步展示如何將一個簡單的單節(jié)點 Node.js Web 應(yīng)用容器化。

如果是 Windows 操作系統(tǒng)的話,處理過程也是大同小異。

應(yīng)用容器化的過程大致分為如下幾個步驟:

? 獲取應(yīng)用代碼。

? 分析 Dockerfile。

? 構(gòu)建應(yīng)用鏡像。

? 運行該應(yīng)用。

? 測試應(yīng)用。

? 容器應(yīng)用化細(xì)節(jié)。

? 生產(chǎn)環(huán)境中的多階段構(gòu)建。

? 最佳實踐。

⒈ 獲取應(yīng)用代碼

應(yīng)用代碼可以從網(wǎng)盤獲取(https://pan.baidu.com/s/150UgIJPvuQUf0yO3KBLegg 提取碼:pkx4)。

$ cd psweb

$ ls -l
total 28
-rw-r--r-- 1 root root 341 Sep 29 16:26 app.js
-rw-r--r-- 1 root root 216 Sep 29 16:26 circle.yml
-rw-r--r-- 1 root root 338 Sep 29 16:26 Dockerfile
-rw-r--r-- 1 root root 421 Sep 29 16:26 package.json
-rw-r--r-- 1 root root 370 Sep 29 16:26 README.md
drwxr-xr-x 2 root root 4096 Sep 29 16:26 test
drwxr-xr-x 2 root root 4096 Sep 29 16:26 views

該目錄下包含了全部的應(yīng)用源碼,以及包含界面和單元測試的子目錄。這個應(yīng)用結(jié)構(gòu)非常簡單。

應(yīng)用代碼準(zhǔn)備就緒后,接下來分析一下 Dockerfile 的具體內(nèi)容。

⒉ 分析 Dockerfile

在代碼目錄當(dāng)中,有個名稱為 Dockerfile 的文件。這個文件包含了對當(dāng)前應(yīng)用的描述,并且能指導(dǎo) Docker 完成鏡像的構(gòu)建。

在 Docker 當(dāng)中,包含應(yīng)用文件的目錄通常被稱為構(gòu)建上下文(Build Context)。通常將 Dockerfile 放到構(gòu)建上下文的根目錄下。

另外很重要的一點是,文件開頭字母是大寫 D,這里是一個單詞。像“dockerfile”或者“Docker file”這種寫法都是不允許的。

接下來了解一下 Dockerfile 文件當(dāng)中都包含哪些具體內(nèi)容。

$ cat Dockerfile

FROM alpine
LABEL maintainer="[email protected]"
RUN apk add --update nodejs nodejs-npm
COPY . /src
WORKDIR /src
RUN npm install
EXPOSE 8080
ENTRYPOINT ["node", "./app.js"]

Dockerfile 主要包括兩個用途:

? 對當(dāng)前應(yīng)用的描述。

? 指導(dǎo) Docker 完成應(yīng)用的容器化(創(chuàng)建一個包含當(dāng)前應(yīng)用的鏡像)。

不要因 Dockerfile 就是一個描述文件而對其有所輕視!Dockerfile 能實現(xiàn)開發(fā)和部署兩個過程的無縫切換。

同時 Dockerfile 還能幫助新手快速熟悉這個項目。Dockerfile 對當(dāng)前的應(yīng)用及其依賴有一個清晰準(zhǔn)確的描述,并且非常容易閱讀和理解。

因此,要像重視你的代碼一樣重視這個文件,并且將它納入到源控制系統(tǒng)當(dāng)中。

下面是這個文件中的一些關(guān)鍵步驟概述:以 alpine 鏡像作為當(dāng)前鏡像基礎(chǔ),指定維護(hù)者(maintainer)為“[email protected]”,安裝 Node.js 和 NPM,將應(yīng)用的代碼復(fù)制到鏡像當(dāng)中,設(shè)置新的工作目錄,安裝依賴包,記錄應(yīng)用的網(wǎng)絡(luò)端口,最后將 app.js 設(shè)置為默認(rèn)運行的應(yīng)用。

具體分析一下每一步的作用。

每個 Dockerfile 文件第一行都是 FROM 指令。

FROM 指令指定的鏡像,會作為當(dāng)前鏡像的一個基礎(chǔ)鏡像層,當(dāng)前應(yīng)用的剩余內(nèi)容會作為新增鏡像層添加到基礎(chǔ)鏡像層之上。

本例中的應(yīng)用基于 Linux 操作系統(tǒng),所以在 FROM 指令當(dāng)中所引用的也是一個 Linux 基礎(chǔ)鏡像;如果要容器化的應(yīng)用是一個基于 Windows 操作系統(tǒng)的應(yīng)用,就需要指定一個像 microsoft/aspnetcore-build 這樣的 Windows 基礎(chǔ)鏡像了。

截至目前,基礎(chǔ)鏡像的結(jié)構(gòu)如下圖所示。

接下來,Dockerfile 中通過標(biāo)簽(LABLE)方式指定了當(dāng)前鏡像的維護(hù)者為“nigelpoulton@hotmail. com”。

每個標(biāo)簽其實是一個鍵值對(Key-Value),在一個鏡像當(dāng)中可以通過增加標(biāo)簽的方式來為鏡像添加自定義元數(shù)據(jù)。

備注維護(hù)者信息有助于為該鏡像的潛在使用者提供溝通途徑,這是一種值得提倡的做法。

RUN apk add --update nodejs nodejs-npm 指令使用 alpine 的 apk 包管理器將 nodejs 和 nodejs-npm 安裝到當(dāng)前鏡像之中。

RUN 指令會在 FROM 指定的 alpine 基礎(chǔ)鏡像之上,新建一個鏡像層來存儲這些安裝內(nèi)容。當(dāng)前鏡像的結(jié)構(gòu)如下圖所示。

COPY. / src 指令將應(yīng)用相關(guān)文件從構(gòu)建上下文復(fù)制到了當(dāng)前鏡像中,并且新建一個鏡像層來存儲。COPY 執(zhí)行結(jié)束之后,當(dāng)前鏡像共包含 3 層,如下圖所示。

下一步,Dockerfile 通過 WORKDIR 指令,為 Dockerfile 中尚未執(zhí)行的指令設(shè)置工作目錄。

該目錄與鏡像相關(guān),并且會作為元數(shù)據(jù)記錄到鏡像配置中,但不會創(chuàng)建新的鏡像層。

然后,RUN npm install 指令會根據(jù) package.json 中的配置信息,使用 npm 來安裝當(dāng)前應(yīng)用的相關(guān)依賴包。

npm 命令會在前文設(shè)置的工作目錄中執(zhí)行,并且在鏡像中新建鏡像層來保存相應(yīng)的依賴文件。

目前鏡像一共包含 4 層,如下圖所示。

因為當(dāng)前應(yīng)用需要通過 TCP 端口 8080 對外提供一個 Web 服務(wù),所以在 Dockerfile 中通過 EXPOSE 8080 指令來完成相應(yīng)端口的設(shè)置。

這個配置信息會作為鏡像的元數(shù)據(jù)被保存下來,并不會產(chǎn)生新的鏡像層。

最終,通過 ENTRYPOINT 指令來指定當(dāng)前鏡像的入口程序。ENTRYPOINT 指定的配置信息也是通過鏡像元數(shù)據(jù)的形式保存下來,而不是新增鏡像層。

⒊ 容器化當(dāng)前應(yīng)用/構(gòu)建具體的鏡像

到目前為止,應(yīng)該已經(jīng)了解基本的原理和流程,接下來是時候嘗試構(gòu)建自己的鏡像了。

下面的命令會構(gòu)建并生成一個名為 web:latest 的鏡像。命令最后的點(.)表示 Docker 在進(jìn)行構(gòu)建的時候,使用當(dāng)前目錄作為構(gòu)建上下文。

一定要在命令最后包含這個點,并且在執(zhí)行命令前,要確認(rèn)當(dāng)前目錄是 psweb(包含 Dockerfile 和應(yīng)用代碼的目錄)。

命令執(zhí)行結(jié)束后,檢查本地 Docker 鏡像庫是否包含了剛才構(gòu)建的鏡像。

$ docker image ls
REPO TAG IMAGE ID CREATED SIZE
web latest fc69fdc4c18e 10 seconds ago 64.4MB

恭喜,應(yīng)用容器化已經(jīng)成功了!

讀者可以通過 docker image inspect web:latest 來確認(rèn)剛剛構(gòu)建的鏡像配置是否正確。這個命令會列出 Dockerfile 中設(shè)置的所有配置項。

⒋ 推送鏡像到倉庫

在創(chuàng)建一個鏡像之后,將其保存在一個鏡像倉庫服務(wù)是一個不錯的方式。這樣存儲鏡像會比較安全,并且可以被其他人訪問使用。

Docker Hub 就是這樣的一個開放的公共鏡像倉庫服務(wù),并且這也是docker image push 命令默認(rèn)的推送地址。

在推送鏡像之前,需要先使用 Docker ID 登錄 Docker Hub。除此之外,還需要為待推送的鏡像打上合適的標(biāo)簽。

接下來介紹一下如何登錄 Docker Hub,并將鏡像推送到其中。

在后續(xù)的例子中,需要用自己的 Docker ID 替換示例中所使用的 ID。所以每當(dāng)看到“nigelpoulton”時,記得替換為自己的 Docker ID。

$ docker login
Login with **your** Docker ID to push and pull images from Docker Hub...
Username: nigelpoulton
Password:
Login Succeeded

推送 Docker 鏡像之前,還需要為鏡像打標(biāo)簽。這是因為 Docker 在鏡像推送的過程中需要如下信息。

? Registry(鏡像倉庫服務(wù))。

? Repository(鏡像倉庫)。

? Tag(鏡像標(biāo)簽)。

無須為 Registry 和 Tag 指定值。當(dāng)沒有為上述信息指定具體值的時候,Docker 會默認(rèn) Registry=docker.io、Tag=latest。

但是 Docker 并沒有給 Repository 提供默認(rèn)值,而是從被推送鏡像中的 REPOSITORY 屬性值獲取。

這一點可能不好理解,下面會通過一個完整的例子來介紹如何向 Docker Hub 中推送一個鏡像。

在前面的例子中執(zhí)行了 docker image ls 命令。在該命令對應(yīng)的輸出內(nèi)容中可以看到,鏡像倉庫的名稱是 web。

這意味著執(zhí)行 docker image push 命令,會嘗試將鏡像推送到 docker.io/web:latest 中。

但是其實 nigelpoulton 這個用戶并沒有 web 這個鏡像倉庫的訪問權(quán)限,所以只能嘗試推送到 nigelpoulton 這個二級命名空間(Namespace)之下。

因此需要使用 nigelpoulton 這個 ID,為當(dāng)前鏡像重新打一個標(biāo)簽。

$ docker image tag web:latest nigelpoulton/web:latest

為鏡像打標(biāo)簽命令的格式是docker image tag<current-tag> <new-tag> ,其作用是為指定的鏡像添加一個額外的標(biāo)簽,并且不需要覆蓋已經(jīng)存在的標(biāo)簽。

再次執(zhí)行 docker image ls 命令,可以看到這個鏡像現(xiàn)在有了兩個標(biāo)簽,其中一個包含 Docker ID nigelpoulton。

$ docker image ls
REPO TAG IMAGE ID CREATED SIZE
web latest fc69fdc4c18e 10 secs ago 64.4MB
nigelpoulton/web latest fc69fdc4c18e 10 secs ago 64.4MB

現(xiàn)在將該鏡像推送到 Docker Hub。

$ docker image push nigelpoulton/web:latest
The push refers to repository [docker.io/nigelpoulton/web]
2444b4ec39ad: Pushed
ed8142d2affb: Pushed
d77e2754766d: Pushed
cd7100a72410: Mounted from library/alpine
latest: digest: sha256:68c2dea730...f8cf7478 size: 1160

下圖展示了 Docker 如何確定鏡像所要推送的目的倉庫。

因為權(quán)限問題,所以需要把上面例子中出現(xiàn)的 ID(nigelpoulton)替換為自己的 Docker ID,才能進(jìn)行推送操作。

在接下來的例子當(dāng)中,將使用 web:latest 這個標(biāo)簽。

⒌ 運行應(yīng)用程序

前文中容器化的這個應(yīng)用程序其實很簡單,從 app.js 這個文件內(nèi)容中可以看出,這其實就是一個在 8080 端口提供 Web 服務(wù)的應(yīng)用程序。

下面的命令會基于 web:latest 這個鏡像,啟動一個名為 c1 的容器。該容器將內(nèi)部的 8080 端口與 Docker 主機的 80 端口進(jìn)行映射。

這意味讀者可以打開一個瀏覽器,在地址欄輸入 Docker 主機的 DNS 名稱或者 IP 地址,然后就能直接訪問這個 Web 應(yīng)用了。

如果 Docker 主機已經(jīng)運行了某個使用 80 端口的應(yīng)用程序,讀者可以在執(zhí)行 docker container run 命令時指定一個不同的映射端口。例如,可以使用 -p 5000:8080 參數(shù),將 Docker 內(nèi)部應(yīng)用程序的 8080 端口映射到主機的 5000 端口。

$ docker container run -d --name c1 \
-p 80:8080 \
web:latest

-d 參數(shù)的作用是讓應(yīng)用程序以守護(hù)線程的方式在后臺運行。

-p 80:8080 參數(shù)的作用是將主機的80端口與容器內(nèi)的8080端口進(jìn)行映射。

接下來驗證一下程序是否真的成功運行,并且對外提供服務(wù)的端口是否正常工作。

$ docker container ls

ID IMAGE COMMAND STATUS PORTS
49.. web:latest "node ./app.js" UP 6 secs 0.0.0.0:80->8080/tcp

為了方便閱讀,只截取了命令輸出內(nèi)容的一部分。從上面的輸出內(nèi)容中可以看到,容器已經(jīng)正常運行。需要注意的是,80端口已經(jīng)成功映射到了 8080 之上,并且任意外部主機(0.0.0.0:80)均可以通過 80 端口訪問該容器。

⒍ APP測試

打開瀏覽器,在地址欄輸入 DNS 名稱或者 IP 地址,就能訪問到正在運行的應(yīng)用程序了。可以看到下圖所示的界面。

如果沒有出現(xiàn)這樣的界面,嘗試執(zhí)行下面的檢查來確認(rèn)原因所在。

使用 docker container ls指令來確認(rèn)容器已經(jīng)啟動并且正常運行。容器名稱是c1,并且從輸出內(nèi)容中能看到 0.0.0.0:80->8080/tcp。

確認(rèn)防火墻或者其他網(wǎng)絡(luò)安全設(shè)置沒有阻止訪問 Docker 主機的 80 端口。

如此,應(yīng)用程序已經(jīng)容器化并成功運行了。

⒎ 詳述

到現(xiàn)在為止,應(yīng)當(dāng)成功完成一個示例應(yīng)用程序的容器化。下面是其中一些細(xì)節(jié)部分的回顧和總結(jié)。

Dockerfile 中的注釋行,都是以#開頭的。

除注釋之外,每一行都是一條指令(Instruction)。指令的格式是指令參數(shù)如下。

INSTRUCTION argument

指令是不區(qū)分大小寫的,但是通常都采用大寫的方式。這樣 Dockerfile 的可讀性會高一些。

Docker image build命令會按行來解析 Dockerfile 中的指令并順序執(zhí)行。

部分指令會在鏡像中創(chuàng)建新的鏡像層,其他指令只會增加或修改鏡像的元數(shù)據(jù)信息。

在上面的例子當(dāng)中,新增鏡像層的指令包括 FROM、RUN 以及 COPY,而新增元數(shù)據(jù)的指令包括 EXPOSE、WORKDIR、ENV以 及 ENTERPOINT。

關(guān)于如何區(qū)分命令是否會新建鏡像層,一個基本的原則是,如果指令的作用是向鏡像中增添新的文件或者程序,那么這條指令就會新建鏡像層;如果只是告訴 Docker 如何完成構(gòu)建或者如何運行應(yīng)用程序,那么就只會增加鏡像的元數(shù)據(jù)。

可以通過docker image history 來查看在構(gòu)建鏡像的過程中都執(zhí)行了哪些指令。

在上面的輸出內(nèi)容當(dāng)中,有兩點是需要注意的。

首先,每行內(nèi)容都對應(yīng)了 Dockerfile 中的一條指令(順序是自下而上)。CREATE BY 這一列中還展示了當(dāng)前行具體對應(yīng) Dockerfile 中的哪條指令。

其次,從這個輸出內(nèi)容中,可以觀察到只有 4 條指令會新建鏡像層(就是那些 SIZE 列對應(yīng)的數(shù)值不為零的指令),分別對應(yīng) Dockerfile 中的 FROM、RUN 以及 COPY 指令。

雖然其他指令看上去跟這些新建鏡像層的指令并無區(qū)別,但實際上它們只在鏡像中新增了元數(shù)據(jù)信息。這些指令之所以看起來沒有區(qū)別,是因為 Docker 對之前構(gòu)建鏡像層方式的兼容。

可以通過執(zhí)行 docker image inspect 指令來確認(rèn)確實只有 4 個層被創(chuàng)建了。

$ docker image inspect web:latest
<Snip>
},
"RootFS": {
"Type": "layers",
"Layers": [
    "sha256:cd7100...1882bd56d263e02b6215",
    "sha256:b3f88e...cae0e290980576e24885",
    "sha256:3cfa21...cc819ef5e3246ec4fe16",
    "sha256:4408b4...d52c731ba0b205392567"
]
},

使用 FROM 指令引用官方基礎(chǔ)鏡像是一個很好的習(xí)慣,這是因為官方的鏡像通常會遵循一些最佳實踐,并且能幫助使用者規(guī)避一些已知的問題。

除此之外,使用 FROM 的時候選擇一個相對較小的鏡像文件通常也能避免一些潛在的問題。

通過 docker image build 命令具體的輸出內(nèi)容,可以了解鏡像構(gòu)建的過程。

在下面的片段中,可以看到基本的構(gòu)建過程是,運行臨時容器 -> 在該容器中運行 Dockerfile 中的指令 -> 將指令運行結(jié)果保存為一個新的鏡像層 -> 刪除臨時容器。

Step 3/8 : RUN apk add --update nodejs nodejs-npm
---> Running in e690ddca785f << Run inside of temp container
fetch http://dl-cdn...APKINDEX.tar.gz
fetch http://dl-cdn...APKINDEX.tar.gz
(1/10) Installing ca-certificates (20171114-r0)
<Snip>
OK: 61 MiB in 21 packages
---> c1d31d36b81f << Create new layer
Removing intermediate container << Remove temp container
Step 4/8 : COPY . /src

生產(chǎn)環(huán)境中的多階段構(gòu)建

對于 Docker 鏡像來說,過大的體積并不好!

越大則越慢,這就意味著更難使用,而且可能更加脆弱,更容易遭受攻擊。

鑒于此,Docker 鏡像應(yīng)該盡量小。對于生產(chǎn)環(huán)境鏡像來說,目標(biāo)是將其縮小到僅包含運行應(yīng)用所必需的內(nèi)容即可。問題在于,生成較小的鏡像并非易事。

不同的 Dockerfile 寫法就會對鏡像的大小產(chǎn)生顯著影響。

常見的例子是,每一個 RUN 指令會新增一個鏡像層。因此,通過使用 && 連接多個命令以及使用反斜杠(\)換行的方法,將多個命令包含在一個 RUN 指令中,通常來說是一種值得提倡的方式。

另一個問題是開發(fā)者通常不會在構(gòu)建完成后進(jìn)行清理。當(dāng)使用 RUN 執(zhí)行一個命令時,可能會拉取一些構(gòu)建工具,這些工具會留在鏡像中移交至生產(chǎn)環(huán)境。

有多種方式來改善這一問題——比如常見的是采用建造者模式(Builder Pattern)。但無論采用哪種方式,通常都需要額外的培訓(xùn),并且會增加構(gòu)建的復(fù)雜度。

建造者模式需要至少兩個 Dockerfile,一個用于開發(fā)環(huán)境,一個用于生產(chǎn)環(huán)境。

首先需要編寫 Dockerfile.dev,它基于一個大型基礎(chǔ)鏡像(Base Image),拉取所需的構(gòu)建工具,并構(gòu)建應(yīng)用。

接下來,需要基于 Dockerfile.dev 構(gòu)建一個鏡像,并用這個鏡像創(chuàng)建一個容器。

這時再編寫 Dockerfile.prod,它基于一個較小的基礎(chǔ)鏡像開始構(gòu)建,并從剛才創(chuàng)建的容器中將應(yīng)用程序相關(guān)的部分復(fù)制過來。

整個過程需要編寫額外的腳本才能串聯(lián)起來。

這種方式是可行的,但是比較復(fù)雜。

多階段構(gòu)建(Multi-Stage Build)是一種更好的方式!

多階段構(gòu)建能夠在不增加復(fù)雜性的情況下優(yōu)化構(gòu)建過程。

下面介紹一下多階段構(gòu)建方式。

多階段構(gòu)建方式使用一個 Dockerfile,其中包含多個 FROM 指令。每一個 FROM 指令都是一個新的構(gòu)建階段(Build Stage),并且可以方便地復(fù)制之前階段的構(gòu)件。

示例源碼可從百度網(wǎng)盤獲取(https://pan.baidu.com/s/1M2paPY0f0lE5wm48HBk-Zw 提取碼: 2e7s ),Dockerfile 位于app目錄。

這是一個基于 Linux 系統(tǒng)的應(yīng)用,因此只能運行在 Linux 容器環(huán)境上。

Dockerfile 如下所示。

FROM node:latest AS storefront
WORKDIR /usr/src/atsea/app/react-app
COPY react-app .
RUN npm install
RUN npm run build

FROM maven:latest AS appserver
WORKDIR /usr/src/atsea
COPY pom.xml .
RUN mvn -B -f pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency
\:resolve
COPY . .
RUN mvn -B -s /usr/share/maven/ref/settings-docker.xml package -DskipTests

FROM java:8-jdk-alpine AS production
RUN adduser -Dh /home/gordon gordon
WORKDIR /static
COPY --from=storefront /usr/src/atsea/app/react-app/build/ .
WORKDIR /app
COPY --from=appserver /usr/src/atsea/target/AtSea-0.0.1-SNAPSHOT.jar .
ENTRYPOINT ["java", "-jar", "/app/AtSea-0.0.1-SNAPSHOT.jar"]
CMD ["--spring.profiles.active=postgres"]

首先注意到,Dockerfile 中有 3 個 FROM 指令。每一個 FROM 指令構(gòu)成一個單獨的構(gòu)建階段。

各個階段在內(nèi)部從 0 開始編號。不過,示例中針對每個階段都定義了便于理解的名字。

? 階段 0 叫作 storefront。

? 階段 1 叫作 appserver。

? 階段 2 叫作 production。

storefront 階段拉取了大小超過 600MB 的 node:latest 鏡像,然后設(shè)置了工作目錄,復(fù)制一些應(yīng)用代碼進(jìn)去,然后使用 2 個 RUN 指令來執(zhí)行 npm 操作。

這會生成 3 個鏡像層并顯著增加鏡像大小。指令執(zhí)行結(jié)束后會得到一個比原鏡像大得多的鏡像,其中包含許多構(gòu)建工具和少量應(yīng)用程序代碼。

appserver 階段拉取了大小超過 700MB 的 maven:latest 鏡像。然后通過 2 個 COPY 指令和 2 個 RUN 指令生成了 4 個鏡像層。

這個階段同樣會構(gòu)建出一個非常大的包含許多構(gòu)建工具和非常少量應(yīng)用程序代碼的鏡像。

production 階段拉取 java:8-jdk-alpine 鏡像,這個鏡像大約 150MB,明顯小于前兩個構(gòu)建階段用到的 node 和 maven 鏡像。

這個階段會創(chuàng)建一個用戶,設(shè)置工作目錄,從 storefront 階段生成的鏡像中復(fù)制一些應(yīng)用代碼過來。

之后,設(shè)置一個不同的工作目錄,然后從 appserver 階段生成的鏡像中復(fù)制應(yīng)用相關(guān)的代碼。最后,production 設(shè)置當(dāng)前應(yīng)用程序為容器啟動時的主程序。

重點在于 COPY --from 指令,它從之前的階段構(gòu)建的鏡像中僅復(fù)制生產(chǎn)環(huán)境相關(guān)的應(yīng)用代碼,而不會復(fù)制生產(chǎn)環(huán)境不需要的構(gòu)件。

還有一點也很重要,多階段構(gòu)建這種方式僅用到了一個 Dockerfile,并且 docker image build 命令不需要增加額外參數(shù)。

下面演示一下構(gòu)建操作。克隆代碼庫并切換到 app 目錄,并確保其中有 Dockerfile。

$ cd atsea-sample-shop-app/app

$ ls -l
total 24
-rw-r--r-- 1 root root 682 Oct 1 22:03 Dockerfile
-rw-r--r-- 1 root root 4365 Oct 1 22:03 pom.xml
drwxr-xr-x 4 root root 4096 Oct 1 22:03 react-app
drwxr-xr-x 4 root root 4096 Oct 1 22:03 src

執(zhí)行構(gòu)建(這可能會花費幾分鐘)。

$ docker image build -t multi:stage .

Sending build context to Docker daemon 3.658MB
Step 1/19 : FROM node:latest AS storefront
latest: Pulling from library/node
aa18ad1a0d33: Pull complete
15a33158a136: Pull complete
<Snip>
Step 19/19 : CMD --spring.profiles.active=postgres
---> Running in b4df9850f7ed
---> 3dc0d5e6223e
Removing intermediate container b4df9850f7ed
Successfully built 3dc0d5e6223e
Successfully tagged multi:stage

示例中 multi:stage 標(biāo)簽是自行定義的,可以根據(jù)自己的需要和規(guī)范來指定標(biāo)簽名稱。不過并不要求一定必須為多階段構(gòu)建指定標(biāo)簽。

執(zhí)行 docker image ls 命令查看由構(gòu)建命令拉取和生成的鏡像。

$ docker image ls

REPO TAG IMAGE ID CREATED SIZE
node latest 9ea1c3e33a0b 4 days ago 673MB
<none> <none> 6598db3cefaf 3 mins ago 816MB
maven latest cbf114925530 2 weeks ago 750MB
<none> <none> d5b619b83d9e 1 min ago 891MB
java 8-jdk-alpine 3fd9dd82815c 7 months ago 145MB
multi stage 3dc0d5e6223e 1 min ago 210MB

輸出內(nèi)容的第一行顯示了在 storefront 階段拉取的 node:latest 鏡像,下一行內(nèi)容為該階段生成的鏡像(通過添加代碼,執(zhí)行 npm 安裝和構(gòu)建操作生成該鏡像)。

這兩個都包含許多的構(gòu)建工具,因此鏡像體積非常大。

第 3~4 行是在 appserver 階段拉取和生成的鏡像,它們也都因為包含許多構(gòu)建工具而導(dǎo)致體積較大。

最后一行是 Dockerfile 中的最后一個構(gòu)建階段(stage2/production)生成的 multi:stage 鏡像。

可見它明顯比之前階段拉取和生成的鏡像要小。這是因為該鏡像是基于相對精簡的 java:8-jdk-alpine 鏡像構(gòu)建的,并且僅添加了用于生產(chǎn)環(huán)境的應(yīng)用程序文件。

最終,無須額外的腳本,僅對一個單獨的 Dockerfile 執(zhí)行 docker image build 命令,就創(chuàng)建了一個精簡的生產(chǎn)環(huán)境鏡像。

多階段構(gòu)建是隨 Docker 17.05 版本新增的一個特性,用于構(gòu)建精簡的生產(chǎn)環(huán)境鏡像。

最佳實踐

下面介紹一些最佳實踐。

⒈ 利用構(gòu)建緩存

Docker 的構(gòu)建過程利用了緩存機制。觀察緩存效果的一個方法,就是在一個干凈的 Docker 主機上構(gòu)建一個新的鏡像,然后再重復(fù)同樣的構(gòu)建。

第一次構(gòu)建會拉取基礎(chǔ)鏡像,并構(gòu)建鏡像層,構(gòu)建過程需要花費一定時間;第二次構(gòu)建幾乎能夠立即完成。

這就是因為第一次構(gòu)建的內(nèi)容(如鏡像層)能夠被緩存下來,并被后續(xù)的構(gòu)建過程復(fù)用。

docker image build 命令會從頂層開始解析 Dockerfile 中的指令并逐行執(zhí)行。而對每一條指令,Docker 都會檢查緩存中是否已經(jīng)有與該指令對應(yīng)的鏡像層。

如果有,即為緩存命中(Cache Hit),并且會使用這個鏡像層;如果沒有,則是緩存未命中(Cache Miss),Docker 會基于該指令構(gòu)建新的鏡像層。

緩存命中能夠顯著加快構(gòu)建過程。

下面通過實例演示其效果。

示例用的 Dockerfile 如下。

FROM alpine
RUN apk add --update nodejs nodejs-npm
COPY . /src
WORKDIR /src
RUN npm install
EXPOSE 8080
ENTRYPOINT ["node", "./app.js"]

第一條指令告訴 Docker 使用 alpine:latest 作為基礎(chǔ)鏡像。

如果主機中已經(jīng)存在這個鏡像,那么構(gòu)建時會直接跳到下一條指令;如果鏡像不存在,則會從 Docker Hub(docker.io)拉取。

下一條指令(RUN apk...)對鏡像執(zhí)行一條命令。

此時,Docker 會檢查構(gòu)建緩存中是否存在基于同一基礎(chǔ)鏡像,并且執(zhí)行了相同指令的鏡像層。

在此例中,Docker 會檢查緩存中是否存在一個基于 alpine:latest 鏡像且執(zhí)行了 RUN apk add --update nodejs nodejs-npm 指令構(gòu)建得到的鏡像層。

如果找到該鏡像層,Docker 會跳過這條指令,并鏈接到這個已經(jīng)存在的鏡像層,然后繼續(xù)構(gòu)建;如果無法找到符合要求的鏡像層,則設(shè)置緩存無效并構(gòu)建該鏡像層。

此處“設(shè)置緩存無效”作用于本次構(gòu)建的后續(xù)部分。也就是說 Dockerfile 中接下來的指令將全部執(zhí)行而不會再嘗試查找構(gòu)建緩存。

假設(shè) Docker 已經(jīng)在緩存中找到了該指令對應(yīng)的鏡像層(緩存命中),并且假設(shè)這個鏡像層的 ID 是 AAA。

下一條指令會復(fù)制一些代碼到鏡像中(COPY . /src)。因為上一條指令命中了緩存,Docker 會繼續(xù)查找是否有一個緩存的鏡像層也是基于 AAA 層并執(zhí)行了 COPY . /src 命令。

如果有,Docker 會鏈接到這個緩存的鏡像層并繼續(xù)執(zhí)行后續(xù)指令;如果沒有,則構(gòu)建鏡像層,并對后續(xù)的構(gòu)建操作設(shè)置緩存無效。

假設(shè) Docker 已經(jīng)有一個對應(yīng)該指令的緩存鏡像層(緩存命中),并且假設(shè)這個鏡像層的 ID 是 BBB。

那么 Docker 將繼續(xù)執(zhí)行 Dockerfile 中剩余的指令。

理解以下幾點很重要。

首先,一旦有指令在緩存中未命中(沒有該指令對應(yīng)的鏡像層),則后續(xù)的整個構(gòu)建過程將不再使用緩存。

在編寫 Dockerfile 時須特別注意這一點,盡量將易于發(fā)生變化的指令置于 Dockerfile 文件的后方執(zhí)行。

這意味著緩存未命中的情況將直到構(gòu)建的后期才會出現(xiàn),從而構(gòu)建過程能夠盡量從緩存中獲益。

通過對 docker image build 命令加入 --nocache=true 參數(shù)可以強制忽略對緩存的使用。

還有一點也很重要,那就是 COPY 和 ADD 指令會檢查復(fù)制到鏡像中的內(nèi)容自上一次構(gòu)建之后是否發(fā)生了變化。

例如,有可能 Dockerfile 中的 COPY . /src 指令沒有發(fā)生變化,但是被復(fù)制的目錄中的內(nèi)容已經(jīng)發(fā)生變化了。

為了應(yīng)對這一問題,Docker 會計算每一個被復(fù)制文件的 Checksum 值,并與緩存鏡像層中同一文件的 checksum 進(jìn)行對比。如果不匹配,那么就認(rèn)為緩存無效并構(gòu)建新的鏡像層。

⒉ 合并鏡像

合并鏡像并非一個最佳實踐,因為這種方式利弊參半。

總體來說,Docker 會遵循正常的方式構(gòu)建鏡像,但之后會增加一個額外的步驟,將所有的內(nèi)容合并到一個鏡像層中。

當(dāng)鏡像中層數(shù)太多時,合并是一個不錯的優(yōu)化方式。例如,當(dāng)創(chuàng)建一個新的基礎(chǔ)鏡像,以便基于它來構(gòu)建其他鏡像的時候,這個基礎(chǔ)鏡像就最好被合并為一層。

缺點是,合并的鏡像將無法共享鏡像層。這會導(dǎo)致存儲空間的低效利用,而且 push 和 pull 操作的鏡像體積更大。

執(zhí)行 docker image build命令時,可以通過增加 --squash 參數(shù)來創(chuàng)建一個合并的鏡像。

下圖闡釋了合并鏡像層帶來的存儲空間低效利用的問題。

兩個鏡像的內(nèi)容是完全一樣的,區(qū)別在于是否進(jìn)行了合并。在使用 docker image push 命令發(fā)送鏡像到 Docker Hub 時,合并的鏡像需要發(fā)送全部字節(jié),而不合并的鏡像只需要發(fā)送不同的鏡像層即可。

⒊ 使用 no-install-recommends

在構(gòu)建 Linux 鏡像時,若使用的是 APT 包管理器,則應(yīng)該在執(zhí)行 apt-get install 命令時增加 no-install-recommends 參數(shù)。

這能夠確保 APT 僅安裝核心依賴(Depends 中定義)包,而不是推薦和建議的包。這樣能夠顯著減少不必要包的下載數(shù)量。

⒋ 不要安裝 MSI 包(Windows)

在構(gòu)建 Windows 鏡像時,盡量避免使用 MSI 包管理器。因其對空間的利用率不高,會大幅增加鏡像的體積。

全部教程
主站蜘蛛池模板: 亚洲国产欧美在线不卡中文 | 最近中文2019视频在线 | 精品久久中文久久久 | 成人久久网 | 国产精品亚洲欧美一区麻豆 | 国产成综合| 奇米影视奇米色777欧美 | 亚洲黄色大片 | 97色在线观看免费视频 | 伊人色综合网一区二区三区 | 日本一级毛片免费播 | 久久精品综合国产二区 | 国产精品久久久久久久成人午夜 | 久久久美女视频 | 欧美乱大交xxxxxx喷潮免费 | 91成人啪国产啪永久地址 | 久久一本热 | 成人精品一区二区久久 | 久久93精品国产91久久综合 | 国产不卡在线观看视频 | 亚洲精品国产专区一区 | 免费国产一区二区三区 | 色片在线免费观看 | 激情五月婷婷基地 | 欧美一级特毛片 | 亚洲欧美小视频 | 亚洲最大激情网 | 在线看福利影 | 女bbbbxxxx另类亚洲 | 国产综合另类小说色区色噜噜 | 97视频在线观看免费播放 | 久久一区二区三区不卡 | 色综合天天综合中文网 | 久久久无码精品亚洲日韩按摩 | 成人毛片免费观看视频大全 | 五月花在线观看播放视频 | 九九热在线视频 | 色网站视频 | 久久久久在线视频 | 亚洲精品高清视频 | 天天操综合网 |