博客
关于我
Node.js 在个推的微服务实践:基于容器的一站式命令行工具链
阅读量:798 次
发布时间:2023-02-16

本文共 3382 字,大约阅读时间需要 11 分钟。

背景与摘要

随着工程数量的快速增长,在实践基于Node.js的微服务开发过程中,我们遇到了以下问题:

  • 每次新建项目都需要安装一次依赖,这些依赖之间基本相似却又有微妙的区别;
  • 每次新建项目都要配置一遍相似的配置(如tsconfig、lint规则等);
  • 本地Mac环境与线上Docker内的Linux环境不一致(尤其是有C++依赖的情况)。
  • 为了解决上述问题,我们开发了一个命令行小工具来标准化项目初始化流程、简化配置甚至是零配置,提供基于Docker的一致构建、运行环境。


    CLI: init, build, test & pack

    新建一个Node.js项目时,我们通常会执行以下操作:

  • 安装大量开发依赖(如TypeScript、Jest、TSLint、benchmark、typedoc等);
  • 配置tsconfig、lint规则、.prettierrc等;
  • 安装项目依赖(如koa、lodash、sequelize、ioredis、zipkin、node-fetch等);
  • 初始化项目目录结构;
  • 配置CI脚本。
  • 然而,选择复制现成项目进行修改的做法,导致了众多看似相似却又不完全相同的项目。对于同时跨多个工程的开发人员来说,众多配置组合会显著增加工作难度。此外,当安全审计发现某些npm包存在安全隐患时,开发人员需要对每个引用这些包的项目逐一检查和修正。

    在确定的开发场景下,几乎所有项目的开发依赖都非常相似,开发配置也非常一致。因此,我们基于commander.js开发了一个init工具,它会通过命令行向导自动安装依赖、初始化项目目录结构和配置,从而创建项目,并按照场景将所有配置收缩为特定几种模板,进行统一处理。

    随后,我们开发了build、test、pack命令,托管了tsconfig、jest配置、打包配置,自动调用tsc编译,构建测试环境,然后调用Jest进行测试,进行标准化打包。CI脚本基本可以简化为几行标准脚本。


    CLI: Docker Build

    在介绍这个命令前,需要先简单了解个推的镜像体系。前面提到我们将大部分依赖封装到了一个npm包,这一层封装也反映在个推的Docker镜像体系内。以下是一个简化的Dockerfile示例:

    # 公共依赖层的DockerfileFROM node:10RUN mkdir -p /usr/local/lib/webnode/node_modules && \    cd /usr/local/lib/webnode && \    npm install webnodeENV NODE_PATH /usr/local/lib/webnode/node_modules# 项目的DockerfileFROM getui/webnode:1.2.3COPY package*.json ./RUN npm installCOPY . .

    当将这层依赖直接做进Docker镜像时,尽管每个镜像的大小还是1G多,但每个镜像的UNIQUE SIZE都非常小,仅有数M的差分层。

    一个简单的对比:

    • 原先每个服务都会npm install大量重复依赖,20个服务就会有800M + 200M * 20 + 1M * 20 = 4.82G的总UNIQUE SIZE。
    • 采用依赖分层共享后,仅有800M + 200M + 1M * 20 = 1.02G的总UNIQUE SIZE。

    在考虑应用的多版本之后,依赖分层共享带来的存储优势会更加明显。我们以一定的依赖锁定周期和控制为代价,换取了以下优势:

  • 减少依赖组合、依赖版本组合的可能性,开发者选择包的简化、初始化项目的简化;审计简化、安全更新简化;
  • CI显著提速,节省等待时间;
  • 传输和存储的压力减少许多;
  • 公共依赖被多个项目使用,得到了更加充分的测试。
  • webnode docker build命令可以帮助开发者进行统一化的镜像构建、统一实践最佳优化,节约资源,还能避免所有开发人员都需要接触优化细节,省时省力。


    CLI: Webnode Docker Start

    在本地调试开发过程中,我们遇到了一些环境差异引起的问题:

  • 生产环境与本地开发环境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

    基于容器开发 CLI 工具

    基于容器的开发可以带来诸多好处:

  • 便于分发,基于Docker的Tag,开发者可以很方便地做基于小版本、大版本、分支的分发,可以像nvm一样去切换版本;
  • CLI脚本不用处处考虑跨平台兼容的问题,比如:
    • sed在Linux和Mac下的工作行为不一致;
    • 有的环境有Python 3,有的环境只有Python 2。
  • 所有的依赖通过容器带进来,简洁而高效。

    在基于Docker的工具开发过程中,我们也遇到了一些问题:

  • 容器内外UID/GID不一致。如果是以非ROOT用户运行docker run,会导致容器内程序在挂载的目录产生的文件权限与当前用户不一致;
  • Docker for Mac对于文件权限有一些特别的行为,具体可以参见:Docker文档
  • 对于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微服务开发实践中关于CLI工具的实践。个推试图通过标准化、优化项目结构以及镜像构建,减少组合的可能性,有效降低了存储、传输、构建的成本,让开发人员更加省时省力。

    后续我们还会继续为大家介绍个推的Docker镜像体系设计以及Node.js微服务开发框架,敬请期待。

    转载地址:http://kpjfk.baihongyu.com/

    你可能感兴趣的文章
    npm淘宝镜像过期npm ERR! request to https://registry.npm.taobao.org/vuex failed, reason: certificate has ex
    查看>>
    npm版本过高问题
    查看>>
    npm的“--force“和“--legacy-peer-deps“参数
    查看>>
    npm的安装和更新---npm工作笔记002
    查看>>
    npm的常用操作---npm工作笔记003
    查看>>
    npm的常用配置项---npm工作笔记004
    查看>>
    npm的问题:config global `--global`, `--local` are deprecated. Use `--location=global` instead 的解决办法
    查看>>
    npm编译报错You may need an additional loader to handle the result of these loaders
    查看>>
    npm设置淘宝镜像、升级等
    查看>>
    npm设置源地址,npm官方地址
    查看>>
    npm设置镜像如淘宝:http://npm.taobao.org/
    查看>>
    npm配置安装最新淘宝镜像,旧镜像会errror
    查看>>
    NPM酷库052:sax,按流解析XML
    查看>>
    npm错误 gyp错误 vs版本不对 msvs_version不兼容
    查看>>
    npm错误Error: Cannot find module ‘postcss-loader‘
    查看>>
    npm,yarn,cnpm 的区别
    查看>>
    NPOI
    查看>>
    NPOI之Excel——合并单元格、设置样式、输入公式
    查看>>
    NPOI初级教程
    查看>>
    NPOI利用多任务模式分批写入多个Excel
    查看>>