大家好,今天想聊一聊 CI/CD 。
刚开始接触部署时,我更多是在按照已有步骤操作:拉取代码、执行构建、上传文件,然后发布到服务器。
但随着实践逐渐深入,我开始从“照着步骤完成部署”,转向理解现代交付流水线为什么要这样设计。
从这个角度再看,CI/CD 就不再是一堆工具的组合,而是一套职责清晰、彼此衔接的软件交付体系。
这篇分享主要讨论两个问题:
代码是如何从 Git 一步步进入生产环境的?
流程中的每个组件为什么存在,它的职责又在哪里结束?
一、CI/CD 真正解决的是什么问题?
在真实的生产环境中,团队最关心的通常不是“代码能不能运行”。
代码能够在开发人员的电脑上运行,只是最基本的要求。真正进入生产环境时,我们还需要回答更多问题:
这个版本是谁构建的?
当前生产环境运行的是哪个 Git Commit?
这个版本是否经过了完整测试?
构建过程是否可以重复执行?
测试环境和生产环境使用的是不是同一个构建产物?
如果上线后出现问题,能否安全回滚?
谁批准了这次发布?
整个发布过程是否可以追溯和审计?
CI/CD 的价值,就是用一套标准化、可重复、可追踪的流程,持续回答这些问题。
因此,CI/CD 不只是为了“让部署更快”,也是为了让软件交付变得更加可靠和可控。

二、Git:整条交付链路的事实来源
Git 是整个交付体系的基础。
进入版本控制系统的不应该只有应用代码,还应该包括:
构建逻辑
测试规则
依赖配置
部署配置
流水线定义
这也是为什么越来越多团队会将 Jenkins Pipeline 定义在代码仓库里的 Jenkinsfile 中。
当交付逻辑也被纳入 Git 管理后,它就具备了和业务代码相同的工程属性:
可以进行代码评审
可以查看修改历史
可以回滚到之前的版本
可以追踪某项部署行为由哪次提交引入
可以让不同环境执行一致的流程
例如,当某次构建突然失败时,我们不仅可以检查业务代码发生了什么变化,也可以确认构建脚本、依赖版本或流水线配置是否被修改。
从这一刻开始,部署就不再是一套依赖个人经验的操作仪式,而是一项可以评审、验证和持续改进的工程活动。
三、Jenkins 与 CI:构建的不只是代码,更是信心
Jenkins 更适合被理解为一个 CI 引擎,而不只是一个“自动部署工具”。
它的核心职责通常包括:
从 Git 获取指定版本的代码
读取并执行仓库中的
Jenkinsfile安装依赖
编译或打包应用
执行自动化测试
进行代码质量或安全检查
生成确定性的构建结果
这里最重要的一点是: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 后直接构建。
因为这样做会产生几个问题:
生产环境的构建结果可能与测试环境不同
依赖仓库中的内容可能已经发生变化
构建工具或系统环境可能不一致
无法证明当前运行版本就是之前测试通过的版本
回滚时可能无法重新得到完全相同的结果
更可靠的流程是:
Jenkins 在受控环境中完成构建
自动化测试验证构建结果
Artifact 上传到制品仓库
CD 平台选择这个确定版本
将同一个 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 体系真正存在的原因。