跳过正文

多环境发版策略设计

·806 字·4 分钟·
目录

一、环境划分标准
#

在一套成熟的研发流程中,至少需要三个独立环境,各自承担不同职责:

环境定位谁可以访问数据变更频率
DEV / QA功能验证、集成测试研发、测试脱敏测试数据随时
PRE / Staging灰度验证、性能测试、产品验收研发、测试、产品类生产数据量级(脱敏)发版前
PROD生产环境运维、on-call真实数据受控窗口

几点说明:

  • DEV 和 QA 可以合并为一个环境,降低维护成本;PRE 和 Staging 同义
  • QA 允许随时推送,不需要审批,快速迭代
  • PRE 应尽量和 PROD 配置对齐(副本数可以少,但配置项必须一致)
  • PROD 变更需要审批记录,回溯时能知道是谁在何时做了什么

二、分支策略
#

GitFlow(适合发版节奏固定的团队)
#

main(只接受 release 分支的 merge,永远是稳定状态)
  ↑
release/1.2.0(从 develop 切出,修 bugfix,打 tag 后合回 main 和 develop)
  ↑
develop(功能集成分支,对应 QA 环境)
  ↑
feature/xxx(功能分支,开发完后 merge 到 develop)

触发规则:

  • feature/* push → 跑单测,不部署
  • develop push → 自动部署 QA
  • release/* push → 自动部署 PRE
  • v*.*.* tag → 自动部署 PROD(可加人工审批)

优点:分支职责清晰,适合迭代周期固定(如双周发版)的团队
缺点:分支多,合并冲突频繁,维护成本高

Trunk-based(适合高频发版团队)
#

main(唯一长期分支,所有开发者频繁合入)
  ↑
short-lived/feature-xxx(最长 2 天生命周期,merge 回 main 即删除)

发版靠 tag:

  • main push → 自动部署 QA
  • main push + 人工触发 → 部署 PRE
  • 打 semver tag → 部署 PROD

优点:集成频繁,冲突少,CI 反馈快
缺点:需要 Feature Flag 支持未完成功能的隔离,对团队纪律要求高

实际推荐
#

  • 团队 < 10 人,发版节奏快 → Trunk-based
  • 团队 > 10 人,有固定发版窗口 → GitFlow 或简化版(去掉 develop 分支,直接 feature → main)

三、镜像 Tag 策略
#

镜像 tag 决定了每次部署的可追溯性。常见策略对比:

策略示例优点缺点
latestapp:latest简单不可追溯,回滚困难
分支名app:main可以区分分支同一 tag 内容不断变化
commit SHAapp:a1b2c3d完全可追溯不够直观
semverapp:v1.2.0语义清晰需要手动打 tag
日期+commitapp:20251209-a1b2c3d可追溯+可排序稍长

推荐组合

  • QA 环境:{branch}-{short-sha},例如 app:main-a1b2c3d
  • PRE 环境:{branch}-{short-sha} 或 release 分支名
  • PROD 环境:v{semver}{short-sha}禁止使用 latest

在 GitHub Actions 中生成 tag:

- name: 生成镜像 Tag
  id: meta
  run: |
    SHORT_SHA=$(echo $GITHUB_SHA | head -c 7)
    BRANCH=$(echo $GITHUB_REF_NAME | tr '/' '-')
    echo "tag=${BRANCH}-${SHORT_SHA}" >> $GITHUB_OUTPUT
    echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT

- name: 构建并推送
  uses: docker/build-push-action@v5
  with:
    tags: |
      ${{ env.ECR_REGISTRY }}/${{ env.SERVICE_NAME }}:${{ steps.meta.outputs.tag }}
      ${{ env.ECR_REGISTRY }}/${{ env.SERVICE_NAME }}:${{ steps.meta.outputs.sha }}

四、发版流程设计
#

自动部署节点(无需人工干预)
#

代码 merge 到 develop/main
       ↓
CI: 单测 + 构建镜像 + 推送
       ↓
自动更新 GitOps 仓库(kustomize image tag)
       ↓
ArgoCD 检测到变更,自动同步到 QA
       ↓
QA 冒烟测试(自动化)

手动审批节点(PRE / PROD)
#

PRE 部署:
  - 触发方式:手动点击流水线 / PR 合并到 release 分支
  - 审批:无需审批,但需要 QA 验证通过
  - 通知:发 IM 通知相关方

PROD 部署:
  - 触发方式:打 semver tag / 手动触发
  - 审批:需要至少 1 人 approve(GitHub Environment protection rules)
  - 时间窗口:仅允许工作日 10:00–17:00
  - 通知:发 IM + 邮件通知

GitHub Actions 的 environment 审批配置:

deploy-prod:
  needs: deploy-pre
  environment:
    name: production
    url: https://app.example.com
  runs-on: ubuntu-latest
  steps:
    - name: 部署到生产
      run: |
        # 更新 GitOps 仓库镜像 tag
        ./scripts/update-image-tag.sh $IMAGE_TAG

在 GitHub repo 的 Settings → Environments → production 中配置 Required reviewers,push 到该 environment 的 workflow 会暂停等待审批。


五、变更冻结窗口
#

变更冻结是降低发版风险的有效手段:

# 流水线中检查是否在冻结期
- name: 检查变更冻结窗口
  run: |
    CURRENT_HOUR=$(TZ=Asia/Shanghai date +%H)
    CURRENT_DOW=$(TZ=Asia/Shanghai date +%u)  # 1=周一, 7=周日
    
    # 禁止周末部署生产
    if [ "$CURRENT_DOW" -ge 6 ]; then
      echo "❌ 禁止在周末部署生产环境"
      exit 1
    fi
    
    # 禁止非工作时间部署生产
    if [ "$CURRENT_HOUR" -lt 10 ] || [ "$CURRENT_HOUR" -ge 18 ]; then
      echo "❌ 仅允许 10:00-18:00 (CST) 部署生产"
      exit 1
    fi
    
    echo "✅ 在允许的发版窗口内"

节假日冻结通常通过配置文件或环境变量维护:

# 检查冻结列表
FROZEN_DATES="2025-12-24 2025-12-25 2025-12-31 2026-01-01"
TODAY=$(TZ=Asia/Shanghai date +%Y-%m-%d)
if echo "$FROZEN_DATES" | grep -qw "$TODAY"; then
  echo "❌ 今日为变更冻结期"
  exit 1
fi

六、金丝雀发布
#

金丝雀发布的核心是先让少量流量验证新版本,确认无误后再全量切换

基于 Kubernetes 的流量切分
#

最简单的方式是利用多个 Deployment 副本数比例:

# v1: stable,5 个副本
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-stable
spec:
  replicas: 5
  selector:
    matchLabels:
      app: my-app
      version: stable
  template:
    metadata:
      labels:
        app: my-app
        version: stable

---
# v2: canary,1 个副本(约 1/6 流量)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: canary
  template:
    metadata:
      labels:
        app: my-app
        version: canary

Service selector 只匹配 app: my-app,流量按副本数比例分配。

分阶段放量流程
#

阶段 1: 10% 流量
  → 观察 5–10 分钟
  → 指标:错误率 < 0.1%,P99 延迟无明显上涨
  → 告警:无新增 CRITICAL 告警

阶段 2: 50% 流量
  → 观察 10–20 分钟
  → 同上

阶段 3: 100% 流量(全量切换)
  → 删除 stable Deployment
  → 下线金丝雀标记

如果任何阶段出现问题,立即缩减 canary 副本至 0,等于秒级回滚。


七、蓝绿部署
#

蓝绿部署维护两套完全相同的生产环境,切换时修改 Service selector:

# 当前生产流量指向 blue
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  selector:
    app: my-app
    slot: blue    # 修改这里为 green 即可完成切换
  ports:
  - port: 80
    targetPort: 8080

切换脚本:

#!/bin/bash
set -e

CURRENT_SLOT=$(kubectl get svc my-app -o jsonpath='{.spec.selector.slot}')
NEW_SLOT=$([[ "$CURRENT_SLOT" == "blue" ]] && echo "green" || echo "blue")

echo "当前 slot: $CURRENT_SLOT → 切换到: $NEW_SLOT"

# 确认新 slot 的 Pod 都 Ready
kubectl rollout status deployment/my-app-${NEW_SLOT} --timeout=120s

# 切换 Service selector
kubectl patch svc my-app -p "{\"spec\":{\"selector\":{\"slot\":\"${NEW_SLOT}\"}}}"

echo "✅ 切换完成"

蓝绿的优点是回滚极快(把 Service selector 改回来),缺点是资源成本翻倍。


八、发版通知与审计
#

发版后应自动通知相关方,并留下可审计的记录:

# 钉钉通知示例
send_dingtalk_notification() {
  local env=$1
  local service=$2
  local version=$3
  local status=$4
  local operator=$5

  curl -s -X POST "$DINGTALK_WEBHOOK" \
    -H "Content-Type: application/json" \
    -d "{
      \"msgtype\": \"markdown\",
      \"markdown\": {
        \"title\": \"发版通知\",
        \"text\": \"### 发版通知\\n\"
          \"- **环境**: ${env}\\n\"
          \"- **服务**: ${service}\\n\"
          \"- **版本**: \`${version}\`\\n\"
          \"- **状态**: ${status}\\n\"
          \"- **操作人**: ${operator}\\n\"
          \"- **时间**: $(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M:%S')\\n\"
      }
    }"
}

九、发版后验证 Checklist
#

每次发版完成后,值班人员应执行以下验证:

## 发版后验证 Checklist

### 基础指标(发版后 5 分钟内)
- [ ] Pod 全部 Ready(kubectl get pods -l app=xxx)
- [ ] 错误率与发版前持平(Grafana / DataDog)
- [ ] P99 延迟无明显上涨
- [ ] 无新增 CRITICAL/ERROR 日志
- [ ] 健康检查接口返回 200

### 业务验证(发版后 15 分钟内)
- [ ] 核心链路冒烟测试通过
- [ ] 数据库连接池无异常
- [ ] 缓存命中率正常
- [ ] 外部依赖(第三方 API)调用正常

### 收尾
- [ ] 更新 CHANGELOG / 发版记录
- [ ] 关闭对应 ticket/issue
- [ ] 如有数据库变更,确认迁移脚本执行完毕
- [ ] 通知产品/测试确认功能上线
Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。

相关文章

ArgoCD + Kustomize GitOps 体系实践

·2128 字·10 分钟
记录在多套 K8s 集群(AWS EKS + 阿里云 ACK)上落地 GitOps 的完整过程:目录结构设计、Kustomize overlay 环境差异管理、ArgoCD ApplicationSet 自动化、以及真实踩过的坑。

Docker 镜像优化实践

·900 字·5 分钟
覆盖多阶段构建、基础镜像选型(alpine/distroless/scratch)、layer 缓存优化、BuildKit cache mount、漏洞扫描等实战技巧,附优化前后对比数据。