溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Node.js項目中怎么優(yōu)化docker鏡像

發(fā)布時間:2021-08-03 10:44:29 來源:億速云 閱讀:194 作者:Leah 欄目:web開發(fā)

這篇文章給大家介紹Node.js項目中怎么優(yōu)化docker鏡像,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

Node 項目

簡單寫了一個自己用的 wechat-bot ,接下來就以這個項目演示怎么去優(yōu)化 Docker 鏡像

以下是我沒有仔細研究 Docker 剛開始寫的 Dockerfile 文件

FROM node:14.17.3

# 設置環(huán)境變量
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 多,接下來我們將逐步去優(yōu)化減少這個大小

Node.js項目中怎么優(yōu)化docker鏡像

優(yōu)化前言

在優(yōu)化之前,有些東西我們必須了解,解決問題的第一步就是先找出導致問題的原因

  • Dockerfile 文件,其內(nèi)包含了一條條的指令,每一條指令構(gòu)建一層,因此每一條指令的內(nèi)容,就是描述該層如何構(gòu)建

  • Docker 鏡像并非只是一個文件,而是由一堆文件組成,最主要的文件是 (Layers)

    參考資料:docker 鏡像分層原理

    • 鏡像構(gòu)建時,會一層層構(gòu)建,前一層是后一層的基礎

      每一層構(gòu)建完就不會再發(fā)生改變,后一層上的任何改變只發(fā)生在自己這一層。比如,刪除前一層文件的操作,實際不是真的刪除前一層的文件,而是僅在當前層標記為該文件已刪除。在最終容器運行的時候,雖然不會看到這個文件,但是實際上該文件會一直跟隨鏡像

    • 鏡像層將會被緩存和復用(這也是從第二次開始構(gòu)建鏡像時,速度會快的原因,優(yōu)化鏡像構(gòu)建速度的原理也是利用緩存原理來做)

    • 當 Dockerfile 的指令修改了,操作的文件變化了,或者構(gòu)建鏡像時指定的變量不同了,對應的鏡像層緩存就會失效

      docker build 的緩存機制,docker 是怎么知道文件變化的呢?

      Docker 采取的策略是:獲取 Dockerfile 下內(nèi)容(包括文件的部分 inode 信息),計算出一個唯一的 hash 值,若 hash 值未發(fā)生變化,則可以認為文件內(nèi)容沒有發(fā)生變化,可以使用緩存機制,反之亦然。

    • 某一層的鏡像緩存失效之后,它之后的鏡像層緩存都會失效

    • 鏡像的每一層只記錄文件變更,在容器啟動時,Docker 會將鏡像的各個層進行計算,最后生成一個文件系統(tǒng)

      當我知道這點時,我恍然大悟,我們使用的操作系統(tǒng),比如安卓、ios、win、mac 等,其實就是一個文件系統(tǒng),我們的軟件界面交互等,其實就是在讀寫文件,我們網(wǎng)頁寫個彈框,操作 dom,就是在讀寫本地文件或者是讀寫內(nèi)存里的數(shù)據(jù),個人的一些見解不知道對不對,本人非科班出身的前端 coder

優(yōu)化 Dockerfile

優(yōu)化第一層 FROM node:14.17.3

方案一:使用 node 的 Alpine 版本

這也是絕多數(shù)人知道的優(yōu)化鏡像手段,Alpine 是一個很小的 Linux 發(fā)行版,只要選擇 Node 的 Alpine 版本,就會有很大改進,我們把這一句改成指令改成 FROM node:14.17.4-alpine (可以去 dockerhub 查看 node 有哪些版本標簽),build 后鏡像大小如下圖,瞬間從 1.06G 降到 238M,可以說是效果顯著

Node.js項目中怎么優(yōu)化docker鏡像

還可以使用其它的基礎小鏡像,比如 mhart/alpine-node,這個還能再小,改成 FROM mhart/alpine-node:14.17.3 再試試,可以看到又小了 5M ,雖然不多,但是秉著能壓榨一點是一點的“老板原則”,積少成多,極致壓榨

Node.js項目中怎么優(yōu)化docker鏡像

方案二:使用純凈 Alpine 鏡像手動裝 Node

既然 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 了,又小了不少

Node.js項目中怎么優(yōu)化docker鏡像

結(jié)論就是不嫌麻煩追求極致就用方案二,從 1.06G 減少到 174M

減少層數(shù)、不經(jīng)常變動的層提到前面去

  • ENV 指令是可以一次性設置多個環(huán)境變量,能一次指令執(zhí)行完,就不用兩次,多一個指令就多一層

  • EXPOSE 指令是暴露端口,其實也可以不用寫這個指令,在啟動容器的時候自己映射端口,如果寫了這個指令的話,因為端口不經(jīng)常變,所以把這個指令提前,寫上這個指令有兩個好處:

    至于寫還是不寫,看個人吧,我個人一般不寫,因為我在項目啟動命令會指定項目端口,啟動容器的時候映射出來就好,這樣我就要維護一個地方,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

# 設置環(huán)境變量
ENV NODE_ENV=production \
    APP_PATH=/node/app

# 設置工作目錄
WORKDIR $APP_PATH

# 把當前目錄下的所有文件拷貝到鏡像的工作目錄下 .dockerignore 指定的文件不會拷貝
COPY . $APP_PATH

# 安裝依賴
RUN yarn

# 啟動命令
CMD yarn start

這一步的優(yōu)化,無論從鏡像大小還是構(gòu)建鏡像速度都看不到明顯的差別,因為改動的層內(nèi)容少(體現(xiàn)不出來),但是可以查看到鏡像的層是變少了的,可以自行試試查看鏡像的層試試

減少鏡像層數(shù)是“好老板”的傳統(tǒng)優(yōu)良習慣,不讓“員工”浪費資源

package.json 提前提高編譯速度

從下圖可以看到每次我們 build 的時候最耗時的就是在執(zhí)行 yarn 命令裝依賴的時候,大部分時候我們只是改代碼,依賴不變,這時候如果可以讓這一步緩存起來,依賴沒有變化的時候,就不需要重新裝依賴,就可以大大改進編譯速度

Node.js項目中怎么優(yōu)化docker鏡像

前面我們說了鏡像構(gòu)建時,是一層層構(gòu)建,前一層是后一層的基礎,既然是這樣的話,我們就把 package.json 文件單獨提前拷貝到鏡像,然后下一步裝依賴,執(zhí)行命令裝依賴這層的前一層是拷貝 package.json 文件,因為安裝依賴命令不會變化,所以只要 package.json 文件沒變化,就不會重新執(zhí)行 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

# 設置環(huán)境變量
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.js項目中怎么優(yōu)化docker鏡像

使用多階段構(gòu)建再次壓榨鏡像大小

多階段構(gòu)建這里不多說了,不了解的可以先搜相關資料了解

因為我們運行 node 程序是只需要生產(chǎn)的依賴和最終 node 可以運行的文件,就是說我們運行項目只需要 package.js 文件里 dependencies 里的依賴,devDependencies 依賴只是編譯階段用的,比如 eslint 等這些工具在項目運行時是用不到的,再比如我們項目是用 typescript 寫的,node 是不能直接運行 ts 文件,ts 文件需要編譯成 js 文件,運行項目我們只需要編譯后的文件和 dependencies 里的依賴就可以運行,也就是說最終鏡像只需要我們需要的東西,任何其他東西都可以刪掉,下面我們使用多階段改寫 Dockerfile

# 構(gòu)建基礎鏡像
    FROM alpine:3.14 AS base

    # 設置環(huán)境變量
    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

# 最終階段,也就是輸出的鏡像是這個階段構(gòu)建的,前面的階段都是為這個階段做鋪墊
    FROM base

    # 拷貝 裝依賴階段 生成的 node_modules 文件夾到工作目錄下
    COPY --from=install $APP_PATH/node_modules ./node_modules

    # 將當前目錄下的所有文件(除了.dockerignore排除的路徑),都拷貝進入鏡像的工作目錄下
    COPY . .

    # 啟動
    CMD yarn start

細心的朋友會發(fā)現(xiàn)我這里有指定 alpine 版本,而上面都是用的 latest 版本,因為就在剛剛發(fā)現(xiàn)有個坑需要注意下,就是我們選擇 alpine 版本的時候,最好不要選擇 latest 版本,因為后面要裝的軟件版本可能會在 alpine 的 latest 版本沒有對應軟件的版本號,就會安裝錯誤,我剛剛就翻車了,點擊查看 alpine 版本下的包信息

Node.js項目中怎么優(yōu)化docker鏡像

build 后,我們看看鏡像大小,上次的是 174M 再次降到 73.4M,極致壓榨。鏡像:”放過我把,我真的沒有了“

Node.js項目中怎么優(yōu)化docker鏡像

講解:

我把這個構(gòu)建分成了三個階段:

  • 第一階段:構(gòu)建基礎鏡像

    安裝依賴、編譯、運行等等階段,就是所有階段共用的東西都在第一階段封到一個基礎鏡像里供其它階段使用,比如設置環(huán)境變量、設置工作目錄、安裝 nodejs、yarn 等等

  • 第二階段:裝依賴階段

    在這個階段,裝依賴,如果項目需要編譯,可以在這個階段裝依賴編譯好

    這里在說下裝依賴的小細節(jié),就是執(zhí)行 yarn --production 加個 production 參數(shù)或者環(huán)境變量 NODE_ENVproduction,yarn 將不會安裝 devDependencies 中列出的任何軟件包,點我查看官方文檔說明,因為我設置了環(huán)境變量所以就沒加這個參數(shù)

  • 第三階段:最終使用鏡像

    拷貝第二階段安裝的好的依賴文件夾,然后在拷貝代碼文件到工作目錄,執(zhí)行啟動命令,第二階段裝依賴多出的一些垃圾我們不需要,我們就只拷貝我們要用的東西,大大減少鏡像的大小

    如果項目需要編譯,在拷貝編譯后的文件夾,不需要拷貝編譯前的代碼,有編譯后的代碼和依賴就可以跑起項目

多階段構(gòu)建,最后生成的鏡像只能是最后一個階段的結(jié)果,但是,能夠?qū)⑶爸秒A段中的文件拷貝到后邊的階段中,這就是多階段構(gòu)建的最大意義。

關于Node.js項目中怎么優(yōu)化docker鏡像就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI