写点什么

NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案

  • 2021-07-30
  • 本文字数:5101 字

    阅读完需:约 17 分钟

NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案

前言


按照国际惯例,正文开始之前,我们先简单介绍下目前市面上的 NPM 私库开源框架


  • Verdaccio

Verdaccio 是 Sinopia 开源框架的一个分支。它提供了自己的小数据库,以及代理其他注册中心的能力(例如:npmjs.org 网站),配置以及部署相对简单,一步到"胃"。如果公司的私包比较少的话或者你想偷懒,可以考虑一下。


  • Cnpmjs.org

大名鼎鼎的 CNPM,想必各位早就感受到了它的速度之“快”,没错,它的 Register 服务就是淘宝镜像 (https://registry.npm.taobao.org/)。主要是基于 Koa、MySQL 和简单存储服务的企业专用 NPM 注册和 WEB 服务,其中最强大的功能就是它的同步模块机制(定时同步所有源 Registry 的模块、只同步已经存在于数据库的模块、只同步 Popular 模块)。


  • Nexus

后端开发的小伙伴应该比较熟悉。Nexus2 主要是用于 Maven/Gralde 仓库的统一管理,而 Nexus3 则添加了 NPM 插件,可以对 NPM 提供支持,其中 NPM 仓库有三种类型,分别是 Hosted(私有仓库)、Proxy(代理仓库)、Group(组合仓库)。


总体来讲,抛开 Nexus,虽然 Cnpmjs.org 在部署过程以及总体设计方案上相对于 Verdaccio 复杂的多,但是它提供更高的拓展性,定制性,可以支持多种业务使用场景。接下来,我们分别从 Cnpmjs.org 容器化部署数据迁移OSS 容灾备份等内容,层层展开。


Cnpmjs.org 容器化部署


目前,公司的应用部署都是容器化部署,内部搭建了 Ipaas 平台,应用流程化部署以及一键发布。而 Cnpmjs.org 也附带了 Dockerfile 以及 docker-compose.yml 文件,所以,这里大致讲解下怎么用 Docker 部署吧。


  • 首先让我们看看 Dockerfile 文件

FROM node:12MAINTAINER zian yuanzhian@cai-inc.com
# Working enviromentENV \    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 脚本,目前全局变量全都存放在这里。


示例: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" 
复制代码


注意点:1、全局配置文件路径: /docs/dockerize/config.js;2、bindingHost 为 0.0.0.0。


  • 最后,在控制台敲下 docker-compose up -d,即以守护进程模式形式启动应用,然后打开浏览器入 http://127.0.0.1:7002,就会看到 WEB 页面。执行 npm config set registry http://127.0.0.1:7001 可设置为搭建的私库的镜像源地址,这里推荐使用 nrm,可自由切换 NPM 源。


展示站点如下图:



注意点:1、当你改变本地代码之后,先执行 docker-compose build 构建新的镜像,然后执行 docker-compose up -d 取代运行中的容器。


数据迁移


由于公司之前用的 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 itif (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// 或 /-/verdaccio/sidebar/)来拉取对应模块的 JSON 数据。



OK,接下来我们写一个定时任务,每隔一段时间执行 npm install [name],这样原来私库的 NPM 包都能够 install 并进入到上面的控制器逻辑,大功告成!


OSS 容灾备份


首先,简单说明下为什么要做 OSS 容灾备份,有以下几点。


  • 如果服务器上磁盘损坏,易丢失文件,有一定的风险

  • 若服务器磁盘爆满,可自动降级上传模块文件到 OSS


基于以上几点,我们整理了下容灾备份方案:


  • package publish



即发布模块文件时本地存储,同时上传到 OSS 作为备份,用到的插件分别是 fs-cnpmoss-cnpm


  • package install



即下载模块文件时,先判断是否是私包(即包名是否有带  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 私有库,希望这篇文章对你有所帮助。



头图:Unsplash

作者:梓安

原文:https://mp.weixin.qq.com/s/NUYooqqTklsSs77VDRLwKA

原文:NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-07-30 20:502360

评论

发布
暂无评论
发现更多内容

香港服务器购买指南:如何挑选最适合你的网络解决方案?

一只扑棱蛾子

香港服务器

技术革命——云计算结合人工智能

天翼云开发者社区

云计算

如何管理公厕电器化设备从而实现节能减排

光明源智慧厕所

智慧厕所 智慧公厕

为什么低功耗引擎是iot设备的必要项

FN0

低功耗模式 低功耗

HarmonyOS网络管理开发—HTTP与WebSocket

HarmonyOS开发者

终端云的探索与研究

天翼云开发者社区

云计算

华为云CodeArts Check代码检查插件(Cloud IDE版本)使用指南

华为云PaaS服务小智

云计算 软件开发 华为云 代码检查

住宅socks代理对于爬虫来说有什么帮助?

巨量HTTP

socks5代理

如何让 Llama2、通义千问开源大语言模型快速跑在函数计算上?

阿里巴巴云原生

阿里云 云原生

05. 机器学习入门1 - 动态规划

茶桁

人工智能 动态规划

IDEA插件版本升级和兼容新版本idea

华为云PaaS服务小智

云计算 软件开发 华为云

告别Netflix组件,拥抱SpringCloudAlibaba(B站狂转50k的文档)

程序员万金游

SpringCloud Alibaba spring-cloud #java java 架构

软件测试/测试开发丨Python文件操作 学习笔记

测试人

Python 程序员 软件测试 测试开发 文件操作

九章云极DataCanvas公司获评中国信通院两大AI标准核心参编单位

九章云极DataCanvas

议题征集|Flink Forward Asia 2023 正式启动

Apache Flink

大数据 flink 实时计算

ERP再解读:端点科技新一代ERP如何拓展传统ERP的边界

科技热闻

TDengine+OpenVINO+AIxBoard,助力时序数据分类

TDengine

时序数据库 ​TDengine

OpenHarmony父子组件单项同步使用:@Prop装饰器

OpenHarmony开发者

软件测试/测试开发丨Python深拷贝与浅拷贝 学习笔记

测试人

Python 软件测试 测试开发 深拷贝与浅拷贝

微信审核趋严,小程序引流需要配一个小程序化App

Speedoooo

超级app 小程序化App superapp App小程序化

NPM 私库从搭建到数据迁移最后容灾备份的一些解决方案_语言 & 开发_政采云前端团队_InfoQ精选文章