首页
/ agents24/agents cicd-automation 插件高级部署策略参考:GitHub Actions / GitLab CI / Azure Pipelines 多平台流水线与金丝雀及数据库回滚实战

agents24/agents cicd-automation 插件高级部署策略参考:GitHub Actions / GitLab CI / Azure Pipelines 多平台流水线与金丝雀及数据库回滚实战

2026-09-08 09:05:45作者:裘旻烁

本文是 agents24/agents 插件市场中 cicd-automation 插件 deployment-pipeline-design 技能的进阶参考指南,主题聚焦于部署流水线的“高级纵深”:三大主流 CI/CD 平台(GitHub Actions、GitLab CI、Azure Pipelines)的完整生产级流水线配置、多区域金丝雀推广、Argo Rollouts 高级流量模式(基于实验的 A/B 分析、基于 Header 的内测分流)、数据库迁移回滚策略(Expand/Contract、Flyway Undo、零停机索引)、蓝绿切换与数据库共舞的完整示例,以及部署冻结自动化与通知脚本。读完本文,你将能直接复刻这些配置,为自己的多环境、多区域 Kubernetes 服务设计出“质量门禁 + 渐进式发布 + 快速可逆”的生产交付链路。

deployment-pipeline-design 技能采用“渐进式披露”分层组织文档:核心模式与决策表位于 SKILL.md,常规模式详解与工作示例位于 details.md,而本文所讲的正是其中的“Advanced Topics”层——平台定制化配置与高级回滚策略。本文由该参考文档(plugins/cicd-automation/skills/deployment-pipeline-design/references/advanced-strategies.md)为骨架展开,可配合仓库内同插件的 github-actions-templatesgitlab-ci-patternssecrets-management 等技能一并查阅,技能如何被 Agent 挂载与激活可参考 docs/agent-skills.md

前置定位:一份“参考层”文档在技能三层结构中的位置

在动手阅读 YAML 之前,先明确这份参考文档的定位。deployment-pipeline-design 技能解决的问题域是:设计带审批门禁、安全检查与部署编排的多阶段 CI/CD 流水线,尤其当需要零停机部署、金丝雀/蓝绿发布、多环境推广或多区域发布时使用。其主文档 SKILL.md 给出了五类核心交付物:流水线阶段定义、部署策略选择(带注释的权重/切换参数)、健康检查设计(浅/深探测与冒烟脚本)、门禁定义(自动化指标阈值 + 人工审批)、回滚计划。它还维护了四类常见故障的排查指引(深健康检查、inconclusiveLimit 卡死、生产环境审批未配置、Docker 层缓存失效、回滚后数据库迁移错位)。

本文档在 SKILL.md 中通过 references/advanced-strategies.md 被引用,负责补齐“扩展 YAML 示例、平台定制配置、多区域金丝雀与数据库回滚策略”。因此如果你要落地一套“build → test → security → staging → e2e → production”的完整链路,details.md 提供各阶段拼图,而本文提供可直接整体照搬的“整条流水线”。

GitHub Actions:一条覆盖安全扫描与 Argo Rollouts 金丝雀的全生产流水线

仓库内 github-actions-templates 技能覆盖 GitHub Actions 实现模式,而本参考文档给出的是一条“从 push 到生产金丝雀、再到失败自动回滚”的端到端流水线。整体阶段依赖为:build → (security-scan ‖ test) → deploy-staging → e2e-tests → deploy-production。这段配置可以直接保存为 .github/workflows/production.yml

# .github/workflows/production.yml
name: Production Pipeline

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      skip_tests:
        type: boolean
        default: false

permissions:
  contents: read
  id-token: write   # for OIDC auth to cloud providers

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

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

      - name: Log in to registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=,format=short

      - name: Build and push (with layer cache)
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  security-scan:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Run Trivy vulnerability scan
        uses: aquasecurity/trivy-action@0.28.0
        with:
          image-ref: ghcr.io/${{ github.repository }}:${{ needs.build.outputs.image_tag }}
          exit-code: 1
          severity: CRITICAL,HIGH

      - name: SAST with Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: auto

  test:
    needs: build
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
    steps:
      - uses: actions/checkout@v4
      - name: Run test suite
        run: make test-ci
        env:
          DATABASE_URL: postgres://postgres:test@localhost/test

  deploy-staging:
    needs: [test, security-scan]
    environment:
      name: staging
      url: https://staging.example.com
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/deploy-staging
          aws-region: us-east-1

      - name: Deploy to EKS staging
        run: |
          aws eks update-kubeconfig --name my-cluster-staging
          kubectl set image deployment/my-app \
            app=ghcr.io/${{ github.repository }}:${{ needs.build.outputs.image_tag }}
          kubectl rollout status deployment/my-app --timeout=5m

  e2e-tests:
    needs: deploy-staging
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Playwright E2E suite
        run: npx playwright test --reporter=github
        env:
          BASE_URL: https://staging.example.com

  deploy-production:
    needs: e2e-tests
    environment:
      name: production
      url: https://app.example.com
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/deploy-production
          aws-region: us-east-1

      - name: Deploy canary to production
        run: |
          aws eks update-kubeconfig --name my-cluster-prod
          kubectl argo rollouts set image my-app \
            app=ghcr.io/${{ github.repository }}:${{ needs.build.outputs.image_tag }}

      - name: Monitor canary promotion
        run: |
          kubectl argo rollouts status my-app --timeout=30m

      - name: Rollback on failure
        if: failure()
        run: kubectl argo rollouts abort my-app

这条配置里值得拆解的要点:

  • OIDC 联邦而非长期密钥permissions.id-token: writeaws-actions/configure-aws-credentials@v4role-to-assume 配合,用工作负载身份直接换取临时 AWS 凭证,staging 与 production 使用不同的 IAM 角色,从根上避免了在仓库 Secrets 中长期存放云厂商密钥。
  • 一次构建、镜像标签贯穿全程build job 通过 outputs.image_tag(来自 docker/metadata-actiontype=sha,format=short)把镜像标签暴露给后续所有 job。后续 staging、production 部署引用的都是同一个标签,这符合 details.md 中“Artifact promotion——build once,promote the same artifact”的最佳实践。
  • 构建层缓存cache-from: type=ghacache-to: type=gha,mode=max 把镜像层缓存放到 GitHub Actions 缓存中,避免每次从零构建。这一点恰好呼应 SKILL.md 的排障条目——若 COPY . . 出现在依赖安装之前,任何源码改动都会使依赖层缓存失效。
  • 安全门禁“卡死”流水线security-scan job 中 Trivy 配置了 exit-code: 1 且只对 CRITICAL,HIGH 级别生效,一旦镜像存在高危漏洞,扫描直接以非零码退出,后续 deploy-stagingneeds: [test, security-scan])不会启动。
  • staging 是生产的“试金石”:E2E 测试只在部署到 staging 之后运行,且通过环境变量把 BASE_URL 指向 staging。也就是说,Playwright 套件验证的是“真实运行中的暂存版本”,而不是本地模拟。
  • 生产使用 Argo Rollouts 而非原生 Deploymentdeploy-productionkubectl argo rollouts set image 触发金丝雀,kubectl argo rollouts status my-app --timeout=30m 阻塞等待自动晋级或失败,而 if: failure()kubectl argo rollouts abort my-app 在监控超时/失败时强制中止金丝雀、回落稳定版本——这就是一个标准的“自动化回滚触发器”。

值得注意:示例依赖 kubectl argo rollouts 插件与 EKS 集群配置,使用前请确保 runner 上安装 Argo Rollouts kubectl 插件、并存在对应的 Rollout 资源与 AnalysisTemplate(见下文“高级 Argo Rollouts 模式”一节)。

GitLab CI:动态环境 + 按需启停的 staging 环境

GitLab CI 示例对应同插件 gitlab-ci-patterns 技能。它展示了一个与 GitHub Actions 不同但同样重要的能力——动态环境(Dynamic Environments):每次部署到 staging 都可以创建/销毁独立环境,生产部署则以人工触发方式把关。完整 .gitlab-ci.yml 如下:

# .gitlab-ci.yml
stages:
  - build
  - test
  - staging
  - production

variables:
  DOCKER_DRIVER: overlay2
  IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $IMAGE .
    - docker push $IMAGE
    - docker tag $IMAGE $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:latest

test:unit:
  stage: test
  image: $IMAGE
  script:
    - make test
  coverage: '/coverage: \d+\.\d+%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

test:security:
  stage: test
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image --exit-code 1 --severity CRITICAL,HIGH $IMAGE

deploy:staging:
  stage: staging
  environment:
    name: staging
    url: https://staging.example.com
    on_stop: stop:staging
  script:
    - kubectl apply -f k8s/staging/
    - kubectl set image deployment/my-app app=$IMAGE -n staging
    - kubectl rollout status deployment/my-app -n staging
  only:
    - main

stop:staging:
  stage: staging
  environment:
    name: staging
    action: stop
  script:
    - kubectl delete namespace staging --ignore-not-found
  when: manual
  only:
    - main

deploy:production:
  stage: production
  environment:
    name: production
    url: https://app.example.com
  script:
    - kubectl set image deployment/my-app app=$IMAGE -n production
    - kubectl rollout status deployment/my-app -n production --timeout=10m
  when: manual
  only:
    - main
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
      allow_failure: false

可关注的细节:

  • 用当前提交 SHA 打镜像标签IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA,保证流水线内所有 job 引用同一不可变标签;同时把 latest 作为后续构建的 --cache-from 缓存基准,形成“上次构建即缓存”的滚动优化。
  • 用构建产物自身跑测试test:unit 使用 image: $IMAGE,直接在刚构建出的镜像里执行 make test,避免测试环境与运行环境漂移;还能通过 coverage: '/coverage: \d+\.\d+%/' 正则从日志中解析覆盖率并生成 Cobertura 报告制品。
  • Trivy 容器内扫描test:security 使用 entrypoint: [""] 覆盖 Trivy 镜像默认入口,使其可以作为普通 job 运行,并对 CRITICAL,HIGH 级别的漏洞以 --exit-code 1 阻断流水线。
  • on_stop 动态环境回收deploy:stagingenvironment.on_stop: stop:staging 声明了配套的停止 job,stop:staging 设置 action: stop 并通过 kubectl delete namespace staging --ignore-not-found 在人工触发时销毁整个 staging 命名空间。这是 GitLab 动态环境“按需存在、用完即焚”的标准写法。
  • 生产门禁三件套deploy:production 同时使用 when: manualrulesallow_failure: false)——只有 main 分支会出现该 job、必须人工点击触发、且人工 job 不允许被忽略;与之对比 details.md 给出了“时间窗口式”人工审批(when: delayed; start_in: 30 minutes),两种门禁可按团队节奏选用。

Azure Pipelines:内置 canary 策略与人工审批的部署 job

Azure Pipelines 示例把“人工审批”与“金丝雀自动放量”封装进 deployment job 的 strategy 之中,是三种平台里对渐进式发布原生支持最直观的一种。完整 azure-pipelines.yml 如下:

# azure-pipelines.yml
trigger:
  branches:
    include:
      - main

variables:
  imageRepository: 'myapp'
  containerRegistry: 'myregistry.azurecr.io'
  tag: '$(Build.BuildId)'

stages:
  - stage: Build
    displayName: 'Build & Test'
    jobs:
      - job: BuildAndTest
        pool:
          vmImage: ubuntu-latest
        steps:
          - task: Docker@2
            displayName: Build image
            inputs:
              command: build
              repository: $(imageRepository)
              dockerfile: Dockerfile
              tags: $(tag)

          - task: Docker@2
            displayName: Push image
            inputs:
              command: push
              repository: $(imageRepository)
              tags: $(tag)

          - script: make test
            displayName: Run tests

  - stage: Staging
    displayName: 'Deploy to Staging'
    dependsOn: Build
    jobs:
      - deployment: DeployStaging
        environment: staging
        pool:
          vmImage: ubuntu-latest
        strategy:
          runOnce:
            deploy:
              steps:
                - task: KubernetesManifest@0
                  inputs:
                    action: deploy
                    manifests: k8s/staging/*.yaml
                    containers: $(containerRegistry)/$(imageRepository):$(tag)

  - stage: Production
    displayName: 'Deploy to Production'
    dependsOn: Staging
    jobs:
      - deployment: DeployProduction
        environment:
          name: production
          resourceType: Kubernetes
        pool:
          vmImage: ubuntu-latest
        strategy:
          canary:
            increments: [10, 25, 50]
            preDeploy:
              steps:
                - task: ManualValidation@0
                  inputs:
                    notifyUsers: 'release-managers@example.com'
                    instructions: 'Verify staging metrics. Approve to start canary.'
                    onTimeout: reject
            deploy:
              steps:
                - task: KubernetesManifest@0
                  inputs:
                    action: deploy
                    manifests: k8s/production/*.yaml
                    containers: $(containerRegistry)/$(imageRepository):$(tag)
            postRouteTraffic:
              steps:
                - script: ./scripts/verify-deployment.sh https://app.example.com
            on:
              failure:
                steps:
                  - task: KubernetesManifest@0
                    inputs:
                      action: reject

要点解读:

  • 阶段依赖与镜像 tagBuild → Staging → Production 通过 dependsOn 串行;镜像 tag 使用 $(Build.BuildId)(Azure 全局唯一构建号),保证每个 stage 拉取同一个不可变镜像。
  • deployment job 才是“会部署”的 job:普通 jobdeployment job 的区别在于后者绑定 environment、支持生命周期 hook 与发布策略。staging 用 runOnce,production 用 canary
  • canary 策略三段式 hook
    • preDeploy:放量前的人工验证门禁。ManualValidation@0 通知 release-managers@example.com 检查 staging 指标,onTimeout: reject 保证长时间无人审批时自动拒绝而非无限挂起——对应 SKILL.md 中“staging 成功但 production 不启动,通常是环境保护规则/审批人未配置”的排查点。
    • deploy + increments: [10, 25, 50]KubernetesManifest@0 分三次放量 10% → 25% → 50%,由 Azure Pipelines 在路由层面切流。
    • postRouteTraffic:每次放量后执行 ./scripts/verify-deployment.sh 做健康校验(该脚本模式见 details.md 的 Post-Deployment Verification Script)。
    • on.failure:任一步骤失败即对发布执行 action: reject,自动拒绝金丝雀。
  • 参考文档中该配置的 staging 环境未额外声明 resourceType,production 声明为 Kubernetes,表示生产环境资源由 Kubernetes 集群承载——KubernetesManifest@0 会使用这些 manifests 指向的清单进行部署/拒绝操作。

多区域金丝雀推广:先导区域验证,再并行铺满其余区域

当服务需要部署到多个 AWS 区域时,最安全的顺序是“单点突破、再横向扩散”。参考文档给出的模式分为两步:

  1. deploy-pilot:先只向 us-east-1 这一个“试点区域”发布金丝雀,并阻塞等待其完成自动晋级(--timeout=20m);
  2. deploy-secondaryneeds: deploy-pilot 确认试点成功后才启动,用 GitHub Actions 的 strategy.matrix[us-west-2, eu-west-1, ap-southeast-1] 三个区域并行执行相同的发布流程,并通过 environment: production-${{ matrix.region }} 让每个区域拥有独立的环境保护规则。
# deploy-multiregion.yml (GitHub Actions)
jobs:
  deploy-pilot:
    environment: production-us-east-1
    runs-on: ubuntu-latest
    steps:
      - name: Deploy canary to pilot region
        run: |
          aws eks update-kubeconfig --name cluster-us-east-1 --region us-east-1
          kubectl argo rollouts set image my-app app=$IMAGE
          kubectl argo rollouts status my-app --timeout=20m

  deploy-secondary:
    needs: deploy-pilot
    strategy:
      matrix:
        region: [us-west-2, eu-west-1, ap-southeast-1]
    environment: production-${{ matrix.region }}
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to ${{ matrix.region }}
        run: |
          aws eks update-kubeconfig --name cluster-${{ matrix.region }} \
            --region ${{ matrix.region }}
          kubectl argo rollouts set image my-app app=$IMAGE
          kubectl argo rollouts status my-app --timeout=20m

这个“试点-扩散”拓扑的价值在于:把故障爆炸半径限制在单一区域。若 pilot 区域的金丝雀未能通过分析(如错误率超标),deploy-secondary 根本不会启动,其余区域不受影响;同时各区域有各自的 environment,便于在 GitHub Actions 的 Environment 设置中配置按区域划分的审批人。注意这段片段中 $IMAGE 是占位变量,真实落地时应替换为上节“GitHub Actions”示例里 build job 输出的 image_tag,确保所有区域使用完全相同的制品版本。

高级 Argo Rollouts 模式

当滚动发布需要“用真实流量验证版本好坏”时,Deployment 与简单金丝雀已经不够用。参考文档给出两种高阶用法。

基于实验的金丝雀(A/B 分析):stable 与 canary 同台对比

普通金丝雀只能看到“新版跑得怎样”;实验式金丝雀则让基线(stable)与金丝雀(canary)同时承载流量并接受同一个分析模板对比打分,从而获得具备统计意义的 A/B 结论。对应 Rollout 清单如下:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 20
  strategy:
    canary:
      canaryService: my-app-canary
      stableService: my-app-stable
      trafficRouting:
        istio:
          virtualService:
            name: my-app-vsvc
      analysis:
        templates:
          - templateName: success-rate
          - templateName: latency-p99
        startingStep: 1
        args:
          - name: service-name
            value: my-app-canary
      steps:
        - setWeight: 5
        - experiment:
            templates:
              - name: baseline
                specRef: stable
              - name: canary
                specRef: canary
            analyses:
              - templateName: ab-test
                requiredForCompletion: true
        - setWeight: 20
        - pause: { duration: 10m }
        - setWeight: 50
        - pause: { duration: 15m }
        - setWeight: 100

结构逐段拆解:

  • 流量路由层trafficRouting.istio.virtualService 声明由名为 my-app-vsvc 的 Istio VirtualService 负责切流;canaryService / stableService 分别对应新旧版本的 Kubernetes Service。
  • 分析即门禁analysis.templates 挂载 success-ratelatency-p99 两个模板,startingStep: 1 表示分析从第 1 步起即开始运行。模板的具体指标定义(成功条件/失败条件/inconclusiveLimit)参考 details.md 的 Automated Metric Gate 示例与 SKILL.md 的“canary 永不晋级”排障(Prometheus 无数据时分析会一直 inconclusive,应设置 inconclusiveLimit 让它快速失败而非无限挂起)。这里给 analysis 传入了参数 service-name=my-app-canary,供分析模板的 PromQL 精确圈定金丝雀服务的指标范围。
  • 实验步骤:先 setWeight: 5 放 5% 试探,然后进入 experiment 步骤:同时拉起基于 stablebaseline 与基于 canary 的副本,运行 ab-test 分析,且 requiredForCompletion: true 表明该实验分析必须出结论才算完成。只有当 A/B 结论通过后,才依次 setWeight: 20(暂停 10 分钟)→ 50(暂停 15 分钟)→ 100 完成晋级。若 ab-test 判定金丝雀劣于基线,Rollout 自动回退。

基于 Header 的金丝雀:切流之前先做“内部人”验证

在生产流量按权重拆分之前,团队往往希望先让自己人、QA 或内部测试流量“定向”访问新版本。基于 Header 的路由让带有 X-Canary: true 请求头的用户固定打到金丝雀 Pod,其余流量仍按 90/10 权重在 stable/canary 间分配:

# Route users with X-Canary: true header to canary pods
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: my-app-vsvc
spec:
  http:
    - match:
        - headers:
            x-canary:
              exact: "true"
      route:
        - destination:
            host: my-app-canary
    - route:
        - destination:
            host: my-app-stable
          weight: 90
        - destination:
            host: my-app-canary
          weight: 10

这段配置与前文 Rollout 中的 trafficRouting.istio.virtualService.name: my-app-vsvc 是配套关系:Rollout 声明“用这个 VirtualService 切流”,此 VirtualService 定义“怎么切”。两类规则并列存在:

  • match 优先:凡是 x-canary: exact: "true" 的请求 100% 进入 my-app-canary。通常由浏览器插件、调试代理或内部入口网关统一附加该 Header,实现“员工内测金丝雀”;
  • 兜底权重:无该 Header 的普通请求 90% 到 stable、10% 到 canary,仍然保留小流量真实用户体验。

两个模式结合,即构成“Header 定向内测 → 确认无误后进入权重放量 → 分析通过后全量”的完整渐进链路。

数据库迁移回滚策略

应用层可以“秒级回滚”,数据库却无法秒级“回滚”,这是所有高可用发布中最危险的部分。参考文档在此给出三套互补策略。

Expand/Contract:用三个发布周期换取永不破坏向后兼容

核心约束一句话:永远不要在同一个发布周期内破坏向后兼容性。迁移被拆成三个独立发布窗口:

Release N:   Add nullable column (expand)
Release N+1: Backfill data, deploy new code that reads new column
Release N+2: Drop old column (contract) — safe because no code references it

对应的两条迁移脚本:

# Release N migration — backward compatible
cat > migrations/V20240315__expand_add_email_v2.sql <<'EOF'
ALTER TABLE users ADD COLUMN email_v2 VARCHAR(255);
CREATE INDEX CONCURRENTLY idx_users_email_v2 ON users(email_v2);
EOF

# Release N+2 migration — contract (only after old code retired)
cat > migrations/V20240415__contract_drop_email.sql <<'EOF'
ALTER TABLE users DROP COLUMN email;
ALTER TABLE users RENAME COLUMN email_v2 TO email;
EOF
  • Release N 只“加列”(email_v2 可空、不阻塞),这是加性变更,旧代码依旧读 email,互不干扰;
  • Release N+1 回填数据、部署读新列的新代码;
  • Release N+2 才真正 DROP COLUMN emailRENAME——此时旧代码已从所有环境退役,删列不会引发 schema/code 错位。

这正好呼应 SKILL.md 中“回滚后数据库迁移仍停留在旧代码”的排障建议:服务回滚而不回滚迁移会引发 schema/code 不匹配,务必让迁移在至少一个发布周期内保持向后兼容(仅加性),并把 undo 脚本与迁移一并版本化。

Flyway Undo 脚本:向前迁移、向后可撤

Flyway 是版本化数据库迁移工具,其 undo 能力提供结构化回滚路径:

# Forward migration
flyway migrate

# Undo the most recent migration (requires Flyway Teams)
flyway undo

# Undo a specific version
flyway undo -target=20240315

配套的命名约定:正向迁移用 V 前缀,回退脚本用 U 前缀,同一版本号一一对应:

Undo script naming convention: `U20240315__expand_add_email_v2.sql` (prefix `U` instead of `V`).

需要如实说明的限制:flyway undo 是 Flyway Teams(商业版)能力;且 undo 只适合“最近一次、可安全逆转”的变更,破坏性变更的兜底仍是 Expand/Contract + 充分的前向兼容窗口。

零停机索引创建(PostgreSQL)

在在线系统上建索引时,普通 CREATE INDEX 会持有表锁、阻塞读写:

-- Blocking (avoid in production):
CREATE INDEX idx_users_email ON users(email);

-- Non-blocking (safe for live systems):
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

结论:所有生产迁移都应使用 CREATE INDEX CONCURRENTLY,并且把它放进“部署应用之前”的预部署迁移步骤执行。以三发布 Expand/Contract 为例,索引创建就应放在 Release N 的预部署阶段,让新索引在应用切换读写前先就绪。

蓝绿切换与数据库的完整配套示例

蓝绿部署(blue-green)本身切换极快、回滚瞬时,但一旦涉及数据库,就必须把“向前兼容的迁移”编排进绿环境的启动流程。参考文档给出的完整步骤序列值得逐行阅读:

# blue-green-deploy.yml
steps:
  - name: Deploy green environment
    run: |
      kubectl apply -f k8s/green/

  - name: Run database migrations (forward, backward-compatible)
    run: |
      kubectl exec -n production deploy/migration-job -- flyway migrate

  - name: Smoke test green
    run: |
      kubectl port-forward -n production svc/my-app-green 8080:80 &
      sleep 3
      curl -sf http://localhost:8080/health/ready

  - name: Switch traffic to green
    run: |
      kubectl patch service my-app \
        -p '{"spec":{"selector":{"slot":"green"}}}'

  - name: Verify green is live
    run: ./scripts/verify-deployment.sh https://app.example.com

  - name: Scale down blue
    run: |
      kubectl scale deployment my-app-blue --replicas=0

  - name: Rollback to blue on failure
    if: failure()
    run: |
      kubectl patch service my-app \
        -p '{"spec":{"selector":{"slot":"blue"}}}'
      kubectl scale deployment my-app-blue --replicas=5

时序中的关键决策点:

  1. 先部署、后迁移、再切流:绿环境(slot: green)先就绪,紧接着通过 migration-job 执行 flyway migrate——这里的迁移必须“只前向、且向后兼容”(即上文的加性变更),因为此刻蓝环境还在继续承载全部流量;
  2. 冒烟探测真实依赖port-forward 到绿环境后 curl /health/ready,探测的是“包含数据库等真实依赖”的深健康端点,而非只返回 200 的浅 /ping(深浅健康检查的对比见 details.md);
  3. 切流是“改 Service selector”而不是改代码kubectl patch service my-app 把 selector 从 slot: blue 改成 slot: green,流量瞬时翻转,这是蓝绿回滚速度“Instant”的由来(对应 details.md 决策表中 Blue-Green 的“零停机、瞬时回滚、2x 临时基础设施成本”);
  4. 失败即一键倒回:任一步失败都会执行反向 patch(selector 指回 slot: blue)并把蓝环境扩容回 --replicas=5——因为迁移是向后兼容的,旧代码在旧 schema 上仍能正常运行。

部署冻结自动化:用脚本在流水线入口挡住“节假日发布”

大型团队常在感恩节、年末等高风险窗口冻结生产发布。参考文档给出一个轻量方案:用一个 Python 脚本作为流水线首个检查步骤,命中冻结窗口即失败退出。脚本维护一个冻结窗口表,每个窗口以 (月, 起始日, 结束日, 描述) 表示:

#!/usr/bin/env python3
# scripts/check-freeze-window.py
import sys
from datetime import datetime, timezone

FREEZE_WINDOWS = [
    # (month, day_start, day_end, description)
    (11, 25, 30, "US Thanksgiving"),
    (12, 20, 31, "Year-end freeze"),
    (1, 1, 2, "New Year"),
]

now = datetime.now(timezone.utc)
month, day = now.month, now.day

for fm, d_start, d_end, label in FREEZE_WINDOWS:
    if fm == month and d_start <= day <= d_end:
        print(f"BLOCKED: Deployment freeze active — {label}")
        print("Override with FORCE_DEPLOY=true environment variable if critical.")
        if not os.environ.get("FORCE_DEPLOY"):
            sys.exit(1)

print("No active freeze window — deployment allowed.")

可复制性提示:以上片段依赖 os.environ,但原文未显式 import os;实际运行前请补上 import os,否则冻结窗口判断会因 NameError 失败。

接入流水线时把它作为首个执行步骤,并将仓库级变量 FORCE_DEPLOY 注入环境变量,让应急发布可以显式覆盖冻结:

- name: Check deployment freeze window
  run: python scripts/check-freeze-window.py
  env:
    FORCE_DEPLOY: ${{ vars.FORCE_DEPLOY }}

设计要点:脚本以 UTC 计算“当前时刻”,规避不同时区 runner 造成的窗口判断漂移;默认策略是“命中即拦”,FORCE_DEPLOY 是刻意需要人工配置的逃生舱——对应 details.md 最佳实践中“Deployment windows——优先低流量窗口,用门禁策略强制变更冻结期”。

通知模板:把发布结果以结构化信息推给团队

发布链路闭环的最后一块是“让正确的人第一时间知道结果”。参考文档的 Slack 通知脚本把部署结果封装为带颜色与字段的 Slack attachment:

#!/usr/bin/env bash
# scripts/notify-slack.sh
STATUS="${1:?pass 'success' or 'failure'}"
WEBHOOK="${SLACK_WEBHOOK:?SLACK_WEBHOOK not set}"
REPO="${GITHUB_REPOSITORY:-unknown}"
SHA="${GITHUB_SHA:-unknown}"
ACTOR="${GITHUB_ACTOR:-unknown}"

if [ "$STATUS" = "success" ]; then
  COLOR="good"
  EMOJI=":white_check_mark:"
  TEXT="Production deploy succeeded"
else
  COLOR="danger"
  EMOJI=":red_circle:"
  TEXT="Production deploy FAILED — rollback triggered"
fi

curl -sf -X POST "$WEBHOOK" \
  -H "Content-Type: application/json" \
  -d "{
    \"attachments\": [{
      \"color\": \"$COLOR\",
      \"text\": \"$EMOJI $TEXT\",
      \"fields\": [
        {\"title\": \"Repo\", \"value\": \"$REPO\", \"short\": true},
        {\"title\": \"SHA\", \"value\": \"${SHA:0:7}\", \"short\": true},
        {\"title\": \"Deployed by\", \"value\": \"$ACTOR\", \"short\": true}
      ]
    }]
  }"

使用要点:

  • 脚本前置守卫 "${1:?...}""${SLACK_WEBHOOK:?...}" 保证缺少必传参数或环境变量时立即失败并给出可读错误,属于防御式 Bash 的典型写法;
  • 成功时绿色 good + 白勾,失败时红色 danger + 红圈,字段里同时带上 Repo、截断到 7 位的 SHA 与触发人 $ACTOR
  • 它默认读取 GitHub Actions 注入的 GITHUB_REPOSITORY / GITHUB_SHA / GITHUB_ACTOR 环境变量并带 :-unknown 兜底,因此也可复用于本地或其他平台的手动执行;
  • 在流水线中把 STATUS 传成 success/failureSLACK_WEBHOOK 从 CI/CD 平台的加密 Secret 读取(不应硬编码),即完成部署通知闭环。该脚本与 details.md 中“Notify on success”步骤及同插件 secrets-management 技能强调的“绝不明文硬编码 Secret”原则完全一致。

在本仓库中如何被 Agent 使用

以上策略并非孤立文本,而是 agents24/agents 这一 multi-harness Agentic 插件市场中 cicd-automation 插件的组成部分。整套资料按“命令 → Agent → Skill”三层组织:

参考文档的开篇即注明“核心模式与决策表位于 SKILL.md”,因此无论由 Claude Code、Codex、Cursor、OpenCode、GitHub Copilot 还是 Google Antigravity 接入本插件市场,推荐的检索路径都是:先读 SKILL.md 决定策略(滚动/蓝绿/金丝雀/重建/特性开关),再查 details.md 补常规编排,最后按需翻开本文档照搬生产级多平台配置与高级回滚方案。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391