真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Node服務(wù)如何進(jìn)行Docker鏡像化

本文小編為大家詳細(xì)介紹“Node服務(wù)如何進(jìn)行Docker鏡像化”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Node服務(wù)如何進(jìn)行Docker鏡像化”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。

創(chuàng)新互聯(lián)公司主營義安網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,手機(jī)APP定制開發(fā),義安h5微信小程序開發(fā)搭建,義安網(wǎng)站營銷推廣歡迎義安等地區(qū)企業(yè)咨詢

以一個(gè)例子開頭,大部分剛接觸 Docker 的同學(xué)應(yīng)該都會(huì)這樣編寫項(xiàng)目的 Dockerfile,如下所示:

FROM node:14
WORKDIR /app

COPY . .
# 安裝 npm 依賴
RUN npm install

# 暴露端口
EXPOSE 8000

CMD ["npm", "start"]

構(gòu)建,打包,上傳,一氣呵成。然后看下鏡像狀態(tài),臥槽,一個(gè)簡(jiǎn)單的 node web 服務(wù)體積居然達(dá)到了驚人的 1.3 個(gè) G,并且鏡像傳輸與構(gòu)建速度也很慢:

Node服務(wù)如何進(jìn)行Docker鏡像化

要是這個(gè)鏡像只需要部署一個(gè)實(shí)例也就算了,但是這個(gè)服務(wù)得提供給所有開發(fā)同學(xué)進(jìn)行高頻集成并部署環(huán)境的(實(shí)現(xiàn)高頻集成的方案可參見我的 上一篇文章)。首先,鏡像體積過大必然會(huì)對(duì)鏡像的拉取和更新速度造成影響,集成體驗(yàn)會(huì)變差。其次,項(xiàng)目上線后,同時(shí)在線的測(cè)試環(huán)境實(shí)例可能成千上萬,這樣的容器內(nèi)存占用成本對(duì)于任何一個(gè)項(xiàng)目都是無法接受的。必須找到優(yōu)化的辦法解決。

發(fā)現(xiàn)問題后,我就開始研究 Docker 的優(yōu)化方案,準(zhǔn)備給我的鏡像動(dòng)手術(shù)了。

node 項(xiàng)目生產(chǎn)環(huán)境優(yōu)化

首先開刀的是當(dāng)然是前端最為熟悉的領(lǐng)域,對(duì)代碼本身體積進(jìn)行優(yōu)化。之前開發(fā)項(xiàng)目時(shí)使用了 Typescript,為了圖省事,項(xiàng)目直接使用 tsc 打包生成 es5 后就直接運(yùn)行起來了。這里的體積問題主要有兩個(gè),一個(gè)是開發(fā)環(huán)境 ts 源碼并未處理,并且用于生產(chǎn)環(huán)境的 js 代碼也未經(jīng)壓縮。

Node服務(wù)如何進(jìn)行Docker鏡像化

另一個(gè)是引用的 node_modules 過于臃腫。仍然包含了許多開發(fā)調(diào)試環(huán)境中的 npm 包,如 ts-node,typescript 等等。既然打包成 js 了,這些依賴自然就該去除。

一般來說,由于服務(wù)端代碼不會(huì)像前端代碼一樣暴露出去,運(yùn)行在物理機(jī)上的服務(wù)更多考慮的是穩(wěn)定性,也不在乎多一些體積,因此這些地方一般也不會(huì)做處理。但是 Docker 化后,由于部署規(guī)模變大,這些問題就非常明顯了,在生產(chǎn)環(huán)境下需要優(yōu)化的。

對(duì)于這兩點(diǎn)的優(yōu)化的方式其實(shí)我們前端非常熟悉了,不是本文的重點(diǎn)就粗略帶過了。對(duì)于第一點(diǎn),使用 Webpack + babel 降級(jí)并壓縮 Typescript 源碼,如果擔(dān)心錯(cuò)誤排查可以加上 sourcemap,不過對(duì)于 docker 鏡像來說有點(diǎn)多余,一會(huì)兒會(huì)說到。對(duì)于第二點(diǎn),梳理 npm 包的 dependencies 與 devDependencies 依賴,去除不是必要存在于運(yùn)行時(shí)的依賴,方便生產(chǎn)環(huán)境使用 npm install --production 安裝依賴。

優(yōu)化項(xiàng)目鏡像體積

使用盡量精簡(jiǎn)的基礎(chǔ)鏡像

我們知道,容器技術(shù)提供的是操作系統(tǒng)級(jí)別的進(jìn)程隔離,Docker 容器本身是一個(gè)運(yùn)行在獨(dú)立操作系統(tǒng)下的進(jìn)程,也就是說,Docker 鏡像需要打包的是一個(gè)能夠獨(dú)立運(yùn)行的操作系統(tǒng)級(jí)環(huán)境。因此,決定鏡像體積的一個(gè)重要因素就顯而易見了:打包進(jìn)鏡像的 Linux 操作系統(tǒng)的體積。

一般來說,減小依賴的操作系統(tǒng)的大小主要需要考慮從兩個(gè)方面下手,第一個(gè)是盡可能去除 Linux 下不需要的各類工具庫,如 python,cmake, telnet 等。第二個(gè)是選取更輕量級(jí)的 Linux 發(fā)行版系統(tǒng)。正規(guī)的官方鏡像應(yīng)該會(huì)依據(jù)上述兩個(gè)因素對(duì)每個(gè)發(fā)行版提供閹割版本。

以 node 官方提供的版本 node:14 為例,默認(rèn)版本中,它的運(yùn)行基礎(chǔ)環(huán)境是 Ubuntu,是一個(gè)大而全的 Linux 發(fā)行版,以保證最大的兼容性。去除了無用工具庫的依賴版本稱為 node:14-slim 版本。而最小的鏡像發(fā)行版稱為 node:14-alpine。Linux alpine 是一個(gè)高度精簡(jiǎn),僅包含基本工具的輕量級(jí) Linux 發(fā)行版,本身的 Docker 鏡像只有 4~5M 大小,因此非常適合制作最小版本的 Docker 鏡像。

在我們的服務(wù)中,由于運(yùn)行該服務(wù)的依賴是確定的,因此為了盡可能的縮減基礎(chǔ)鏡像的體積,我們選擇 alpine 版本作為生產(chǎn)環(huán)境的基礎(chǔ)鏡像。

分級(jí)構(gòu)建

這時(shí)候,我們遇到了新的問題。由于 alpine 的基本工具庫過于簡(jiǎn)陋,而像 webpack 這樣的打包工具背后可能使用的插件庫極多,構(gòu)建項(xiàng)目時(shí)對(duì)環(huán)境的依賴較大。并且這些工具庫只有編譯時(shí)需要用到,在運(yùn)行時(shí)是可以去除的。對(duì)于這種情況,我們可以利用 Docker 的分級(jí)構(gòu)建的特性來解決這一問題。

首先,我們可以在完整版鏡像下進(jìn)行依賴安裝,并給該任務(wù)設(shè)立一個(gè)別名(此處為build)。

# 安裝完整依賴并構(gòu)建產(chǎn)物
FROM node:14 AS build
WORKDIR /app

COPY package*.json /app/
RUN ["npm", "install"]
COPY . /app/

RUN npm run build

之后我們可以啟用另一個(gè)鏡像任務(wù)來運(yùn)行生產(chǎn)環(huán)境,生產(chǎn)的基礎(chǔ)鏡像就可以換成 alpine 版本了。其中編譯完成后的源碼可以通過--from參數(shù)獲取到處于build任務(wù)中的文件,移動(dòng)到此任務(wù)內(nèi)。

FROM node:14-alpine AS release
WORKDIR /release

COPY package*.json /
RUN ["npm", "install", "--registry=http://r.tnpm.oa.com", "--production"]

# 移入依賴與源碼
COPY public /release/public
COPY --from=build /app/dist /release/dist

# 啟動(dòng)服務(wù)
EXPOSE 8000

CMD ["node", "./dist/index.js"]

Docker 鏡像的生成規(guī)則是,生成鏡像的結(jié)果僅以最后一個(gè)鏡像任務(wù)為準(zhǔn)。因此前面的任務(wù)并不會(huì)占用最終鏡像的體積,從而完美解決這一問題。

當(dāng)然,隨著項(xiàng)目越來越復(fù)雜,在運(yùn)行時(shí)仍可能會(huì)遇到工具庫報(bào)錯(cuò),如果曝出問題的工具庫所需依賴不多,我們可以自行補(bǔ)充所需的依賴,這樣的鏡像體積仍然能保持較小的水平。

其中最常見的問題就是對(duì)node-gypnode-sass庫的引用。由于這個(gè)庫是用來將其他語言編寫的模塊轉(zhuǎn)譯為 node 模塊,因此,我們需要手動(dòng)增加g++ make python這三個(gè)依賴。

# 安裝生產(chǎn)環(huán)境依賴(為兼容 node-gyp 所需環(huán)境需要對(duì) alpine 進(jìn)行改造)
FROM node:14-alpine AS dependencies

RUN apk add --no-cache python make g++
COPY package*.json /
RUN ["npm", "install", "--registry=http://r.tnpm.oa.com", "--production"]
RUN apk del .gyp

合理規(guī)劃 Docker Layer

構(gòu)建速度優(yōu)化

我們知道,Docker 使用 Layer 概念來創(chuàng)建與組織鏡像,Dockerfile 的每條指令都會(huì)產(chǎn)生一個(gè)新的文件層,每層都包含執(zhí)行命令前后的狀態(tài)之間鏡像的文件系統(tǒng)更改,文件層越多,鏡像體積就越大。而 Docker 使用緩存方式實(shí)現(xiàn)了構(gòu)建速度的提升。若 Dockerfile 中某層的語句及依賴未更改,則該層重建時(shí)可以直接復(fù)用本地緩存。

如下所示,如果 log 中出現(xiàn)Using cache字樣時(shí),說明緩存生效了,該層將不會(huì)執(zhí)行運(yùn)算,直接拿原緩存作為該層的輸出結(jié)果。

Step 2/3 : npm install
 ---> Using cache
 ---> efvbf79sd1eb

通過研究 Docker 緩存算法,發(fā)現(xiàn)在 Docker 構(gòu)建過程中,如果某層無法應(yīng)用緩存,則依賴此步的后續(xù)層都不能從緩存加載。例如下面這個(gè)例子:

COPY . .
RUN npm install

此時(shí)如果我們更改了倉庫的任意一個(gè)文件,此時(shí)因?yàn)?code>npm install層的上層依賴變更了,哪怕依賴沒有進(jìn)行任何變動(dòng),緩存也不會(huì)被復(fù)用。

因此,若想盡可能的利用上npm install層緩存,我們可以把 Dockerfile 改成這樣:

COPY package*.json .
RUN npm install
COPY src .

這樣在僅變更源碼時(shí),node_modules的依賴緩存仍然能被利用上了。

由此,我們得到了優(yōu)化原則:

  • 最小化處理變更文件,僅變更下一步所需的文件,以盡可能減少構(gòu)建過程中的緩存失效。

  • 對(duì)于處理文件變更的 ADD 命令、COPY 命令,盡量延遲執(zhí)行。

構(gòu)建體積優(yōu)化

在保證速度的前提下,體積優(yōu)化也是我們需要去考慮的。這里我們需要考慮的有三點(diǎn):

  • Docker 是以層為單位上傳鏡像倉庫的,這樣也能最大化的利用緩存的能力。因此,執(zhí)行結(jié)果很少變化的命令需要抽出來單獨(dú)成層,如上面提到的npm install的例子里,也用到了這方面的思想。

  • 如果鏡像層數(shù)越少,總上傳體積就越小。因此,在命令處于執(zhí)行鏈尾部,即不會(huì)對(duì)其他層緩存產(chǎn)生影響的情況下,盡量合并命令,從而減少緩存體積。例如,設(shè)置環(huán)境變量和清理無用文件的指令,它們的輸出都是不會(huì)被使用的,因此可以將這些命令合并為一行 RUN 命令。

RUN set ENV=prod && rm -rf ./trash

  1. Docker cache 的下載也是通過層緩存的方式,因此為了減少鏡像的傳輸下載時(shí)間,我們最好使用固定的物理機(jī)器來進(jìn)行構(gòu)建。例如在流水線中指定專用宿主機(jī),能是的鏡像的準(zhǔn)備時(shí)間大大減少。

當(dāng)然,時(shí)間和空間的優(yōu)化從來就沒有兩全其美的辦法,這一點(diǎn)需要我們?cè)谠O(shè)計(jì) Dockerfile 時(shí),對(duì) Docker Layer 層數(shù)做出權(quán)衡。例如為了時(shí)間優(yōu)化,需要我們拆分文件的復(fù)制等操作,而這一點(diǎn)會(huì)導(dǎo)致層數(shù)增多,略微增加空間。

這里我的建議是,優(yōu)先保證構(gòu)建時(shí)間,其次在不影響時(shí)間的情況下,盡可能的縮小構(gòu)建緩存體積。

以 Docker 的思維管理服務(wù)

避免使用進(jìn)程守護(hù)

我們編寫傳統(tǒng)的后臺(tái)服務(wù)時(shí),總是會(huì)使用例如 pm2、forever 等等進(jìn)程守護(hù)程序,以保證服務(wù)在意外崩潰時(shí)能被監(jiān)測(cè)到并自動(dòng)重啟。但這一點(diǎn)在 Docker 下非但沒有益處,還帶來了額外的不穩(wěn)定因素。

首先,Docker 本身就是一個(gè)流程管理器,因此,進(jìn)程守護(hù)程序提供的崩潰重啟,日志記錄等等工作 Docker 本身或是基于 Docker 的編排程序(如 kubernetes)就能提供了,無需使用額外應(yīng)用實(shí)現(xiàn)。除此之外,由于守護(hù)進(jìn)程的特性,將不可避免的對(duì)于以下的情況產(chǎn)生影響:

  • 增加進(jìn)程守護(hù)程序會(huì)使得占用的內(nèi)存增多,鏡像體積也會(huì)相應(yīng)增大。

  • 由于守護(hù)進(jìn)程一直能正常運(yùn)行,服務(wù)發(fā)生故障時(shí),Docker 自身的重啟策略將不會(huì)生效,Docker 日志里將不會(huì)記錄崩潰信息,排障溯源困難。

  • 由于多了個(gè)進(jìn)程的加入,Docker 提供的 CPU、內(nèi)存等監(jiān)控指標(biāo)將變得不準(zhǔn)確。

因此,盡管 pm2 這樣的進(jìn)程守護(hù)程序提供了能夠適配 Docker 的版本:pm2-runtime,但我仍然不推薦大家使用進(jìn)程守護(hù)程序。

其實(shí)這一點(diǎn)其實(shí)是源自于我們的固有思想而犯下的錯(cuò)誤。在服務(wù)上云的過程中,難點(diǎn)其實(shí)不僅僅在于寫法與架構(gòu)上的調(diào)整,開發(fā)思路的轉(zhuǎn)變才是最重要的,我們會(huì)在上云的過程中更加深刻體會(huì)到這一點(diǎn)。

日志的持久化存儲(chǔ)

無論是為了排障還是審計(jì)的需要,后臺(tái)服務(wù)總是需要日志能力。按照以往的思路,我們將日志分好類后,統(tǒng)一寫入某個(gè)目錄下的日志文件即可。但是在 Docker 中,任何本地文件都不是持久化的,會(huì)隨著容器的生命周期結(jié)束而銷毀。因此,我們需要將日志的存儲(chǔ)跳出容器之外。

最簡(jiǎn)單的做法是利用 Docker Manager Volume,這個(gè)特性能繞過容器自身的文件系統(tǒng),直接將數(shù)據(jù)寫到宿主物理機(jī)器上。具體用法如下:

docker run -d -it --name=app -v /app/log:/usr/share/log app

運(yùn)行 docker 時(shí),通過-v 參數(shù)為容器綁定 volumes,將宿主機(jī)上的 /app/log 目錄(如果沒有會(huì)自動(dòng)創(chuàng)建)掛載到容器的 /usr/share/log 中。這樣服務(wù)在將日志寫入該文件夾時(shí),就能持久化存儲(chǔ)在宿主機(jī)上,不隨著 docker 的銷毀而丟失了。

當(dāng)然,當(dāng)部署集群變多后,物理宿主機(jī)上的日志也會(huì)變得難以管理。此時(shí)就需要一個(gè)服務(wù)編排系統(tǒng)來統(tǒng)一管理了。從單純管理日志的角度出發(fā),我們可以進(jìn)行網(wǎng)絡(luò)上報(bào),給到云日志服務(wù)(如騰訊云 CLS)托管?;蛘吒纱鄬⑷萜鬟M(jìn)行批量管理,例如Kubernetes這樣的容器編排系統(tǒng),這樣日志作為其中的一個(gè)模塊自然也能得到妥善保管了。這樣的方法很多,就不多加贅述了。

k8s 服務(wù)控制器的選擇

鏡像優(yōu)化之外,服務(wù)編排以及控制部署的負(fù)載形式對(duì)性能的影響也很大。這里以最流行的Kubernetes的兩種控制器(Controller):DeploymentStatefulSet 為例,簡(jiǎn)要比較一下這兩類組織形式,幫助選擇出最適合服務(wù)的 Controller。

StatefulSet是 K8S 在 1.5 版本后引入的 Controller,主要特點(diǎn)為:能夠?qū)崿F(xiàn) pod 間的有序部署、更新和銷毀。那么我們的制品是否需要使用 StatefulSet 做 pod 管理呢?官方簡(jiǎn)要概括為一句話:

Deployment 用于部署無狀態(tài)服務(wù),StatefulSet 用來部署有狀態(tài)服務(wù)。

這句話十分精確,但不易于理解。那么,什么是無狀態(tài)呢?在我看來,StatefulSet的特點(diǎn)可以從如下幾個(gè)步驟進(jìn)行理解:

  • StatefulSet管理的多個(gè) pod 之間進(jìn)行部署,更新,刪除操作時(shí)能夠按照固定順序依次進(jìn)行。適用于多服務(wù)之間有依賴的情況,如先啟動(dòng)數(shù)據(jù)庫服務(wù)再開啟查詢服務(wù)。

  • 由于 pod 之間有依賴關(guān)系,因此每個(gè) pod 提供的服務(wù)必定不同,所以 StatefulSet 管理的 pod 之間沒有負(fù)載均衡的能力。

  • 又因?yàn)?pod 提供的服務(wù)不同,所以每個(gè) pod 都會(huì)有自己獨(dú)立的存儲(chǔ)空間,pod 間不共享。

  • 為了保證 pod 部署更新時(shí)順序,必須固定 pod 的名稱,因此不像 Deployment 那樣生成的 pod 名稱后會(huì)帶一串隨機(jī)數(shù)。

  • 而由于 pod 名稱固定,因此跟 StatefulSet 對(duì)接的 Service 中可以直接以 pod 名稱作為訪問域名,而不需要提供Cluster IP,因此跟 StatefulSet 對(duì)接的 Service 被稱為 Headless Service。

通過這里我們就應(yīng)該明白,如果在 k8s 上部署的是單個(gè)服務(wù),或是多服務(wù)間沒有依賴關(guān)系,那么 Deployment 一定是簡(jiǎn)單而又效果最佳的選擇,自動(dòng)調(diào)度,自動(dòng)負(fù)載均衡。而如果服務(wù)的啟停必須滿足一定順序,或者每一個(gè) pod 所掛載的數(shù)據(jù) volume 需要在銷毀后依然存在,那么建議選擇 StatefulSet。

本著如無必要,勿增實(shí)體的原則,強(qiáng)烈建議所有運(yùn)行單個(gè)服務(wù)工作負(fù)載采用 Deployment 作為 Controller。

讀到這里,這篇“Node服務(wù)如何進(jìn)行Docker鏡像化”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。


當(dāng)前標(biāo)題:Node服務(wù)如何進(jìn)行Docker鏡像化
網(wǎng)頁路徑:http://weahome.cn/article/jsghjp.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部