Administrator
Published on 2026-05-06 / 10 Visits
0

从 Git 到生产环境:理解 CI/CD 为什么这样设计

大家好,今天想聊一聊 CI/CD 。

刚开始接触部署时,我更多是在按照已有步骤操作:拉取代码、执行构建、上传文件,然后发布到服务器。

但随着实践逐渐深入,我开始从“照着步骤完成部署”,转向理解现代交付流水线为什么要这样设计。

从这个角度再看,CI/CD 就不再是一堆工具的组合,而是一套职责清晰、彼此衔接的软件交付体系。

这篇分享主要讨论两个问题:

  • 代码是如何从 Git 一步步进入生产环境的?

  • 流程中的每个组件为什么存在,它的职责又在哪里结束?

一、CI/CD 真正解决的是什么问题?

在真实的生产环境中,团队最关心的通常不是“代码能不能运行”。

代码能够在开发人员的电脑上运行,只是最基本的要求。真正进入生产环境时,我们还需要回答更多问题:

  • 这个版本是谁构建的?

  • 当前生产环境运行的是哪个 Git Commit?

  • 这个版本是否经过了完整测试?

  • 构建过程是否可以重复执行?

  • 测试环境和生产环境使用的是不是同一个构建产物?

  • 如果上线后出现问题,能否安全回滚?

  • 谁批准了这次发布?

  • 整个发布过程是否可以追溯和审计?

CI/CD 的价值,就是用一套标准化、可重复、可追踪的流程,持续回答这些问题。

因此,CI/CD 不只是为了“让部署更快”,也是为了让软件交付变得更加可靠和可控。


二、Git:整条交付链路的事实来源

Git 是整个交付体系的基础。

进入版本控制系统的不应该只有应用代码,还应该包括:

  • 构建逻辑

  • 测试规则

  • 依赖配置

  • 部署配置

  • 流水线定义

这也是为什么越来越多团队会将 Jenkins Pipeline 定义在代码仓库里的 Jenkinsfile 中。

当交付逻辑也被纳入 Git 管理后,它就具备了和业务代码相同的工程属性:

  • 可以进行代码评审

  • 可以查看修改历史

  • 可以回滚到之前的版本

  • 可以追踪某项部署行为由哪次提交引入

  • 可以让不同环境执行一致的流程

例如,当某次构建突然失败时,我们不仅可以检查业务代码发生了什么变化,也可以确认构建脚本、依赖版本或流水线配置是否被修改。

从这一刻开始,部署就不再是一套依赖个人经验的操作仪式,而是一项可以评审、验证和持续改进的工程活动。

三、Jenkins 与 CI:构建的不只是代码,更是信心

Jenkins 更适合被理解为一个 CI 引擎,而不只是一个“自动部署工具”。

它的核心职责通常包括:

  1. 从 Git 获取指定版本的代码

  2. 读取并执行仓库中的 Jenkinsfile

  3. 安装依赖

  4. 编译或打包应用

  5. 执行自动化测试

  6. 进行代码质量或安全检查

  7. 生成确定性的构建结果

这里最重要的一点是:CI 的最终输出通常不是一个已经运行起来的生产服务,而是一个可以被部署的构建产物,也就是 Artifact。

Jenkins 的价值,在于把原本依赖人工执行的构建过程标准化。

同样的代码、依赖和构建配置,应该能够稳定地产生同样的结果。这样,团队才有理由相信测试通过的版本,就是之后准备发布的版本。

从职责划分上看,让 Jenkins 直接长期管理生产环境并不是最理想的设计。

Jenkins 可以触发部署,但在成熟的交付体系中,生产发布通常还需要审批、权限隔离、环境编排、回滚和审计等能力。这些职责更适合交给专门的 CD 平台处理。

四、Artifact:CI 真正生产出来的产品

Artifact,也就是构建产物,是理解 CI/CD 最重要的概念之一。

可以把它定义为:

由某一次明确的构建过程生成,内容不可变、来源可追踪,并且可以被部署到目标环境中的交付结果。

不同技术栈产生的构建产物并不相同。

例如:

  • Java 项目可能生成 JAR 或 WAR 文件

  • Python 项目可能生成 Wheel、压缩包或经过整理的代码包

  • 前端项目通常生成编译后的 HTML、CSS 和 JavaScript 静态资源

  • 容器化项目通常生成 Docker Image

Artifact 的具体格式并不是重点。

真正重要的是两个特征:可追踪和不可变。

可追踪

每个 Artifact 都应该能够关联到明确的信息,例如:

  • Git Commit

  • 分支或 Tag

  • 构建编号

  • 构建时间

  • 依赖版本

  • 测试结果

  • 构建流水线版本

当生产环境出现问题时,我们应该能够迅速回答:

当前运行的这个文件或镜像,到底是由哪一份代码构建出来的?

不可变

一个 Artifact 一旦构建完成,就不应该再被修改。

如果内容发生变化,就应该生成一个新版本,而不是覆盖原来的版本。

这样才能保证测试环境验证过的 Artifact,与最终进入生产环境的 Artifact 完全一致。

正确的做法是:

Build once,deploy many。

也就是只构建一次,然后将同一个构建产物依次推广到测试、预发布和生产环境。

环境之间改变的应该是外部配置,而不是 Artifact 本身。

五、为什么只有 Jenkins 还不够?

Jenkins 可以保存构建输出,但它并不是一个专业的 Artifact Repository,也就是制品仓库。

如果长期把 Jenkins 当成文件存储系统,会遇到一系列问题:

  • 构建记录可能因清理策略被删除

  • Artifact 缺少统一的版本管理

  • 不适合被多个下游系统稳定访问

  • 权限、生命周期和存储策略难以统一

  • 构建任务和发布流程会产生过度耦合

因此,成熟的流水线通常会使用 Nexus、Artifactory 或容器镜像仓库保存构建产物。

制品仓库主要负责:

  • 长期保存 Artifact

  • 管理不同版本

  • 提供稳定的下载和分发能力

  • 控制访问权限

  • 记录产物来源和元数据

  • 为测试、预发布和生产环境提供统一的软件来源

将 Jenkins 与制品仓库分开之后,构建和部署也就真正解耦了。

Jenkins 负责回答:

这个版本是如何被构建和验证出来的?

制品仓库负责回答:

这个经过验证的版本保存在哪里?

CD 平台则负责回答:

这个版本应该在什么时间、经过谁的批准,以什么方式进入哪个环境?

六、CI 与 CD:关注点并不相同

CI 和 CD 经常被放在一起讨论,但它们解决的问题并不完全相同。

CI 更关注代码质量和构建结果,例如:

  • 代码是否可以成功编译

  • 自动化测试是否通过

  • 依赖是否完整

  • 构建过程是否稳定

  • 是否生成了可以部署的 Artifact

CD 更关注生产发布的安全性和可控性,例如:

  • 谁可以发布

  • 谁需要审批

  • Artifact 可以进入哪些环境

  • 多台服务器应该按照什么顺序更新

  • 发布失败后如何停止或回滚

  • 如何记录完整的操作过程

可以简单地概括为:

CI 建立对代码和构建结果的信心,CD 建立对生产发布过程的控制。

在银行等受监管行业中,生产部署通常还必须满足更严格的要求:

  • 权限控制

  • 审批流程

  • 开发、测试和生产环境隔离

  • 操作人员职责分离

  • 发布窗口管理

  • 回滚机制

  • 完整审计记录

这些需求已经超出了单纯构建代码的范围,因此大型组织通常会在 Jenkins 之上,再引入专门的 CD 编排平台。

七、平台级 CD:蓝鲸流水线扮演什么角色?

在大型组织中,发布管理往往不是某个项目组自己的脚本问题,而是一个平台级问题。

以腾讯蓝鲸流水线这类平台为例,它更关注企业范围内的发布治理,包括:

  • 发布审批

  • 多环境编排

  • 多主机协同部署

  • 分批发布

  • 发布暂停与继续

  • 失败回滚

  • 权限控制

  • 合规审计

这类平台并不是为了取代 Jenkins。

更合理的关系是:Jenkins 负责完成 CI,蓝鲸等 CD 平台消费 CI 产生的 Artifact,并负责将它安全地推广到生产环境。

一条典型的企业级交付链路可以表示为:

Git
  ↓
Jenkins(CI)
  ↓
制品仓库
  ↓
CD 发布平台
  ↓
生产环境

每个组件都有明确的职责边界。

Git

管理源代码和交付逻辑,是所有变更的事实来源。

Jenkins

执行构建和测试,生成经过验证的 Artifact。

制品仓库

保存不可变的 Artifact,并提供版本管理和稳定分发能力。

CD 平台

负责审批、环境编排、发布策略、权限控制和回滚。

生产环境

只接收经过验证和批准的确定版本,不在生产服务器上临时修改或重新构建代码。

这种分层设计可以避免让某一个工具承担过多职责,也能够显著降低发布流程中的不确定性。

八、为什么不建议在生产环境重新构建?

理解 Artifact 以后,也就能理解为什么不应该在生产服务器上执行 git pull 后直接构建。

因为这样做会产生几个问题:

  • 生产环境的构建结果可能与测试环境不同

  • 依赖仓库中的内容可能已经发生变化

  • 构建工具或系统环境可能不一致

  • 无法证明当前运行版本就是之前测试通过的版本

  • 回滚时可能无法重新得到完全相同的结果

更可靠的流程是:

  1. Jenkins 在受控环境中完成构建

  2. 自动化测试验证构建结果

  3. Artifact 上传到制品仓库

  4. CD 平台选择这个确定版本

  5. 将同一个 Artifact 部署到目标环境

这样,部署的对象就不再是“某个分支当前的代码”,而是“一个已经被验证过、拥有唯一版本号的构建产物”。

九、Java 与 Python:运行方式会影响交付设计

不同技术栈的运行方式不同,因此不能完全照搬同一套部署脚本。

以 Java 和 Python Web 应用为例,它们在生产环境中的运行模型就存在明显差异。

Java 应用

以常见的 Spring Boot 项目为例,构建完成后通常会生成一个可执行 JAR。

JAR 中可以包含 Tomcat、Jetty 或 Undertow 等嵌入式 Web Server,因此应用通常可以直接启动:

java -jar application.jar

在这种模式下,JAR 本身已经包含了应用运行所需的大部分内容,交付边界相对明确。

Python Web 应用

Python Web 框架提供的开发服务器通常不适合直接用于生产环境。

WSGI 应用一般需要通过 Gunicorn 等生产级服务器运行;如果是 ASGI 应用,则可能使用 Uvicorn、Daphne,或者 Gunicorn 配合相应 Worker。

例如,一个 Flask 应用可能通过下面的方式启动:

gunicorn app:app

因此,Python 项目的 Artifact 除了应用代码,还需要明确:

  • Python 版本

  • 依赖版本

  • WSGI 或 ASGI Server

  • Worker 数量

  • 启动命令

  • 超时配置

  • 环境变量

  • 日志输出方式

容器化可以统一这些运行条件,但它并不会自动消除技术栈之间的差异。

理解应用在生产环境中究竟如何运行,才能设计出正确的构建产物和部署方式。

十、一次完整发布应该能够回答什么?

一套成熟的 CI/CD 流程,最终应该能够清楚地回答以下问题:

这次发布来自哪个 Git Commit?
        ↓
由哪一次 Jenkins 构建生成?
        ↓
执行了哪些测试?
        ↓
产生了哪个 Artifact 版本?
        ↓
Artifact 保存在哪个制品仓库?
        ↓
谁批准了它进入生产环境?
        ↓
由哪个 CD 流程完成部署?
        ↓
生产环境当前运行的是哪个版本?
        ↓
如果失败,应该回滚到哪个版本?

如果这些问题都能快速获得明确答案,说明整条交付链路已经具备较好的可追踪性和可审计性。

反过来,如果生产环境中的代码只能通过登录服务器查看,或者必须依靠某个人回忆发布过程,就说明交付流程仍然存在较大的运维风险。

十一、我对 CI/CD 的最终理解

通过这个过程,我逐渐意识到,CI/CD 并不是把几个工具连接起来。

它真正设计的是一条可信的软件供应链。

在这条链路中:

  • Git 保证变更有来源

  • Jenkins 保证构建和测试可重复

  • Artifact 保证交付对象确定且不可变

  • 制品仓库保证版本可以长期保存和稳定分发

  • CD 平台保证生产发布受到控制

  • 审批和审计机制保证整个过程符合组织要求

当每个组件只负责自己最擅长的部分,整个系统才会变得稳定、清晰并且可治理。

这也解释了几个曾经让我困惑的问题:

  • 为什么不应该让 Jenkins 独自管理所有生产发布?

  • 为什么 Artifact 是 CI/CD 的中心?

  • 为什么银行和大型企业需要平台级的 CD 系统?

  • 为什么部署步骤背后还需要权限、审批和审计?

  • 为什么同一个 Artifact 应该在不同环境之间逐级推广?

最终,我对 CI/CD 的理解从“自动执行部署命令”,变成了:

通过可追踪、可重复、可验证和可回滚的流程,把一个明确的代码版本安全地交付到生产环境。

这才是现代 CI/CD 体系真正存在的原因。