PAYATHON 2026

如何使用 Jenkins 和 Git 实现 CI / CD 与发布管理?

技术老齐

可以用 Jenkins Pipeline 配合 input 进行人工确认,将同一份构建产物从 UAT 推进到生产环境。这套环境建议将 CI 和发布分开:CI 负责构建、测试和保存产物,发布流程负责选择产物、核对变更工单、部署、审批及记录结果。

生产环境应部署通过 UAT 验证的同一份包。如果发布时重新拉取 master 编译,生成的包就无法与 UAT 验证结果对应。

controller 与 agent 如何部署

起步时可以沿用原方案,让 controller 和独立 agent 暂时运行在同一台 VM 上,之后再将 agent 移到其他服务器。Jenkins 现在通常将原来的 master 称为 controller。

从一开始就应做好以下配置:

  • 将 controller 内置节点的 executor 数量设为 0,构建任务交给 agent。
  • controller 与 agent 使用不同的服务账户,agent 不应拥有 JENKINS_HOME 的读写权限。
  • agent 使用独立工作目录,避免构建文件与 Jenkins 配置混放。
  • 按能力设置 label,例如 windows-vs2013、oracle-integration,job 根据 label 分配任务。

Jenkins 官方也提供了在同一台机器上、以不同账户运行 agent 进程的过渡方案。不过,同机部署仍会共享 CPU、内存和磁盘,也承担同一台机器的故障风险。controller 隔离说明

Pipeline 中可以这样指定构建环境:

agent {
    label 'windows-vs2013'
}

以后在新服务器上安装所需工具,并配置相同的 label,任务就能在新 agent 上运行。为减少迁移时的修改,还应集中管理 MSBuild 路径、共享目录和服务器名称,避免在各个 job 中写死。label 只负责调度,不会自动安装编译器或测试工具。agent 配置说明

Windows Server 2012 和 VS2013 属于遗留环境。新建 Jenkins 时,应先核对所选 Jenkins、Java、Windows 和插件的兼容性,旧部署教程未必仍然适用。编译 VS2013 项目不要求 controller 使用同样的操作系统,旧构建工具可以放在专用 Windows agent 上,但 agent 本身也必须满足 Jenkins 的运行要求。Windows 支持策略、Java 支持策略

Git 与分支流程如何调整

小团队使用 master 加短期功能分支即可,不必为了发布管理引入完整的 Git Flow。流程可以调整为:

  1. 从最新的 master 创建短期分支。
  2. 提交后执行构建和测试。
  3. 通过代码审查及必要检查后合并。
  4. 合并后的 master 再构建一次,生成候选发布产物。

如果只在合并后构建,发现问题时主分支已经受到影响。分支构建可以提前反馈,合并后的构建则验证实际合并结果。应尽量缩短分支的存续时间,只有需要同时维护多个发布系列时,再增加 release 分支。

发布时要记录完整 commit SHA。可以创建 v2.1.0 这样的 Git tag,并限制修改或删除发布 tag 的权限。产物清单仍应保存 SHA,不能只依赖 tag 名称。

可以使用网络文件系统上的 bare repository,但多人直接拥有仓库目录写权限时,容易误改或删除 Git 内部文件。共享文件系统的性能也需要实际评估。Git 协议说明

如果团队需要合并请求、分支保护、统一权限和 webhook,可以引入 Gitea 等 Git 托管服务,通过 SSH 或 HTTPS 访问。SourceTree 可以继续作为客户端使用。

版本号如何管理

由人工决定 major/minor 的做法合理,patch 的规则也需要明确。对于应用程序,应先约定哪些变化影响兼容性,哪些属于新增功能,哪些只是修复。

AssemblyVersion("2.1.*") 中的 * 有编译器定义的自动版本生成含义,并不直接代表 Jenkins 构建号。如果要由 CI 控制版本,建议生成明确的值。AssemblyVersion 说明

可以采用以下约定:

标识示例用途
发布版本2.1.0面向用户和变更工单
AssemblyVersion2.1.0.0程序集身份与绑定
AssemblyFileVersion2.1.0.347定位具体二进制文件
AssemblyInformationalVersion2.1.0+build.347.sha.a1b2c3d保存构建与源码信息

例如,在编译前生成:

using System.Reflection;

[assembly: AssemblyVersion("2.1.0.0")]
[assembly: AssemblyFileVersion("2.1.0.347")]
[assembly: AssemblyInformationalVersion("2.1.0+build.347.sha.a1b2c3d")]

需要保持程序集绑定兼容的组件,可以在一个兼容系列内保持 AssemblyVersion 稳定,通过其他版本属性区分构建。是否调整 AssemblyVersion,应结合强名称、引用关系和兼容性要求决定。程序集版本与文件版本的区别

生成版本属性时,要移除或替换原有的重复声明。在构建工作区中修改即可,不必每次构建都提交一次 AssemblyInfo.cs。

程序集数字版本字段有范围限制,不能无限填入递增构建号。Jenkins 的 BUILD_NUMBER 也只是某个 job 内的编号,因此发布清单还应保存 job 标识、完整 commit SHA 和产物校验值。

构建产物怎样进入发布流程

CI 成功后,应保存可部署的包,避免发布流程依赖 agent 工作区里的文件。流程可以这样安排:

Git 提交
  → 编译和测试
  → 生成部署包及发布清单
  → 上传产物仓库
  → 关联变更工单
  → 部署 UAT
  → 验收并批准指定产物
  → 在发布窗口部署生产
  → 健康检查并回写结果

发布清单至少记录:

  • 发布版本、完整 commit SHA、Jenkins 构建地址。
  • 包的固定下载地址与 SHA-256。
  • 测试结果、所需数据库迁移脚本。
  • 对应变更工单和部署记录。

同一个版本的产物应禁止覆盖,并限制删除权限。起步时可以使用 Jenkins 构建归档;需要长期保存的发布产物,还应单独规划保留策略、备份和访问控制。

UAT 和生产使用同一份应用包,环境配置在部署时注入。数据库密码、生产连接串等敏感内容应通过受控凭据提供。

Pipeline 的人工审批怎样写

Jenkins 的 input 可以用于短时间的人工确认,需要配置审批人、超时和发布权限。等待审批期间不应占用构建 executor。

以下 Pipeline 示例假设 job 从受保护的发布脚本仓库加载,且已安装相应 Pipeline 插件。ci/Release.ps1 是团队需要实现的部署入口,并非 Jenkins 内置命令。

pipeline {
    agent none

    options {
        skipDefaultCheckout(true)
        disableConcurrentBuilds()
    }

    parameters {
        string(
            name: 'RELEASE_ID',
            defaultValue: '',
            description: '已保存且不可覆盖的产物标识'
        )
        string(
            name: 'CHANGE_ID',
            defaultValue: '',
            description: '变更工单编号'
        )
        choice(
            name: 'TARGET',
            choices: ['UAT', 'PROD'],
            description: '本次流程推进到的环境'
        )
    }

    stages {
        stage('Deploy UAT') {
            agent { label 'windows-deploy-uat' }

            steps {
                checkout scm

                powershell '''
                    $ErrorActionPreference = 'Stop'

                    & .\\ci\\Release.ps1 `
                        -ReleaseId $env:RELEASE_ID `
                        -ChangeId $env:CHANGE_ID `
                        -Environment UAT
                '''
            }
        }

        stage('Deploy production') {
            when {
                beforeInput true
                expression { params.TARGET == 'PROD' }
            }

            options {
                timeout(time: 2, unit: 'HOURS')
            }

            input {
                message "批准发布 ${params.RELEASE_ID}?变更单:${params.CHANGE_ID}"
                ok '批准发布'
                submitter 'release-managers'
                submitterParameter 'APPROVED_BY'
            }

            agent { label 'windows-deploy-prod' }

            steps {
                checkout scm

                powershell '''
                    $ErrorActionPreference = 'Stop'

                    & .\\ci\\Release.ps1 `
                        -ReleaseId $env:RELEASE_ID `
                        -ChangeId $env:CHANGE_ID `
                        -Environment PROD `
                        -ApprovedBy $env:APPROVED_BY
                '''
            }
        }
    }
}

这段配置的执行行为如下:

  • 顶层 agent none 避免整个流程一直占用 executor。
  • stage 的 input 在分配该 stage 的 agent 前执行。
  • beforeInput true 让 TARGET=UAT 时跳过生产审批。
  • 两小时的超时限制覆盖整个生产 stage,包括审批、排队及部署。

具体规则见 Declarative Pipeline 语法。

release-managers 必须替换成 Jenkins 实际可识别的用户 ID 或外部组名。Jenkins 管理员也可以响应受 submitter 限制的审批,因此仅靠这一设置无法满足完整的职责分离要求。input 权限说明

部署脚本需要负责:

  1. 校验参数,确认产物存在,且构建、测试符合发布条件。
  2. 核对工单允许发布的产物、环境与时间窗口。
  3. 下载指定包并验证校验值。
  4. 在生产部署前,确认同一份包已有有效的 UAT 验收记录。
  5. 执行部署和健康检查,保存结果;失败时返回失败状态。

开发人员选择 PROD 只表示提出生产发布请求。生产凭据、job 配置权限、发布脚本修改权限和审批权限都应单独控制。普通功能分支构建不能获得生产部署凭据。

UAT 验收需要几天时怎么办

如果 UAT 需要数天,建议拆成独立的 UAT 发布 job 和生产发布 job。生产 job 接收已通过 UAT 的发布记录,并重新核验工单及产物。

这样可以避免一个 Pipeline 长期等待,也能避免 disableConcurrentBuilds() 让后续发布一直排队。该选项只限制同一个 job;如果多个 job 可能操作同一环境,还需要共享部署锁。Lockable Resources 插件可以提供这种互斥控制。

环境锁只能防止同时部署。取得锁后,仍应检查当前部署版本,防止较旧的审批流程覆盖已经发布的新版本。

预约生产发布时,可以让定时 job 查询已批准且到达发布窗口的记录,再触发生产部署。记录中要固定产物标识,并防止重复执行。定时任务不能直接发布“最新成功构建”,否则审批对象可能在等待期间发生变化。

可以采用哪些开源工具

当前团队可以先采用以下组合:

职责工具或方案
Git 托管、合并审查Gitea
构建、测试、部署编排Jenkins Pipeline
通用部署包保存Gitea Generic Package Registry
变更工单、状态流转Redmine,或继续使用现有 Domino 系统

Gitea 的 Generic Package Registry 支持通过 HTTP 上传和下载通用文件,可以保存 ZIP、安装包等产物。删除权限和保留策略仍需配置。通用包仓库文档

Redmine 支持按角色控制工单状态流转,并配置自定义字段。可以设置“待审批”“批准发布”“发布完成”等状态,保存产物标识、UAT 结果和计划时间。Jenkins 可以通过 REST API 读取和更新工单。工作流配置、工单 REST API

这些工具需要少量集成,安装后还不能直接形成完整的变更管理系统。通常实现工单查询、产物核验和结果回写就够了,不必另做一套发布平台。

如果现有 Lotus Notes/Domino 已能可靠保存审批和变更历史,可以先保留。能否自动对接,取决于该系统实际开放的接口与权限,需要核实,不能仅凭使用 Domino 就假定有可直接调用的 API。

Oracle 与 Windows 应用还要注意什么

集成测试应使用隔离的 Oracle 测试 schema 或测试库,并准备可重复执行的数据初始化和清理流程。如果多个构建共享可变测试数据,增加 agent 可能加重相互干扰,未必能缩短测试时间。

数据库迁移脚本应随发布产物保存,并记录执行顺序。应用回退到旧包时,数据库未必能安全回退;涉及表结构和数据修改时,需要事先验证兼容性与恢复办法。

Web API/MVC 的发布步骤通常需要处理 IIS 应用池、文件替换、配置注入和健康检查。WinForms 则要明确采用 MSI、ClickOnce 还是其他分发方式,以及客户端如何升级。两类应用可以共享审批和产物管理流程,部署脚本应分别实现。

实施时,先将固定产物发布到 UAT,保存验收记录,再经人工批准将同一产物部署到生产环境。完成这套流程后,再增加预约发布和工单自动回写。每次发布都应能查到使用的包、批准人、部署目标,以及失败后的恢复办法。

备注:内容仅供参考。