您好,登錄后才能下訂單哦!
這篇文章主要介紹了Node.js中如何使用基于容器的一站式命令行工具鏈,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
由于工程數(shù)量的快速增長(zhǎng),個(gè)推在實(shí)踐基于 Node.js 的微服務(wù)開(kāi)發(fā)的過(guò)程中,遇到了如下問(wèn)題:
每次新建項(xiàng)目都需要安裝一次依賴,這些依賴之間基本相似卻又有微妙的區(qū)別;
每次新建項(xiàng)目都要配置一遍相似的配置(比如 tsconfig、lint 規(guī)則等);
本地 Mac 環(huán)境與線上 Docker 內(nèi)的 Linux 環(huán)境不一致(尤其是有 C++ 依賴的情況)。
為了解決上述問(wèn)題,個(gè)推內(nèi)部開(kāi)發(fā)了一個(gè)命令行小工具來(lái)標(biāo)準(zhǔn)化項(xiàng)目初始化流程、簡(jiǎn)化配置甚至是零配置,提供基于 Docker 的一致構(gòu)建、運(yùn)行環(huán)境。
新建一個(gè) Node.js 項(xiàng)目的時(shí)候,我們一般會(huì):
安裝許多開(kāi)發(fā)依賴:TypeScript、Jest、TSLint、benchmark、typedoc 等;
配置 tsconfig、lint 規(guī)則、.prettierrc 等;
安裝眾多項(xiàng)目依賴:koa、lodash、sequelize、ioredis、zipkin、node-fetch 等;
初始化目錄結(jié)構(gòu);
配置CI 腳本。
通常,我們會(huì)選擇復(fù)制一個(gè)現(xiàn)成的項(xiàng)目進(jìn)行修改,導(dǎo)致出現(xiàn)眾多看似相似卻又不完全相同的項(xiàng)目,比如十個(gè)項(xiàng)目可能會(huì)對(duì)應(yīng)十種配置組合。對(duì)于同時(shí)跨多個(gè)工程的開(kāi)發(fā)人員來(lái)說(shuō),眾多配置組合會(huì)增加他們的工作難度。而且,當(dāng)安全審計(jì)發(fā)現(xiàn)某些 npm package 出現(xiàn)安全隱患時(shí),開(kāi)發(fā)人員則需要對(duì)每個(gè)引用這些包的項(xiàng)目逐一檢查和修正。
在確定的開(kāi)發(fā)場(chǎng)景下,幾乎所有項(xiàng)目的開(kāi)發(fā)依賴都差不多,開(kāi)發(fā)配置也非常相似,因此我們基于 commander.js 寫了一個(gè) init 工具,它會(huì)開(kāi)個(gè)命令行的向?qū)?,自?dòng)安裝依賴、初始化項(xiàng)目目錄結(jié)構(gòu)和配置。從而創(chuàng)建項(xiàng)目,并按照?qǐng)鼍皩⑺信渲檬湛s為特定幾種模板,進(jìn)行統(tǒng)一處理。
隨后,我們有了 build、test、pack 命令,托管了 tsconfig、jest 配置、打包配置,自動(dòng)調(diào)用 tsc 編譯,構(gòu)建測(cè)試環(huán)境,然后調(diào)用 Jest 進(jìn)行測(cè)試,進(jìn)行標(biāo)準(zhǔn)化打包, CI 腳本基本可以簡(jiǎn)化為幾行標(biāo)準(zhǔn)腳本。
在介紹這個(gè)命令前需要先簡(jiǎn)單了解一下個(gè)推的鏡像體系:
前面提到我們將大部分依賴封裝到了一個(gè) npm 包,這一層封裝也反映在個(gè)推的 Docker 鏡像體系內(nèi),可以簡(jiǎn)單表述為下面的 Dockerfile:
# 公共依賴層的 Dockerfile FROM node:10 RUN mkdir -p /usr/local/lib/webnode/node_modules \ && cd /usr/local/lib/webnode \ && npm install webnode ENV NODE_PATH /usr/local/lib/webnode/node_modules # 項(xiàng)目的 Dockerfile FROM getui/webnode:1.2.3 COPY package*.json ./ RUN npm install COPY . .
當(dāng)把這層依賴直接做進(jìn) Docker 鏡像時(shí),雖然每個(gè)鏡像的 SIZE 還是 1G 多,但是每個(gè)鏡像的 UNIQUE SIZE 都是極小的,僅有數(shù)M的差分層。
一個(gè)簡(jiǎn)單的對(duì)比,比如有 800M 公共系統(tǒng)依賴 + 每個(gè)服務(wù)平均 200M 的 npm 依賴 + 1M 的服務(wù)代碼,那么由于原先每個(gè)服務(wù)都會(huì) npm install 大量重復(fù)依賴,20 個(gè)服務(wù),就會(huì)有 800M + 200M 20 + 1M 20 = 4.82G 的總 UNIQUE SIZE。而采用依賴分層共享,則僅有 800M + 200M + 1M * 20 = 1.02G 的總 UNIQUE SIZE。在考慮應(yīng)用的多版本之后,依賴分層共享帶來(lái)在存儲(chǔ)上的優(yōu)勢(shì)會(huì)更加明顯。
我們以一定的依賴鎖定周期和控制為代價(jià),換取了:
減少依賴組合、依賴版本組合的可能性,開(kāi)發(fā)者選擇包的簡(jiǎn)化、初始化項(xiàng)目的簡(jiǎn)化;審計(jì)簡(jiǎn)化、安全更新簡(jiǎn)化 。
CI 顯著提速,節(jié)省等待時(shí)間。
傳輸和存儲(chǔ)的壓力減少許多。
公共依賴被多個(gè)項(xiàng)目使用,得到了更加充分的測(cè)試。
webnode docker build 命令可以幫助簡(jiǎn)化 Docker image 的構(gòu)建過(guò)程,它內(nèi)置了一個(gè) Dockerfile 和dockerignore,該命令運(yùn)行時(shí),會(huì)基于這兩個(gè)文件和當(dāng)前的 Context,自動(dòng)構(gòu)建docker 鏡像。其中 Dockerfile 內(nèi)含一些優(yōu)化和我們的最佳實(shí)踐,開(kāi)發(fā)人員只需要專注 Node.js 的項(xiàng)目的開(kāi)發(fā),這個(gè)命令則可以負(fù)責(zé)配置文件權(quán)限等操作以及生成標(biāo)準(zhǔn)化的、優(yōu)化的 Docker 鏡像。
其設(shè)計(jì)目標(biāo)是:
快:合理的依賴分層,最大程度應(yīng)用 Docker 緩存機(jī)制,通過(guò) .dockerignore 裁剪不必要的 Context,因此可以實(shí)現(xiàn)飛快的構(gòu)建速度 。
?。阂罁?jù)變更頻度做 Docker 分層設(shè)計(jì)、應(yīng)用 multi-stage build,盡最大可能縮小一個(gè)鏡像的 UNIQUE SIZE 。
可重現(xiàn):同樣的內(nèi)容總是構(gòu)建出相同的結(jié)果。
以 node_modules 依賴優(yōu)化為例,下面兩種 Dockerfile 其實(shí)會(huì)有很大的區(qū)別:
FROM getui/webnode:1.2.3 COPY . . RUN npm install FROM getui/webnode:1.2.3 COPY package*.json ./ RUN npm install COPY . .
前者,每次 docker build 時(shí),只要項(xiàng)目?jī)?nèi)任何代碼變了,npm install 的緩存都會(huì)失效,需要重新安裝,而后者僅當(dāng) package*.json 發(fā)生改變之時(shí)才會(huì)觸發(fā)重新 npm install。另外,我們還會(huì)對(duì) package.json 進(jìn)行預(yù)編譯,僅保留依賴相關(guān)的字段,避免出現(xiàn)修改 package.json 的版本號(hào)就重新 npm install的情況。
webnode docker build 不僅可以幫助開(kāi)發(fā)者進(jìn)行統(tǒng)一化的鏡像構(gòu)建、統(tǒng)一實(shí)踐最佳優(yōu)化,節(jié)約資源,還能避免所有開(kāi)發(fā)人員都需要接觸優(yōu)化細(xì)節(jié),省時(shí)省力。
在本地調(diào)試開(kāi)發(fā)的過(guò)程中,我們遇到了一些環(huán)境差異引起的問(wèn)題:
生產(chǎn)環(huán)境與本地開(kāi)發(fā)環(huán)境 Node.js 版本不一致。
一些含有 C++ 代碼的 npm 依賴運(yùn)行的跨平臺(tái)問(wèn)題 。
文件權(quán)限配置、系統(tǒng)目錄結(jié)構(gòu)與線上運(yùn)行環(huán)境不完全一致 。
啟動(dòng)初始化流程不一致(比如配置預(yù)拉?。?br/>開(kāi)發(fā)本地常常缺少一些二進(jìn)制工具或版本不一致(比如 consul-template、nc 等)。
與本地直接啟動(dòng) Node.js 程序有所不同,這個(gè)命令會(huì)優(yōu)先基于當(dāng)前項(xiàng)目利用上面的 webnode docker build 命令構(gòu)建 Docker 鏡像,然后啟動(dòng)鏡像。
Docker 可以幫助消解環(huán)境差異:
便捷地?cái)y帶與生產(chǎn)環(huán)境一致的Node.js 版本以及其他二進(jìn)制依賴。
一致的初始化流程。
輕松運(yùn)行含有 C++ 的 npm 依賴。
文件權(quán)限、目錄結(jié)構(gòu)與線上運(yùn)行環(huán)境一致。
容器化的Node.js調(diào)試方法有些許變化,需要暴露Node.js的Inspector端口,然后配一下Visual Studio Code的localRoot和remoteRoot:
WEBNODE_HOST=${WEBNODE_HOST:-127.0.0.1} WEBNODE_PORT=${WEBNODE_PORT:-3000} DOCKER_RUN_OPTIONS="$DOCKER_RUN_OPTIONS \ -it \ --rm \ --network=\"getui-dev\" -p $WEBNODE_HOST:$WEBNODE_PORT:3000 \ -p 127.0.0.1:9229:9229 \ -e NODE_FLAGS=--inspect=0.0.0.0:9229 \ --name $CONTAINER" docker run \ $DOCKER_RUN_OPTIONS \ $DOCKER_IMAGE_TAG { "version": "0.2.0", "configurations": [ { "type": "node", "request": "attach", "name": "Attach Local WebNode", "address": "127.0.0.1", "port": 9229, "restart": true, "protocol": "inspector", "localRoot": "${workspaceFolder}", "remoteRoot": "YOUR_REMOTE_ROOT", "sourceMaps": true }, ] }
基于容器的開(kāi)發(fā)可以帶來(lái)諸多好處。一是便于分發(fā),基于 Docker 的 Tag,開(kāi)發(fā)者可以很方便地做基于小版本、大版本、分支的分發(fā),可以像 nvm 一樣去切換版本。
二是CLI 腳本不用處處考慮跨平臺(tái)兼容的問(wèn)題,比如:
sed 在 Linux 和 Mac 下工作行為不一致的問(wèn)題之類的。
有的環(huán)境有 Python 3 有的環(huán)境只有 Python 2
所有的依賴通過(guò)容器帶進(jìn)來(lái),簡(jiǎn)潔而高效。
在基于 Docker 的工具開(kāi)發(fā)的過(guò)程中,我們也遇到一些問(wèn)題:
一是容器內(nèi)外 UID/GID 不一致,如果是以非 ROOT 用戶運(yùn)行 docker run,會(huì)導(dǎo)致容器內(nèi)程序在掛載的目錄產(chǎn)生的文件權(quán)限與當(dāng)前用戶不一致。
Docker for Mac對(duì)于文件權(quán)限有一些特別的行為,具體可以參見(jiàn):https://docs.docker.com/docker-for-mac/osxfs/#ownership
對(duì)于 Host 是 Linux 的情況,尤其在 CI 時(shí),需要考慮 UID/GID 的問(wèn)題。對(duì)于這種情況,我們選擇覆蓋掉了 entrypoint ,然后用 gosu 去做降權(quán)來(lái)處理。
CLI_EXEC_UID=${CLI_EXEC_UID:-0} CLI_EXEC_GID=${CLI_EXEC_GID:-0} exec gosu $CLI_EXEC_UID:$CLI_EXEC_GID env "$@"
其實(shí)RedHat 旗下用于設(shè)計(jì)container runtime 的daemonless (例如 podman),就很適合做CLI工具,可以 rootless 運(yùn)行,又尊重系統(tǒng)的權(quán)限配置。然而其目前尚未成熟,業(yè)界采用率也不高,仍需要繼續(xù)觀望。
二是有時(shí)候 docker run 速度較慢,個(gè)推的解決方案是在首次啟動(dòng)時(shí)啟動(dòng)一個(gè) docker run --detach,然后后續(xù)的 CLI 執(zhí)行完全通過(guò) docker exec 來(lái)進(jìn)行,這樣避免掉了每次執(zhí)行命令時(shí)啟動(dòng)的開(kāi)銷,速度提升明顯。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Node.js中如何使用基于容器的一站式命令行工具鏈”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。