Skip to content
DevOps高级阅读约 1 分钟

CI/CD 进阶与渐进式交付 ​

本篇定位

你已经会写"push 了就跑个脚本"的流水线,但想搞懂 GitHub Actions / GitLab CI 怎么写得专业、安全、可复用,以及怎么把发布从"一把全量替换"升级到蓝绿 / 金丝雀 / 自动回滚。适合已经搭过 Jenkins/GitLab CI、想往企业级流水线和渐进式交付演进的 SRE。 本篇版本:GitHub Actions(托管服务,随平台更新)、GitLab CI 17.x、Argo Rollouts v1.10.0、Flagger v1.44.0、Unleash v8.2.0(自托管需 PostgreSQL 15+)。所有版本以官网当前稳定版为准。

一、背景与基础 ​

1.1 小白类比:流水线即代码 ​

CI/CD 流水线就像工厂的自动传送带:原料(代码)从一头进来,经过一道道工序(编译 → 测试 → 扫漏洞 → 打包镜像 → 部署),成品从另一头出去。**流水线即代码(Pipeline as Code)**就是把这条传送带怎么造写成 YAML 放进 Git,和代码一起评审、版本化,改流水线提个 MR 就行,不用找管理员点界面。

方式问题Pipeline as Code 的好处
界面点选配置配置在服务器上,没法评审、没法 diff配置进 Git,可评审、可回滚、可复用
每个项目各写各的标准不统一抽成 reusable 模板,一处改处处生效

1.2 CI 与 CD 到底各管什么 ​

阶段全称干什么失败了会怎样
CI(持续集成)Continuous Integration合并代码后自动构建、跑测试、出制品拦住坏代码,不进下游
CD(持续交付)Continuous Delivery制品自动部署到类生产环境,手动点一下才上生产生产保持随时可发布
CD(持续部署)Continuous Deployment合格后全自动上生产,无人值守对自动化和回滚能力要求极高

二、GitHub Actions 深化 ​

2.1 三层结构:workflow / job / step ​

  • workflow / job / step:workflow 是一个 .github/workflows/*.yml(定义触发事件与 job);job 是跑在干净 VM 上的独立单元,多个默认并行;step 是 job 里的一条命令或一个 uses: action。

2.2 一个生产级 workflow 完整片段 ​

yaml
# .github/workflows/build-and-deploy.yml
name: build-and-deploy
# 触发:推 main、打 v* 标签、或对 main 发 PR
on:
  push: { branches: [main], tags: ["v*"] }
  pull_request: { branches: [main] }

# 关键:默认最小权限,整个 workflow 只读(要写权限的 job 单独放开)
permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4          # 缓存依赖,加速 CI
        with: { node-version: "22", cache: "npm" }
      - run: npm ci
      - run: npm run lint
      - run: npm run test:cov
  build:
    needs: test
    runs-on: ubuntu-24.04
    outputs: { image: ${{ steps.meta.outputs.image }} }
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3         # 凭据放 GitHub Secrets
        with:
          registry: harbor.example.com
          username: ${{ secrets.HARBOR_USER }}
          password: ${{ secrets.HARBOR_TOKEN }}
      - name: 生成镜像标签
        id: meta
        run: echo "image=harbor.example.com/team/order:${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.image }}
  deploy-prod:
    needs: build
    environment: production       # 绑定环境保护(手动审批 / 环境级 secret)
    runs-on: ubuntu-24.04
    if: startsWith(github.ref, 'refs/tags/v')   # 只有打 tag 才上生产
    steps:
      - uses: actions/checkout@v4
      - run: echo "部署 ${{ needs.build.outputs.image }} 到生产"

2.3 关键进阶点 ​

主题要点示例
最小权限 permissions别全仓库 write-all;默认 contents: read,发版 job 单独放开见上面顶层 permissions
缓存 + 并发缓存 node_modules/.m2 提速;同分支新 push 取消旧 runcache: npm;concurrency.group: ci-${{ github.ref }}
environment + Secrets生产部署绑 environment: production 配手动审批;敏感值放 Secrets 不写进 yml${{ secrets.XXX }}

2.4 Composite Action 与 Reusable Workflow ​

跨仓库复用流水线有两种方式,别用混:

方式用途特点
Composite Action复用一段步骤组合(如"装 Go + 缓存 + lint")在同一个 job 里 uses: 进你的步骤
Reusable Workflow复用一整套完整 job 编排(如"标准发布流程")别的仓库 uses: org/.github/.github/workflows/release.yml@v1 调用
yaml
# reusable workflow 被调用方写法(业务仓库里,固定版本别用 main)
jobs:
  call-release:
    uses: your-org/.github/workflows/reusable-release.yml@v1.5.0
    with: { image-name: order-service }
    secrets: inherit

别用 @main 引用 reusable workflow

生产中引用 reusable workflow / 第三方 action 要固定到具体 tag 或 commit SHA(如 @v1.5.0),不要用 @main——上游被改了你不知道。这是供应链安全的基本要求。

三、GitLab CI 深化 ​

3.1 核心:.gitlab-ci.yml 的阶段与作业 ​

GitLab CI 以 stage 为流水线阶段、job 为作业单元。先看一张图:

3.2 完整 .gitlab-ci.yml(含 include / rules / needs DAG) ​

yaml
# 全局默认镜像与缓存
image: harbor.example.com/library/golang:1.23
default: { tags: [kubernetes] }
cache:
  key: "$CI_COMMIT_REF_SLUG"
  paths: [.go/pkg/mod/]

# include:抽公共配置,避免一个文件上千行
include:
  - local: "/ci/build-templates.yml"
  - remote: 'https://gitlab.com/your-org/cicd-templates/-/raw/main/security-scan.yml'

variables:
  REGISTRY: harbor.example.com/team/order

# needs 构建 DAG,不被 stage 顺序硬卡
unit_test:
  stage: test
  needs: []                       # 不依赖前置,尽早并行
  script: [go test ./... -cover]
build_image:
  stage: build
  image: harbor.example.com/library/kaniko-project/executor:v1.23.2
  script:
    - echo "{\"auth\":{\"auth\":\"$HARBOR_PULL\"}}" > /kaniko/.docker/config.json
    - /kaniko/executor --context . --dockerfile Dockerfile --destination $REGISTRY:$CI_COMMIT_SHORT_SHA
  only: [main, tags]
# rules:比 only/except 更灵活,可组合 &&/|| 与 changes
deploy_staging:
  stage: deploy
  script: [echo "部署到 staging"]
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: always
    - if: '$CI_COMMIT_BRANCH =~ /^feature\//'
      when: manual              # feature 分支手动触发
    - when: never
deploy_production:
  stage: deploy
  script: [echo "生产只在打 tag 且手动审批后部署"]
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
      when: manual
      allow_failure: false

3.3 include / parent-child / rules / needs 速查 ​

特性干什么什么时候用
include / parent-child把 yml 拆出去复用;父流水线按改动模块触发多条子流水线公共模板多仓库共享;monorepo 按服务拆分构建
needs(DAG)/ rulesneeds 声明 job 依赖跳过串行等待;rules 取代 only/except 灵活决定是否跑让流水线更快、按环境分支定触发策略

四、分支策略:Trunk-based vs GitFlow ​

维度Trunk-based(主干开发)GitFlow
主分支所有人频繁合到 main,短生命周期特性分支main + develop + feature/release/hotfix 多层分支
发布频率高频、小批量、持续部署周期性发版,release 分支冻结
配合手段强依赖 Feature Flag + 自动化测试分支纪律 + 手动切分支
复杂度低(分支简单),但要求自动化测试和灰度很成熟高(分支多、合并痛苦)
适合云原生、天天发版的互联网团队有固定发版周期、桌面/企业软件

现代云原生主流是 Trunk-based + Feature Flag + 渐进式发布:代码天天合 main,没做完的功能用开关藏起来,发出去也能一键关。

五、发布策略对比 ​

策略怎么做优点缺点适用
滚动 Rolling新版本逐步替换旧 Pod(K8s 默认)简单、无额外资源新旧版本短暂共存,出问题影响范围边滚动边扩大大多数无状态服务默认
蓝绿 Blue-Green起一套全新"绿"环境,验证后流量一次性切过去切换快、回滚秒切要双倍资源重要、需要快速回滚的系统
金丝雀 Canary先放 1%~5% 流量给新版,观察指标,逐步放大风险最小,自动按指标回滚需要流量治理 + 指标观测核心服务、大版本升级

蓝绿是"两套环境整体切流量";金丝雀是"同环境内按百分比逐步导流、指标恶化自动回滚"。

六、Feature Flag(特性开关) ​

6.1 小白类比 ​

Feature Flag 就像电灯开关:代码已上线,但某功能默认"关灯"藏着;产品想灰度给某批用户,远程"开灯"即可,不用重新发版,出问题远程关灯秒回退。它解决三件事:① 未完成功能先合并主干(配合 Trunk-based);② 灰度给特定用户/比例;③ 紧急 kill switch。

6.2 自托管 Unleash(v8.2.0) ​

Unleash 是开源特性开关平台。v8 起要求 PostgreSQL 15+。用 Docker Compose 起一个:

yaml
# docker-compose.yml(自托管 Unleash v8,需 PostgreSQL 15+)
services:
  postgres:
    image: postgres:15-alpine
    environment: { POSTGRES_USER: unleash, POSTGRES_PASSWORD: change-me-strong, POSTGRES_DB: unleash }
  unleash:
    image: unleashorg/unleash-server:v8.2.0
    depends_on: [postgres]
    environment:
      DATABASE_HOST: postgres
      DATABASE_NAME: unleash
      DATABASE_USER: unleash
      DATABASE_PASSWORD: change-me-strong
    ports: ["4242:4242"]

应用里用 SDK 判定开关(伪代码):

java
// 业务代码里:开关开了才走新逻辑,否则老逻辑
if (unleash.isEnabled("checkout-v2", userContext)) {
    return newCheckoutService.pay(request);
} else {
    return legacyCheckoutService.pay(request);
}

七、渐进式交付:Argo Rollouts 与 Flagger ​

前面说的金丝雀,手动一步步调流量太麻烦。渐进式交付控制器自动做:先切 5% → 等几分钟看错误率/延迟 → 没问题再切 25%/50%/100%;指标一旦恶化自动回滚。

7.1 Argo Rollouts vs Flagger ​

维度Argo RolloutsFlagger
资源类型Rollout CRD 替代 DeploymentCanary CRD 包装已有 Deployment
流量治理需配合 Istio / NGINX / ALB Ingress原生支持 Istio、Linkerd、App Mesh、NGINX
指标分析AnalysisTemplate 查 PrometheusMetricTemplate + CanaryAnalysis
适合谁已经用 Argo CD 的团队已经用 Flux / Service Mesh 的团队

7.2 Argo Rollouts 完整 Canary 清单(v1.10.0) ​

yaml
# 1) AnalysisTemplate:定义"健康指标"(Prometheus 查询)
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: { name: error-rate }
spec:
  metrics:
    - name: pod-error-rate
      interval: 30s
      successCondition: result[0] <= 0.02     # 错误率 <= 2% 才算健康
      failureLimit: 3
      provider:
        prometheus:
          address: http://prometheus.monitoring.svc:9090
          query: |
            sum(rate(http_requests_total{status=~"5..",service="order"}[2m]))
            / sum(rate(http_requests_total{service="order"}[2m]))
---
# 2) Rollout:用 CRD 替代 Deployment,定义金丝雀步骤
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: { name: order }
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: order-canary
      stableService: order-stable
      steps:
        - setWeight: 5                 # 先切 5% 流量给新版
        - pause: { duration: 5m }      # 观察 5 分钟
        - analysis: { templates: [{ templateName: error-rate }] }
        - setWeight: 25
        - pause: { duration: 10m }
        - analysis: { templates: [{ templateName: error-rate }] }
        - setWeight: 100               # 全量
  selector: { matchLabels: { app: order } }
  template:
    metadata: { labels: { app: order } }
    spec:
      containers:
        - name: order
          image: harbor.example.com/team/order:v1.4.0
bash
# 安装 Argo Rollouts(固定 v1.10.0)
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/download/v1.10.0/install.yaml
kubectl argo rollouts get rollout order --watch     # 查看发布进度
kubectl argo rollouts abort rollout order           # 手动回滚

配合 Argo CD 做 GitOps:Argo CD 负责把期望状态写进集群,Rollouts 负责把新版本平滑推上去。

八、常见问题排错 ​

现象原因解决
Actions 里拉私有仓库 action 报 403token 权限不足给 GITHUB_TOKEN 加 contents: read,或用 Fine-grained PAT
GitLab CI job 一直 pendingrunner 没有匹配标签检查 tags: 是否有对应 runner,或 runner 没上线
金丝雀卡住不继续pause 时间未到 / analysis 没通过kubectl argo rollouts pause/resume;查 Prometheus 查询是否报错
Argo Rollouts 流量不切没配 canary/stable Service 或缺流量网关确认 service selector,并接入 Istio/NGINX 流量拆分

生产红线

  • 生产部署必须绑定 environment 保护 + 手动审批,禁止 main push 自动直上生产(除非你已有成熟的全自动回滚)。
  • 引用第三方 Action / Reusable Workflow 固定到 tag 或 commit SHA,不要跟 @main。
  • 金丝雀一定要配自动回滚指标(错误率、延迟),否则"自动灰度"只是"自动放大事故"。
  • 镜像 tag 固定具体版本,流水线里禁止用 latest 做部署标签。
  • Feature Flag 是临时设施,上线稳定后要及时清理死开关,否则代码里全是 if/else 烂尾。

相关速查 ​

官方文档 ​