devops:为了稳定三分钟,请你花这半小时

477 阅读6分钟

背景

我有一个朋友搭了一个CICD平台。发布平台后端项目基本秒发,前端工程构建耗时较长,还经常错误终止,被吐槽。针对此情况尝试分析出的常见原因如下:

  1. 部分项目缺乏必备lock文件,部分三方依赖版本兼容存在问题,导致重新安装出错
  2. 部分同学在构建平台打包期间,误操作执行了安装yarn的流程,导致其它所有打包中的项目崩溃
  3. node版本问题。前端工程先前并未进行严格的node版本限制,导致出现兼容问题。代表性的node-sass,各版本依赖于不同的node版本。node-sass版本对照:github.com/sass/node-s…
  4. 滥用包管理器,本地开发使用pnpm,构建平台却又变成了npm或yarn

发布平台环境说明

  1. 发布平台——Jenkins
  2. 构建执行逻辑——shell脚本,并非pipeline脚本
  3. 共享node,为最大化利用包管理工具缓存,加快构建速度故并未提供单独的容器
  4. 统一使用了yarn管理

风险分析

1. 缺乏lock文件

缺乏根本的lock文件会导致项目处于裸奔的环境,无任何保障措施。考虑到众多依赖包间相互协作的可靠性,基本此类项目维护时间超过一年,都会出现兼容问题。从而导致开发环境无法启动,构建环境无法打包。影响范围——单个项目

经调研,市面上经常有不少同学版本管理意识较薄弱,团队内又未作严格要求,项目中的lock文件经常直接丢到.gitignore里。这种行为,基本等同于让项目处于裸奔状态,无任何保障。

2. lock文件绝对匹配问题

由于修改依赖包方式不规范,导致即使有lock文件,也出现了依赖匹配问题。继而导致部分情况下存在lock文件,却还要删除重新install才能正常安装的情况。通常出现此类问题,是由于开发者修改依赖包未进通过合法手段修改(eg: 手动修改了package.json的依赖版本号后不重新生成可用lock文件,而是直接提交git)

团队人员关于lock文件作用不了解,习惯性install,未通过严格的 npm ci(yarn install --frozen-lockfile; pnpm install --frozen-lockfile)的方式安装

lock文件说明:blog.csdn.net/xgangzai/ar…

3. CI平台重新安装包管理工具

上文提到,为复用包管理器本地缓存,installbuild的流程,并未新启一个容器进行,这就导致若是某个工程的shell中出现了npm install yarn -g的行为,将会导致其他进行中的构建错误中止。

4. node版本切换能力

node作为前端工程的基本环境,其兼容性通常被很多人视为可靠。而这一并不可靠可靠会经常出现兼容问题。

打包服务器的环境是全局共享,可能会出现 a 项目必备的依赖于node v8.0.0版本,b项目必备的依赖于node v14.1.0版本,此类情况,打包服务器目前未提供node环境隔离方案

5. 滥用包管理器(乱花渐欲迷人眼???)

相信大家也曾经见到过这样一个场景:刚拿到一个项目,clone下来发现里面既有npm lock文件,又有yarn lock文件。其危害基本等同于没有lock文件,往往让人不知所措。

优化草案

1. 解决常见的包管理器滥用问题

强制限制同一个项目只能使用一种包管理器。分析node工程的执行流程,不难想到preinstall钩子,在install时机做出判断。碰巧,有现成的开源库only-allow可以实现。在项目package.json中加入如下代码即可。

{
  "scripts": { "preinstall": "npx only-allow yarn" } 
}

2. 解决不能提供多种node版本的缺陷

切换node版本,社区已经有较多的解决方案。典型的如 nvmnvsn等等。 结合多方考虑,最终选择了nvm的方案,碰巧Jenkins社区也提供了nvm相关插件nvm-wrapper

image.png

3. 解决不能提供多种包管理器的缺陷

CI平台力求一统,但难免各个开发人员有各种各样的原因选择了不同的包管理器,有npm,有yarn,nvm切换后无法识别是否兼容。 见此情形,思考通过shell解决

uninstalled_module=""
# 判断是否已安装当前命令
command_has() {
  type "$1" > /dev/null 2>&1
}

if test -f $HOME/.nvm/nvm.sh;then
  echo "当前处于nvm环境"

  for i in "yarn" "pnpm"
  do
    if ! command_has $i;then
      if [ "$i" = "ni" ];
        then
          uninstalled_module="$uninstalled_module $i"
      fi
    fi
  done

  if [ ! -z "$uninstalled_module" ];then
    echo "安装如下依赖:$uninstalled_module"
    npm install -g $uninstalled_module
  fi
fi

shell可以实现,但十分不推荐将过多的执行流程放在构建脚本中,构建脚本理应越简单越好。 多方查询之下,发现antfu大佬早已默默的实现好了这一切——@antfu/ni。 @antfu/ni可以判断当前项目所使用的包管理工具(留对lock文件),并自动执行后续构建流程,若发现所依赖的包管理工具缺失,也会自动下载,可省去上述繁琐的shell判断。

antfu/ni: github.com/antfu/ni

4. 解决nvm环境切换后,不存在@antfu/ni包的问题

全新的node环境无法在本地使用未安装过的@antfu/ni。直接调用不行,可是,npx可以,并且node装好后npx也一并打包出售了,完美解决!

利用npx的能力,思考将install流程和build流程一次性执行完,引出如下脚本:

npx @antfu/ni nci && npm run build

短暂思考,不够严谨,我们将npm run build也交给 ni 完成:

npx -p @antfu/ni -c 'nci && nr build'

最终效果

1. node版本切换能力

得益于nvm,Jenkins可实现node版本切换功能

image.png

2. 包管理器兼容能力

作为CI平台,不限制每一个项目都必须使用同一个包管理器,不同的项目允许存在差异,@antfu/ni的nci模式可以简单实现 npx -p @antfu/ni -c 'nci && nr build'

得益于pnpm的卓越性能,能极大的缩短原有yarn install的执行时间,加快构建速度,不至于让CICD时间超过三分钟

3. 简洁的shell

回顾流程,基本只有一句 npx -p @antfu/ni -c 'nci && nr build'需要执行。后续的 docker build,docker push等操作暂不在本文讨论

image.png

4. 约定

CI平台与前端工程的约定,压缩至一个 .nvm 文件,一份可靠的lock文件

回顾总结

  1. 日常开发,留心lock
  2. @antfu/ni,通用的ci专用nci
  3. nvm版本管理,留下环境说明,利己利人

参考文章

lock文件重要性说明:blog.csdn.net/xgangzai/ar…

@antfu/ni:github.com/antfu/ni

node-sass版本对照说明:github.com/sass/node-s…

Jenkins nvm插件说明:plugins.jenkins.io/nvm-wrapper…