這篇文章給大家介紹Node.js項(xiàng)目中怎么優(yōu)化docker鏡像,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),淇濱企業(yè)網(wǎng)站建設(shè),淇濱品牌網(wǎng)站建設(shè),網(wǎng)站定制,淇濱網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,淇濱網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競爭力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
簡單寫了一個(gè)自己用的 wechat-bot ,接下來就以這個(gè)項(xiàng)目演示怎么去優(yōu)化 Docker 鏡像
以下是我沒有仔細(xì)研究 Docker 剛開始寫的 Dockerfile 文件
FROM node:14.17.3 # 設(shè)置環(huán)境變量 ENV NODE_ENV=production ENV APP_PATH=/node/app # 設(shè)置工作目錄 WORKDIR $APP_PATH # 把當(dāng)前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝 COPY . $APP_PATH # 安裝依賴 RUN yarn # 暴露端口 EXPOSE 4300 CMD yarn start
build 之后,如下圖,我這個(gè)簡單的 Node 程序鏡像竟然有 1G多,接下來我們將逐步去優(yōu)化減少這個(gè)大小
在優(yōu)化之前,有些東西我們必須了解,解決問題的第一步就是先找出導(dǎo)致問題的原因
Dockerfile 文件,其內(nèi)包含了一條條的指令,每一條指令構(gòu)建一層,因此每一條指令的內(nèi)容,就是描述該層如何構(gòu)建
Docker 鏡像并非只是一個(gè)文件,而是由一堆文件組成,最主要的文件是 層(Layers)
參考資料:docker 鏡像分層原理
鏡像構(gòu)建時(shí),會一層層構(gòu)建,前一層是后一層的基礎(chǔ)
每一層構(gòu)建完就不會再發(fā)生改變,后一層上的任何改變只發(fā)生在自己這一層。比如,刪除前一層文件的操作,實(shí)際不是真的刪除前一層的文件,而是僅在當(dāng)前層標(biāo)記為該文件已刪除。在最終容器運(yùn)行的時(shí)候,雖然不會看到這個(gè)文件,但是實(shí)際上該文件會一直跟隨鏡像
鏡像層將會被緩存和復(fù)用(這也是從第二次開始構(gòu)建鏡像時(shí),速度會快的原因,優(yōu)化鏡像構(gòu)建速度的原理也是利用緩存原理來做)
當(dāng) Dockerfile 的指令修改了,操作的文件變化了,或者構(gòu)建鏡像時(shí)指定的變量不同了,對應(yīng)的鏡像層緩存就會失效
docker build 的緩存機(jī)制,docker 是怎么知道文件變化的呢?
Docker 采取的策略是:獲取 Dockerfile 下內(nèi)容(包括文件的部分 inode 信息),計(jì)算出一個(gè)唯一的 hash 值,若 hash 值未發(fā)生變化,則可以認(rèn)為文件內(nèi)容沒有發(fā)生變化,可以使用緩存機(jī)制,反之亦然。
某一層的鏡像緩存失效之后,它之后的鏡像層緩存都會失效
鏡像的每一層只記錄文件變更,在容器啟動時(shí),Docker 會將鏡像的各個(gè)層進(jìn)行計(jì)算,最后生成一個(gè)文件系統(tǒng)
當(dāng)我知道這點(diǎn)時(shí),我恍然大悟,我們使用的操作系統(tǒng),比如安卓、ios、win、mac 等,其實(shí)就是一個(gè)文件系統(tǒng),我們的軟件界面交互等,其實(shí)就是在讀寫文件,我們網(wǎng)頁寫個(gè)彈框,操作 dom,就是在讀寫本地文件或者是讀寫內(nèi)存里的數(shù)據(jù),個(gè)人的一些見解不知道對不對,本人非科班出身的前端 coder
優(yōu)化 Dockerfile
FROM node:14.17.3
這也是絕多數(shù)人知道的優(yōu)化鏡像手段,Alpine 是一個(gè)很小的 Linux 發(fā)行版,只要選擇 Node 的 Alpine 版本,就會有很大改進(jìn),我們把這一句改成指令改成 FROM node:14.17.4-alpine
(可以去 dockerhub 查看 node 有哪些版本標(biāo)簽),build 后鏡像大小如下圖,瞬間從 1.06G 降到 238M,可以說是效果顯著
還可以使用其它的基礎(chǔ)小鏡像,比如 mhart/alpine-node,這個(gè)還能再小,改成 FROM mhart/alpine-node:14.17.3
再試試,可以看到又小了 5M,雖然不多,但是秉著能壓榨一點(diǎn)是一點(diǎn)的“老板原則”,積少成多,極致壓榨
既然 Alpine 是最小的 Linux,那我們試下用純凈的 Alpine 鏡像,自己再裝 Node 試試
FROM alpine:latest # 使用 apk 命令安裝 nodejs 和 yarn,如果使用 npm 啟動,就不需要裝 yarn RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0 # ... 后面的步驟不變
build 后看下圖,只有 174M了,又小了不少
結(jié)論就是不嫌麻煩追求極致就用方案二,從 1.06G 減少到 174M
減少層數(shù)、不經(jīng)常變動的層提到前面去
ENV
指令是可以一次性設(shè)置多個(gè)環(huán)境變量,能一次指令執(zhí)行完,就不用兩次,多一個(gè)指令就多一層
EXPOSE
指令是暴露端口,其實(shí)也可以不用寫這個(gè)指令,在啟動容器的時(shí)候自己映射端口,如果寫了這個(gè)指令的話,因?yàn)槎丝诓唤?jīng)常變,所以把這個(gè)指令提前,寫上這個(gè)指令有兩個(gè)好處:
至于寫還是不寫,看個(gè)人吧,我個(gè)人一般不寫,因?yàn)槲以陧?xiàng)目啟動命令會指定項(xiàng)目端口,啟動容器的時(shí)候映射出來就好,這樣我就要維護(hù)一個(gè)地方,Dockerfile 也寫了的話,項(xiàng)目端口變了,這里也要修改,多了點(diǎn)維護(hù)成本,當(dāng)然也有辦法讓兩邊端口變量取自配置文件,只要改配置文件即可
幫助鏡像使用者理解這個(gè)鏡像服務(wù)的守護(hù)端口,以方便配置映射
在運(yùn)行時(shí)使用隨機(jī)端口映射時(shí),也就是 docker run -P
時(shí),會自動隨機(jī)映射 EXPOSE
的端口
下面是改寫后的 Dockerfile
FROM alpine:latest # 使用 apk 命令安裝 nodejs 和 yarn,如果使用 npm 啟動,就不需要裝 yarn RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0 # 暴露端口 EXPOSE 4300 # 設(shè)置環(huán)境變量 ENV NODE_ENV=production \ APP_PATH=/node/app # 設(shè)置工作目錄 WORKDIR $APP_PATH # 把當(dāng)前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝 COPY . $APP_PATH # 安裝依賴 RUN yarn # 啟動命令 CMD yarn start
這一步的優(yōu)化,無論從鏡像大小還是構(gòu)建鏡像速度都看不到明顯的差別,因?yàn)楦膭拥膶觾?nèi)容少(體現(xiàn)不出來),但是可以查看到鏡像的層是變少了的,可以自行試試查看鏡像的層試試
減少鏡像層數(shù)是“好老板”的傳統(tǒng)優(yōu)良習(xí)慣,不讓“員工”浪費(fèi)資源
package.json 提前提高編譯速度
從下圖可以看到每次我們 build 的時(shí)候最耗時(shí)的就是在執(zhí)行 yarn
命令裝依賴的時(shí)候,大部分時(shí)候我們只是改代碼,依賴不變,這時(shí)候如果可以讓這一步緩存起來,依賴沒有變化的時(shí)候,就不需要重新裝依賴,就可以大大改進(jìn)編譯速度
前面我們說了鏡像構(gòu)建時(shí),是一層層構(gòu)建,前一層是后一層的基礎(chǔ),既然是這樣的話,我們就把 package.json 文件單獨(dú)提前拷貝到鏡像,然后下一步裝依賴,執(zhí)行命令裝依賴這層的前一層是拷貝 package.json 文件,因?yàn)榘惭b依賴命令不會變化,所以只要 package.json 文件沒變化,就不會重新執(zhí)行 yarn
安裝依賴,它會復(fù)用之前安裝好的依賴,原理講清楚了,下面我們看效果
改變后的 Dockerfile 文件
FROM alpine:latest # 使用 apk 命令安裝 nodejs 和 yarn,如果使用 npm 啟動,就不需要裝 yarn RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0 # 暴露端口 EXPOSE 4300 # 設(shè)置環(huán)境變量 ENV NODE_ENV=production \ APP_PATH=/node/app # 設(shè)置工作目錄 WORKDIR $APP_PATH # 拷貝 package.json 到工作跟目錄下 COPY package.json . # 安裝依賴 RUN yarn # 把當(dāng)前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝 COPY . . # 啟動命令 CMD yarn start
build 看下圖,編譯時(shí)間從 29.6s 到 1.3s,使用了緩存的層前面會有個(gè) CACHED 字眼,仔細(xì)看下圖可以看到
使用多階段構(gòu)建再次壓榨鏡像大小
多階段構(gòu)建這里不多說了,不了解的可以先搜相關(guān)資料了解
因?yàn)槲覀冞\(yùn)行 node 程序是只需要生產(chǎn)的依賴和最終 node 可以運(yùn)行的文件,就是說我們運(yùn)行項(xiàng)目只需要 package.js 文件里 dependencies 里的依賴,devDependencies 依賴只是編譯階段用的,比如 eslint 等這些工具在項(xiàng)目運(yùn)行時(shí)是用不到的,再比如我們項(xiàng)目是用 typescript 寫的,node 是不能直接運(yùn)行 ts 文件,ts 文件需要編譯成 js 文件,運(yùn)行項(xiàng)目我們只需要編譯后的文件和 dependencies 里的依賴就可以運(yùn)行,也就是說最終鏡像只需要我們需要的東西,任何其他東西都可以刪掉,下面我們使用多階段改寫 Dockerfile
# 構(gòu)建基礎(chǔ)鏡像 FROM alpine:3.14 AS base # 設(shè)置環(huán)境變量 ENV NODE_ENV=production \ APP_PATH=/node/app # 設(shè)置工作目錄 WORKDIR $APP_PATH # 安裝 nodejs 和 yarn RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0 # 使用基礎(chǔ)鏡像 裝依賴階段 FROM base AS install # 拷貝 package.json 到工作跟目錄下 COPY package.json ./ # 安裝依賴 RUN yarn # 最終階段,也就是輸出的鏡像是這個(gè)階段構(gòu)建的,前面的階段都是為這個(gè)階段做鋪墊 FROM base # 拷貝 裝依賴階段 生成的 node_modules 文件夾到工作目錄下 COPY --from=install $APP_PATH/node_modules ./node_modules # 將當(dāng)前目錄下的所有文件(除了.dockerignore排除的路徑),都拷貝進(jìn)入鏡像的工作目錄下 COPY . . # 啟動 CMD yarn start
細(xì)心的朋友會發(fā)現(xiàn)我這里有指定 alpine 版本,而上面都是用的 latest 版本,因?yàn)榫驮趧倓偘l(fā)現(xiàn)有個(gè)坑需要注意下,就是我們選擇 alpine 版本的時(shí)候,最好不要選擇 latest 版本,因?yàn)楹竺嬉b的軟件版本可能會在 alpine 的 latest 版本沒有對應(yīng)軟件的版本號,就會安裝錯誤,我剛剛就翻車了,點(diǎn)擊查看 alpine 版本下的包信息
build 后,我們看看鏡像大小,上次的是 174M 再次降到 73.4M,極致壓榨。鏡像:”放過我把,我真的沒有了“
講解:
我把這個(gè)構(gòu)建分成了三個(gè)階段:
第一階段:構(gòu)建基礎(chǔ)鏡像
安裝依賴、編譯、運(yùn)行等等階段,就是所有階段共用的東西都在第一階段封到一個(gè)基礎(chǔ)鏡像里供其它階段使用,比如設(shè)置環(huán)境變量、設(shè)置工作目錄、安裝 nodejs、yarn 等等
第二階段:裝依賴階段
在這個(gè)階段,裝依賴,如果項(xiàng)目需要編譯,可以在這個(gè)階段裝依賴編譯好
這里在說下裝依賴的小細(xì)節(jié),就是執(zhí)行 yarn --production
加個(gè) production 參數(shù)或者環(huán)境變量 NODE_ENV
為 production
,yarn 將不會安裝 devDependencies 中列出的任何軟件包,點(diǎn)我查看官方文檔說明,因?yàn)槲以O(shè)置了環(huán)境變量所以就沒加這個(gè)參數(shù)
第三階段:最終使用鏡像
拷貝第二階段安裝的好的依賴文件夾,然后在拷貝代碼文件到工作目錄,執(zhí)行啟動命令,第二階段裝依賴多出的一些垃圾我們不需要,我們就只拷貝我們要用的東西,大大減少鏡像的大小
如果項(xiàng)目需要編譯,在拷貝編譯后的文件夾,不需要拷貝編譯前的代碼,有編譯后的代碼和依賴就可以跑起項(xiàng)目
多階段構(gòu)建,最后生成的鏡像只能是最后一個(gè)階段的結(jié)果,但是,能夠?qū)⑶爸秒A段中的文件拷貝到后邊的階段中,這就是多階段構(gòu)建的最大意義。
關(guān)于Node.js項(xiàng)目中怎么優(yōu)化docker鏡像就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。