一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天。
总结概要
我们最终根据团队之间的协作,采用了gitlab CI/CD的方式搭配前端服务器实现前端项目自动化构建与部署。即使它目前是适配团队方案的,但它依旧是不完美的。
可以跳过前期直接进入现阶看具体实现流程与各项困难解决方案
前期
进入新的一年后,部门前端项目基本进入稳定期,在新增功能业务等任务不频繁的情况下,团队对于前端项目的优化与增长有做过讨论(即使我当时只想着摸鱼),我balabala一通后,决定将自动化这事提上日程。
旧有流程
公司项目(不分前后端)在前期实施的时候都是由自己部门搭建git服务器后自助管理,后期即使中心有专门负责运维gitlab平台统一管理代码的部门后,我们也没想过把代码迁移过去(也是这次自动化实施我尝试使用gitlab一键登录也做不到,因为git服务器的配置几乎无法实现)。
基于这套基础的git服务器,前端的开发流程是这样的:
- 根据开发性质自行新建git分支(feature,bugfix,dev-deploy等)
- 在分支里根据蓝湖及素材开发页面与交互
- 对于发布测试,需要将所有需要测试的分支合并到dev-deploy中
- cmd命令 make(我在项目中配置了一个makefile文件,让它可以流程化执行项目版本号的新增,项目的打包,产物压缩成压缩包以及自动scp上传目标服务器)
- ssh登录开发服务器,进入项目目录,执行make命令(开发中使用了makefile,所以我也将deploy脚本运行命令缩略在makefile配置中实现压缩包解压等操作,这样所有开发人员想发布只需要记住make这个单词就可以/doge)
- 生产的部署在于前面打包压缩的环节成功后将压缩包scp上传到指定目录,由运维去帮我执行我在服务器上的make命令
这个流程的演化在于一开始没有所谓前端开发服务器的时期,部门领导并不需要前端接触到服务器,所以前期是将压缩包通过通讯软件发给后端后让后端去部署的。即使后面来了运维人员,但运维人员主要负责DBA,并不主要操作项目部署这块。
现阶
在实现了自动化后,我们在开发过程中仅需要注意的就是对于代码分支的管理。
代码迁移
对于用gitlab管理代码的,可以使用gitlab的import功能将具备git配置的项目迁移过去。
当初使用import时由于原git服务器的限制并不能成功。
所以我们使用git配置来修改项目git repo;
git remote rm origin
git remote add origin http://gitlabhost:port/yourname/yourproject.git
git push -u origin --all
git push origin --tags
实现gitlab CI/CD
gitlab CI/CD的实现主要在于ci文件的编写以及为项目添加runner负责处理ci文件中的每一个step步骤。
由于gitlab不像GitHub那样,GitHub Action默认具备runner运行ci,而gitlab需要我们自行添加。我选择使用Docker在开发服务器上创建runner。
- 启动一个gitlab-runner容器
docker run -d --name gitlab-runner \
--restart always \
-v /home/devops/gitlab/runner:/etc/gitlab-runner \
-v /home/devops/gitlab/cache:/cache \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /home/www:/home/www \
-v /release/web:/release/web \
gitlab/gitlab-runner:latest
关于volumes的配置,第一个在于对runner配置目录的管理;第二个对于runner中需要进行cache缓存时起到效果,这点后说;第四个docker部署项目的目录;第五个我们在发布生产时也可以由docker帮我们完成。
- 注册runner
一个容易可以拥有多个runner来支持到项目CI/CD的运行,我们在gitlab的项目中setting CI/CD选项中获取到runner token,然后在上面的gitlab容器中注册runner。
docker exec gitlab-runner \
gitlab-runner register -n \
--url https://git.domain.com/ \
--registration-token RUNNER.TOKEN \
--tag-list moly-admin \
--executor docker \
--docker-image docker \
--docker-volumes /home/.npm:/root/.npm \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock \
--docker-volumes /home/devops/gitlab/cache:/cache \
--docker-volumes /home/www:/home/www \
--docker-volumes /release/web:/release/web \
--description "Runner"
- --url,gitlab的URL
- --registration-token,在gitlab平台中项目内的runner token
- --tag-list,tag的配置在于后期编写ci文件时我们可以指定特定tag的runner仅限某步step运行
- --description,描述的配置我建议并不需要过长,之后如果需要删除某个runner时可以用到它 其余的和创建容器的目的类似。
编写gitlab-ci.yml
考虑到公司文件私密性,我贴一个ci的模版来描述这次任务的实现。
# 阶段,先后顺序是阶段的执行顺序。
stages:
- build
# job名称
daily-build:
stage: build # 此job对应stages内的某个阶段
only: # 匹配分支,tag或trigger
- master # 只有master分支执行
except:
- tags # 忽略tag
- triggers # 忽略trigger
script:
- command line # job执行的脚本
CI的流程主要分为build打包,tar压缩,mv移动,sh执行几个步骤。
依赖安装
在runner中运行build阶段的时候需要注意的是,国内在安装依赖时有个永远的痛node-sass,因此在script配置中对于npm下载也是修改下载地址。
npm install -g cnpm --registry=https://registry.npm.taobao.org
cnpm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
在ci文件中我们也可以将这些默认的配置写在全局中。
variables:
NPM_CONFIG_CACHE: npm_cache
NPM_CONFIG_REGISTRY: https://registry.npm.taobao.org
NPM_CONFIG_SASS_BINARY_SITE: https://npm.taobao.org/mirrors/node-sass
镜像拉取
然后我们说到ci中的镜像问题。每步step我们需要让它在一个特定的镜像中去运行,在runner运行过程中通过日志我们发现,每次从新执行pipeline时,都会从新去dockerhub上寻找目标镜像,即使该镜像早早就下载在你本地了。出于此原因我们需要进入到配置volumes时涉及到的runner目录,对目录里的config.toml文件进行修改。找到特定的runner项,新增一条配置:
#即本地不存在 才去拉取
pull_policy="if-not-present"
设置了这条命令后基本可以省掉每次拉取镜像的时间。而如果是自己自定义build的镜像,有时作为内部使用我们也没必要push到docker hub上,使用runner在尝试拉取时找不到目标镜像,会报denied: requested access to the resource is denied的错误,这里我不清楚是不是docker不信任镜像的问题,在使用了docker login生成config.json(/root/.docker/config.json)文件后,自定义镜像也可以通过使用了。
依赖缓存
每次执行pipeline都是会安装每步step中的script来运行,所以每次运行到npm i时都要重新安装依赖。我参考了gitlab doc上关于cache的解释,决定将npm相关的缓存下来,这里涉及到挂载数据卷时配置/cache的原因。要保持缓存的持久化就需要一开始就将它与宿主目录实现挂载。
然后是依赖的大小问题,实现缓存后依赖的存在除了根据package.json新增依赖外,并不会减少。基于这个问题,我参考了网上的方案:
- 实现一个依赖镜像。在服务器设置一个定时任务(闲时任务),固定根据项目的package.json进行依赖的下载,然后打包成一个镜像,确保依赖是最符合项目的,使用时也不需要再去下载。
- npm ci。此命令与 npm install 类似,不同之处在于它旨在用于自动化环境,例如测试平台、持续集成和部署——或任何您希望确保对依赖项进行全新安装的情况。 通过跳过某些面向用户的功能,它可以比常规的 npm 安装快得多。 它也比常规安装更严格,可以帮助捕获大多数 npm 用户增量安装的本地环境导致的错误或不一致。
npm ci的安装会先删除node_modules目录,确保不会有冗余的依赖,其次就是严格遵循package-lock.json锁定的版本号,确保和本地开发时版本一致,最后安装时使用npm cache目录,版本一致的时候不会向中央仓库发起请求验证版本号之类的操作,也提高了构建速度。所以最后我选择了使用npm ci。
一些想法
到了这里后基本上我们整个开发到部署的环节算是过了一遍,可以说它确实释放了部门前端开发的一些开发外的操作,即使在部门前端仅2人的情况下我也毅然将自动化进行到底。但它终究是不完美的,因为说它是自动化却在整个流程的“最后一公里”中受限于人员权限与配合,还是有一部分需要手动的。
但也是有了这次体验,让我在专注实现UI与交互的工作之外开始尝试一些开发基建任务。从自己主推到完成,过程中攻克每一个知识点,学习Docker,Shell脚本,Linux命令与权限等知识时都充满了喜悦(真的很爽)。
CI/CD还能做什么?现阶段会以当前的流程继续跑着,我想着之后再对接上与企业微信的机器人bot的交互,实现runner自动艾特通知关键人员的操作;每次发布生产时的工作报告产出(我纯粹就是不想手写)。但这一切看自己还有没有时间做了,因为即使实现了这套自动化流程,在部门中并没有人觉得多大事。
仅实现自动化后对于开发的规范又该如何完善,这些在开篇另说。如果你觉得本文对你有帮助,希望能给个大大的赞支持下(写作新人急需鼓励,害)。