--- url: /sre/devops/intermediate/16-cicd-separation.md description: 以制品与权限为边界分离构建和发布,避免把组织结构当作技术定律。 --- ## 从仓库交接推导分离 前面已经完成两段操作:构建镜像并推送仓库,目标环境拉取并启动。健康检查又说明“镜像存在”和“服务可用”之间还有距离。现在才给这两段工作划分职责:上游生成并验证制品,下游在确定环境中发布和验收。 一个镜像可以经过测试,但目标环境可能没有容量,或数据库还不兼容。CI 与 CD 分离让构建结果和发布决定各有明确输入、权限与证据。 个人项目中可以由同一个人承担两种职责,也可以把两个阶段放在同一个工作流。分离不要求两个部门、两个平台或每次都人工审批。[上一课](15-compose-health.md)的健康判断会成为 CD 的验收步骤,而不是重新写一套成功标准。 ## 制品是交接契约 | 阶段 | 输入 | 输出 | 权限 | | --- | --- | --- | --- | | CI | 确定的源码 commit、依赖和构建配置 | 经过测试的镜像 digest、报告与来源记录 | 读取源码、写目标镜像仓库 | | CD | 已批准的制品和配置、目标环境 | 实际发布版本、验收和恢复记录 | 读取制品、变更指定环境 | 同一制品从 dev 晋级到 prod 时不重新构建。镜像 digest 用于识别内容,签名用于验证身份;不能把 MD5 或一个标签当作可信来源保证。 ## 责任按能力与场景划分 开发维护应用行为、测试与部署声明;平台维护执行环境、权限和可观测能力;发布责任人确认目标与验收条件。实际可由一个团队共同承担,也可按组织要求分工。 故障归因需要查看应用、基础设施、配置和流程证据,不预设“所有生产故障运维全责”。CI 与 CD 都可能并行或串行,执行模型取决于依赖关系,不是两者的定义差异。 ## 一个可落地的边界 * PR 仅运行检查,不读取生产密钥。 * 合并受保护分支后构建并推送镜像,保存 digest。 * 发布申请固定镜像和配置版本,按风险决定自动准入或人工审批。 * CD 验证来源、部署、检查健康,失败保持失败并保留诊断信息。 * 发布记录关联执行身份、版本、环境和结果,保留周期按实际要求确定。 正式实施时,用独立账号测试”CI 无法修改生产、CD 无法覆盖镜像”;只在架构图上画两个框不算完成分离。 ## 流水线上的权限边界 以 GitHub Actions 为例,CI 和 CD 可以放在同一个仓库的两个 workflow 中,但权限配置不同: ```yaml # .github/workflows/ci.yml — PR 和合并触发 name: CI on: push: branches: [main] pull_request: permissions: contents: read # 只读源码 packages: write # 推送镜像到制品库 jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: docker build -t registry/app:${{ github.sha }} . - run: docker push registry/app:${{ github.sha }} ``` ```yaml # .github/workflows/cd.yml — 仅 main 合并后触发,需环境审批 name: CD on: workflow_run: workflows: [CI] types: [completed] permissions: contents: read # 只读部署配置 id-token: write # 用于 OIDC 认证到目标环境 environment: production # 触发环境审批(如果配置了保护) jobs: deploy: if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: - run: | DIGEST=${{ github.event.workflow_run.head_sha }} kubectl set image deployment/app app=registry/app@sha256:${DIGEST} ``` CI workflow 能推镜像但不能访问生产环境;CD workflow 能部署但不能修改镜像。两个 workflow 通过镜像 digest 交接,而不是通过标签——标签可以被覆盖,digest 指向不可变内容。 ## 常见反模式 | 反模式 | 问题 | 修正方向 | | --- | --- | --- | | CI 流水线直接 `kubectl apply` 到生产 | 构建和发布没有边界,PR 测试代码可能直接影响生产 | 拆成 CI(构建)+ CD(发布)两个阶段 | | CD 阶段重新 `docker build` | 部署的内容未经 CI 验证,制品契约被绕过 | CD 只拉取 CI 产出的固定 digest | | 用 `latest` 标签做发布 | 无法追溯部署的具体内容,回滚指向不明 | 用 digest 或不可变标签标识制品 | | 同一个 token 同时用于构建和部署 | 凭据泄露时影响范围不可控 | CI 和 CD 使用独立的凭据和权限范围 | 判断分离是否有效的标准不是画了多少个框,而是:**CI 的凭据泄露后,攻击者能否直接修改生产环境?** 如果答案是否,分离就落地了。 ## 最小验收与思考 提交代码后只看到 CI 运行,生产版本没有改变;发布失败可以查到具体 digest 和恢复记录;无需重新构建就能把 staging 验证过的制品交给生产。 下一课通过 [CI 流水线](17-ci-pipeline.md)生成这份交接物。思考:如果审批后又修改了镜像标签,原审批还指向同一个内容吗?