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

Docker教程
Docker安裝
Docker使用
Docker實(shí)例

Docker容器化

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

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

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

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

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

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

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

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

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

下圖展示了上述步驟。

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

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

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

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

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

? 分析 Dockerfile。

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

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

? 測試應(yīng)用。

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

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

? 最佳實(shí)踐。

⒈ 獲取應(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)用源碼,以及包含界面和單元測試的子目錄。這個(gè)應(yīng)用結(jié)構(gòu)非常簡單。

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

⒉ 分析 Dockerfile

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

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

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

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

$ cat Dockerfile

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

Dockerfile 主要包括兩個(gè)用途:

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

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

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

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

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

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

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

每個(gè) Dockerfile 文件第一行都是 FROM 指令。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

一定要在命令最后包含這個(gè)點(diǎn),并且在執(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)建的鏡像配置是否正確。這個(gè)命令會(huì)列出 Dockerfile 中設(shè)置的所有配置項(xiàng)。

⒋ 推送鏡像到倉庫

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

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

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

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

在后續(xù)的例子中,需要用自己的 Docker ID 替換示例中所使用的 ID。所以每當(dāng)看到“nigelpoulton”時(shí),記得替換為自己的 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)簽。這是因?yàn)?Docker 在鏡像推送的過程中需要如下信息。

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

? Repository(鏡像倉庫)。

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

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

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

這一點(diǎn)可能不好理解,下面會(huì)通過一個(gè)完整的例子來介紹如何向 Docker Hub 中推送一個(gè)鏡像。

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

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

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

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

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

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

再次執(zhí)行 docker image ls 命令,可以看到這個(gè)鏡像現(xiàn)在有了兩個(gè)標(biāo)簽,其中一個(gè)包含 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 如何確定鏡像所要推送的目的倉庫。

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

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

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

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

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

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

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

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

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

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

接下來驗(yàn)證一下程序是否真的成功運(yùn)行,并且對(duì)外提供服務(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)正常運(yùn)行。需要注意的是,80端口已經(jīng)成功映射到了 8080 之上,并且任意外部主機(jī)(0.0.0.0:80)均可以通過 80 端口訪問該容器。

⒍ APP測試

打開瀏覽器,在地址欄輸入 DNS 名稱或者 IP 地址,就能訪問到正在運(yùn)行的應(yīng)用程序了??梢钥吹较聢D所示的界面。

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

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

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

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

⒎ 詳述

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

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

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

INSTRUCTION argument

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

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

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

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

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

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

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

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

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

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

可以通過執(zhí)行 docker image inspect 指令來確認(rèn)確實(shí)只有 4 個(gè)層被創(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ǔ)鏡像是一個(gè)很好的習(xí)慣,這是因?yàn)楣俜降溺R像通常會(huì)遵循一些最佳實(shí)踐,并且能幫助使用者規(guī)避一些已知的問題。

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

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

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

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)建

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

這是一個(gè)基于 Linux 系統(tǒng)的應(yīng)用,因此只能運(yùn)行在 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 個(gè) FROM 指令。每一個(gè) FROM 指令構(gòu)成一個(gè)單獨(dú)的構(gòu)建階段。

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

? 階段 0 叫作 storefront。

? 階段 1 叫作 appserver。

? 階段 2 叫作 production。

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

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

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

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

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

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

之后,設(shè)置一個(gè)不同的工作目錄,然后從 appserver 階段生成的鏡像中復(fù)制應(yīng)用相關(guān)的代碼。最后,production 設(shè)置當(dāng)前應(yīng)用程序?yàn)槿萜鲉?dòng)時(shí)的主程序。

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

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

下面演示一下構(gòu)建操作??寺〈a庫并切換到 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)建(這可能會(huì)花費(fèi)幾分鐘)。

$ 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è)都包含許多的構(gòu)建工具,因此鏡像體積非常大。

第 3~4 行是在 appserver 階段拉取和生成的鏡像,它們也都因?yàn)榘S多構(gòu)建工具而導(dǎo)致體積較大。

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

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

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

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

最佳實(shí)踐

下面介紹一些最佳實(shí)踐。

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

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

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

這就是因?yàn)榈谝淮螛?gòu)建的內(nèi)容(如鏡像層)能夠被緩存下來,并被后續(xù)的構(gòu)建過程復(fù)用。

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

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

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

下面通過實(shí)例演示其效果。

示例用的 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ī)中已經(jīng)存在這個(gè)鏡像,那么構(gòu)建時(shí)會(huì)直接跳到下一條指令;如果鏡像不存在,則會(huì)從 Docker Hub(docker.io)拉取。

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

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

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

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

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

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

下一條指令會(huì)復(fù)制一些代碼到鏡像中(COPY . /src)。因?yàn)樯弦粭l指令命中了緩存,Docker 會(huì)繼續(xù)查找是否有一個(gè)緩存的鏡像層也是基于 AAA 層并執(zhí)行了 COPY . /src 命令。

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

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

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

理解以下幾點(diǎn)很重要。

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

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

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

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

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

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

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

⒉ 合并鏡像

合并鏡像并非一個(gè)最佳實(shí)踐,因?yàn)檫@種方式利弊參半。

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

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

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

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

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

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

⒊ 使用 no-install-recommends

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

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

⒋ 不要安裝 MSI 包(Windows)

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

全部教程
主站蜘蛛池模板: 久久99精品一级毛片 | 澳门成人免费永久视频 | 四虎在线播放 | 久久国产香蕉视频 | www.伊人.com| 欧美日韩国产一区三区 | 欧美一级人与动毛片免费播放 | 国产精品久久久久久五月尺 | 久草在线最新 | 九九99香蕉在线视频免费 | 欧美日韩亚洲国产一区二区综合 | 日韩美视频网站 | 日本无翼乌全彩无遮挡动漫 | 久久国产片 | 欧美最猛的24k毛片视频 | 视频一区日韩 | 青青青视频精品中文字幕 | 日本aa在线 | 四虎影视成人精品 | 久久国产综合精品欧美 | 欧美成人精品在线 | 精品视频一区二区三区在线播放 | 国产天天操| 日本四虎影院 | 欧美成人精品一区二区 | 中文亚洲字幕 | 四虎影在永久地址在线观看 | 99热久久国产精品 | 国产精品亚洲欧美日韩久久 | 国产色婷婷视频在线观看 | 国产精品一区二区不卡 | 日韩亚洲一区中文字幕在线 | 成人国产精品视频频 | 91福利精品老师国产自产在线 | 北岛玲日韩精品一区二区三区 | 亚洲国产成人在线 | 欧美日韩一区二区视频免费看 | 四虎影视884a精品国产古代 | 玖玖爱免费 | 亚洲精品片 | 四虎影视在线永久免费观看 |