持续集成、持续交付与持续部署

三个词经常被混用,但它们描述的其实是自动化程度递进的三件事:

  • 持续集成(CI):开发者频繁把代码合入主干,每次合入都自动构建并跑测试,尽早发现集成问题。
  • 持续交付(CD,Delivery):在 CI 之上,保证任何一次通过验证的变更都能随时部署到生产,只是部署这步由人点击确认。
  • 持续部署(CD,Deployment):连那一次人工确认也去掉,通过流水线的变更自动进入生产。

大多数团队的现实目标是持续交付:把「能上线」变成常态,把「要不要上线」留给人的判断。真正做到持续交付后,发版会从一件需要提前一周准备的大事,变成一天里可以发生很多次的小事。

持续交付衡量的不是流水线有多快,而是「从提交到可以上线」这段距离有多短、有多稳。
本文核心知识点
  • 四阶段构建、测试、制品、部署,每阶段产出可验证的中间物。
  • 制品不可变一次构建、多次部署,用镜像摘要而非 tag 保证一致。
  • 灰度策略蓝绿、金丝雀、滚动各有取舍,按风险与资源选。
  • 回滚预案发布前先想好怎么退,回滚要能一条命令完成。
CI/CD 流水线示意:传送带、三个抽象工位、齿轮与火箭组成的蓝色扁平插画
好的流水线像传送带:代码放上去,自动走完构建、测试与发布,人只在关键闸口把关。

流水线的四个阶段

不管用什么工具,一条健康的流水线都可以拆成四个阶段。理解每个阶段的职责,比记住某个工具的语法重要得多。

阶段做什么产出与判定
构建 Build装依赖、编译、打包产物 / 镜像,失败即阻断
测试 Test单元、集成、静态检查测试报告与覆盖率
制品 Artifact打标签、推送到仓库带版本号的不可变制品
部署 Deploy按策略发布到各环境健康检查通过、指标正常

设计时有两条硬原则:

  • 快速失败:把最便宜、最容易失败的检查放最前面(lint、单元测试),让问题在 1 分钟内暴露,而不是等 10 分钟后才在部署阶段报错。
  • 同源制品:测试环境验证过的那个制品,应当原封不动地部署到生产。中间重新构建一次,就等于引入了一次未经测试的变量。

用 GitHub Actions 写第一条流水线

下面这份配置覆盖了「构建镜像并推送到镜像仓库」这条最小闭环。它放在仓库的 .github/workflows/ 目录下,推送到主干或打标签时自动触发。

name: build-and-push

on:
  push:
    branches: [ main ]
    tags: [ 'v*' ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Set up Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to registry
        run: echo "$REGISTRY_TOKEN" | docker login ghcr.io -u "$REGISTRY_USER" --password-stdin

      - name: Build and push
        run: |
          IMAGE="ghcr.io/$GITHUB_REPOSITORY:$GITHUB_SHA"
          docker buildx build --push --cache-from type=gha --cache-to type=gha,mode=max -t "$IMAGE" .

几个刻意写进去的细节值得留意:

  • 用 commit SHA 做 tag:比 latest 可追溯,任何一次部署都能定位到具体代码。
  • 开启构建缓存:cache-from/to 让后续构建提速数倍。
  • 显式声明权限:最小权限原则,避免 job 拿到不必要的写权限。
  • 凭据不进仓库:REGISTRY_USER 与 REGISTRY_TOKEN 配在仓库的 Settings → Secrets 里,工作流在该步骤的 env 中以 secrets 上下文引用它们;YAML 与脚本里只出现变量名,永远不出现明文口令。

制品管理与版本可追溯

很多团队上了流水线却依然发愁,问题往往出在制品管理:镜像用 latest 覆盖,出了问题不知道线上跑的到底是哪一版。把制品管理做对,回滚和审计才有基础。

  • 制品不可变:一旦推送就不再覆盖,用内容摘要(digest)标识唯一版本。
  • 元数据齐全:每个制品记录提交哈希、构建时间、构建者、流水线运行号。
  • 版本可追溯:从线上运行的镜像 digest,能反查到对应的代码提交与流水线记录。
  • 保留策略:按需保留最近 N 个版本,老版本归档而非随意删除。
# 部署时固定到具体 digest,杜绝「同名不同内容」
docker run -d ghcr.io/acme/api@sha256:9f2c1ab7e41b5d8c

# 查询线上容器实际使用的镜像摘要
docker inspect api | jq -r '.[0].Image'

# 从镜像标签反查代码提交
docker inspect api:1.4.2 | jq -r '.[0].Config.Labels["org.opencontainers.image.revision"]'

把这几条写进团队规范后,一个常见问题会自然消失——「线上那个 bug 到底修没修」不再靠人回忆,而是查一下 digest 就有答案。

部署策略:蓝绿、金丝雀与滚动

部署不只是「把新版本换上去」,而是「如何让新版本在不伤害用户的前提下接管流量」。常用策略有三种,取舍点在于风险、资源与回滚速度。

策略做法优点 / 代价
滚动 Rolling分批替换旧实例,新实例就绪后再动下一批省资源;但新旧版本会短暂共存
蓝绿 Blue-Green起一套全新环境,验证后流量整体切换切换与回滚极快;需要双倍资源
金丝雀 Canary先放少量真实流量给新版,观察指标再逐步放大风险最低;需要流量治理能力

金丝雀是实践中最值得投入的一种:它把「发布」变成一次可观测的实验。典型做法是 5% 流量给新版,观察 15 分钟的错误率与延迟,若无异常再放大到 25%、50%、100%;任何一项指标越界就自动回滚。判断好坏要有明确的放行标准,而不是肉眼看面板决定。

没有观测的灰度等于赌博。灰度发布的前提,是你能对两个版本的关键指标做对比。