今天給朋友們分享的是我們的嘉賓- 前端大佬神光的關(guān)于 Dockerfile 使用技巧的文章,希望能對大家有所幫助。
以下是大佬原文:
Docker 是一種容器技術(shù),它可以在操作系統(tǒng)上創(chuàng)建多個相互隔離的容器。容器內(nèi)獨(dú)立安裝軟件、運(yùn)行服務(wù)。
(資料圖片)
但是,這個容器和宿主機(jī)還是有關(guān)聯(lián)的,比如可以把宿主機(jī)的端口映射到容器內(nèi)的端口、宿主機(jī)某個目錄掛載到容器內(nèi)的目錄。
比如映射了 3000 端口,那容器內(nèi) 3000 端口的服務(wù),就可以在宿主機(jī)的 3000 端口訪問了。
比如掛載了 /aaa 到容器的 /bbb/ccc,那容器內(nèi)讀寫 /bbb/ccc 目錄的時候,改的就是宿主機(jī)的 /aaa 目錄,反過來,改宿主機(jī) /aaa 目錄,容器內(nèi)的 /bbb/ccc 也會改,這倆同一個。
這分別叫做端口映射、數(shù)據(jù)卷(volume)掛載。
這個容器是通過鏡像起來的,通過 docker run image-name。
比如:
dockerrun-p3000:3000-v/aaa:/bbb/ccc--namexxx-containerxxx-image通過 xxx-image 鏡像跑起來一個叫做 xxx-container 的容器。
-p 指定端口映射,映射宿主機(jī)的 3000 到容器的 3000 端口。
-v 指定數(shù)據(jù)卷掛載,掛載宿主機(jī)的 /aaa 到容器的 /bbb/ccc 目錄。
這個鏡像是通過 Dockerfile 經(jīng)過 build 產(chǎn)生的。
也就是這樣的流程:
一般在項(xiàng)目里維護(hù) Dockerfile ,然后執(zhí)行 docker build 構(gòu)建出鏡像、push 到鏡像倉庫,部署的時候 pull 下來用 docker run 跑起來。
基本 CI/CD 也是這樣的流程:
CI 的時候 git clone 項(xiàng)目,根據(jù) dockerfile 構(gòu)建出鏡像,打上 tag,push 到倉庫。
CD 的時候把打 tag 的鏡像下下來,docker run 跑起來。
這個 Dockerfile 是在項(xiàng)目里維護(hù)的,雖然 CI/CD 流程不用自己搞,但是 Dockefile 還是要開發(fā)者自己寫的。
比如我創(chuàng)建一個 nest 項(xiàng)目:
npxnestnewdockerfile-test-pnpm然后執(zhí)行 npm run build,之后把它跑起來:
npmrunbuildnode./dist/main.js這時候訪問 http://localhost:3000 可以看到 hello world,說明服務(wù)跑成功了:
那如何通過 Docker 部署這個服務(wù)呢?
首先,如果你沒安裝 docker,可以從 docker.com 下載 docker desktop,它自帶了 docker 命令:
跑起來可以看到本地的所有 docker 容器和鏡像:
命令行也是可用的:
然后我們來寫下 Dockerfile:
FROMnode:18WORKDIR/appCOPYpackage.json.COPY*.lock.RUNnpmconfigsetregistryhttps://registry.npmmirror.com/RUNnpminstallCOPY..RUNnpmrunbuildEXPOSE3000CMD[\"node\",\"./dist/main.js\"]FROM node:18 是繼承 node:18 基礎(chǔ)鏡像。
WORKDIR /app 是指定當(dāng)前目錄為 /app
COPY 復(fù)制宿主機(jī)的 package.json 和 lock 文件到容器的當(dāng)前目錄,也就是 /app 下
RUN 是執(zhí)行命令,這里執(zhí)行了 npm install。
然后再復(fù)制其余的文件到容器內(nèi)。
EXPOSE 指定容器需要暴露的端口是 3000。
CMD 指定容器跑起來時執(zhí)行的命令是 node ./dist/main.js。
然后通過 docker build 把它構(gòu)建成鏡像:
dockerbuild-tdockerfile-test:first.-t 是指定名字和標(biāo)簽,這里鏡像名為 dockerfile-test 標(biāo)簽為 first。
然后在 docker desktop 的 images 里就可以看到這個鏡像了:
就是現(xiàn)在鏡像稍微大了點(diǎn),有 1.45 G。
我們先跑起來看看:
docker run -d -p 2333:3000 --name first-container dockerfile-test:first-d 是后臺運(yùn)行。
-p 指定端口映射,映射宿主機(jī)的 2333 端口到容器的 3000 端口。
--name 指定容器名
然后就可以看到容器部分有了這個容器了:
瀏覽器訪問 http://localhost:2333 就可以訪問容器內(nèi)跑的這個服務(wù):
這就是 Dockerfile 構(gòu)建成鏡像,然后通過容器跑起來的流程。
但是剛才也發(fā)現(xiàn)了,現(xiàn)在鏡像太大了,有 1.45G 呢,怎么優(yōu)化一下呢?
這就涉及到了第一個技巧:
docker 容器內(nèi)跑的是 linux 系統(tǒng),各種鏡像的 dockerfile 都會繼承 linux 鏡像作為基礎(chǔ)鏡像。
比如我們剛剛創(chuàng)建的那個鏡像,點(diǎn)開詳情可以看到它的鏡像繼承關(guān)系:
最終還是繼承了 debian 的 Linux 鏡像,這是一個 linux 發(fā)行版。
但其實(shí)這個 linux 鏡像可以換成更小的版本,也就是 alpine。
它裁剪了很多不必要的 linux 功能,使得鏡像體積大幅減小了。
alpine 是高山植物,就是很少的資源就能存活的意思。
我們改下 dockerfile,使用 alpine 的鏡像:
node:18-alpine3.14 是使用 18 版本的 node 鏡像,它底層使用 alpine 3.14 的基礎(chǔ)鏡像。
然后 docker build
dockerbuild-tdockerfile-test:second.這次的 tag 為 second。
然后在 docker desktop 里看下:
好家伙,足足小了 900M。
我們點(diǎn)開看看:
可以看到它的底層 linux 鏡像是 alpine3.14。
體積小了這么多,功能還正常么?
我們跑跑看:
dockerrun-d-p2334:3000--namesecond-containerdockerfile-test:seconddocker desktop 可以看到這個跑起來的容器:
瀏覽器訪問下,依然是正常的:
alpine 只是去掉了很多 linux 里用不到的功能,使得鏡像體積更小。
這就是第一個技巧。
然后再來看第二個:
看下這個 dockerfile,大家發(fā)現(xiàn)有啥問題沒:
有的同學(xué)可能會說:為什么先復(fù)制 package.json 進(jìn)去,安裝依賴之后再復(fù)制其他文件,直接全部復(fù)制進(jìn)去不就行了?
不是的,這兩種寫法的效果不同。
docker 是分層存儲的,dockerfile 里的每一行指令是一層,會做緩存。
每次 docker build 的時候,只會從變化的層開始重新構(gòu)建,沒變的層會直接復(fù)用。
也就說現(xiàn)在這種寫法,如果 package.json 沒變,那么就不會執(zhí)行 npm install,直接復(fù)用之前的。
那如果一開始就把所有文件復(fù)制進(jìn)去呢?
那不管 package.json 變沒變,任何一個文件變了,都會重新 npm install,這樣沒法充分利用緩存,性能不好。
我們試試看就知道了:
現(xiàn)在重新跑 docker build,不管跑多少次,速度都很快,因?yàn)槲募]變,直接用了鏡像緩存:
dockerbuild-tdockerfile-test:second.現(xiàn)在我們改下 README.md:
然后重新跑 build:
現(xiàn)在花了 25s,其實(shí)是沒有重新 npm install 的。
然后改下 package.json:
再跑 docker build
時間明顯多了很多,過程中你可以看到在 npm install 那層停留了很長時間。
這就是為什么要這樣寫:
這里沒問題,大家還能發(fā)現(xiàn)有沒有什么別的問題么?
問題就是源碼和很多構(gòu)建的依賴是不需要的,但是現(xiàn)在都保存在了鏡像里。
實(shí)際上我們只需要構(gòu)建出來的 ./dist 目錄下的文件還有運(yùn)行時的依賴。
那怎么辦呢?
這時可以用多階段構(gòu)建:
FROMnode:18-alpine3.14asbuild-stageWORKDIR/appCOPYpackage.json.RUNnpminstallCOPY..RUNnpmrunbuild#productionstageFROMnode:18-alpine3.14asproduction-stageCOPY--from=build-stage/app/dist/appCOPY--from=build-stage/app/package.json/app/package.jsonWORKDIR/appRUNnpminstall--productionEXPOSE3000CMD[\"node\",\"/app/main.js\"]FROM 后面添加一個 as 來指定當(dāng)前構(gòu)建階段的名字。
通過 COPY --from=xxx 可以從上個階段復(fù)制文件過來。
然后 npm install 的時候添加 --production,這樣只會安裝 dependencies 的依賴。
docker build 之后,只會留下最后一個階段的鏡像。
也就是說,最終構(gòu)建出來的鏡像里是沒有源碼的,有的只是 dist 的文件和運(yùn)行時依賴。
這樣鏡像就會小很多。
我們來試試看:
dockerbuild-tdockerfile-test:third-f222.Dockerfile.標(biāo)簽為 third。
-f 是指定 Dockerfile 的名字。
然后 desktop 里看下構(gòu)建出來的鏡像:
鏡像體積比沒有用多階段構(gòu)建的時候小了 250 M。
然后跑起來試試看:
這次映射 2335 端口到容器內(nèi)的 3000 端口。
依然能正常訪問:
這就是第二個技巧,多階段構(gòu)建。
我們寫一個 test.js
console.log(process.env.aaa);console.log(process.env.bbb);打印了環(huán)境變量 aaa、bbb
跑一下:
exportaaa=1bbb=2node./test.js可以看到打印了這倆環(huán)境變量:
然后我們寫個 dockerfile,文件名是 333.Dockerfile:
FROMnode:18-alpine3.14ARGaaaARGbbbWORKDIR/appCOPY./test.js.ENVaaa=${aaa}\bbb=${bbb}CMD[\"node\",\"/app/test.js\"]使用 ARG 聲明構(gòu)建參數(shù),使用 ${xxx} 來取
然后用 ENV 聲明環(huán)境變量。
dockerfile 內(nèi)換行使用 \
之后構(gòu)建的時候傳入構(gòu)建參數(shù):
dockerbuild--build-argaaa=3--build-argbbb=4-targ-test-f333.Dockerfile.通過 --build-arg xxx=yyy 傳入 ARG 參數(shù)的值。
點(diǎn)擊查看鏡像詳情,可以看到 ARG 已經(jīng)被替換為具體的值了:
然后跑起來:
dockerrun--namefourth-containerarg-test這次就不用 -d 后臺運(yùn)行了,直接看下日志:
可以看到容器內(nèi)拿到的環(huán)境變量就是 ENV 設(shè)置的。
也就是說 ARG 是構(gòu)建時的參數(shù),ENV 時運(yùn)行時的變量。
靈活使用 ARG,可以增加 dockerfile 的靈活性。
這就是第三個技巧。
前面我們指定容器跑起來之后運(yùn)行什么命令,用的是 CMD:
其實(shí)還可以寫成 ENTRYPOINT:
這兩種寫法有什么區(qū)別么?
我們來試試:
寫個 444.Dockerfile
FROMnode:18-alpine3.14CMD[\"echo\",\"光光\",\"到此一游\"]然后 build:
dockerbuild-tcmd-test-f444.Dockerfile.然后 run 一下:
dockerruncmd-test沒有指定 --name 時,會生成一個隨機(jī)容器名。
就是這種:
這不是重點(diǎn)。
重點(diǎn)是用 CMD 的時候,啟動命令是可以重寫的:
dockerruncmd-testecho\"東東\"可以替換成任何命令。
而用 ENTRYPOINT 就不會:
FROMnode:18-alpine3.14ENTRYPOINT[\"echo\",\"光光\",\"到此一游\"]docker build:
dockerbuild-tcmd-test-f444.Dockerfile.docker run:
dockerruncmd-testecho\"東東\"可以看到,現(xiàn)在 dockerfile 里 ENTRYPOINT 的命令依然執(zhí)行了。
docker run 傳入的參數(shù)作為了 echo 的額外參數(shù)。
這就是 ENTRYPOINT 和 CMD 的區(qū)別。
一般還是 CMD 用的多點(diǎn),可以靈活修改啟動命令。
其實(shí) ENTRYPOINT 和 CMD 是可以結(jié)合使用的。
比如這樣:
FROMnode:18-alpine3.14ENTRYPOINT[\"echo\",\"光光\"]CMD[\"到此一游\"]docker build:
dockerbuild-tcmd-test-f444.Dockerfile.docker run:
dockerruncmd-testdockerruncmd-test66666當(dāng)沒傳參數(shù)的時候,執(zhí)行的是 ENTRYPOINT + CMD 組合的命令,而傳入?yún)?shù)的時候,只有 CMD 部分會被覆蓋。
這就起到了默認(rèn)值的作用。
所以,用 ENTRYPOINT + CMD 的方式更加靈活。
這是第四個技巧。
其實(shí)不只是 ENTRYPOINT 和 CMD 相似,dockerfile 里還有一對指令也比較相似,就是 ADD 和 COPY。
這倆都可以把宿主機(jī)的文件復(fù)制到容器內(nèi)。
但有一點(diǎn)區(qū)別,就是對于 tar.gz 這種壓縮文件的處理上:
我們創(chuàng)建一個 aaa 目錄,下面添加兩個文件:
使用 tar 命令打包:
tar-zcvfaaa.tar.gz./aaa然后寫個 555.Dockerfile
FROMnode:18-alpine3.14ADD./aaa.tar.gz/aaaCOPY./aaa.tar.gz/bbbdocker build 生成鏡像:
dockerbuild-tadd-test-f555.Dockerfile.docker run 跑起來:
dockerrun-d--namesixth-containeradd-test可以看到,ADD 把 tar.gz 給解壓然后復(fù)制到容器內(nèi)了。
而 COPY 沒有解壓,它把文件整個復(fù)制過去了:
也就是說,ADD、COPY 都可以用于把目錄下的文件復(fù)制到容器內(nèi)的目錄下。
但是 ADD 還可以解壓 tar.gz 文件。
一般情況下,還是用 COPY 居多。
案例代碼上傳了 github:
https://github.com/QuarkGluonPlasma/nestjs-course-code/tree/main/dockerfile-test
Docker 是流行的容器技術(shù),它可以在操作系統(tǒng)上創(chuàng)建多個隔離的容器,在容器內(nèi)跑各種服務(wù)。
它的流程是 Dockerfile 經(jīng)過 docker build 生成 docker 鏡像,然后 docker run 來跑容器。
docker run 的時候可以通過 -p 指定宿主機(jī)和容器的端口映射,通過 -v 掛載數(shù)據(jù)卷到容器內(nèi)的某個目錄。
CI/CD 基本也是這套流程,但是 Dockerfile 是要開發(fā)者自己維護(hù)的。
Dockerfile 有挺多技巧:
使用 alpine 的鏡像,而不是默認(rèn)的 linux 鏡像,可以極大減小鏡像體積,比如 node:18-alpine3.14 這種 使用多階段構(gòu)建,比如一個階段來執(zhí)行 build,一個階段把文件復(fù)制過去,跑起服務(wù)來,最后只保留最后一個階段的鏡像。這樣使鏡像內(nèi)只保留運(yùn)行需要的文件以及 dependencies。 使用 ARG 增加構(gòu)建靈活性,ARG 可以在 docker build 時通過 --build-arg xxx=yyy 傳入,在 dockerfile 中生效,可以使構(gòu)建過程更靈活。如果是想定義運(yùn)行時可以訪問的變量,可以通過 ENV 定義環(huán)境變量,值使用 ARG 傳入。 CMD 和 ENTRYPOINT 都可以指定容器跑起來之后運(yùn)行的命令,CMD 可以被覆蓋,而 ENTRYPOINT 不可以,兩者結(jié)合使用可以實(shí)現(xiàn)參數(shù)默認(rèn)值的功能。 ADD 和 COPY 都可以復(fù)制文件到容器內(nèi),但是 ADD 處理 tar.gz 的時候,還會做一下解壓。靈活使用這些技巧,可以讓你的 Dockerfile 更加靈活、性能更好。
最后,歡迎學(xué)編程的朋友們加入魚皮的,和上萬名學(xué)編程的同學(xué)共享知識、交流進(jìn)步,學(xué)習(xí)原創(chuàng)項(xiàng)目并享有答疑指導(dǎo)服務(wù)。
往期推薦
關(guān)鍵詞:
天天消息!2023年下半年,哪個星座健康運(yùn)最旺,健康福祉有保障?
星座信仰自古以來,一直被認(rèn)為是人...
【世界熱聞】血瘀體質(zhì)易得什么病_血瘀體質(zhì)的人宜吃的食物
1 血瘀體質(zhì):機(jī)體具有血液循環(huán)不...
衛(wèi)星當(dāng)教具,導(dǎo)師在太空!太酷啦!|精彩看點(diǎn)
證實(shí)!在太空的導(dǎo)師確實(shí)在改論文他...
胡錫進(jìn)A股開戶,稱要力爭少交學(xué)費(fèi)、收益超存款利率
胡錫進(jìn)A股開戶,稱要力爭少交學(xué)費(fèi)...
30億“賣子”!酒企順鑫農(nóng)業(yè)剝離房地產(chǎn)業(yè)務(wù)還債,控股股東或兜底-當(dāng)前聚焦
6月26日晚,順鑫農(nóng)業(yè)公告表示,擬...
端午餐飲住宿消費(fèi)活力持續(xù)釋放 夏日夜經(jīng)濟(jì)增長顯著|天天快播報
端午餐飲住宿消費(fèi)活力持續(xù)釋放夏日...
世界熱點(diǎn)評!65%小微企業(yè)主認(rèn)為資金問題是成長最大的“煩惱” 如何助推小微發(fā)展?
2023年6月27日,是第七個聯(lián)合國中...
徐蓉:推動發(fā)展和安全深度融合-環(huán)球百事通
黨的二十大報告提出,“從現(xiàn)在起,...