DevOps, Code Review 和项目管理工具

浏览量:746

本文旨在向读者展示一种软件开发流程方法,以达到如下三个目标:提高产品质量、规范化发布流程、控制开发边界(和客户确定需求边界不在本文探讨范围内)。

为了达到上述目标,我们将使用 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 代码审查

代码审查制度是为了借用第二个开发人员的视角来审查代码,以便尽早分析出代码实现是否能达成任务目标。

一个可行的代码审查需要满足以下三点要求:

  1. 本次审查的代码,要实现的功能必须是明确的。
  2. 知道本次审查的代码,都修改了原始代码的哪些部分。
  3. 审查员有足够的项目背景知识,和技术知识。

这里我们使用 GitLab 的 Merge Request (下称 MR)来做代码审查。

Merge Request 代码合并请求
这是一种分支合并操作。通常由开发人员发起,将该开发人员自己负责的一个 feature 分支合并到 develop 分支。
GitLab 会自动比较源分支相比目标分支更改的内容,以列表形式展现出来,方便审查。
通常要发布正式版本时,需要将 develop 合并到 master 分支,此时可以不做 MR。因为 develop 分支的代码是可靠的。
在其他的 Git 系统里可能称为 Pull Request。

典型过程如下:

  1. 开发人员在自己的 feature 分支开发完毕,提交代码到GitLab。
  2. 创建 MR,源分支是 feature,目标分支 develop
  3. 设置合适的标题和描述。这很重要,因为这里应当体现了 “明确的开发目标”。
  4. 设置合适的审查员,如项目主程、其他团队同伴等。生成 MR。
  5. 主动推动该 MR 的审查,比如随时群里、或每日例会上告知该审查员及时审查。
  6. 审查员首先确保流水线顺利执行。 然后浏览本次 MR 所有的代码变化,给出自己的意见,开发人员响应意见。双方讨论过程记录在 MR 内。
  7. 最终解决了所有意见,审查员同意合并。
  8. 由 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 能明确知道本次发布都实现了哪些任务修复了哪些问题。

在这个基础上,我们就进一步的接近了本文开头想要达到的三个目标:提高产品质量、规范化发布流程、控制开发边界。

留下评论