一、问题背景:分支乱如麻,发布靠祈祷

在2021年,我们团队规模从40人扩张到200人,代码仓库从单仓裂变为30+子仓。混乱的Git使用方式成为最大瓶颈:

  • 分支策略缺失:每个开发随意拉分支,feature_xxx_final_v2这种名字满天飞。
  • Code Review形同虚设:MR直接自我合并,没有任何保护。
  • CI与Git脱节:Jenkins定时轮询,构建完也不知道是哪个分支的产物。

最痛的一次,开发A在release-1.2分支上热修,开发B误把dev分支合并进去,导致线上订单服务崩溃40分钟。那一次我们下定决心,必须设计一套适合大型团队的双轨制Git工作流。

二、环境与版本:技术栈基线

  • GitLab:Community Edition 15.11(支持合并队列、MR规则引擎)
  • Jenkins:2.414.2(Pipeline插件,使用Declarative语法)
  • Argo CD:2.8.4(GitOps部署,同步策略为自动+自愈)
  • Kubernetes:1.26.3(生产集群3节点,namespace隔离环境)
  • Git版本:2.40.1(团队统一使用,禁止--force-with-lease以外的强推)

三、双轨制方案设计:不是非黑即白

我们调研了Git Flow(Vincent Driessen)和Trunk Based(Paul Hammant),发现极端方案都不适用:

  • Git Flow的developrelease分支,在高速迭代下合并回master冲突极大。
  • 纯TBD要求所有特性开开关,但我们的多个长周期项目(3-6个月)无法承受。

最终设计

仓库类型 策略 适用场景
核心服务(订单、支付) Trunk Based + 短生命周期特性分支 每日多次发布
业务中台(营销、会员) Git Flow简化版(仅main/dev/feature) 双周迭代
基础组件库(SDK) 类似Git Flow,但强化release分支 月度发版

分支命名规范:

feature/{JIRA-ID}-{简短描述}
fix/{JIRA-ID}-{简短描述}
release/{version}
hotfix/{version}-{描述}

关键决策main分支永远是稳定可部署的。所有合并到main的MR必须通过:2个Approver + 流水线绿灯 + 无未解决的Thread。

四、核心实现:分支保护与MR规则(含真实配置)

4.1 GitLab分支保护(通过GitLab API设置,不再用UI点击)

# 设置main分支保护:禁止任何人直接push,仅允许Maintainer合并
curl --request POST --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  "https://gitlab.example.com/api/v4/projects/${PROJECT_ID}/protected_branches" \
  --data "name=main&push_access_level=0&merge_access_level=40&unprotect_access_level=0"

# 对release/*分支:开发者可push,但合并必须由Maintainer执行
curl --request POST --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
  "https://gitlab.example.com/api/v4/projects/${PROJECT_ID}/protected_branches" \
  --data "name=release/*&push_access_level=30&merge_access_level=40"

4.2 MR模板(.gitlab/merge_request_templates/Default.md

## 变更描述(必填)
- 关联JIRA: [PROJ-1234](https://jira.example.com/browse/PROJ-1234)
- 变更类型: New Feature / Bugfix / Refactor
- 影响范围: 服务名 + 接口名

## 测试清单(勾选)
- [ ] 单元测试通过(`go test ./...` 覆盖率≥80%)
- [ ] 集成测试通过(`docker-compose -f test/docker-compose.yml up`)
- [ ] 本地已跑通 `make lint``make build`

## 自测截图/日志(粘贴关键输出)

## 部署说明
- 是否需要DB Migration: 是/否
- 是否有破坏性变更(Breaking Change): 是/否,说明兼容方案

4.3 Code Review强制指令(GitLab Code Owners + Merge Approval)

在仓库根目录添加CODEOWNERS文件:

# 核心模块必须由资深工程师review
/services/payment/* @team-payment-lead @backend-lead
/services/order/*  @team-order-lead
# 所有Go文件至少需要两位Approver
*.go @backend-reviewers @xiaoming @xiaohong

Settings -> Merge Request中开启:
- Approvals required: 2
- Prevent author approval: 开启(作者不能自批)
- Check if merged MR can be reverted: 开启

五、CI/CD联动:从MR到生产只花15分钟

这是整个工作流的精髓。我们使用Jenkins Pipeline监听GitLab事件,实现MR触发测试 → 合并触发镜像构建 → Argo CD自动同步

5.1 Jenkinsfile(Declarative Pipeline,支持多分支)

pipeline {
    agent { label 'go-builder' }
    environment {
        IMAGE_NAME = "registry.example.com/service/order-service"
        IMAGE_TAG = "${env.BRANCH_NAME}-${env.GIT_COMMIT.take(8)}"
    }
    stages {
        stage('Checkout') {
            steps { checkout scm }
        }
        stage('Unit Test') {
            steps { sh 'go test ./... -coverprofile=coverage.out' }
            post {
                success { junit '**/report.xml' }
            }
        }
        stage('Build & Push') {
            when { branch 'main' }  // 只有main分支合并后构建镜像
            steps {
                sh """
                    docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
                    docker push ${IMAGE_NAME}:${IMAGE_TAG}
                """
            }
        }
        stage('Update GitOps Repo') {
            when { branch 'main' }
            steps {
                sh """
                    git clone https://token:${GITOPS_TOKEN}@gitlab.example.com/gitops/config.git
                    cd config
                    # 用sed替换镜像tag,然后commit & push
                    sed -i "s|image: registry.example.com/service/order-service:.*|image: ${IMAGE_NAME}:${IMAGE_TAG}|" overlays/prod/order-service/deployment.yaml
                    git commit -m "release: update order-service to ${IMAGE_TAG}"
                    git push origin main
                """
            }
        }
    }
    post {
        failure { 
            // 通知飞书机器人
            sh 'curl -X POST -H "Content-Type: application/json" -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"[CI] ${JOB_NAME} 构建失败\"}}" https://open.feishu.cn/open-apis/bot/v2/hook/xxx'
        }
    }
}

5.2 Argo CD Application(GitOps部署,自动回滚)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://gitlab.example.com/gitops/config.git
    targetRevision: main
    path: overlays/prod/order-service
  destination:
    server: https://kubernetes.default.svc
    namespace: prod
  syncPolicy:
    automated:
      prune: true
      selfHeal: true   # 自愈,K8s状态漂移自动恢复
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
  healthCheck:
    - name: order-service
      kind: Deployment
      liveState: "deployed"

关键联动逻辑
1. 开发推送分支 → GitLab触发MR → Jenkins跑单元测试(无分支限制)
2. MR合并到main → GitLab Webhook触发Jenkins的main分支流水线
3. Jenkins构建镜像并推送到Harbor,然后更新GitOps仓库的yaml文件
4. Argo CD检测到GitOps仓库变化,自动kubectl apply到生产集群
5. 如果Pod启动探测失败(LivenessProbe),Argo CD自动回滚到上一个健康版本

六、踩坑与优化:我们走过的弯路

6.1 坑1:Jenkins多分支扫描导致资源耗尽

现象:30个仓库、每个仓库100+分支,Jenkins磁盘和CPU爆满。
解决:设置orphanedItemStrategy丢弃超过7天未活跃的分支任务:

properties([
    pipelineTriggers([pollSCM('')]),
    disableConcurrentBuilds()
])
// 在Jenkins多分支job配置中,限制扫描深度:
// "Advanced" -> "Suppress automatic SCM triggering" 并设置cron为"H/6 * * * *"

6.2 坑2:Argo CD自动同步导致滚动更新风暴

现象:每次GitOps仓库push,Argo CD会对所有Deployment执行kubectl apply,即使镜像没变。
解决:在Application中加入ignoreDifferences

spec:
  ignoreDifferences:
  - group: apps
    kind: Deployment
    jsonPointers:
    - /spec/replicas
    - /spec/template/spec/containers/0/image
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      # 添加这个:只同步有变更的资源
      syncOptions:
      - ApplyOutOfSyncOnly=true

6.3 坑3:Code Review形式化,MR没人认真看

解决:引入小步合并原则——单个MR改动超过500行自动阻塞,必须拆分。我们在GitLab CI中加了一个check:

# .gitlab-ci.yml 片段
check-mr-size:
  stage: test
  script:
    - |
      DIFF_LINES=$(git diff --numstat origin/main...HEAD | awk '{sum+=$1+$2} END {print sum}')
      echo "MR改动行数: ${DIFF_LINES}"
      if [ "$DIFF_LINES" -gt 500 ]; then
        echo "❌ MR超过500行,请拆分为多个MR"
        exit 1
      fi
  only:
    - merge_requests

七、效果数据与总结

改造前后对比(基于2022年Q1 vs 2023年Q1数据):

指标 改造前 改造后
发布频率(次/天) 0.4 8
线上事故回滚时间(分钟) 25 3
误合并到main的次数(次/周) 3 0.1
MR平均评审周期(小时) 24 4
新员工上手时间(天) 7 1.5

总结:双轨制不是理论上的最优解,但它是200人团队现实中的最优解。核心不在于选择哪个flow,而在于用自动化强制纪律——把分支保护、MR规则、CI/CD联动变成不可绕过的流水线关卡。如果你还在为分支混乱发愁,建议先做两件事:一是把main分支保护起来,二是强制Code Review流水线。这两点落地,至少能减少70%的合并灾难。

行动建议
- 如果团队100人,建议按业务模块拆仓库,每个仓库独立走流程。
- 永远不要相信开发者的自觉,把规则写进代码和配置里。