路线图

09-CI 核心概念

星辉 2026-07-02 阅读 5 min 882 字 路线图
09-CI 核心概念 封面

CI(Continuous Integration,持续集成)是 DevOps 工具链的核心环节。本章不讲具体工具,讲 CI 的概念框架:为什么需要 CI、流水线的标准结构、缓存与并行、触发策略、以及一个完整 CI 闭环的六个环节。

集成地狱#

没有 CI 的团队会陷入"集成地狱":

text
开发 A 在自己分支写了 2 周
开发 B 在自己分支写了 2 周
开发 C 在自己分支写了 3 周
        │
        ▼ 项目要上线了,开始合并
        │
   合并冲突像滚雪球
        │
   解决冲突用了 3 天
        │
   合完后测试挂了一半
        │
   修复又用了 2 天
        │
   上线延期一周

这是"集成频率太低"的代价。Martin Fowler 的说法:集成痛苦总量是固定的,越晚集成单次痛苦越大。每天集成 10 次小冲突,远比 2 周后集成 1 次大冲突轻松。

CI 的核心思想就是高频集成 + 自动化验证:每次代码提交都自动跑构建 + 测试 + 扫描,让问题在"刚写完 5 分钟"时就被发现,而不是"2 周后合并时"。

流水线五阶段#

一条完整的 CI 流水线通常有五个阶段,从代码到报告:

text
① 代码 ──► ② 构建 ──► ③ 测试 ──► ④ 扫描 ──► ⑤ 报告
  checkout    build       test        scan       report

① 代码(Checkout)#

从仓库拉取代码。关键点:

  • 浅克隆加速:默认 clone 拉全部历史,CI 只需要最新代码时用 --depth 1
  • 完整历史场景:semantic-release、release-please 这类工具要算版本号,需要 fetch-depth: 0
  • 分支策略:CI 通常只在 main/PR 触发完整流水线,feature 分支可只跑 lint+test
  • submodule 处理:如果仓库有 submodule,CI 要加 --recurse-submodules 或在配置里开启 submodule 拉取

② 构建(Build)#

把源码 + 依赖封装成可运行产物。对容器化应用,这步就是 docker build。关键点:

  • 多阶段构建:构建时需要的工具链不进最终镜像,大幅减小镜像体积(详见 14 章)
  • 构建缓存:层缓存、BuildKit cache mount、registry cache(详见 14 章)
  • 多架构:amd64 + arm64 双架构镜像
  • 多 registry 推送:海外 ECR + 国内 ACR 双推

③ 测试(Test)#

自动验证代码行为正确。层级:

  • 单元测试:函数级别,秒级完成
  • 集成测试:模块间协作,分钟级
  • 端到端测试(E2E):完整流程,分钟级到小时级
yaml
# 典型测试 stage
test:
  stage: test
  script:
    - go test -race -coverprofile=coverage.out ./...
    - go tool cover -func=coverage.out | tail -1
  coverage: '/total:\s+\(statements\)\s+(\d+\.\d+)%/'

④ 扫描(Scan)#

在 CI 阶段把质量问题拦在合并前:

  • 代码质量:SonarQube、CodeClimate
  • Lint/格式化:golangci-lint、ESLint、black
  • 依赖漏洞(SCA):Trivy、Snyk 扫描依赖中的 CVE
  • 镜像漏洞:Trivy 扫描构建出的镜像
  • 敏感信息:gitleaks 扫描代码里的密钥泄漏
  • Schema 检查:DB schema diff(详见 13 章)

⑤ 报告(Report)#

把测试覆盖率、扫描结果、构建产物收集起来。关键点:

  • artifact 传递:构建产物传给下游 stage(如测试报告)
  • 报告可视化:覆盖率进 SonarQube,漏洞进 GitHub Security tab
  • 通知:钉钉/Slack 通知流水线结果

报告 stage 常被忽视,但它是"让 CI 价值可见"的关键。没有报告,覆盖率数字只在日志里看一眼就丢;有了报告,覆盖率趋势能在 Grafana 看板里持续追踪,漏洞能在 GitHub Security tab 里集中管理。

流水线的失败处理#

CI 流水线的失败处理策略直接影响开发者体验:

  • fail-fast:一个 job 失败立即取消其他并行 job。适合"确定要修就趁早"的场景
  • fail-slow(fail-fast: false):让所有 job 跑完再报失败。适合"想一次看完所有问题"的场景,如矩阵构建测试多版本兼容性
  • allow_failure:某个 job 失败不影响整体状态。适合"警告但不阻塞"的检查(如可选的代码质量检查)
yaml
# GitHub Actions 矩阵不 fail-fast
strategy:
  fail-fast: false
  matrix:
    go-version: ["1.21", "1.22", "1.23"]

构建缓存与并行#

CI 速度是开发者体验的核心。两个杠杆:缓存和并行。

构建缓存#

缓存的核心思想:如果输入没变,输出可以复用。

yaml
# Go modules 缓存示例
cache:
  key: "$CI_PROJECT_NAME-go-modules"
  paths:
    - .go/pkg/mod/
    - .go/cache/

Docker 镜像构建的缓存有两层:

  1. 层缓存:Dockerfile 每条指令一层,前面所有层命中缓存时这一层才复用。改了 COPY . . 这层,后面的 RUN go build 会重建;但前面的 COPY go.mod 不变,RUN go mod download 会复用。
  2. BuildKit cache mount:--mount=type=cache 让包管理器缓存(pip、npm、go mod)跨构建持久化,不进镜像层。
dockerfile
# BuildKit cache mount
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o /app/server ./cmd/server

并行#

流水线里独立的部分应该并行执行。三种并行模式:

Job 级并行:同 stage 内多个 job 自动并行跑(GitLab CI 里就是几个 job 声明同一个 stage)。

yaml
unit-test:
  stage: test
  script: [go test ./...]
integration-test:
  stage: test
  script: [go test -tags=integration ./...]
e2e-test:
  stage: test
  script: [./e2e.sh]
# 三个 job 同属 test stage,自动并行

矩阵构建:同一 job 在多环境并行。

yaml
test:
  strategy:
    matrix:
      go-version: ["1.21", "1.22", "1.23"]
      os: [ubuntu-latest, macos-latest]
  # 6 个 job 并行跑

Stage 间串行 + Stage 内并行:流水线整体是 stage 串行(test → build → deploy),每个 stage 内的 job 并行。这是大多数 CI 系统的默认模型。

触发策略#

什么时候触发流水线?四种主流触发方式:

触发方式时机典型用途
push推到指定分支main 分支自动构建发版
PR/MR创建或更新 PR/MR合并前跑完整检查
定时(schedule)cron 定时每晚跑 E2E、每周扫漏洞
路径过滤只在特定路径变更时触发Monorepo 只构建改动的服务
手动(manual)手动点击PROD 部署需要人工触发

路径过滤(Monorepo 必备)#

yaml
# GitHub Actions 路径过滤
on:
  pull_request:
    paths:
      - 'services/user-service/**'
      - 'libs/auth-lib/**'
      - '.github/workflows/user-service.yml'

# GitLab CI rules
build-user-service:
  rules:
    - changes:
        - services/user-service/**/*
        - libs/auth-lib/**/*

分支策略#

yaml
# 只在 main 分支触发完整流水线
rules:
  - if: '$CI_COMMIT_BRANCH == "main"'

# PR 触发检查但不部署
rules:
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

# tag 触发发版
rules:
  - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'

从代码到生产:CI 闭环六环节#

把 CI 放在整个 CICD 闭环里看,完整链路是六个环节:

text
①代码仓库 → ②触发 → ③构建(CI) → ④制品(镜像) → ⑤部署声明 → ⑥CD收敛(GitOps) → 线上
   repo      trigger   build       artifact      manifest      reconcile
     ▲                                                              │
     └──────────────────── 回滚 = revert 声明 + 重新收敛 ───────────┘

① 仓库(Repo)#

代码组织方式(Monorepo vs Multirepo)+ 平台选型 + 分支模型(GitHub Flow 等)。详见前面章节。

仓库选型直接影响后续所有环节。Monorepo 改一处全局可见、原子提交、统一 CI,但权限/构建粒度粗,需要路径过滤触发;Polyrepo(一服务一仓)隔离清晰但跨服务改动要多 PR。GitOps 声明仓常单独一个——声明与业务代码分离,机器人回写 newTag 不污染业务仓历史。

分支模型上,main 受保护 + squash merge 是主流基线。squash merge 让主线历史线性可信,一个 PR 对应一个 commit,便于 revert 和 cherry-pick。

② 触发(Trigger)#

push / PR / manual / schedule / API,配合 branchesFilter 控制只让该触发的流水线触发。生产环境部署通常要 manual 审批。

触发策略的关键工程细节:

  • branchesFilter 必须匹配 default branch:仓库默认分支是 master 而 yaml 里写 main,会静默不触发——这是所有"基于代码源触发"工具(云效、Jenkins multi-branch、GitLab CI)的共同坑
  • 同仓多流水线要双重隔离:branchesFilter + triggerFilter.exclude 防止互相误触发
  • PR 触发只跑无 secret 的 stage:CI 用默认的 pull_request 事件即可——fork PR 天然拿不到 secrets(GITHUB_TOKEN 只读)。pull_request_target 恰恰相反,它带着 base 仓库的写权限和 secrets 运行,只在确需 secrets(如自动打标签)时才用,且绝不 checkout 执行 PR 里的不可信代码,并加仓库归属检查
  • cloneDepth 注意:semantic-release/changesets 这类工具要算版本号,需要 fetch-depth: 0 拉完整历史;纯构建用 --depth 1 浅克隆加速

③ 构建(Build)#

源码 → 不可变制品(镜像)。关键原则:

  • 一次构建,多次部署:同一个镜像 tag 从 QA → PRE → PROD,不重新构建。这是避免"测的不是发的"的根本措施。
  • 构建即测试左移:单测、lint、安全扫描、schema 检查都在这阶段拦,越早拦越便宜。

构建阶段还要处理多架构(amd64 + arm64)和多 registry 推送(海外 ECR + 国内 ACR 双推)。双推的关键不是"推两次"而是"一次构建双推"——用同一个 Dockerfile、同一个 buildArgs,构建一次后 docker push 到两个 registry,保证字节级一致。CN 构建机如果在国内访问不了 Docker Hub,基础镜像要走私有 registry,包管理器走国内 mirror(npm/pip/goproxy)。

④ 制品(Artifact)#

镜像 tag 策略:{commit短hash}-{timestamp},全环境复用。绝不用 latest——不可溯源、不可回滚。

text
build ──► 镜像 abc123-2026-06-09-10-30 ──┬─► [QA] 自动部署
                                            ├─► [PRE] 人工审批后部署
                                            └─► [PROD] 审批后部署

✓ 测的镜像 = 发的镜像(字节级一致)
✗ 反模式:每个环境各 build 一次

⑤ 部署声明(Manifest)#

声明式 vs 命令式:

  • 命令式(kubectl apply -f):无单一事实源、漂移难查
  • 声明式(GitOps):期望状态写进 Git,系统负责收敛

Kustomize 的 base + overlay 分层是多环境管理的标准方案:

text
base/{服务}/                    overlay: clusters/{env}/applications/...
┌──────────────────────┐       ┌───────────────────────────────────────┐
│ deployment.yaml      │       │ kustomization.yaml:                     │
│ service.yaml         │◄──引用─│   resources: [../../base/.../服务]       │
│ kustomization.yaml   │       │   images: [{name:.., newTag: <commit>}] │
└──────────────────────┘       │   patches: [副本数/配额/...]              │
                                └───────────────────────────────────────┘

Kustomize 的关键能力:namePrefix/labels(批量加前缀/标签)、images(换 tag/registry)、patches(strategic-merge / json6902 精确改字段)、generators(ConfigMap/Secret 生成)。为什么 Kustomize 不用 Helm:Helm 是模板语言({{ }})+ values,灵活但易写出不可读的逻辑;Kustomize 是"拿现成 yaml 打补丁",无模板语言、更声明式、K8s 原生(kubectl -k),多环境 overlay 场景更清爽。复杂参数化/分发第三方 chart 才用 Helm。

ApplicationSet Generator 让"新服务建目录即被纳管"——Matrix(clusters × git directories)自动为每个集群每个服务目录生成一个 ArgoCD Application,目录即配置。

⑥ CD 收敛(GitOps)#

Pull-based(ArgoCD/Flux,主流)vs Push-based(流水线直接 kubectl apply):

  • Pull 模式:CD 控制器在集群内主动拉 Git diff 收敛。好处=可审计、可回滚(revert commit)、防漂移、凭据不外泄。
  • Push 模式:流水线直接 kubectl apply。简单但风险大——kubeconfig 给流水线、无单一事实源。

选 Pull-based。

一句话串起来#

代码进受保护的 main → 触发流水线 → 构建出可溯源镜像双推 registry → deploy.py 把镜像 tag 回写进 Kustomize overlay → ArgoCD pull 模式检测到 git 变化 → Kustomize build 渲染 → 收敛到集群 → 线上滚动更新;回滚 = revert 那个 commit,ArgoCD 自动收敛回旧版。

实战要点#

  • CI 的目标是"高频小痛苦",不是"零痛苦"。每次提交跑 5 分钟 CI 比两周后跑 30 分钟集成要好。
  • 一次构建多次部署是铁律。每个环境重新构建会引入"测的不是发的"风险。
  • 缓存和并行是 CI 速度的两个杠杆。先做缓存(依赖层分离、cache mount),再做并行(job 级并行、矩阵构建)。
  • 路径过滤是 Monorepo CI 的必备。没有路径过滤,Monorepo 的 CI 会变成"改一行,全仓库构建"。
  • PROD 部署必须 manual。CI 自动跑到 PRE,PROD 要人工审批——这是"快速验证 + 谨慎上线"的平衡。
  • 门禁要分层:pre warning(PR 时提醒)+ post fail(合并前阻塞)。详见 13 章。
  • CI 失败要快速反馈。开发者的注意力窗口很短,CI 跑 15 分钟才报失败,开发已经切去别的事了。把 lint、format 这种快速检查放前面,慢的集成测试放后面。
  • 流水线模板化是规模化 CI 的关键。超过 30 条流水线时必须做模板化,否则维护成本线性增长。详见后续工具章节。

小结#

CI 的核心是"高频集成 + 自动化验证"。一条完整的 CI 流水线有代码 → 构建 → 测试 → 扫描 → 报告五个阶段,配合缓存和并行加速,配合触发策略控制"什么时候跑什么"。

把 CI 放在整个 CICD 闭环里看,它是六个环节(代码 → 触发 → 构建 → 制品 → 声明 → 收敛)的前半段。CI 的产物(不可变镜像 + GitOps 声明)是 CD 的输入。后面几章讲具体的 CI 工具:GitHub Actions、GitLab CI、Jenkins。