"我们上了 CI/CD"是很多技术团队在介绍自己时的一句标配话术。但这句话背后的东西差别极大:有的团队只是让服务器自动拉了一次代码,有的团队能做到一天上线二十次、每次都能在五分钟内安全回滚。这两者中间隔着的不是工具,而是对流程的理解。
这篇文章不推销任何一款工具,而是把"从代码提交到服务器自动部署"这条链路完整拆开,讲清楚每个环节到底在解决什么问题、有哪些常见做法、哪些环节是必须的、哪些是可以后加的。你会看到 CI 和 CD 其实是两个不同的概念,CD 还是两个不同的缩写;你会看到多环境设计该怎么划边界,部署策略(滚动、蓝绿、金丝雀)分别适合什么业务,回滚到底该怎么做才叫"能回滚"。
读完之后,你应该能对着自己团队现在的做法,指出哪里缺了一环、哪一环是形式主义,以及下一步最该补的是什么。
一、先分清概念:CI、CD、又一个 CD
持续集成(Continuous Integration,CI)解决的是"代码合到一起会不会炸"的问题。核心动作是:开发者把代码提交到共享仓库后,自动触发一次构建和测试,尽早发现集成问题。它强调"持续",是因为传统做法是每周或每两周集中合并一次,那时候冲突和错误会一次性爆发,排查成本极高。CI 把这个动作变成每次提交都做,让问题出现的时刻和引入的时刻尽可能接近。
持续交付(Continuous Delivery)解决的是"代码能不能随时上线"的问题。它在 CI 的基础上要求:每次通过测试的构建,都产出可部署的制品,并且已经部署到类生产环境验证过,随时可以一键发布到生产。"随时可以"是关键词——发布与否是业务决策,而不是技术障碍。
持续部署(Continuous Deployment)则是把最后一脚也自动化了:通过所有检查就自动上生产,不需要人工点确认。这是最高级别的自动化,但只适合测试覆盖率高、可观测性强、且业务能接受的风险场景。多数企业实际停在持续交付这一层,保留人工发布按钮,这是一个合理的选择而不是落后。
三个概念的关系可以用一句话概括:CI 保证代码是可构建可测试的,持续交付保证它是可发布的,持续部署保证它是自动发布的。它们是一层层叠加的,跳过 CI 直接谈持续部署,等于在地基没打好的情况下盖楼。
二、把流程拆开:从提交到上线的六个环节
抛开工具,一次完整的自动化发布包含六个环节。理解这六个环节,比记住任何工具的名字都重要。
第一个是触发。代码提交到仓库后,怎么通知流水线开始工作。方式有 Webhook 推送、定时轮询、人工点击。生产上首选 Webhook,延迟最低。
第二个是构建。把源码变成可运行的产物:Java 编译打包成 jar,前端构建成静态文件,Go 直接编译成二进制。这一步的关键是"确定性"——同样的代码在任何机器上构建出同样的产物。所以构建应该在干净的、容器化的或至少是标准化的环境里进行。
第三个是测试。单元测试、集成测试、静态代码扫描都属于这一步。测试是流水线里最容易被敷衍的环节,很多团队因为测试跑得慢就关掉,结果流水线退化成一个自动打包器,失去了 CI 的意义。务实的做法是分层:单元测试必须跑且要快(几分钟内),集成测试和端到端测试按需跑。
第四个是制品管理。构建产物需要有一个统一的存放和版本管理地方,叫制品仓库。它的价值在于:部署时用的一定是"被测试和验证过的那一份"产物,而不是重新构建一份新的。这一点极其重要——如果测试用的是 A 产物、部署用的是现场重新构建的 B 产物,那这份测试的保证就是假的。
第五个是部署。把制品送到目标环境并让它运行起来。这里面藏着最多的工程细节:配置下发、进程重启、健康检查、流量切换。
第六个是验证与回滚。上线后要确认它真的работает,出问题要能快速退回去。没有回滚设计的部署不算完整。
| 环节 | 解决什么问题 | 常见做法 | 缺失后果 |
|---|---|---|---|
| 触发 | 什么时候开始 | Webhook / 轮询 / 手动 | 靠人记得点,容易漏 |
| 构建 | 源码变产物 | 容器内构建、工具链固定 | 本地能跑服务器不能跑 |
| 测试 | 变更是否安全 | 单元 + 集成分层 | 缺陷泄漏到生产 |
| 制品 | 部署哪一份 | 统一制品仓库 | 测的和部署的不是一份 |
| 部署 | 怎么送到目标 | 脚本 / 编排系统 | 人工操作易错 |
| 回滚 | 出错怎么办 | 版本切换 / 重新部署旧版 | 故障时间不可控 |
三、工具链怎么分工
工具不是越多越好,关键是每层职责不重叠。按职责看,常见工具可以分成四类。
代码托管与触发层:Git 仓库(自建或托管平台)是起点。GitLab 的好处是把仓库、CI、制品、议题都集成在一个系统里,配置跟着代码走;GitHub 类似。用它们时,CI 部分几乎零成本获得。
流水线执行层:负责跑构建和测试。Jenkins 的优势是灵活、插件多、能兼容各种历史遗留;GitLab CI / GitHub Actions 的优势是配置即代码、和仓库天然打通。选哪个主要看你代码在哪。
制品存放层:容器镜像用镜像仓库(如 Harbor、云厂商的容器镜像服务);非容器制品(jar、二进制、前端包)用通用制品仓库或对象存储。哪怕只是放一个目录、用文件名带版本号,也比没有强。
部署编排层:传统的做法是脚本加 SSH,中大型或容器化环境用 Kubernetes 加 ArgoCD 这类 GitOps 工具。ArgoCD 的核心思想是"集群状态以 Git 仓库里的声明为准",它持续对比集群实际状态和 Git 里声明的状态,不一致就拉回来。这样"回滚"就变成了"把 Git 里的版本号改回上一个提交",非常直观。
一个典型的组合是:代码在 GitLab,用 GitLab CI 跑构建测试并打包镜像,镜像推到镜像仓库,ArgoCD 监听 Git 里记录版本的清单文件,把新镜像部署到 Kubernetes。整条链路里没有一次人工登录服务器,所有变更可追溯。
把 ArgoCD 和 Jenkins 放在一起看会更清楚:Jenkins 擅长"执行一串动作",ArgoCD 擅长"维持期望状态"。前者是任务驱动,后者是状态驱动。状态驱动的系统天生更适合回滚和自愈。
四、多环境:dev / test / prod 的边界
几乎所有的部署事故都和多环境配置有关。多环境设计的核心问题只有一个:什么东西在不同环境间必须不同,什么东西必须相同。
必须相同的是制品本身——同一个镜像、同一个 jar,从测试环境一路走到生产,中间不允许重新构建。必须不同的是配置:数据库地址、第三方密钥、资源配额、日志级别。所以配置一定要外部化,不能编译进产物里。Java 生态里 Spring Boot 的 profile 机制、Node 里的环境变量、容器里的 ConfigMap 都是干这个的。
| 环境 | 主要用途 | 数据 | 发布频率 | 稳定性要求 |
|---|---|---|---|---|
| dev | 开发自测、联调 | 造数据,可随意重置 | 每次提交 | 低 |
| test | 功能/回归测试 | 脱敏的真实数据 | 每天多次 | 中 |
| staging | 发布前验收,尽量像生产 | 脱敏或影子数据 | 每次待发布 | 高 |
| prod | 对外服务 | 真实数据 | 受控 | 最高 |
staging(预发)环境是最容易被砍掉、又最容易出事的一环。它的价值在于用生产的配置和规模跑一遍,把"测试环境好好的,上生产就挂"这类问题提前暴露。如果资源有限,至少保证 staging 的配置结构和生产一致,哪怕机器少一半。
另一个常见错误是环境之间共用数据库。用同一套库让 dev 和 test 混跑,结果是测试数据污染开发、开发误删测试数据,最后谁都说不清数据的来源。每个环境独立数据库是最基本的纪律。
五、部署策略:滚动、蓝绿、金丝雀
部署策略解决的是"新版本怎么替换旧版本,才能把风险控制在可接受范围"。
滚动部署(Rolling Update)是默认做法:一批批地停旧实例、起新实例,直到全部替换。优点是资源占用少、不需要双倍机器;缺点是发布期间新旧版本同时存在,如果接口不兼容会出问题,而且出问题时回滚也要一批批来。用 Kubernetes 时调 maxSurge 和 maxUnavailable 就能控制节奏。
蓝绿部署(Blue-Green)维护两套完全一样的环境,旧版本(蓝)继续服务,新版本(绿)上线后先验证,确认没问题再把流量一次性切过去。优点是切换是原子的,回滚就是切回来,几乎瞬间;缺点是需要双倍的资源。适合发布必须"要么全上新、要么全不上"的业务。
金丝雀发布(Canary)先把一小部分流量(比如 5%)导到新版本,观察关键指标——错误率、延迟、业务转化——没问题再逐步放大比例。它比蓝绿更精细,能捕捉到"低频异常",但要求你必须有足够细的监控,否则放出去的 5% 出了事你也看不见。中小团队没有完善监控的话,金丝雀很容易变成走过场。
| 策略 | 资源开销 | 回滚速度 | 风险控制 | 适合场景 |
|---|---|---|---|---|
| 滚动 | 低 | 中等 | 一般 | 无状态服务、兼容性好的变更 |
| 蓝绿 | 高(约 2 倍) | 极快 | 好 | 关键业务、切换必须原子 |
| 金丝雀 | 中 | 快 | 最好 | 有成熟监控、流量可分层 |
六、回滚不能只靠"再发一次"
很多人理解的回滚是"发现问题后把代码改回去再部署一次",这不叫回滚,这叫抢修。真正的回滚设计包含三个要素。
第一,旧版本随时可用。也就是说上一个版本的制品必须还在制品仓库里,能直接拉起来。这就要求制品有明确的版本号和保留策略,不能只保留 latest。
第二,配置和代码一起回滚。线上故障的成因里,配置变更占的比例不比代码低,而且经常是配置变了代码没变。所以配置也要版本化,回滚时一起退。
第三,数据库变更要能兼容。这是回滚里最难的部分。如果新版本加了一列并删了旧列,回滚到旧版本时旧代码找不到它需要的列就会崩。通用做法是采用"扩展-迁移-收缩"的三步走:先加新列(新旧代码都能跑),发布新代码,稳定后再删旧列。这样任何一步回滚都是安全的。破坏性的数据库变更(删表、删列、改类型)一定要单独评审并单独执行。
一个可执行的建议:给每次部署打上版本标签,并把"回滚到上一版"做成流水线里的一个按钮或一条命令,而不是一份需要临场思考的操作手册。压力下的人会出错,脚本不会。
七、落地顺序
CI/CD 不是一个周末就能建成的工程,硬上只会得到一个没人用的流水线。推荐按下面的顺序推进,每一步都产生独立价值。
第一步,先做到自动化构建。把"从代码到产物"这件事从开发者本地搬到服务器上,消灭"我这能跑"的争论。这一步不需要测试,不需要部署。
第二步,加上单元测试和代码扫描。让流水线能拦住明显的问题,同时建立"红灯了就不许合并"的纪律。这一步最大的阻力通常是人的习惯,不是技术。
第三步,打通自动部署到测试环境。让开发者提交后能很快看到一个跑起来的新版本。
第四步,建立制品仓库和版本管理。让"部署的一定是测过的那一份"成为事实。
第五步,补生产发布和回滚。有了前面四步的积累,这一步才是安全的。
常见问题
Q:小团队只有两三个人,需要上 CI/CD 吗?
需要,但可以极简。一个能自动构建、自动部署到测试环境、并且有版本可回滚的脚本就足够。规模小的时候,重点不是工具,而是"每次发布都是同一条路径、都留下记录"这个纪律。
Q:流水线跑得太慢,开发者都不愿意等,怎么办?
先量化,看时间花在哪。常见的大头是依赖下载,解决办法是配本地镜像源和在流水线里缓存依赖目录。其次是测试本身慢,可以用并行、按影响范围选择测试子集。目标是主分支的快速反馈能控制在十分钟以内。
Q:容器化是不是 CI/CD 的前提?
不是,但它确实让构建和部署的一致性变好,因为"环境"被打包进了产物。传统方式用脚本加版本化制品一样能做,只是要花更多心思保证各环境一致。
Q:部署时新旧版本同时在线,接口不兼容怎么办?
接口变更要遵循向后兼容原则:加字段可以,删字段和改语义要分两次发布。这也是为什么滚动部署对兼容性有要求。如果实在无法兼容,就得用蓝绿。
Q:怎么说服老板投入时间做这些?
用数字沟通。统计当前一次发布要几个人、花多少时间、出过几次事故、平均恢复多久,再对比自动化之后的数字。多数团队算下来会发现,省下的人力几个月就能回本,更不用说故障减少的隐性收益。
总结
CI/CD 的本质是把"代码变更如何安全地到达用户"这件事标准化、自动化、可追溯。理解它只需要记住层次关系:CI 管代码能不能合,持续交付管能不能发,持续部署管发不发由机器决定。
落地时,六个环节缺一不可,其中制品管理和回滚设计最容易被低估,却直接决定了出问题时的损失大小。工具选择上,代码在哪就用哪家的流水线,容器化环境用 GitOps 类工具做状态管理,传统环境用脚本加版本化制品也完全可行。
多环境的边界要守住两条:制品完全相同,配置完全分离,绝不共用数据库。部署策略按风险和资源来选,没监控别硬上金丝雀。最后,回滚要当成一等公民来设计,把"退回去"做成一条命令,而不是一场需要临场发挥的抢修。
企业QQ咨询




