CI/CD 流水线
把「能编译」变成「可随时发布的制品、可控的发布过程」。运维视角重点关注:流水线怎么设计、密钥怎么管、制品版本怎么不可变、发布怎么可回滚。示例以 GitHub Actions 与 GitLab CI 为主,需结合真实仓库验证。
先分清 CI 与 CD
- CI(持续集成):每次提交都跑 检查 → 测试 → 构建,产出可部署的制品并归档;
- CD(持续交付/部署):把制品发布到环境。持续交付停在「可发布」人工确认,持续部署全自动到生产。
原则:CI 决定「能不能上」、CD 决定「敢不敢上」。CI 不绿不产出制品,CD 不带门禁不放行。
流水线的七个常见阶段
text
触发 → lint/格式 → 单元测试 → 构建制品 → 制品扫描/签名 → 部署预发 → 灰度/生产(门禁)- 触发要窄:push / pull request 分开,PR 只跑检查不发布,主干才出制品;
- 测试分层:单测快跑在 PR,集成/E2E 在合并后或定时跑,别让慢测试拖垮每次提交;
- 制品不可变:构建一次、处处部署——同一个 sha 的镜像既用于预发也用于生产,绝不在 CD 里重新构建(否则「测的」和「跑的」不是同一份);
- 缓存讲策略:依赖缓存加速 PR(
node_modules/ Maven 仓库),但每次git clean保证不受污染;版本锁定文件(package-lock.json/go.sum)必须提交; - 环境是牛马,不是牛仔:每个环境用独立 secret 与独立凭证,预发接近生产配置(尤其是数据库 schema 迁移要能在预发真实演练)。
GitHub Actions:概念对照
yaml
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [20, 22] # 多版本矩阵:一份配置跑多组
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test
build-and-push:
if: github.event_name == 'push'
needs: test # 依赖:test 过才跑
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.example.com
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build & push image
run: |
docker build -t registry.example.com/app:${{ github.sha }} .
docker push registry.example.com/app:${{ github.sha }}GitLab CI 用 .gitlab-ci.yml,思路一致:stages 定义阶段(build/test/deploy),job 带 stage、only/except(或 rules)、artifacts、environment;Runner 是执行者,services 起依赖(如数据库容器)。
制品与版本:让回滚成为可能
- 镜像 tag 用不可变标识:
git-<commit sha>或<构建号>,同一 sha 可重复部署;latest只在本地开发用; - 构建信息进制品元数据(源码 commit、构建时间、触发人),部署页/审计能追溯到「哪次提交产生了这包」;
- 保留最近 N 个可回滚版本,超期清理镜像仓库,避免「盘满了没法回滚」。
发布策略:灰度是 CD 的灵魂
| 策略 | 方式 | 优点 | 代价 |
|---|---|---|---|
| 滚动 | 逐个替换实例 | 零停机,简单 | 新旧共存,兼容性要求高 |
| 蓝绿 | 整组切换 | 回滚极快(切回旧组) | 双倍资源 |
| 金丝雀 | 小流量新版本 | 风险最可控、真实流量验证 | 需要流量切分与观测 |
- 蓝绿/金丝雀的准入门槛是观测:灰度期间对比错误率、延迟、业务指标,异常即自动回滚——没有观测的灰度等于「让新版本裸奔」;
- 发布节奏要配回滚手册:每条发布记录记下部署的镜像 sha 与前一个 sha,「一键回滚」= 重放旧 sha 再配流量切换;
- 数据库变更先于代码发布并向后兼容(expand/contract),否则「先回滚代码」也救不了已执行的破坏性 DDL。
安全:流水线是供应链的第一道门
- 密钥只进 secrets:运行参数、配置一律经 CI 平台的 secret 注入,不打印、不进日志、不进制品;
- 第三方 actions/images 锁 tag 或 sha,审一遍来源再允许进流水线;
- 依赖与镜像扫描进门禁:高危(已知 CVE / 可疑依赖)阻断发布;
- 流水线权限最小化:只读源码、只推本团队的镜像仓库、deploy 凭证按环境隔离;
- 出包签名(cosign)并在运行时校验,防止「仓库被写坏但没人知道」。
GitOps 一句话
把「部署动作」也声明化:git 仓库是唯一事实源,operator(如 ArgoCD)持续把仓库状态同步到集群,人不再手敲 kubectl apply。适合成熟团队,先保证 CI/CD 基础扎实再引入,不要为了潮流叠复杂度。
常见坑速查
| 坑 | 现象 | 解法 |
|---|---|---|
| CD 里重新构建 | 测的与跑的不是同一份 | 构建一次,制品贯穿所有阶段 |
| tag 用 latest | 部署结果不可预期 | 不可变 tag(sha/构建号) |
| PR 也触发发布 | 测试环境被 PR 污染 | 触发分支收窄,主干才出制品 |
| 密钥写进 yaml | 泄露进仓库历史 | 一律 secrets 注入 |
| 依赖缓存不锁版本 | 构建时好时坏 | 提交 lockfile,CI 用 npm ci |
| 蓝绿/金丝雀无观测 | 带病发布到 100% | 灰度期对比错误率/延迟,异常自动回滚 |
| 先破坏性 DDL 后回滚代码 | 数据不可逆 | expand/contract,变更向后兼容 |
| 回滚手册只在脑子里 | 出事手忙脚乱 | 发布记录含前后 sha,回滚 = 重放旧 sha |
检查清单
- [ ] 流水线覆盖七阶段且 CI 绿才产出制品,制品带不可变 tag 与溯源信息;
- [ ] 测试分层合理,PR 只跑快速检查,慢测试不进关键路径;
- [ ] 密钥全部 secret 注入,三方依赖锁定版本并做来源审查;
- [ ] 发布有门禁与观测基线(错误率/延迟),失败能自动或一键回滚;
- [ ] 数据库变更向后兼容,破坏性操作有前置演练;
- [ ] 回滚手册与发布记录齐全,定期演练灾难恢复路径。