作者:GetuiNode.js开发工程师Connaught背景及总结由于项目数量的快速增长,Getui正在基于Node进行实践。在开发js微服务的过程中,遇到了以下问题:每个新项目都需要安装一个依赖,这些依赖基本相似,但有细微差别;每个新项目都必须配置类似的配置(如tsconfig、lintrules等);本地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工具,会打开一个命令行向导,自动安装依赖,并初始化项目目录结构和配置。这样就创建了一个project,所有的配置根据场景收缩到几个具体的模板中统一处理。然后,我们有build,test,pack命令,hosttsconfig,jest配置,打包配置,自动调用tsc编译,搭建测试环境,然后调用Jest进行测试,标准化打包。CI脚本基本上可以简化为几行标准脚本。CLI:DockerBuild在介绍这个命令之前,需要先简单了解一下Getui的镜像系统:前面提到,我们将大部分的依赖封装成一个npm包,这一层封装也体现在Getui的Docker镜像系统中.简单表示为如下Dockerfile:#公共依赖层的DockerfileFROMnode:10RUNmkdir-p/usr/local/lib/webnode/node_modules\&&cd/usr/local/lib/webnode\&&npminstallwebnodeENVNODE_PATH/usr/local/lib/webnode/node_modules#项目的DockerfileFROMgetui/webnode:1.2.3COPYpackage*.json./RUNnpminstallCOPY。但是每个镜像的UNIQUESIZE都非常小,只有几M个差异层。简单比较,比如800M公共系统依赖+每个服务平均200Mnpm依赖+1M服务代码,那么由于每个服务都会有npminstall大量重复依赖,20个服务,就会有800M+200M20+1M20=总的唯一大小为4.82G。而依赖分层共享,总共只有800M+200M+1M*20=1.02G的UNIQUESIZE。在考虑应用的多版本后,依赖分层共享的优势在存储方面会更加明显。我们以一定的依赖锁定期和控制权为代价,换来了:减少依赖组合和依赖版本组合的可能性、开发者选择包的简化、项目初始化的简化、审计的简化、安全更新的简化。CI明显更快并节省等待时间。运输和储存的压力要小得多。公共依赖被多个项目使用,并且经过更充分的测试。webnodedockerbuild命令可以帮助简化构建Docker镜像的过程。它有一个内置的Dockerfile和dockerignore。命令运行时,会自动根据这两个文件和当前的Context构建一个docker镜像。Dockerfile包含一些优化和我们的最佳实践。开发者只需要专注于Node.js项目的开发。该命令可以负责配置文件权限等操作,生成标准化优化的Docker镜像。其设计目标是:快速:合理的依赖分层,最大限度的应用Docker缓存机制,通过.dockerignore进行不必要的Context裁剪,从而达到快速的构建速度。Small:按照变化频率做Docker分层设计,应用多阶段构建,尽量减少镜像的UNIQUESIZE。可重现:相同的内容总是会产生相同的结果。以node_modules依赖优化为例,下面两个Dockerfile其实是有很大区别的:FROMgetui/webnode:1.2.3COPY。.RUNnpminstallFROMgetui/webnode:1.2.3COPYpackage*.json./RUNnpminstallCOPY。.对于前者,每次dockerbuild时,只要项目中任何代码发生变化,npminstall的缓存就会失效,需要重新安装,而后者只会在package*.json发生变化时触发re-npminstall。另外我们也会预编译package.json,只保留dependencies相关的字段,避免出现修改package.json版本号后重新npminstall的情况。webnodedockerbuild不仅可以帮助开发者构建统一的镜像,实践最佳优化,节省资源,还可以避免所有开发者需要触及优化细节,省时省力。CLI:WebnodeDockerStart在本地调试开发的过程中,遇到了一些环境差异导致的问题:生产环境和本地开发环境的Node.js版本不一致。某些使用C++代码运行的npm依赖项存在跨平台问题。文件权限配置和系统目录结构与线上运行环境不完全一致。启动初始化过程不一致(如配置预取)。本地开发往往缺少一些二进制工具或版本不一致(如consul-template、nc等)。不同于直接在本地启动Node.js程序,该命令会先使用上面的webnodedockerbuild命令,根据当前项目构建一个Docker镜像,然后启动该镜像。Docker可以帮助解决环境差异:轻松携带与生产环境相同版本的Node.js和其他二进制依赖项。一致的初始化流程。使用C++轻松运行npm依赖项。文件权限和目录结构与线上运行环境一致。容器化的Node.js调试方法有一些变化。需要暴露Node.js的Inspector端口,然后配置VisualStudioCode的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\-p127.0.0.1:9229:9229\-eNODE_FLAGS=--inspect=0.0.0.0:9229\--name$CONTAINER"dockerrun\$DOCKER_RUN_OPTIONS\$DOCKER_IMAGE_TAG{"version":"0.2.0","configurations":[{"type":"node","request":"attach","name":"AttachLocalWebNode","address":"127.0.0.1","port":9229,"restart":true,"protocol":"inspector","localRoot":"${workspaceFolder}","remoteRoot":"YOUR_REMOTE_ROOT","sourceMaps":true},]}基于容器的开发CLI工具基于容器的开发可以带来很多好处。首先,它易于分发。基于Docker的Tag,开发者可以方便地基于小版本、大版本、分支进行分发,可以像nvm一样进行版本切换。二是CLI脚本不需要处处考虑跨平台兼容性问题,比如:sed在Linux和Mac下的工作行为不一致。有的环境有Python3,有的环境只有Python2,所有依赖都通过容器引入,简洁高效。在基于Docker开发工具的过程中,我们也遇到了一些问题:首先,容器内外的UID/GID不一致。如果以非ROOT用户运行dockerrun,会导致容器中程序生成的文件权限被挂载到目录中。与当前用户不一致。DockerforMac对文件权限有一些特殊的行为。具体可以参考:https://docs.docker.com/docke...对于Host为Linux的情况,尤其是在CI中,需要考虑UID/GID的问题。这种情况我们选择覆盖入口点,然后使用gosu降权处理。CLI_EXEC_UID=${CLI_EXEC_UID:-0}CLI_EXEC_GID=${CLI_EXEC_GID:-0}execgosu$CLI_EXEC_UID:$CLI_EXEC_GIDenv"$@"其实RedHat的daemonless(比如podman)用来设计containerruntime很适合CLI该工具无需root即可运行,并尊重系统的权限配置。但目前还不成熟,在行业内的采用率不高,还需拭目以待。二是有时dockerrun速度慢。一个推荐的方案是在第一次启动的时候先启动一个dockerrun--detach,然后后面的CLI执行完全通过dockerexec来进行,这样就避免了每次执行命令的时候都启动。开销得到显着改善。总结以上就是CLI工具在Node.js微服务开发实践中的实践,试图规范、优化项目结构和镜像构建,减少组合的可能性,有效降低存储、传输和构建的成本,使开发为人员节省了时间和精力。后续我们会继续介绍Docker镜像体系设计和Node.js微服务开发框架,敬请期待。参考https://docs.docker.com/docker...https://docs.docker.com/devel...https://www.projectatomic.io/...https://www.slideshare。net/Ak...https://www.debian.org/doc/ma...
