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