npm 私庫從搭建到數據遷移最後容災備份的一些解決方案

這是第 80 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客: npm 私庫從搭建到數據遷移最後容災備份的一些解決方案

前言

按照國際慣例,正文開始以前,咱們先簡單介紹下目前市面上的 npm 私庫開源框架。前端

  • Verdaccio

    Verdaccio 是 sinopia 開源框架的一個分支。它提供了本身的小數據庫,以及代理其餘註冊中心的能力(例如。npmjs.org 網站),配置以及部署相對簡單,一步到"胃"。若是公司的私包比較少的話或者你想偷懶,能夠考慮一下。node

  • Cnpmjs.org

大名鼎鼎的 cnpm,想必各位早就感覺到了它的速度之「快」,沒錯,它的 register 服務就是淘寶鏡像。主要是基於Koa、MySQL 和簡單存儲服務的企業專用 npm 註冊和 web 服務,其中最強大的功能就是它的同步模塊機制(定時同步全部源 registry 的模塊、只同步已經存在於數據庫的模塊、只同步 popular 模塊)。mysql

  • Nexus

後端開發的小夥伴應該比較熟悉。Nexus2 主要是用於 maven/gralde 倉庫的統一管理,而 Nexus3 則添加了npm插件,能夠對 npm 提供支持,其中 npm 倉庫有三種類型,分別是 hosted(私有倉庫)、proxy(代理倉庫)、group(組合倉庫)。web

整體來說,拋開 Nexus,雖然 Cnpmjs.org 在部署過程以及整體設計方案上相對於 Verdaccio 複雜的多,可是它提供更高的拓展性,定製性,能夠支持多種業務使用場景。接下來,咱們分別從 Cnpmjs.org 容器化部署、數據遷移、OSS 容災備份等內容,層層展開。sql

Cnpmjs.org 容器化部署

目前,公司的應用部署基本都是容器化部署,內部搭建了 ipaas 平臺,應用流程化部署以及一鍵發佈。而 Cnpmjs.org 也附帶了 Dockerfile 以及 docker-compose.yml 文件,因此,這裏大體講解下怎麼用 docker 部署吧。docker

  • 首先讓咱們看看 Dockerfile 文件
FROM node:12
MAINTAINER zian yuanzhian@cai-inc.com

# Working enviroment
ENV \
    CNPM_DIR="/var/app/cnpmjs.org" \
    CNPM_DATA_DIR="/var/data/cnpm_data" 

# shell格式
# 在docker build 時運行
RUN mkdir -p ${CNPM_DIR}

# 指定工做目錄:用 WORKDIR 指定的工做目錄,會在構建鏡像的每一層中都存在
WORKDIR ${CNPM_DIR}

# 複製指令:從上下文目錄中複製目錄或文件到容器裏指定的路徑
COPY package.json ${CNPM_DIR}

RUN npm set registry https://registry.npm.taobao.org

RUN npm install --production

COPY .  ${CNPM_DIR}
COPY docs/dockerize/config.js  ${CNPM_DIR}/config/

# 聲明端口(7001爲register服務、7002爲web服務)
EXPOSE 7001/tcp 7002/tcp

# 匿名數據卷:在啓動容器時忘記掛載數據卷,會自動掛載到匿名卷。
VOLUME ["/var/data/cnpm_data"]

RUN chmod +x ${CNPM_DIR}/docker-entrypoint_prod.sh

# Entrypoint 
# exec格式
# 在docker run 時運行
# dockerfile存在多個 CMD 命令,僅最後一個生效
# CMD ["node", "dispatch.js"]
CMD ["npm", "run", "prod"]

這裏把 CMD 命令修改成["npm", "run", "prod"],由於增長了一層不一樣環境的 shell 腳本,目前全局變量全都存放在這裏。shell

示例:docker-entrypoint_env.sh數據庫

export DB='db_cnpmjs'
export DB_USRNAME='root'
export DB_PASSWORD='123456'
export DB_HOST='127.0.0.1'

export BINDING_HOST='0.0.0.0'

DEBUG=cnpm* node dispatch.js
  • 再修改下 docker-compose.yml 文件,這裏把 mysql-db 這個服務刪掉了,緣由是可經過 /docs/dockerize/config.js 下的配置文件去鏈接公司測試環境的 mysql 數據庫,則不須要構建生成 mysql-db鏡像
version: '3' # docker版本
services: # 配置的容器列表
  web: # 自定義,服務名稱
    build: # 基於dockerfile構建鏡像(可增長args)
      context: .
      dockerfile: Dockerfile ## 依賴的Dockerfile文件
    image: cnpmjs.org # 鏡像名稱或id
    volumes:
      - cnpm-files-volume:/var/data/cnpm_data
    ports:
      - "7001:7001"
      - "7002:7002"

<font color="red">注意點:一、全局配置文件路徑: /docs/dockerize/config.js ;二、bindingHost 爲 0.0.0.0 。</font>npm

  • 最後,在控制檯敲下docker-compose up -d,即以守護進程模式形式啓動應用,而後打開瀏覽器入http://127.0.0.1:7002,就會看到 web 頁面。執行 npm config set registry http://127.0.0.1:7001 可設置爲搭建的私庫的鏡像源地址,這裏推薦使用 nrm,可自由切換 npm 源。

展現站點以下圖:json

<font color="red">注意點:一、當你改變本地代碼以後,先執行 docker-compose build 構建新的鏡像,而後執行 docker-compose up -d 取代運行中的容器。</font>

數據遷移

因爲公司以前用的 Verdaccio 搭建的私庫,要切換使用新的 npm 私庫,意味着要把以前發佈過的私包所有遷移過來。大概統計了下,有400 多個 package,總共有 7000 多個版本,按照正常邏輯,作數據遷移首先會從數據庫下手,可是 Verdaccio 並不依賴數據庫。剛開始沒有一點頭緒,大概看了下 Cnpmjs.org 的源碼,瞭解到當咱們 publish 模塊時, 它是怎麼把 npm 模塊 的元數據存儲到數據庫,下面咱們一步步來揭開她的面紗。

經過路由文件(/routes/registry.js)咱們很容易找到/controllers/registry/package/save.js,這個文件即是咱們想要的。

核心代碼:

var pkg = this.request.body; // 這裏拿到npm模塊元數據,即package.json文件通過libnpmpublish模塊處理過的json數據
var username = this.user.name; // 當前用戶名
var name = this.params.name || this.params[0]; // npm模塊名
var filename = Object.keys(pkg._attachments || {})[0]; // npm模塊的壓縮後的文件名
var version = Object.keys(pkg.versions || {})[0]; // npm模塊的最新版本
// upload attachment

// base64解碼,獲取模塊文件二進制數據。從libnpmpublish模塊瞭解到tardata.toString('base64'),即npm模塊文件流轉base64字符串
var tarballBuffer = Buffer.from(attachment.data, 'base64'); 
// 默認使用fs-cnpm,將npm模塊文件保存到本地,默認保存路徑:path.join(process.env.HOME, '.cnpmjs.org', 'nfs')
var uploadResult = yield nfs.uploadBuffer(tarballBuffer, options);

var versionPackage = pkg.versions[version];
var dist = {
  shasum: shasum,
  size: attachment.length
};

// if nfs upload return a key, record it
if (uploadResult.url) {
  dist.tarball = uploadResult.url;
} else if (uploadResult.key) {
  dist.key = uploadResult.key;
  dist.tarball = uploadResult.key;
}
var mod = {
  name: name,
  version: version,
  author: username,
  package: versionPackage
};

mod.package.dist = dist;

// 模塊數據保存到數據庫
var addResult = yield packageService.saveModule(mod);

即只要咱們可以拿到 npm 模塊的元數據(即 package.json 被處理過的 json 數據),就能把模塊文件上傳到文件系統或者 OSS 服務,同時數據落庫。Verdaccio 有兩個 api 能夠拿到其私庫 npm 模塊全量數據和當前 npm 模塊的 json 數據,路徑分別是/-/verdaccio/packages/-/verdaccio/sidebar/$PKG$,其中有 scope 的模塊的請求路徑是/-/verdaccio/sidebar/$SCOPE$/$PKG$

思路已經很明確了,開始動起來吧!新增 save_zcy.js 文件,基於原來的/controllers/registry/package/save.js稍加改造下。

核心代碼:

// 請求遠程文件,並返回二進制流
const handleFiles = function (url) {
  return new Promise((resolve, reject) => {
    try {
      http.get(url, res => {
        res.setEncoding('binary') // 二進制
        let files = ''
        res.on('data', chunk => { // 加載到內存
          files += chunk
        }).on('end', () => { // 加載完
          resolve(files)
        })
      }) 
    } catch (error) {
      reject(error)
    }
  })
};

// 獲取遠程模塊文件的二進制數據
yield handleFiles(dist.tarball).then(res => {
  // 利用 Buffer 轉爲對象
  const tardata = Buffer.from(res, 'binary')
  pkg._attachments = {};
  pkg._attachments[filename] = {
    'content_type': 'application/octet-stream',
    'data': tardata.toString('base64'), // 從緩衝區讀取數據,使用base64編碼並轉換成字符串
    'length': tardata.length,
  };
}, error => {
  this.status = 400;
  this.body = {
    error,
    reason: error,
  };
  return;
});

接下來咱們把控制器 save_zcy.js 接入到 registry 服務的 app 路由上。

// 新增 fetchPackageZcy、savePackageZcy 控制器
app.get('/:name/:version', syncByInstall, fetchPackageZcy, savePackageZcy, getOneVersion);
app.get('/:name', syncByInstall, fetchPackageZcy, savePackageZcy, listAllVersions);

控制器 fetchPackageZcy 做用是請求上面的 api(/-/verdaccio/sidebar/$SCOPE$/$PKG$ 或 /-/verdaccio/sidebar/$PKG$)來拉取對應模塊的 json 數據。

213BFDE6-B389-4376-A959-DC9E2F71FDF7.png

Ok,接下來咱們寫一個定時任務,每隔一段時間執行 npm install [name],這樣原來私庫的 npm 包都可以 install 並進入到上面的控制器邏輯,大功告成!

OSS 容災備份

首先,簡單說明下爲何要作 OSS 容災備份,有如下幾點。

  • 若是服務器上磁盤損壞,易丟失文件,有必定的風險
  • 若服務器磁盤爆滿,可自動降級上傳模塊文件到 OSS

基於以上幾點,咱們整理了下容災備份方案:

  • package publish

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/06b600e8297a434087f43bcad08c7f6d~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:33%;" />

即發佈模塊文件時本地存儲,同時上傳到 oss 做爲備份,用到的插件分別是 fs-cnpmoss-cnpm

  • package install

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/df3ff3801cf74755a0c09be7d2a19029~tplv-k3u1fbpfcp-zoom-1.image" style="zoom:33%;" />

即下載模塊文件時,先判斷是不是私包(便是包名否有帶 scope),若是不是私包代理到上游 registry,如果私包先判斷服務器本地是否有該私包文件,若是不存在先去 oss 下載到本地 nfs 目錄下,若是存在則直接從 nfs 目錄找到模塊文件,而後讀取並寫到 downloads 目錄下,最後調用 fs.createReadStream 方法流讀取該文件。

isEnsureFileExists 即判斷模塊文件本地是否存在,代碼以下:

const mkdirp = require('mkdirp');
const fs = require('fs');

function ensureFileExists(filepath) {
  return function (callback) {
    fs.access(filepath, fs.constants.F_OK, callback);
  };
}

注意,在 oss 下載模塊文件到 nfs 以前,必定要先建立模塊文件目錄,方法以下:

const mkdirp = require('mkdirp');

function ensureDirExists(filepath) {
  return function (callback) {
    mkdirp(path.dirname(filepath), callback);
  };
}

郵件通知

Cnpmjs.org 原本就帶有郵件通知的功能,但只應用錯誤日誌上報。因爲咱們的私包大部分都是業務組件、工具等,有時候發佈正式版本的業務組件須要通知到業務組件的使用方。目前,咱們採用 maintainers 來維護,包含模塊的維護者及使用者。

示例:

"maintainers": [
  {
    "name": "yuanzhian",
    "email": "yuanzhian@cai-inc.com"
  }
]

郵箱配置以下:

mail: {
  enable: true,
  appname: 'cnpmjs.org',
  from: process.env.EMAIL_HOST,
  host: 'smtp.mxhichina.com',
  service: 'qiye.aliyun', // 使用了內置傳輸發送郵件,查看支持列表:https://nodemailer.com/smtp/well-known/
  port: 465, // SMTP 端口
  secureConnection: true, // 使用了 SSL
  auth: {
     user: process.env.EMAIL_HOST,
     pass: process.env.EMAIL_PSD, // 
   }
 }

寫在文末

將來,咱們還能夠在 Cnpmjs.org 上作不少定製化開發,好比接入公司內部權限系統、web 頁面重構、對接業務組件在線文檔等等。若是你正好也須要搭建 npm 私有庫,但願這篇文章對你有所幫助。

推薦閱讀

分分鐘教會你搭建企業級的 npm 私有倉庫

編寫高質量可維護的代碼:組件的抽象與粒度

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索