DevOps, Code Review 和项目管理工具
本文旨在向读者展示一种软件开发流程方法,以达到如下三个目标:提高产品质量、规范化发布流程、控制开发边界(和客户确定需求边界不在本文探讨范围内)。
为了达到上述目标,我们将使用 DevOps 技术,如 GitLab 的 CI 脚本;合适的项目管理工具,如JIRA;引入 Code Review 也就是代码审查流程。
Git 分支约定
master 分支和 develop 分支不许一般开发成员直接推送代码。以此来强制执行代码审核制度。
功能开发放在feature/issue-id-feature-name
分支上,补丁放在hotfix/issue-id-fix-name
分支上。
1. DevOps 技术
DevOps(Development和Operations的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。
摘自百度百科
我对该技术的体会,是它能在一定程度上减轻项目组成员日常繁琐事务的压力,如:
- 统一项目组代码格式化。
- 确保所有人的提交是经过测试的。
- 确保每次提交是能通过编译的。
- 确保不引入风险代码。
- 自动更新测试服务。
- 自动打包发布。
上述事务不属于开发人员最擅长、最有成就感的“开发”工作,但对于保证项目质量和协作有重要意义。所以将这些不得不做但谁也不想做的事情,交给 DevOps 自动完成最合适了。
有很多 DevOps 工具可以用:如 GitHub Action,GitLab CI/CD 流水线, Jenkins, Husky 等等。他们大部分都和 Git 操作紧密结合。
我有一个使用 NodeJS 后端 + Vue3 前端开发的,单代码库项目。我希望每个开发人员提交代码后,系统都能自动的检查代码格式化、跑自动测试、打Docker镜像;当代码合并到 develop 分支时,还能自动部署到测试服务器;当打了 tag 时,自动以 tag 为版本号打包发布。
下面我将以 GitLab CI/CD 流水线为例,描述如何实现这一系列过程:
image: node:16.19.1
stages: # 定义流水线各个步骤顺序。
- install
- test
- pre-build
- build
- deploy
variables: # 定义变量,在后续步骤可以动态修改,如 IMAGE_TAG 。
IMAGE_NAME: harbor.host.com/client/project
IMAGE_TAG: latest
KUBE_NAMESPACE: k8s-namespace
KUBE_DEPLOYMENT: product-ingredient-pricing-system-v1
cache: # 定义缓存,以加速各步骤执行速度。
- key:
files:
- pnpm-lock.yaml
paths:
- dist/
- node_modules/
- packages/backend/node_modules/
- packages/frontend/node_modules/
- .pnpm-store/
policy: pull
- key:
files:
- yarn.lock
paths:
- .yarn-cache/
policy: pull
- paths:
- .pkg-cache/
policy: pull
workflow: # 在默认分支 `master` 的提交操作,不会触发该流水线。以达到只对 `tag` 、其他分支提交操作进行处理的效果。
rules:
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
Install: # 安装依赖,依赖文件将被缓存。
stage: install
script:
- npm i -g pnpm
- pnpm config set store-dir .pnpm-store
- pnpm i
cache:
- key:
files:
- pnpm-lock.yaml
paths:
- node_modules/
- packages/backend/node_modules/
- packages/frontend/node_modules/
- .pnpm-store/
policy: pull-push
Test Lint: # 代码格式化检查。
stage: test
script:
- export NODE_OPTIONS="--max-old-space-size=4096"
- cd packages/backend && npm run lint:test
- cd ../..
- cd packages/frontend && npm run lint:test
Test Backend e2e: # 代码自动测试。
stage: test
script:
- cd packages/backend && npm run test:e2e
Pre-build: # 代码预编译。
stage: pre-build
script:
- if [ "$CI_COMMIT_TAG" ]; then
echo "Building for tag $CI_COMMIT_TAG";
IMAGE_TAG=$CI_COMMIT_TAG;
else
echo "Building for branch $CI_COMMIT_REF_NAME";
IMAGE_TAG=$CI_COMMIT_REF_NAME-$CI_PIPELINE_ID;
fi
- echo "IMAGE_TAG=$IMAGE_TAG" >> build.env
- export NODE_OPTIONS="--max-old-space-size=4096"
- cd packages/backend && npm run build
- cd ../..
- cd packages/frontend && npm run build
- cd ../..
- rm dist/ -rf
- cp packages/backend/dist/ dist/ -r
- cp packages/frontend/dist dist/frontend/views -r
cache:
- key:
files:
- pnpm-lock.yaml
paths:
- dist/
- node_modules/
- packages/backend/node_modules/
- packages/frontend/node_modules/
policy: pull-push
artifacts:
reports:
dotenv: build.env
Build Image: # Docker 镜像打包。
stage: build
dependencies:
- Pre-build
image:
name: anjia0532/kaniko-project.executor:v1.9.2-debug
entrypoint: [""]
script:
- echo "{\"auths\":{\"$HARBOR_URL\":{\"auth\":\"$(echo -n $HARBOR_USERNAME:$HARBOR_PASSWORD | base64)\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/build/Dockerfile"
--destination "${IMAGE_NAME}:${IMAGE_TAG}"
--snapshotMode=redo
--use-new-run
Build PKG: # exe 文件打包。
stage: build
only:
- tags
dependencies:
- Pre-build
script:
- export PKG_CACHE_PATH=.pkg-cache
- rm node_modules packages/backend/node_modules packages/frontend/node_modules -rf
- yarn --frozen-lockfile --cache-folder .yarn-cache
- "[ ! -f .pkg-cache/v3.4/fetched-v16.16.0-linux-x64 ] && wget https://ghproxy.com/https://github.com/vercel/pkg-fetch/releases/download/v3.4/node-v16.16.0-linux-x64 -P .pkg-cache/v3.4/ && mv .pkg-cache/v3.4/node-v16.16.0-linux-x64 .pkg-cache/v3.4/fetched-v16.16.0-linux-x64"
- "[ ! -f .pkg-cache/v3.4/fetched-v16.16.0-win-x64 ] && wget https://ghproxy.com/https://github.com/vercel/pkg-fetch/releases/download/v3.4/node-v16.16.0-win-x64 -P .pkg-cache/v3.4/ && mv .pkg-cache/v3.4/node-v16.16.0-win-x64 .pkg-cache/v3.4/fetched-v16.16.0-win-x64"
- npm run build:pkg
- mv output/product-ingredient-pricing-system.exe output/product-ingredient-pricing-system-${IMAGE_TAG}.exe
cache:
- key:
files:
- pnpm-lock.yaml
paths:
- dist/
policy: pull
- key:
files:
- yarn.lock
paths:
- .yarn-cache/
policy: pull-push
- paths:
- .pkg-cache/
policy: pull-push
artifacts:
paths:
- output/*
Deploy to QA: # 部署到测试 Kubernetes 集群。
stage: deploy
dependencies:
- Pre-build
environment: qa
only:
- develop
image:
name: bitnami/kubectl:latest
entrypoint: [""]
script:
- kubectl config get-contexts
- kubectl config use-context topgroup/gitlab-ci:topgroup
- kubectl config view
- kubectl --namespace $KUBE_NAMESPACE describe deployments/$KUBE_DEPLOYMENT
- kubectl --namespace $KUBE_NAMESPACE set image deployments/$KUBE_DEPLOYMENT main=${IMAGE_NAME}:${IMAGE_TAG}
整个流水线跑完一遍大概在20分钟内。 流水线任何一个步骤失败,GitLab将终止后续步骤,且向代码提交人的邮箱发送邮件。每位开发人员应当养成为自己每一次的代码提交和推送负责的态度。
2. Code Review 代码审查
代码审查制度是为了借用第二个开发人员的视角来审查代码,以便尽早分析出代码实现是否能达成任务目标。
一个可行的代码审查需要满足以下三点要求:
- 本次审查的代码,要实现的功能必须是明确的。
- 知道本次审查的代码,都修改了原始代码的哪些部分。
- 审查员有足够的项目背景知识,和技术知识。
这里我们使用 GitLab 的 Merge Request (下称 MR)来做代码审查。
Merge Request 代码合并请求
这是一种分支合并操作。通常由开发人员发起,将该开发人员自己负责的一个feature
分支合并到develop
分支。
GitLab 会自动比较源分支相比目标分支更改的内容,以列表形式展现出来,方便审查。
通常要发布正式版本时,需要将develop
合并到master
分支,此时可以不做 MR。因为develop
分支的代码是可靠的。
在其他的 Git 系统里可能称为 Pull Request。
典型过程如下:
- 开发人员在自己的
feature
分支开发完毕,提交代码到GitLab。 - 创建 MR,源分支是
feature
,目标分支develop
。 - 设置合适的标题和描述。这很重要,因为这里应当体现了 “明确的开发目标”。
- 设置合适的审查员,如项目主程、其他团队同伴等。生成 MR。
- 主动推动该 MR 的审查,比如随时群里、或每日例会上告知该审查员及时审查。
- 审查员首先确保流水线顺利执行。 然后浏览本次 MR 所有的代码变化,给出自己的意见,开发人员响应意见。双方讨论过程记录在 MR 内。
- 最终解决了所有意见,审查员同意合并。
- 由 PM 或其他有权限的 GitLab 用户确认合并。
不要让一个 MR 过于庞大,要控制它的功能范围。
比如一个开发人员在当前 Sprint 的任务有开发订单详情页面、改善小票打印服务稳定性、更换云存储服务这三个任务。不要创建一个庞大的 MR 涵盖了三个任务,请分别创建三个MR以对应三个任务。
记录沟通过程
即使审查员和发起 MR 的开发人员在办公室里的位子是挨着的,也要将代码审查过程的沟通思考过程记录下来。GitLab 提供了很好的工具来记录这一切。
3. 项目管理工具
我们常用的项目管理工具有禅道、Teambition、Trello、JIRA等等,他们都是很出色的项目管理工具。 具体到软件开发过程中,项目管理工具就必须具备和代码库、DevOps状态相关联的能力,才能让我们事半功倍。
我们以 JIRA 举例。
配置好 GitLab 和 JIRA 的关联后,每当提交代码的 comment 里包含了 JIRA 任务的编号,则在该任务页面会看到相关的代码提交记录;当一个 MR 的详情里包含了 JIRA 任务的链接时,该任务页面也会看到对应 MR 的状态(合并、没合并)。这让开发人员和管理人员都能很好的将任务和代码提交关联在一起。
在 JIRA 的左侧边栏,也可以配置对应代码库、Harbor仓库、演示站地址等常用网址。
当任务对应的 MR 处于审核阶段时,JIRA 任务应当处于 In Review
或类似阶段。 当合并后在测试服务器上进行测试时,可以放到 QA
阶段。测试没问题可以放到完成阶段。Sprint末尾,PM可以把develop
分支合并到master
分支,如果需要发布则可以向master
分支打标签。
4. 总结
结合了 DevOps,Code Review 以及合适的项目管理工具,我们就能实现:
- 开发人员的每一次提交,都变得可控、可靠
- 每一个任务都能和一系列提交,以及 MR 准确对应上
- PM 能明确知道本次发布都实现了哪些任务修复了哪些问题。
在这个基础上,我们就进一步的接近了本文开头想要达到的三个目标:提高产品质量、规范化发布流程、控制开发边界。