CI/CD

CI/CD 是一套让软件的构建、测试和发布自动化的工程实践。

  • CI(Continuous Integration,持续集成)

    • 开发者频繁把代码合并到共享仓库。
    • 每次提交后,系统自动执行编译、代码检查和测试。
    • 目的是尽早发现冲突和缺陷,避免问题积累到发布前。
  • CD 通常有两种含义:

    • Continuous Delivery(持续交付):自动完成测试、打包和部署准备,但发布到生产环境通常需要人工确认。
    • Continuous Deployment(持续部署):通过所有检查后,代码自动发布到生产环境,不需要人工批准。

典型流程如下:

text 复制代码
提交代码
  ↓
代码检查
  ↓
自动测试
  ↓
构建应用或 Docker 镜像
  ↓
部署到测试环境
  ↓
人工批准(持续交付)或自动发布(持续部署)
  ↓
生产环境

例如,开发者把代码推送到 GitHub 后,GitHub Actions 可以自动:

  1. 安装项目依赖。
  2. 运行单元测试。
  3. 构建 Docker 镜像。
  4. 将镜像推送到镜像仓库。
  5. 把新版本部署到服务器。

常见的 CI/CD 工具有:

  • GitHub Actions
  • GitLab CI/CD
  • Jenkins
  • CircleCI
  • Azure DevOps
  • Argo CD

简单来说,CI 负责持续检查代码是否可靠,CD 负责把可靠的代码安全、快速地交付或部署出去。Docker 经常用于 CI/CD,因为同一个镜像可以贯穿测试和生产环境,减少环境差异。

我之前的理解以为CI检查代码的可靠性是检查业务代码是否有bug

你的理解不算错误,但不够完整

CI 确实可以帮助检查业务代码是否有 Bug,但关键在于:

CI 本身不会理解业务逻辑,也不会自动判断业务代码是否正确;它只是自动运行开发团队预先配置好的检查。

例如,每次提交代码后,CI 可以自动运行:

  • 单元测试:检查函数或模块的业务行为。
  • 集成测试:检查数据库、服务和模块之间能否正确协作。
  • 端到端测试:模拟用户操作,检查完整业务流程。
  • 静态分析:发现类型错误、空指针、未使用变量等问题。
  • 代码风格检查:检查代码是否符合项目规范。
  • 安全扫描:检查依赖漏洞、密钥泄露或常见安全问题。
  • 构建检查:确认代码能否成功编译和打包。

假设电商系统中有这样的折扣规则:

text 复制代码
订单满 100 元,打 9 折

只有开发者先写好对应测试:

text 复制代码
输入:订单金额 200 元
期望:实付金额 180 元

CI 才能在代码被修改后自动验证这个规则。如果开发者误把折扣改成了 8 折,测试就会失败,CI 会阻止代码继续合并或部署。

但如果测试本身缺失,或者错误地认为实付金额应该是 160 元,那么 CI 仍然可能显示"全部通过"。因此:

text 复制代码
CI 通过 ≠ 代码绝对没有 Bug
CI 通过 = 代码通过了当前配置的所有检查

更准确地说,CI 的作用是:

在每次代码变更后,自动、重复、尽早地验证代码是否满足团队已经定义好的质量标准,从而降低引入 Bug 的概率。

所以你原先"CI 检查业务代码是否有 Bug"的理解抓住了一部分核心,只需要补充一句:它通过自动执行测试和其他检查来间接发现 Bug,而不是 CI 自己理解业务并检查 Bug。

除此之外还检查什么?

除了通过测试检查业务逻辑,CI 通常还会检查以下内容。具体检查哪些,取决于团队在 CI 流程中配置了什么。

  1. 代码能否构建

    检查代码能否成功编译、打包或生成可运行产物,例如:

    • Java 项目能否生成 JAR 包
    • 前端项目能否完成 npm run build
    • Docker 镜像能否成功构建
  2. 代码格式和规范

    使用 ESLint、Prettier、Checkstyle 等工具检查:

    • 缩进和格式是否一致
    • 是否存在未使用的变量
    • 是否违反团队的编码规则
    • 是否出现可疑或不推荐的写法

    这类检查主要提升可维护性,不一定代表代码有 Bug。

  3. 类型和静态分析

    在不运行程序的情况下分析代码,例如:

    • 参数类型是否正确
    • 是否可能访问空值
    • 某些执行路径是否缺少返回值
    • 是否存在无法到达的代码
    • 资源是否可能未关闭

    常见工具有 TypeScript、MyPy、SonarQube 等。

  4. 模块能否正确协作

    集成测试会检查:

    • 应用能否连接数据库
    • API 与业务服务能否正常交互
    • 消息队列是否正确收发消息
    • 第三方服务的调用方式是否符合约定
  5. 完整用户流程

    端到端测试可能模拟真实操作,例如:

    text 复制代码
    用户注册 → 登录 → 添加商品 → 提交订单 → 支付

    它检查的不只是单个函数,而是整条业务链路能否工作。

  6. 安全问题

    CI 可以运行安全扫描工具,检查:

    • 依赖库是否存在已知漏洞
    • 代码中是否误提交密码、令牌或私钥
    • Docker 基础镜像是否包含漏洞
    • 代码是否存在 SQL 注入等常见危险模式
    • 开源依赖的许可证是否允许项目使用
  7. 测试质量指标

    CI 可以统计:

    • 单元测试是否全部通过
    • 代码覆盖率是否达到要求
    • 是否出现不稳定的测试
    • 新增代码是否附带测试

    需要注意,覆盖率高不代表没有 Bug;它只表示代码在测试中被执行过。

  8. 兼容性

    同一套代码可能需要在多种环境下测试:

    • 不同操作系统
    • 不同浏览器
    • 不同 Python、Java 或 Node.js 版本
    • 不同数据库版本
  9. 数据库变更

    对于包含数据库迁移的项目,可以检查:

    • 迁移脚本能否成功执行
    • 新版本是否能读取旧数据
    • 数据库结构是否与代码匹配
    • 回滚方案是否有效
  10. 部署产物和基础设施配置

CI 还可能验证:

  • Docker 镜像能否正常启动
  • Kubernetes 配置是否有效
  • Terraform 等基础设施配置是否合法
  • 构建产物是否完整
  • 软件包版本是否正确

可以把 CI 理解成一个自动化"检查站":

text 复制代码
代码变更
   ↓
格式与规范检查
   ↓
类型与静态分析
   ↓
编译和构建
   ↓
单元测试
   ↓
集成及端到端测试
   ↓
安全与依赖扫描
   ↓
生成可发布产物

其中最重要的一点仍然是:

CI 没有固定的检查能力。团队把哪些工具和规则接入 CI,它就自动执行哪些检查。

所以,"CI 通过"更准确的含义不是"代码没有问题",而是"这次变更通过了项目目前定义的检查标准"。

具体工程中是如何实现的?

在具体工程里,CI 通常不是一个"自动理解代码"的系统,而是:

把原本需要开发者手动执行的命令,写进一份 CI 配置文件,让 CI 平台在每次提交代码时自动执行。

以 GitHub Actions 为例,完整实践通常分为以下几步。

1. 先让项目本身具备检查命令

CI 只是执行器,所以首先要在项目中准备好可以运行的检查。

例如一个 Node.js 项目可能在 package.json 中定义:

json 复制代码
{
  "scripts": {
    "format:check": "prettier --check .",
    "lint": "eslint .",
    "typecheck": "tsc --noEmit",
    "test": "vitest run",
    "build": "vite build"
  }
}

开发者本地可以执行:

bash 复制代码
npm run format:check
npm run lint
npm run typecheck
npm test
npm run build

这些命令在本地能运行后,再交给 CI 自动执行。

其他技术栈也类似:

bash 复制代码
# Python
ruff check .
mypy .
pytest

# Java
mvn test
mvn package

# Go
go vet ./...
go test ./...
go build ./...

2. 在代码仓库中编写 CI 配置

GitHub Actions 的配置通常放在:

text 复制代码
.github/workflows/ci.yml

例如:

yaml 复制代码
name: CI

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  check:
    runs-on: ubuntu-latest

    steps:
      - name: 下载代码
        uses: actions/checkout@v4

      - name: 安装 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - name: 安装依赖
        run: npm ci

      - name: 检查代码格式
        run: npm run format:check

      - name: 运行代码规范检查
        run: npm run lint

      - name: 检查类型
        run: npm run typecheck

      - name: 运行测试
        run: npm test

      - name: 构建项目
        run: npm run build

这份配置表达的是:

text 复制代码
有人创建或更新 Pull Request
          ↓
GitHub 准备一台临时 Linux 机器
          ↓
拉取代码
          ↓
安装 Node.js 和项目依赖
          ↓
依次运行格式、规范、类型、测试和构建检查
          ↓
返回成功或失败结果

某一个命令返回非零退出码,整个 CI 就会失败。

3. 在 Pull Request 阶段运行 CI

实际团队一般不会让开发者直接修改主分支,而是使用下面的流程:

text 复制代码
创建功能分支
    ↓
编写代码和测试
    ↓
推送到远程仓库
    ↓
创建 Pull Request
    ↓
CI 自动运行
    ↓
代码评审
    ↓
CI 通过后合并

例如:

bash 复制代码
git switch -c feature/order-discount
git add .
git commit -m "Add order discount"
git push -u origin feature/order-discount

推送并创建 Pull Request 后,GitHub Actions 会自动运行。

如果测试失败,Pull Request 页面可能显示:

text 复制代码
❌ lint
✅ typecheck
❌ test
✅ build

开发者需要查看日志、修复问题并再次推送。新提交会自动触发新一轮 CI。

4. 配置主分支保护

仅仅运行 CI 还不够,因为开发者仍可能忽略失败结果、强行合并。

因此,工程中通常会给 main 分支设置保护规则,例如:

  • 禁止直接向 main 推送。
  • 必须通过 Pull Request 合并。
  • CI 必须全部通过。
  • 至少需要一名成员完成代码评审。
  • 分支落后时必须先合并或变基到最新 main
  • 禁止强制推送。

这样,CI 就从"提供参考"变成了真正的质量门禁:

text 复制代码
CI 失败 → 不能合并
CI 通过 + 评审通过 → 可以合并

5. 给业务代码编写自动化测试

例如,订单满 100 元打九折:

ts 复制代码
export function calculatePrice(amount: number): number {
  return amount >= 100 ? amount * 0.9 : amount;
}

对应单元测试:

ts 复制代码
import { describe, expect, it } from "vitest";
import { calculatePrice } from "./calculatePrice";

describe("calculatePrice", () => {
  it("applies a 10% discount when amount reaches 100", () => {
    expect(calculatePrice(200)).toBe(180);
  });

  it("does not apply a discount below 100", () => {
    expect(calculatePrice(99)).toBe(99);
  });

  it("applies the discount at the boundary value", () => {
    expect(calculatePrice(100)).toBe(90);
  });
});

当其他开发者不小心修改了折扣逻辑,CI 执行 npm test 时就可能发现回归。

这里有一个重要的职责划分:

  • 开发者和测试人员负责定义正确行为并编写测试。
  • 测试框架负责执行测试并判断实际结果是否符合预期。
  • CI 平台负责在每次代码变化时自动运行测试框架。
  • 分支保护规则负责阻止未通过检查的代码进入主分支。

6. 集成测试中启动外部依赖

如果应用依赖 PostgreSQL、Redis 等服务,CI 可以临时启动这些服务。

例如:

yaml 复制代码
jobs:
  integration-test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: app_test
        ports:
          - 5432:5432

    env:
      DATABASE_URL: postgresql://test:test@localhost:5432/app_test

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - run: npm ci
      - run: npm run db:migrate
      - run: npm run test:integration

CI 会创建一个临时 PostgreSQL 容器,然后:

  1. 创建测试数据库。
  2. 执行数据库迁移。
  3. 启动或调用应用。
  4. 测试应用与数据库的交互。
  5. 任务结束后销毁临时环境。

这也是 Docker 在 CI 中很常见的原因:它可以快速创建一致、隔离的测试依赖。

7. 将不同检查拆成并行任务

小项目可以把所有命令放在一个任务里。项目变大后,一般会拆分:

yaml 复制代码
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm test

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run build

linttestbuild 可以同时运行,缩短等待时间,也更容易看出是哪类检查失败。

8. 构建并保存发布产物

CI 通过后,通常还会生成后续部署需要的产物,例如:

  • Java JAR 包
  • 前端静态文件
  • npm、Python 或 NuGet 软件包
  • Docker 镜像
  • 测试报告和覆盖率报告

例如构建 Docker 镜像:

yaml 复制代码
- name: 构建 Docker 镜像
  run: docker build -t my-app:${{ github.sha }} .

这里使用 Git 提交哈希作为镜像版本:

text 复制代码
my-app:7fd31ab...

这样能够明确知道某个镜像对应哪次代码提交,方便排查和回滚。

如果需要把镜像推送到 Docker Hub 或云厂商的镜像仓库,则要使用平台提供的密钥管理功能保存用户名、令牌等敏感信息,不能直接写在配置文件里。

9. 从 CI 延伸到 CD

常见实践是:

  • Pull Request 阶段只运行 CI 检查。
  • 合并到 main 后构建正式产物。
  • 自动部署到测试环境。
  • 运行冒烟测试。
  • 人工批准后部署到生产环境。

流程可能是:

text 复制代码
Pull Request
    ↓
格式检查 + 静态分析 + 自动测试 + 构建
    ↓
合并到 main
    ↓
构建 Docker 镜像
    ↓
推送镜像仓库
    ↓
部署测试环境
    ↓
冒烟测试
    ↓
人工审批
    ↓
部署生产环境

生产部署还经常配合:

  • 滚动发布
  • 蓝绿发布
  • 金丝雀发布
  • 健康检查
  • 失败自动回滚
  • 日志、指标和告警监控

10. 实际项目通常分层执行

为了兼顾速度和覆盖范围,团队不会在每次提交时都执行所有昂贵测试。常见分层是:

触发时机 常见检查
本地提交前 格式化、快速静态检查
Pull Request Lint、类型检查、单元测试、构建
合并到主分支 集成测试、构建镜像、部署测试环境
发布前 端到端测试、安全扫描、冒烟测试
定时任务 完整回归、依赖漏洞扫描、性能测试

这样可以让开发者尽快得到反馈,同时保留较完整的质量检查。

归根结底,CI 工程实践可以概括为四部分:

text 复制代码
项目提供可自动执行的检查命令
              +
仓库提供 CI 流程配置
              +
代码平台根据事件触发流程
              +
分支规则强制要求检查通过

因此,真正建立 CI,不只是"安装一个 Jenkins 或启用 GitHub Actions",而是把团队对代码质量的要求转化成一系列可以自动运行、结果明确、失败后能够阻止合并的检查

相关推荐
lxw18449125149 小时前
PHP后端(CI框架方向)面试题库,分为基础必问、MySQL、Redis、工程运维、高阶加分、场景实操六大模块,适配该JD全部考点,附带标准答案
ci/cd·面试·php
heimeiyingwang2 天前
【架构实战】CI/CD流水线:从手动部署到一键上线
ci/cd·架构
小小测试开发5 天前
Promptfoo 源码级解析:LLM 评估框架的核心设计与 CI/CD 集成实践
人工智能·ci/cd
csdn_aspnet5 天前
GitHub Actions自动化运维实战,用CI/CD流水线实现测试、部署、安全扫描一体化
运维·安全·ci/cd·自动化·github
菩提树下的打坐5 天前
CI 中的测试分层:让金字塔真正落地
学习·ci/cd
不加糖4355 天前
CI/CD 与 GitHub Actions 入门
ci/cd·github
这个需求做不了5 天前
Jenkins自动化构建与CI/CD流水线,并配置GitLab
java·ci/cd·自动化·gitlab·jenkins
qq_452396236 天前
第六篇:《GitLab CI 进阶:多环境部署与环境变量管理》
git·ci/cd·gitlab
xixingzhe27 天前
软件开发测试分层与CI/CD、沙箱使用完整指南
ci/cd·测试