← 返回

发版流程实战:从代码提交到生产上线的完整链路

作者:林 | 系列:DevOps 实战 | 适合读者:后端/运维/架构师


一、为什么需要一套规范的发版流程

很多团队的发版流程是"人工操作 + 口头确认":开发本地打包、运维手动部署、群里喊一声"发了"。这种方式在团队小、服务少的时候还能凑合,但一旦服务超过 5 个、团队超过 10 人,各种问题就来了:

  • 发错版本:分支没合并干净,把半成品发到生产
  • 回滚慢:出问题后找不到上一个稳定版本,或者回滚步骤不一致
  • 环境不一致:测试环境和生产环境配置不同,测试通过了生产挂了
  • 发版窗口长:从代码合并到上线要半天,错过业务窗口

一套规范的发版流程并非为了"流程化而流程化",重点是为了让发版可重复、可预测、可回滚


二、整体架构一览

一套完整的发版链路,通常包含以下环节:

flowchart LR Commit["代码提交"] --> Review["代码审查"] Review --> Build["CI 构建与测试"] Build --> Artifact["不可变制品"] Artifact --> Test["测试环境验证"] Test --> Stage["预发布验证"] Stage --> Approval["发布审批"] Approval --> Prod["生产灰度"] Prod --> Observe{"指标达标"} Observe -- "继续" --> Complete["完成发布"] Observe -- "否" --> Rollback["回滚或停止放量"]

每个环境的职责不同:

环境用途部署触发方式数据
dev开发自测手动 / push 触发mock 数据
test功能测试、集成测试合并到 develop 触发测试数据
staging预发布验证,跟生产一致合并到 main / 手动生产数据脱敏副本
prod线上服务打 tag / 手动审批真实数据

三、分支策略

3.1 Git Flow(经典方案)

适合有明确版本发布节奏的项目:

main ────────────────────────────────────────── (生产)
  │
  ├── release/v1.2 ──────────────────────────── (发布分支)
  │     │
  │     ├── hotfix/fix-xxx ──────────────────── (热修复)
  │     │
  │     └── merge back to main + develop
  │
  └── develop ───────────────────────────────── (开发主线)
        │
        ├── feature/user-login ──────────────── (功能分支)
        ├── feature/payment ─────────────────── (功能分支)
        └── bugfix/fix-null-pointer ─────────── (修复分支)

分支命名约定

前缀用途示例
feature/新功能feature/user-login
bugfix/Bug 修复bugfix/fix-null-pointer
hotfix/生产热修复hotfix/fix-payment-timeout
release/发布分支release/v1.2.0

工作流程

  1. developfeature/xxx 分支
  2. 开发完成,提 MR 合并回 develop
  3. develop 自动部署到测试环境
  4. 测试通过,从 developrelease/vx.x.x
  5. release 分支部署到 staging,验证通过后合并到 main
  6. main 打 tag,自动部署到生产

3.2 Trunk-Based(主干开发)

适合持续部署、快速迭代的团队:

main ────────────────────────────────────────── (主干)
  │
  ├── feature/short-lived-branch (1-2天) ───── (短生命周期分支)
  │     └── merge back quickly
  │
  └── release/v1.2 (从 main 切出) ──────────── (发布 tag)

核心原则

  • 分支生命周期不超过 2 天
  • 用 feature flag 控制未完成功能的可见性
  • 每次合并都触发 CI/CD

选择建议

场景推荐
团队 < 10 人,日发多次Trunk-Based
团队 > 10 人,有固定发版节奏Git Flow
有多个版本并行维护Git Flow
微服务架构,独立部署Trunk-Based + Feature Flag

四、CI/CD 流水线设计

4.1 CI(持续集成)阶段

代码合并后自动触发,核心目标是快速反馈。下面是一个生产级的 CI 工作流示例,包含构建、测试、代码扫描、镜像推送全流程:

# .github/workflows/ci.yml
# 完整的 CI 流水线:构建 → 测试 → 代码扫描 → 镜像构建与推送
name: CI Pipeline

on:
  push:
    branches: [develop, main]
  pull_request:
    branches: [develop, main]

# 同一分支的新 push 自动取消之前的运行,避免资源浪费
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ──────────────────────────────────────────────
  # Job 1: 代码编译与单元测试
  # ──────────────────────────────────────────────
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'corretto'
          cache: 'maven'  # 内置缓存支持,无需手动配置 actions/cache

      - name: Build & Unit Test
        run: mvn clean verify -B -DskipITs  # 跳过集成测试,单独跑

      - name: Generate JaCoCo Report
        if: success()
        run: mvn jacoco:report

      # 上传测试报告和覆盖率报告,方便在 PR 中查看
      - name: Upload Test Results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: |
            target/surefire-reports/
            target/site/jacoco/
          retention-days: 7

  # ──────────────────────────────────────────────
  # Job 2: 代码质量与安全扫描
  # ──────────────────────────────────────────────
  code-scan:
    runs-on: ubuntu-latest
    needs: build-and-test  # 构建通过后再扫描
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # SonarQube 需要完整 git 历史来做 blame 分析

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'corretto'
          cache: 'maven'

      # SonarQube 静态代码分析
      - name: SonarQube Scan
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        run: mvn sonar:sonar -Dsonar.projectKey=myapp

      # Trivy 文件系统漏洞扫描(扫描依赖包)
      - name: Trivy FS Scan
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          severity: 'CRITICAL,HIGH'
          exit-code: '1'  # 发现高危漏洞时阻断流水线

  # ──────────────────────────────────────────────
  # Job 3: 构建并推送 Docker 镜像
  # ──────────────────────────────────────────────
  build-image:
    runs-on: ubuntu-latest
    needs: [build-and-test, code-scan]  # 测试和扫描都通过后才构建镜像
    # 仅在 push 到主分支时推送镜像,PR 只做验证
    if: github.event_name == 'push'
    permissions:
      contents: read
      packages: write  # 推送到 GitHub Container Registry
    outputs:
      image-tag: ${{ steps.meta.outputs.version }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      # 使用 Docker Buildx 获得更好的构建缓存
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # 登录 GitHub Container Registry
      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 自动生成镜像 tag:分支名-sha、语义化版本(如果是 tag)
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}

      # 构建并推送,使用 GitHub Actions 缓存加速二次构建
      - name: Build & Push Docker Image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      # 镜像构建后立即扫描镜像漏洞
      - name: Trivy Image Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
          severity: 'CRITICAL,HIGH'
          exit-code: '1'

CI 阶段的关键检查点

检查项工具失败策略
编译通过Maven / Gradle阻断
单元测试通过JUnit 5 / TestNG阻断
代码覆盖率 ≥ 80%JaCoCo告警(可配置为阻断)
代码规范检查Checkstyle / SpotBugs告警
静态代码分析SonarQube质量门禁阻断
依赖漏洞扫描Trivy / Snyk高危阻断
镜像漏洞扫描Trivy高危阻断
镜像构建与推送Docker Buildx + GHCR阻断

4.2 CD(持续部署)阶段

CI 通过后,进入部署环节。核心是分环境逐步推进。下面是一个完整的 CD 工作流,包含 staging 部署、审批、canary 灰度、生产全量发布:

# .github/workflows/deploy.yml
# CD 流水线:staging 部署 → 审批 → canary 灰度 → 生产全量
name: CD Pipeline

on:
  # 通过 workflow_call 被 CI 流水线调用,或手动触发
  workflow_call:
    inputs:
      image-tag:
        required: true
        type: string
        description: '要部署的镜像 tag'
  workflow_dispatch:
    inputs:
      image-tag:
        required: true
        type: string
        description: '要部署的镜像 tag(Git SHA)'
      skip-canary:
        required: false
        type: boolean
        default: false
        description: '是否跳过 canary 灰度阶段'

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ──────────────────────────────────────────────
  # Job 1: 部署到 Staging 环境
  # ──────────────────────────────────────────────
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # 配置 kubectl 连接 staging 集群
      - name: Set up kubeconfig
        uses: azure/k8s-set-context@v4
        with:
          kubeconfig: ${{ secrets.STAGING_KUBECONFIG }}

      # 更新 Deployment 镜像
      - name: Deploy to Staging
        run: |
          kubectl set image deployment/myapp \
            myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ inputs.image-tag }} \
            -n staging
          echo "等待 rollout 完成..."
          kubectl rollout status deployment/myapp -n staging --timeout=300s

      # 冒烟测试:验证核心接口
      - name: Smoke Test
        run: |
          for i in $(seq 1 5); do
            if curl -sf https://staging.example.com/health; then
              echo "\n✅ Staging 健康检查通过"
              exit 0
            fi
            echo "等待服务就绪... ($i/5)"
            sleep 10
          done
          echo "❌ Staging 健康检查失败"
          exit 1

      # E2E 测试(可选)
      - name: E2E Tests
        run: |
          # 在 staging 环境运行端到端测试
          # mvn verify -Pstaging -Dbase.url=https://staging.example.com
          echo "E2E 测试通过"

  # ──────────────────────────────────────────────
  # Job 2: Canary 灰度部署(10% 流量)
  # ──────────────────────────────────────────────
  # 通过 GitHub Environment 的审批门禁
  deploy-canary:
    needs: deploy-staging
    if: ${{ !inputs.skip-canary }}
    runs-on: ubuntu-latest
    environment:
      name: production-canary
      url: https://example.com
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up kubeconfig
        uses: azure/k8s-set-context@v4
        with:
          kubeconfig: ${{ secrets.PROD_KUBECONFIG }}

      # 部署 canary 版本(副本数 1,承载约 10% 流量)
      - name: Deploy Canary
        run: |
          kubectl set image deployment/myapp-canary \
            myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ inputs.image-tag }} \
            -n production
          kubectl rollout status deployment/myapp-canary -n production --timeout=300s

      # 灰度观察期:监控核心指标
      - name: Monitor Canary (5 min)
        run: |
          echo "🔍 开始灰度观察,持续 5 分钟..."
          for i in $(seq 1 10); do
            # 检查错误率(从 Prometheus 拉取)
            ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_server_requests_seconds_count{status=~'5..',application='myapp',version='canary'}[1m])/rate(http_server_requests_seconds_count{application='myapp',version='canary'}[1m])*100" | jq -r '.data.result[0].value[1] // "0"')
            echo "[$((i*30))s] 错误率: ${ERROR_RATE}%"
            if (( $(echo "$ERROR_RATE > 1" | bc -l) )); then
              echo "❌ 错误率超过阈值 1%,触发回滚"
              kubectl rollout undo deployment/myapp-canary -n production
              exit 1
            fi
            sleep 30
          done
          echo "✅ 灰度观察通过"

  # ──────────────────────────────────────────────
  # Job 3: 生产环境全量发布
  # ──────────────────────────────────────────────
  deploy-production:
    needs: [deploy-staging, deploy-canary]
    # 如果跳过 canary,只要有 staging 就行;否则需要 canary 通过
    if: always() && needs.deploy-staging.result == 'success' && (needs.deploy-canary.result == 'success' || needs.deploy-canary.result == 'skipped')
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up kubeconfig
        uses: azure/k8s-set-context@v4
        with:
          kubeconfig: ${{ secrets.PROD_KUBECONFIG }}

      # 全量更新生产 Deployment
      - name: Full Rollout
        run: |
          kubectl set image deployment/myapp \
            myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ inputs.image-tag }} \
            -n production
          echo "等待全量 rollout 完成..."
          kubectl rollout status deployment/myapp -n production --timeout=600s

      # 清理 canary 副本
      - name: Scale Down Canary
        if: ${{ !inputs.skip-canary }}
        run: |
          kubectl scale deployment/myapp-canary --replicas=0 -n production
          echo "✅ Canary 副本已缩容"

      # 发版后验证
      - name: Post-deploy Verification
        run: |
          echo "🔍 生产环境健康检查..."
          for i in $(seq 1 5); do
            if curl -sf https://example.com/health; then
              echo "\n✅ 生产环境健康检查通过"
              exit 0
            fi
            sleep 10
          done
          echo "❌ 生产环境健康检查失败,考虑回滚"
          exit 1

      # 发送发版通知
      - name: Notify
        if: always()
        run: |
          STATUS=${{ job.status }}
          echo "发版 ${STATUS}: ${{ inputs.image-tag }}"
          # 可接入企业微信/钉钉/Slack webhook
          # curl -X POST "$WEBHOOK_URL" -H 'Content-Type: application/json' -d '{"text":"发版完成: ${{ inputs.image-tag }}"}'

4.3 Monorepo 多服务构建优化

在微服务 monorepo 场景下,每次提交可能只改了某个子服务,但全量构建所有服务既浪费时间又浪费资源。GitHub 提供了 dorny/paths-filtertj-actions/changed-files 两个 Action 来检测变更路径,只构建有变化的服务:

# .github/workflows/monorepo-ci.yml
# Monorepo CI:只构建变更的服务
name: Monorepo CI

on:
  push:
    branches: [develop, main]
  pull_request:
    branches: [develop, main]

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ──────────────────────────────────────────────
  # Job 1: 检测哪些服务发生了变更
  # ──────────────────────────────────────────────
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      user-service: ${{ steps.filter.outputs.user-service }}
      order-service: ${{ steps.filter.outputs.order-service }}
      gateway: ${{ steps.filter.outputs.gateway }}
    steps:
      - uses: actions/checkout@v4

      - name: Detect changed paths
        id: filter
        uses: dorny/paths-filter@v3
        with:
          filters: |
            user-service:
              - 'services/user-service/**'
              - 'libs/common/**'       # 公共库变更也要重新构建
            order-service:
              - 'services/order-service/**'
              - 'libs/common/**'
            gateway:
              - 'services/gateway/**'
              - 'libs/common/**'

  # ──────────────────────────────────────────────
  # Job 2: 使用 matrix 并行构建变更的服务
  # ──────────────────────────────────────────────
  build-services:
    needs: detect-changes
    # 仅在有服务变更时运行
    if: needs.detect-changes.outputs.user-service == 'true' || needs.detect-changes.outputs.order-service == 'true' || needs.detect-changes.outputs.gateway == 'true'
    runs-on: ubuntu-latest
    strategy:
      # 动态 matrix:只包含有变更的服务
      matrix:
        include:
          - service: user-service
            enabled: ${{ needs.detect-changes.outputs.user-service }}
            module: services/user-service
            port: 8081
          - service: order-service
            enabled: ${{ needs.detect-changes.outputs.order-service }}
            module: services/order-service
            port: 8082
          - service: gateway
            enabled: ${{ needs.detect-changes.outputs.gateway }}
            module: services/gateway
            port: 8080
      # 某个服务构建失败不影响其他服务
      fail-fast: false
    steps:
      - name: Skip if not changed
        if: matrix.enabled != 'true'
        run: echo "⏭️ ${{ matrix.service }} 未变更,跳过构建"

      - name: Checkout
        if: matrix.enabled == 'true'
        uses: actions/checkout@v4

      - name: Set up JDK 17
        if: matrix.enabled == 'true'
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'corretto'
          cache: 'maven'

      # 只构建变更的模块及其依赖
      - name: Build & Test
        if: matrix.enabled == 'true'
        run: |
          mvn clean verify -B \
            -pl ${{ matrix.module }} \
            -am \
            -DskipITs

      - name: Build Docker Image
        if: matrix.enabled == 'true'
        run: |
          docker build -t ghcr.io/${{ github.repository }}/${{ matrix.service }}:${{ github.sha }} \
            -f ${{ matrix.module }}/Dockerfile .

      - name: Push Image
        if: matrix.enabled == 'true' && github.event_name == 'push'
        run: |
          echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
          docker push ghcr.io/${{ github.repository }}/${{ matrix.service }}:${{ github.sha }}
Maven -pl-am 参数-pl 指定要构建的模块,-am(also-make)会自动构建该模块依赖的其他模块。这样即使公共库 libs/common 有变更,也能正确构建依赖它的服务。

4.4 Reusable Workflows(可复用工作流)

当多个项目的 CI/CD 流水线结构相似时,可以把公共逻辑抽成 reusable workflow,避免重复维护:

# .github/workflows/reusable-ci.yml
# 可复用的 CI 工作流(放在组织级仓库或当前仓库均可)
name: Reusable CI

on:
  workflow_call:
    inputs:
      java-version:
        type: string
        default: '17'
        description: 'JDK 版本'
      module-path:
        type: string
        default: '.'
        description: 'Maven 模块路径'
    secrets:
      SONAR_TOKEN:
        required: true
      SONAR_HOST_URL:
        required: true

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          java-version: ${{ inputs.java-version }}
          distribution: 'corretto'
          cache: 'maven'

      - name: Build & Test
        run: mvn clean verify -B -pl ${{ inputs.module-path }} -am

      - name: SonarQube Scan
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        run: mvn sonar:sonar -Dsonar.projectKey=${{ github.repository }}

      - uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: target/surefire-reports/
          retention-days: 7

在项目中调用 reusable workflow

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    uses: ./.github/workflows/reusable-ci.yml  # 本仓库内引用
    # 或引用组织级仓库:uses: my-org/.github/.github/workflows/reusable-ci.yml@main
    with:
      java-version: '17'
      module-path: '.'
    secrets:
      SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
      SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
组织级 reusable workflow:把通用流水线放在 my-org/.github 仓库的 .github/workflows/ 目录下,组织内所有仓库都可以引用,统一 CI 标准。这是管理多仓库 CI 的成熟实践。

4.5 Matrix Strategy(多版本/多平台并行构建)

当需要同时在多个 JDK 版本或多个平台上验证构建时,matrix strategy 非常有用:

# .github/workflows/matrix-ci.yml
name: Matrix CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
        java: ['17', '21']
        # 排除不合理的组合(可选)
        exclude:
          - os: macos-latest
            java: '17'
      fail-fast: false  # 某个组合失败不影响其他组合
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: 'corretto'
          cache: 'maven'

      - name: Build & Test
        run: mvn clean verify -B

五、GitHub 环境与安全管理

5.1 GitHub Environments 配置

GitHub Environments 提供了环境级别的保护规则和 Secret 管理,是实现分环境部署审批的核心机制。

配置路径:仓库 → Settings → Environments

Staging 环境配置

  • Protection rules
    • ✅ Required reviewers:添加 1-2 个审批人(如 Tech Lead)
    • ⏱️ Wait timer:0 分钟(审批后马上部署)
    • 🌿 Deployment branches:仅 maindevelop

Production 环境配置

  • Protection rules
    • ✅ Required reviewers:添加 2-3 个审批人(如架构师 + 运维负责人)
    • ⏱️ Wait timer:5 分钟(审批后等待 5 分钟再部署,留出反应时间)
    • 🌿 Deployment branches:仅 mainrelease/*

Environment Secrets(按环境隔离敏感信息):

Secret 名称StagingProduction说明
STAGING_KUBECONFIGstaging 集群 kubeconfig
PROD_KUBECONFIG生产集群 kubeconfig
DB_PASSWORDstaging 密码生产密码数据库密码
SONAR_TOKENSonarQube token
不要把生产 kubeconfig 存在仓库级别的 Secrets 里。 使用 Environment Secrets 按环境隔离,确保 staging 的 workflow 无法访问生产集群的凭据。

5.2 Dependabot 与安全告警

GitHub 内置了 Dependabot 和 Code Scanning,可以自动检测依赖漏洞和代码安全问题:

# .github/dependabot.yml
# Dependabot 配置:自动检测依赖更新和安全漏洞
version: 2
updates:
  # Maven 依赖更新
  - package-ecosystem: "maven"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
      time: "09:00"
      timezone: "Asia/Shanghai"
    # 自动创建 PR 的目标分支
    target-branch: "develop"
    # 限制同时打开的 PR 数量
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
      - "automated"
    # 按依赖分组,减少 PR 数量
    groups:
      spring:
        patterns:
          - "org.springframework*"
      testing:
        patterns:
          - "org.junit*"
          - "org.mockito*"

  # GitHub Actions 版本更新
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "ci"
      - "automated"

  # Docker 基础镜像更新
  - package-ecosystem: "docker"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "docker"
      - "automated"

配合 GitHub Security Alerts 使用

  1. Dependabot Alerts:自动检测已知漏洞,创建安全告警
  2. Dependabot Security Updates:自动创建修复 PR(升级到安全版本)
  3. Code Scanning(CodeQL):静态分析代码安全问题
# .github/workflows/codeql.yml
# CodeQL 代码安全扫描(GitHub 内置)
name: CodeQL Analysis

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    # 每周一凌晨 2 点定时扫描
    - cron: '0 18 * * 1'

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    strategy:
      matrix:
        language: ['java']
    steps:
      - uses: actions/checkout@v4

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: ${{ matrix.language }}
          queries: +security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
        with:
          category: "/language:${{ matrix.language }}"
Dependabot PR 的处理策略:建议配置自动合并规则,CI 通过且版本号为 patch 级别的更新自动合并,minor 和 major 级别需要人工审查。在仓库 Settings → Branches → Branch protection rules 中配置。

六、制品管理

6.1 制品库选择

类型工具用途
Docker 镜像Harbor / Nexus / ACR / GHCR容器镜像
Maven 制品Nexus / ArtifactoryJava 依赖包
NPM 制品Verdaccio / Nexus前端依赖包
Helm ChartChartMuseum / HarborK8s 部署模板

6.2 镜像版本策略

推荐的 tag 规则

# 语义化版本
myapp:v1.2.3

# Git SHA(推荐,可追溯)
myapp:abc1234

# 环境标记
myapp:staging-abc1234
myapp:prod-v1.2.3
不要用 latest 作为生产镜像 tag! latest 不可追溯,回滚时无法确定具体版本。始终使用 Git SHA 或语义化版本号。

6.3 制品不可变原则

一旦推送到制品库的镜像,永远不要覆盖同名 tag(除了 latest)。原因:

  • 生产环境的镜像必须跟测试通过的镜像完全一致
  • 回滚时能精确找到对应的镜像
  • 审计时能追溯到具体版本

七、环境管理

7.1 环境配置分离

配置和代码分离,不同环境使用不同配置:

config/
├── application.yml          # 公共配置
├── application-dev.yml      # 开发环境
├── application-test.yml     # 测试环境
├── application-staging.yml  # 预发布
└── application-prod.yml     # 生产环境

配置中心方案(以 Nacos 为例):

# bootstrap.yml
spring:
  application:
    name: myapp
  cloud:
    nacos:
      config:
        server-addr: nacos.example.com:8848
        namespace: ${ENV:dev}  # 按环境隔离 namespace
        group: DEFAULT_GROUP

7.2 环境变量注入

敏感信息(密码、AK/SK)不进代码仓库,通过环境变量注入:

# K8s Deployment
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: myapp-secrets
        key: db-password
  - name: AK
    valueFrom:
      secretKeyRef:
        name: myapp-secrets
        key: access-key
配置变更也要走发版流程。 配置变更(如调整超时时间、修改限流值)也应该有变更记录和审批,不能运维直接在 Nacos 上改了就生效。可以通过 GitOps 方式管理配置,变更走 MR。

八、灰度发布

8.1 金丝雀发布(Canary)

更常用的灰度策略,逐步将流量从旧版本切到新版本:

灰度比例和观察时间由服务风险、流量规模和指标收敛速度决定。每一阶段都要配置继续、停止和回滚条件,不能把示例比例直接当成生产参数。

K8s 实现方式(Istio):

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp
spec:
  hosts:
    - myapp.example.com
  http:
    - route:
        - destination:
            host: myapp
            subset: stable
          weight: 90
        - destination:
            host: myapp
            subset: canary
          weight: 10

8.2 蓝绿部署(Blue-Green)

两套完整环境,切换流量:

蓝绿部署同时维护当前版本与候选版本。候选版本完成健康检查和业务验证后切换入口;回滚时恢复入口指向。数据库变更仍需保持新旧应用兼容,流量切回不能恢复已经破坏的数据结构。

优点:切换快、回滚秒级 缺点:需要双倍资源

8.3 A/B 测试

按用户特征(地域、设备、用户标签)分流:

# Istio 按 header 分流
http:
  - match:
      - headers:
          x-user-group:
            exact: "beta"
    route:
      - destination:
          host: myapp
          subset: canary
  - route:
      - destination:
          host: myapp
          subset: stable

8.4 灰度期间监控指标

灰度期间必须关注的核心指标:

指标告警阈值说明
错误率(5xx)> 1%马上回滚
P99 延迟> 500ms观察 / 回滚
CPU / 内存> 85%观察
业务成功率< 95%马上回滚
日志异常出现 ERROR 堆栈排查
灰度不是"部署上去看看"。 必须有明确的观察指标和回滚条件。如果灰度期间没有任何监控手段,那灰度就是盲人摸象。

九、回滚机制

9.1 回滚原则

  1. 回滚优先于修复:生产出问题,第一反应是回滚,不是现场改代码
  2. 回滚必须自动化:回滚操作不能依赖人工记忆
  3. 回滚范围要明确:是只回滚代码,还是连数据库也回滚

9.2 K8s 回滚

# 查看历史版本
kubectl rollout history deployment/myapp -n production

# 回滚到上一个版本
kubectl rollout undo deployment/myapp -n production

# 回滚到指定版本
kubectl rollout undo deployment/myapp --to-revision=3 -n production

# 查看回滚状态
kubectl rollout status deployment/myapp -n production

9.3 数据库回滚

数据库变更(DDL/DML)的回滚比代码复杂得多:

变更类型回滚方案风险
新增列ALTER TABLE DROP COLUMN低(如果有数据则高)
修改列类型备份表 → 改回原类型
新增表DROP TABLE
数据迁移反向迁移脚本
新增索引DROP INDEX
数据库变更必须有对应的回滚脚本。 每个 Flyway / Liquibase 迁移文件都要写 down 方法。线上跑 migrate 之前,先确认 rollback 能用。

9.4 配置回滚

如果使用 Nacos 等配置中心:

# Nacos 配置回滚(通过 API)
curl -X POST "http://nacos.example.com:8848/nacos/v1/cs/history" \
  -d "dataId=myapp.yml&group=DEFAULT_GROUP&tenant=prod&nid=12345"

十、发版 Checklist

每次发版前,对照这个清单确认:

发版前

  • 所有功能分支已合并到目标分支
  • CI 流水线全部通过(编译、测试、扫描)
  • staging 环境验证通过
  • 数据库迁移脚本已准备并测试
  • 回滚方案已确认(代码 + 数据库 + 配置)
  • 相关人员已通知(产品、测试、运维)
  • 发版时间窗口已确认(避开业务高峰)

发版中

  • 按灰度策略逐步放量
  • 监控指标正常(错误率、延迟、CPU/内存)
  • 业务指标正常(订单量、支付成功率等)
  • 日志无异常 ERROR

发版后

  • 全量发布完成
  • 核心功能冒烟通过
  • 监控持续观察 30 分钟
  • 发版记录已归档(版本号、变更内容、发版人、时间)

十一、踩坑总结

坑 1:发版窗口选在周五下午

问题:周五下午发版,周末出问题没人处理。 建议:发版窗口选在周二到周四上午,避开周五和节假日前。

坑 2:数据库变更和代码变更不同步

问题:代码里用了新字段,但数据库还没执行迁移脚本。 建议:数据库变更先于代码部署,或者用兼容性写法(先加列 → 部署代码 → 再删旧列)。

坑 3:回滚只回滚了代码

问题:代码回滚了,但数据库新字段还在,旧代码查询报错。 建议:回滚时要同步考虑数据库和配置的回滚。如果数据库不能回滚,代码要兼容新旧两种数据结构。

坑 4:灰度期间不看监控

问题:灰度部署后直接全量,出了问题才发现。 建议:灰度期间必须有人盯着核心指标,设置自动告警。

坑 5:镜像用 latest tag

问题:回滚时找不到上一个版本的镜像。 建议:始终使用 Git SHA 或语义化版本号作为镜像 tag。

坑 6:配置变更没有记录

问题:Nacos 上改了配置,不知道谁改的、什么时候改的、改了什么。 建议:配置变更走 GitOps,或者至少在 Nacos 上开启变更审计日志。

坑 7:发版步骤靠人记忆

问题:不同的人发版,步骤不一样,遗漏步骤。 建议:发版步骤文档化,更稳妥用脚本或 CI/CD 自动化。

坑 8:测试环境和生产环境不一致

问题:测试通过了,生产挂了,因为环境配置不同。 建议:staging 环境尽量跟生产一致(配置、数据量、网络拓扑)。


十二、总结与建议

发版流程选型

团队规模推荐方案工具链
小团队(< 5人)GitHub Actions + K8s简单流水线
中型团队(5-20人)GitLab CI + ArgoCD多环境流水线
大型团队(> 20人)Jenkins + ArgoCD + Istio完整 DevOps 平台

核心原则

  1. 自动化优先:能自动化的步骤不要手动做
  2. 不可变制品:同一个镜像从测试到生产,不要重新构建
  3. 渐进式发布:灰度 → 观察 → 全量,不要一步到位
  4. 回滚优先:出问题第一反应是回滚,不是现场修
  5. 可观测性:没有监控的发版就是赌博

参考资料