CI/CD 流水线实战:从代码提交到自动上线
把"人肉发布"变成一条自动化、可重复、有门禁的流水线。五段式结构、六条设计原则、一条完整示例、三种部署策略——以及养着一条永远不绿的流水线这类真实的坑。
一、先分清三个概念
发布这件事,很多团队还停留在「人肉流程」:本地跑完全套测试,手动上传包,深夜守在服务器前重启。事故几乎是标配——测试漏跑、发错版本、关键步骤记在某人脑子里、回滚靠手速。CI/CD 要做的,就是把这条链路变成一条自动化的、可重复的、有门禁的流水线。
| 概念 | 全称 | 一句话 |
|---|---|---|
| CI | Continuous Integration 持续集成 | 每次提交都自动构建并验证,让「集成」从痛苦的大事件变成日常小动作 |
| CD | Continuous Delivery 持续交付 | 验证通过后随时可一键部署到生产——发布是业务决策,不是技术冒险 |
| CD | Continuous Deployment 持续部署 | 验证通过后自动部署到生产,不经人工审批 |
持续交付与持续部署只差一步「人工审批」,但这一步的分量完全不同:前者适合监管敏感、爆炸半径大的业务;后者适合灰度能力成熟、回滚极快的团队。先做持续交付,再谈持续部署。
没有回滚方案的部署不叫部署,叫冒险——先把退路修好,再谈自动化。
二、流水线的五段式结构
- 触发:什么事件启动流水线——推送、PR、打 tag、定时。原则:PR 触发验证,主干合并触发部署,tag 触发发布
- 构建:编译、打包、构建镜像。两个关键:依赖缓存(提速)、产物用 commit sha 打标(唯一可追溯)
- 验证:lint、单元测试、集成测试、安全扫描、制品扫描。原则是「快速失败」——先跑几秒能完成的检查
- 部署:环境递进(dev、staging、prod),每级设门禁;生产部署前保留人工审批关口
- 验证与回滚:冒烟测试、健康检查,失败自动回滚到上一个版本
贯穿始终的原则是:构建一次,处处部署。同一个产物从 staging 一路流到 prod,不重新构建——「每个环境自己构建」是版本不一致的头号来源。
三、一条最小可用的流水线
以 GitHub Actions 为例,一条从 PR 到生产的骨架长这样:
name: ci-cd
on:
pull_request: # PR:只验证,不部署
push:
branches: [main] # 主干合并:部署到 staging
tags: ['v*'] # 打 tag:发布生产
jobs:
verify: # 第一段:快速失败
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm test -- --ci
build: # 第二段:构建唯一产物
needs: verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build -t registry/myapp:${{ github.sha }} .
- run: docker push registry/myapp:${{ github.sha }}
deploy-staging: # 第三段:自动进预发
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh staging registry/myapp:${{ github.sha }}
deploy-prod: # 第四段:生产(environment 配审批)
needs: deploy-staging
if: startsWith(github.ref, 'refs/tags/v')
environment: production
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh prod registry/myapp:${{ github.sha }}
- run: ./smoke-test.sh prod # 冒烟验证,失败自动回滚
四个细节值得注意:产物标签是 commit sha(可追溯);environment: production 绑定了审批关口;staging 与 prod 用的是同一个镜像(构建一次原则);最后一步跑冒烟测试,失败即触发回滚。
四、流水线设计的六个原则
- 快速失败:lint 放最前,单测次之,慢的集成测试最后——失败要尽早、尽便宜
- 确定性:锁死依赖版本、固定基础镜像 tag(别用 latest),同样输入必得同样产物
- 一切皆代码:流水线配置进 git 和代码一起评审;在页面上点的配置,等于藏着一份没人 review 的代码
- 环境一致性:同一产物跨环境流动,环境差异用配置注入解决
- 机密管理:secrets 永不进仓库、不进日志;凭证最小权限、优先短时效
- 可观测:每次运行有日志、时长、结果通知;流水线变慢本身就是一种故障
五、部署策略:三种主流姿势
| 策略 | 做法 | 优点 | 代价 |
|---|---|---|---|
| 滚动发布 | 分批替换实例 | 资源占用低,K8s 默认 | 发布期间新旧版本共存 |
| 蓝绿部署 | 备一套完整环境,切流量 | 切换与回滚都是秒级 | 双倍资源 |
| 金丝雀发布 | 先放 1% 流量,观察后放量 | 爆炸半径最小,数据说话 | 需要流量治理能力 |
选择逻辑:能灰度就灰度——金丝雀优先,其次蓝绿;滚动发布适合无状态、兼容性好的服务。无论哪种,回滚路径必须在发布前就验证过:镜像回退(重部署上一个 sha)加功能开关(feature flag)关掉新功能,两条路至少留一条。
六、五个最常见的坑
- 养着一条永远不绿的流水线:flaky 测试(偶尔失败)出现后,大家学会点「重跑」——三个月后没人再看测试结果。flaky 测试零容忍,坏了立即修或删
- 每个环境各自构建:staging 和 prod 的产物不是同一个,出了问题无法确定差异来源。回到「构建一次、处处部署」
- 手工步骤藏在流水线里:某个 step 其实是「等人手动跑脚本再点继续」,等于把隐患埋进自动化。要么自动化掉,要么显式做成审批关口
- 机密泄漏到日志:明文打印环境变量、回显 secret——CI 日志团队可见,泄漏面比想象大
- 流水线越来越慢:一次跑 40 分钟没人等,于是合并前跳过检查。把主干验证控制在 15 分钟内,慢测试挪到夜间或并行化
七、落地路线:三个月三阶段
- 第 1 个月 · 门禁:PR 必须过 lint、单测、构建,让「能合并的前提是绿的」成为默认规则
- 第 2 个月 · 统一产物与预发:主干合并自动部署 staging,全环境使用同一产物,先解决版本一致
- 第 3 个月 · 生产自动化:生产部署「先审批后自动」,配冒烟测试与一键回滚,做一次回滚演练
速查卡
| 场景 | 做法 |
|---|---|
| PR 验证 | 触发 lint、单测、构建,快速失败 |
| 产物管理 | commit sha 打标,构建一次处处部署 |
| 生产发布 | 审批关口 + 金丝雀或蓝绿 |
| 回滚 | 镜像回退 + 功能开关,发布前演练 |
| 流水线健康 | 15 分钟目标,flaky 测试零容忍 |
| 机密 | secrets 进密钥库,不进仓库与日志 |
写在最后
CI/CD 的价值不在「自动化本身」,而在于把发布从「一次冒险」变成「一次例行操作」:小步、频繁、可回滚。当发布变得无聊,团队才敢一天发十次,功能才敢快速试错——发布频率和回滚速度,才是工程效能的两个真实刻度。
评论(0)