您好,登錄后才能下訂單哦!
這篇文章給大家介紹Node.js項目中怎么優化docker鏡像,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
簡單寫了一個自己用的 wechat-bot ,接下來就以這個項目演示怎么去優化 Docker 鏡像
以下是我沒有仔細研究 Docker 剛開始寫的 Dockerfile 文件
FROM node:14.17.3 # 設置環境變量 ENV NODE_ENV=production ENV APP_PATH=/node/app # 設置工作目錄 WORKDIR $APP_PATH # 把當前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝 COPY . $APP_PATH # 安裝依賴 RUN yarn # 暴露端口 EXPOSE 4300 CMD yarn start
build 之后,如下圖,我這個簡單的 Node 程序鏡像竟然有 1G 多,接下來我們將逐步去優化減少這個大小
在優化之前,有些東西我們必須了解,解決問題的第一步就是先找出導致問題的原因
Dockerfile 文件,其內包含了一條條的指令,每一條指令構建一層,因此每一條指令的內容,就是描述該層如何構建
Docker 鏡像并非只是一個文件,而是由一堆文件組成,最主要的文件是 層(Layers)
參考資料:docker 鏡像分層原理
鏡像構建時,會一層層構建,前一層是后一層的基礎
每一層構建完就不會再發生改變,后一層上的任何改變只發生在自己這一層。比如,刪除前一層文件的操作,實際不是真的刪除前一層的文件,而是僅在當前層標記為該文件已刪除。在最終容器運行的時候,雖然不會看到這個文件,但是實際上該文件會一直跟隨鏡像
鏡像層將會被緩存和復用(這也是從第二次開始構建鏡像時,速度會快的原因,優化鏡像構建速度的原理也是利用緩存原理來做)
當 Dockerfile 的指令修改了,操作的文件變化了,或者構建鏡像時指定的變量不同了,對應的鏡像層緩存就會失效
docker build 的緩存機制,docker 是怎么知道文件變化的呢?
Docker 采取的策略是:獲取 Dockerfile 下內容(包括文件的部分 inode 信息),計算出一個唯一的 hash 值,若 hash 值未發生變化,則可以認為文件內容沒有發生變化,可以使用緩存機制,反之亦然。
某一層的鏡像緩存失效之后,它之后的鏡像層緩存都會失效
鏡像的每一層只記錄文件變更,在容器啟動時,Docker 會將鏡像的各個層進行計算,最后生成一個文件系統
當我知道這點時,我恍然大悟,我們使用的操作系統,比如安卓、ios、win、mac 等,其實就是一個文件系統,我們的軟件界面交互等,其實就是在讀寫文件,我們網頁寫個彈框,操作 dom,就是在讀寫本地文件或者是讀寫內存里的數據,個人的一些見解不知道對不對,本人非科班出身的前端 coder
優化 Dockerfile
FROM node:14.17.3
這也是絕多數人知道的優化鏡像手段,Alpine 是一個很小的 Linux 發行版,只要選擇 Node 的 Alpine 版本,就會有很大改進,我們把這一句改成指令改成 FROM node:14.17.4-alpine
(可以去 dockerhub 查看 node 有哪些版本標簽),build 后鏡像大小如下圖,瞬間從 1.06G 降到 238M,可以說是效果顯著
還可以使用其它的基礎小鏡像,比如 mhart/alpine-node,這個還能再小,改成 FROM mhart/alpine-node:14.17.3
再試試,可以看到又小了 5M ,雖然不多,但是秉著能壓榨一點是一點的“老板原則”,積少成多,極致壓榨
既然 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 了,又小了不少
結論就是不嫌麻煩追求極致就用方案二,從 1.06G 減少到 174M
減少層數、不經常變動的層提到前面去
ENV
指令是可以一次性設置多個環境變量,能一次指令執行完,就不用兩次,多一個指令就多一層
EXPOSE
指令是暴露端口,其實也可以不用寫這個指令,在啟動容器的時候自己映射端口,如果寫了這個指令的話,因為端口不經常變,所以把這個指令提前,寫上這個指令有兩個好處:
至于寫還是不寫,看個人吧,我個人一般不寫,因為我在項目啟動命令會指定項目端口,啟動容器的時候映射出來就好,這樣我就要維護一個地方,Dockerfile 也寫了的話,項目端口變了,這里也要修改,多了點維護成本,當然也有辦法讓兩邊端口變量取自配置文件,只要改配置文件即可
幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射
在運行時使用隨機端口映射時,也就是 docker run -P
時,會自動隨機映射 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 # 設置環境變量 ENV NODE_ENV=production \ APP_PATH=/node/app # 設置工作目錄 WORKDIR $APP_PATH # 把當前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝 COPY . $APP_PATH # 安裝依賴 RUN yarn # 啟動命令 CMD yarn start
這一步的優化,無論從鏡像大小還是構建鏡像速度都看不到明顯的差別,因為改動的層內容少(體現不出來),但是可以查看到鏡像的層是變少了的,可以自行試試查看鏡像的層試試
減少鏡像層數是“好老板”的傳統優良習慣,不讓“員工”浪費資源
package.json 提前提高編譯速度
從下圖可以看到每次我們 build 的時候最耗時的就是在執行 yarn
命令裝依賴的時候,大部分時候我們只是改代碼,依賴不變,這時候如果可以讓這一步緩存起來,依賴沒有變化的時候,就不需要重新裝依賴,就可以大大改進編譯速度
前面我們說了鏡像構建時,是一層層構建,前一層是后一層的基礎,既然是這樣的話,我們就把 package.json 文件單獨提前拷貝到鏡像,然后下一步裝依賴,執行命令裝依賴這層的前一層是拷貝 package.json 文件,因為安裝依賴命令不會變化,所以只要 package.json 文件沒變化,就不會重新執行 yarn
安裝依賴,它會復用之前安裝好的依賴,原理講清楚了,下面我們看效果
改變后的 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 # 設置環境變量 ENV NODE_ENV=production \ APP_PATH=/node/app # 設置工作目錄 WORKDIR $APP_PATH # 拷貝 package.json 到工作跟目錄下 COPY package.json . # 安裝依賴 RUN yarn # 把當前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝 COPY . . # 啟動命令 CMD yarn start
build 看下圖,編譯時間從 29.6s 到 1.3s,使用了緩存的層前面會有個 CACHED 字眼,仔細看下圖可以看到
使用多階段構建再次壓榨鏡像大小
多階段構建這里不多說了,不了解的可以先搜相關資料了解
因為我們運行 node 程序是只需要生產的依賴和最終 node 可以運行的文件,就是說我們運行項目只需要 package.js 文件里 dependencies 里的依賴,devDependencies 依賴只是編譯階段用的,比如 eslint 等這些工具在項目運行時是用不到的,再比如我們項目是用 typescript 寫的,node 是不能直接運行 ts 文件,ts 文件需要編譯成 js 文件,運行項目我們只需要編譯后的文件和 dependencies 里的依賴就可以運行,也就是說最終鏡像只需要我們需要的東西,任何其他東西都可以刪掉,下面我們使用多階段改寫 Dockerfile
# 構建基礎鏡像 FROM alpine:3.14 AS base # 設置環境變量 ENV NODE_ENV=production \ APP_PATH=/node/app # 設置工作目錄 WORKDIR $APP_PATH # 安裝 nodejs 和 yarn RUN apk add --no-cache --update nodejs=14.17.4-r0 yarn=1.22.10-r0 # 使用基礎鏡像 裝依賴階段 FROM base AS install # 拷貝 package.json 到工作跟目錄下 COPY package.json ./ # 安裝依賴 RUN yarn # 最終階段,也就是輸出的鏡像是這個階段構建的,前面的階段都是為這個階段做鋪墊 FROM base # 拷貝 裝依賴階段 生成的 node_modules 文件夾到工作目錄下 COPY --from=install $APP_PATH/node_modules ./node_modules # 將當前目錄下的所有文件(除了.dockerignore排除的路徑),都拷貝進入鏡像的工作目錄下 COPY . . # 啟動 CMD yarn start
細心的朋友會發現我這里有指定 alpine 版本,而上面都是用的 latest 版本,因為就在剛剛發現有個坑需要注意下,就是我們選擇 alpine 版本的時候,最好不要選擇 latest 版本,因為后面要裝的軟件版本可能會在 alpine 的 latest 版本沒有對應軟件的版本號,就會安裝錯誤,我剛剛就翻車了,點擊查看 alpine 版本下的包信息
build 后,我們看看鏡像大小,上次的是 174M 再次降到 73.4M,極致壓榨。鏡像:”放過我把,我真的沒有了“
講解:
我把這個構建分成了三個階段:
第一階段:構建基礎鏡像
安裝依賴、編譯、運行等等階段,就是所有階段共用的東西都在第一階段封到一個基礎鏡像里供其它階段使用,比如設置環境變量、設置工作目錄、安裝 nodejs、yarn 等等
第二階段:裝依賴階段
在這個階段,裝依賴,如果項目需要編譯,可以在這個階段裝依賴編譯好
這里在說下裝依賴的小細節,就是執行 yarn --production
加個 production 參數或者環境變量 NODE_ENV
為 production
,yarn 將不會安裝 devDependencies 中列出的任何軟件包,點我查看官方文檔說明,因為我設置了環境變量所以就沒加這個參數
第三階段:最終使用鏡像
拷貝第二階段安裝的好的依賴文件夾,然后在拷貝代碼文件到工作目錄,執行啟動命令,第二階段裝依賴多出的一些垃圾我們不需要,我們就只拷貝我們要用的東西,大大減少鏡像的大小
如果項目需要編譯,在拷貝編譯后的文件夾,不需要拷貝編譯前的代碼,有編譯后的代碼和依賴就可以跑起項目
多階段構建,最后生成的鏡像只能是最后一個階段的結果,但是,能夠將前置階段中的文件拷貝到后邊的階段中,這就是多階段構建的最大意義。
關于Node.js項目中怎么優化docker鏡像就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。