云效 + Kubernetes:打造可回滚、可观测的DevOps发布流水线
2026/9/6 17:18:06 网站建设 项目流程

简介:《郑云龙-云效-构建基于Kubernetes的DevOps工作流》PDF是一份面向运维开发、平台工程和云计算实践者的技术分享材料。内容以云效为切入点,结合阿里在规模化交付中的真实经验,完整讲解在Kubernetes环境下设计DevOps工作流的方法,包括容器镜像构建、持续集成、代码扫描、制品管理、Helm模板渲染、环境发布与稳定回滚等环节。全文采用图文结合方式,给出从拆分应用仓库、编写Dockerfile与Helm chart,到利用流水线完成自动发布的可操作性路径。资源为单个PDF文件,大小约3.5MB,便于在桌面端或移动端阅读;目前已获得97人次学习,尤其适合已了解DevOps与Kubernetes基础、希望落地云效CI/CD平台或优化发布流程的读者。穿插的架构图、流程图和配置示例能帮助读者快速定位团队在DevOps成熟度中的位置,并据此规划循序渐进的建设节奏,最终建立统一、透明、可审计的交付体系。 我们团队大概是被那场周五晚上的发布事故彻底打醒的。当时服务还跑在几台云主机上,发版靠 ansible 脚本滚动重启,那天脚本跑到一半,新包的解压目录权限写错了,服务起不来,回滚脚本又因为旧包已经被覆盖根本没得退。全组人对着终端干瞪眼到十一点,最后从一个磁盘快照里把旧代码翻出来才勉强恢复。那次之后我认了一件事——交付这件事,靠“服务器 + 脚本 + 人盯人”的模式已经到头了,必须让基础设施和发布流程彻底换个玩法。

刚好那段时间我们在调研阿里云的云效平台,配合 Kubernetes 做应用编排。我的首要目标不是赶时髦,而是把“从代码提交到生产可用”整条链路做成一条有卡点、能回滚、可观测的流水线。折腾了几个月,工作流真正跑起来之后,最大的感受是:Kubernetes 解决的是“部署到哪儿、跑成什么样”,云效解决的是“怎么把代码变成正在运行的 Pod”,这两件事单独拿出来都不难,难的是把研发流程、镜像构建、环境管理和集群操作串成一个整体。这篇就完整复盘一下我们是怎么设计、怎么落地、又踩了哪些坑的。

1. 为什么是 Kubernetes:一次发布事故逼出的选择

1.1 传统脚本发布治标不治本

先说说很多团队还在用的老路子。代码在 GitLab 上,Jenkins 拉下来跑打包,打完包 scp 到服务器,再执行一段部署脚本。这套东西看起来自动化了,但其实处处埋雷:

  • 依赖环境不可复现:每台服务器的 JDK、Nginx、系统库版本都是“历史遗留问题”,新机器装机后要对半天版本,环境漂移严重。
  • 回滚靠运气:脚本部署普遍是“覆盖式”更新,旧版本要么没备份,要么备份位置五花八门,出问题只能临时翻日志找原因。
  • 发布窗口靠人肉:没有滚动、没有灰度,脚本转发到一半如果进程没起来,整个服务就是中断状态,所有流量直接打到失败页面上。

那场事故的根因差不多就是这几条叠加。所以当时我们内部定了个原则:不解决环境一致性和可回滚性,就别谈什么发布效率。容器化和 Kubernetes 恰好就是把这两个问题从“运维纪律问题”变成“平台保障能力”。

1.2 Kubernetes 的核心价值不是“容器编排”四个字

很多人一提 Kubernetes 就想到一堆概念——Pod、Deployment、Service、Ingress,容易劝退。我从实践角度重新翻译一下:

  • 声明式运维:你告诉集群“这个服务要 3 个副本,镜像版本是 v1.2.3”,剩下的事 Kubernetes 自己处理。它会把当前状态持续拉向期望状态,Pod 挂了自动拉起,节点挂了自动调度到别的机器,这些都不需要你再写“守护脚本”了。
  • 滚动发布与回滚是内置能力:Deployment 支持滚动更新,新版本起不来的时候自动停住;一条kubectl rollout undo就能回到上一个版本。这和脚本年代“删旧包放新包”是完全不同的体验。
  • 环境一致性是天然的:镜像把代码、运行时、依赖全部打包,同一个镜像在开发环境怎么跑,在生产就怎么跑。

单靠这三点,Kubernetes 就已经值得引入。但这里要泼一盆冷水:Kubernetes 只是把“部署”这件事做标准了,它并不负责“你的代码怎么变成镜像”“镜像怎么按流程推进到生产环境”“谁有权限用什么方式发布”。这些恰恰是 DevOps 工作流要解决的。

1.3 为什么选择云效而不是纯自建工具链

这个选择我们内部确实争论过。Jenkins + GitLab CI + Spinnaker 这套组合确实是经典方案,但维护成本不低,尤其是 Spinnaker,光是吃透 Halyard 的配置就够喝一壶。云效的优势在于它是阿里云的一站式 DevOps 平台,代码托管、流水线、制品仓库、Kubernetes 发布一体的,不用自己拼装太多组件。

当然,技术选型没有绝对的标准答案。我见过用 GitLab CI + Argo CD 玩得很溜的团队,也见过云效一个大平台全覆盖的团队。对我们这种几十人的研发队伍,云效最大的价值是开箱即用:创建一条流水线,左边接代码仓库,中间跑构建和测试,右边直接连 Kubernetes 集群执行部署,整个闭环不需要自己去造轮子,也不需要备一台专门的 Jenkins master 机器。后面这些实战内容我都基于云效来讲,但整体思路放到任何 CI/CD 工具上都通用。

2. 云效在 Kubernetes 工作流中的真实定位:不止是 CI

2.1 先理清一条流水线里谁干什么

我在不少分享里看到有人把 DevOps 工作流画成一坨概念图,代码、构建、测试、部署全部搅在一起。实践中我更建议大家按“四段式”来划分职责:

  1. 代码阶段:分支策略、MR 或 PR 的评审、静态扫描。这一段的产出是“一个经过确认的代码版本”。
  2. 构建阶段:编译、跑单测、构建镜像、推送到制品仓库。这一段的核心产出是“一个不可变的镜像地址”。
  3. 部署前置阶段:修改 ConfigMap/Secret、更新 Deployment 镜像、等待滚动发布完成、执行冒烟用例。
  4. 发布后阶段:观察日志、监控指标、告警,必要时执行回滚。

云效在这个模型里做得比较聪明的点,是它把这些阶段都串在一条流水线里,同时每个阶段都能设卡点。代码扫描不合格就不允许往下走,测试覆盖率不达标就不推送镜像,staging 环境的冒烟测试不过就不给你发起生产发布的按钮。工作流的“流程感”在这里体现得很明显。

2.2 核心链路:代码、镜像、集群三者的衔接

云效基于 Kubernetes 的部署,本质上还是要回答一个问题:“这个服务的最新镜像在哪,我要把它跑到哪个环境里去?”

我们用的标准衔接方式如下:

  • 云效代码管理 Codeup 作为 Git 仓库,和阿里云容器镜像服务 ACR 打通。流水线构建完成后,直接把镜像推到 ACR 的对应仓库下。
  • 镜像地址里必须带版本信息,我们约定是服务名+分支或tag+commit短哈希+构建时间戳,例如:
    registry.cn-hangzhou.aliyuncs.com/xxx/order-service:feature-order-3f2a9c1-20240117153000
    为什么要带 commit 短哈希和时间戳?因为要保证每个镜像“可追溯、不可变”。同一个 commit 如果构建两次,时间戳能区分出最后一次是什么时候构建的。这是一个很小的规范,但后续排查问题时能省下大量时间。
  • 部署阶段,云效流水线通过 kubectl 或者 Helm 把 Deployment 里面的镜像地址更新掉,然后跟踪 rollout 状态。这一步可以在云效的“Kubernetes 发布”任务里直接配置。

2.3 一个最小可用的流水线配置参考

云效流水线既支持图形化编排,也支持 YAML 方式定义。我拿其中一条核心服务的流水线配置文件做示例,节选关键部分:

stages: build_and_test: jobs: - build: steps: - mvn: clean package - scan: sonar-scanner build_image: jobs: - build_image: steps: - docker_build: image: registry.cn-hangzhou.aliyuncs.com/xxx/order-service:${BRANCH}-${COMMIT_ID}-${BUILD_TIME} dockerfile: Dockerfile - docker_push: registry: registry.cn-hangzhou.aliyuncs.com repo: xxx/order-service deploy_staging: jobs: - deploy: steps: - set_image: cluster: staging-cluster namespace: staging deployment: order-service image: ${IMAGE_ADDRESS} - rollout_status: cluster: staging-cluster namespace: staging deployment: order-service - smoke_test: api: https://staging.example.com/health expect: 200

这套配置的目的是:构建和测试不通过,后面什么都不发生;staging 部署和冒烟通过后,才轮到人工确认去点“生产发布”。

提示:云效流水线里可以直接读取上一次构建的 IMAGE_ADDRESS,也可以把构建和发布拆成两条流水线,通过制品触发。我们最终用的是“一条主干流水线 + 一条发布流水线”的方式,代码提交触发主干,生产发布由人工在发布流水线上触发,这样权限边界更清晰。

3. 一套能落地的端到端工作流长什么样

3.1 分支策略和镜像规范是源头

聊工作流之前,必须先定分支模型,因为后面所有卡点都建立在“哪个分支对应哪个环境”的基础上。我们采用的是精简版的 Git Flow:

  • master(主干分支):始终和生产环境代码保持一致。任何合并都需要 MR 评审。
  • release/vX.Y.Z:发布分支,从主干切出,用于上线前最后修修补补。
  • feature/xxx:日常开发分支,从 master 或 release 切出。
  • hotfix/xxx:紧急修复分支。

环境对应关系:

环境对应分支部署触发方式用途
devfeature/* 合并到 dev 分支云效定时或手动触发开发自测、联调
testdev 合并到 test 分支代码提交后自动触发测试团队验证
stagingrelease/vX.Y.Z手动触发模拟生产环境验收
prodmaster手动触发 + 卡点审批正式生产发布

这个模型比较常规,但它解决了两个很实际的问题:第一,不同环境的代码版本可预期;第二,发布分支在 staging 验证通过后,合并回 master 再发布,生产出问题可以快速回退到上一个 release 版本。

3.2 环境之间如何隔离

在单集群多环境的情况下,最忌讳的就是环境之间互相干扰。我们用的是“命名空间 + 标签”的隔离策略:

  • 每个环境一个 Namespace:devteststagingprod
  • 通过资源配额(ResourceQuota)限制每个环境的 CPU、内存上限,避免测试环境把集群资源吃光。
  • 生产 Namespace 上设置网络策略(NetworkPolicy),只允许来自 Ingress 网关的流量访问服务,测试环境和开发环境网络完全不互通。

这里提一个容易忽略的点:Kubernetes 的命名空间隔离不是安全隔离,只是逻辑隔离。真正要做严格隔离,还是要靠网络策略、RBAC 和节点池隔离。我们早期把生产服务和测试服务放在同一个节点池,高峰期测试服务的资源占用波动甚至拖慢过生产的 Pod 调度,后来给生产服务单独划了节点池才算彻底消停。

3.3 从提交到生产发布:全流程串一遍

下面是我们最核心业务服务一天的真实发布路径,我按步骤拆开讲。

第一步:开发提交代码到 feature 分支。

开发在本地写完代码,提交并推送远程,发起 MR 到 dev 分支。MR 通过后,云效自动触发 dev 环境的流水线。这一步主要做三件事:

  • 单元测试和集成测试。
  • 静态代码扫描(SonarQube),覆盖率不达标会直接挂掉。
  • 构建镜像并推到 ACR 的 dev 仓库。

dev 环境流水线的部署目标固定是namespace: dev下的对应服务。开发看到流水线全绿后,就能把 dev 环境的服务拉起来自测。

第二步:合并到 test 分支,进入自动测试。

测试团队不会去专门等开发口头通知“可以测了”。dev 验证通过后,开发把代码 MR 到 test 分支,云效流水线自动触发:

  • 构建 test 环境的镜像。
  • 部署到namespace: test
  • 执行一轮冒烟测试脚本,包括核心接口健康检查、关键页面可达性测试。

冒烟失败,流水线直接红色,测试不会看到一个“半成品”环境。这里我特别强调一下,冒烟测试脚本越早写越好,哪怕就三个接口,也比全手工点击强百倍。我们早期偷懒没写冒烟测试,结果有好几次镜像推上去了,部署也成功,但服务一启动就 OOM,全靠测试人员点开页面发现白屏才知道。

第三步:从 test 到 staging,接近生产的最后一道关。

staging 环境的标准很高:

  • 必须用 release 分支的代码,而不是 developer 分支随手合并。
  • 流水线里会执行完整的自动化回归测试(大概耗时二十几分钟)。
  • 部署完成后,测试团队按验收清单走一遍。
  • 验收过了,才轮到负责人在云效上点击“提交生产发布申请”。

这步的人工卡点非常重要。自动化能保证流程不跑偏,但不能保证产品真的该上。一个“技术上是正确”的版本,如果产品评审没通过,照样不该进生产。

第四步:生产发布与压测验证。

生产发布的流水线任务里,我们设置了:

  1. 更新 Deployment 镜像到新版本。
  2. rollout status等待发布完成。
  3. 通过 Ingress 网关跑到一个专门的灰度 Header 接口做预检。
  4. 如果失败,自动触发rollout undo,并推送告警到钉钉群。

这个流程跑通后,最直观的改善是,发布期间不再需要所有研发围在电脑前祈祷。流水线自己会盯着副本滚动、自己会判断健康检查是否通过、自己会回滚。人只有在回滚也失败的时候才需要介入。

3.4 质量门禁怎么设置才不惹人烦

质量门禁是最容易做“形而上”的环节。我们在实践迭代后保留了几条“不拖速度、确实有效”的卡点:

  • MR 必须单人通过评审,重大改动必须两人。
  • 单元测试覆盖率不低于 70%,这个数值我们试过 80%,日常开发摩擦太大,会催生大量“为覆盖率而写的假测试”。
  • 所有环境部署前必须镜像扫描通过,高危漏洞直接拦截。
  • 生产发布前必须 staging 环境冒烟通过

有一条我们放弃了:全量静态扫描不通过就不给构建。因为不同历史代码的存量问题太多,一卡全卡,流水线变成天天和规则较劲。后面改为“新增代码必须零新增高危问题”,团队更容易接受,实际效果也更好。门禁的本质是协作契约,不是行政考核,这一点一定要想明白。

4. 跑通之后,最容易翻车的几个环节

工作流设计得再漂亮,落地时总会遇到一些文档里不会写明白的坑。我挑几个我们真实踩过的,按“现象 → 原因 → 处理”来展开。

4.1 部署权限怎么管才安全又不麻烦

Kubernetes 的发布权限如果直接给研发开 kubeconfig,等于把所有资产都暴露了。我们的做法是:

  • 云效流水线用单独的 ServiceAccount 连接 Kubernetes 集群,而不是用个人账号。
  • 这个 ServiceAccount 只授予所需命名空间下的 Deployment、Service、ConfigMap 等必要权限,用 RBAC 严格限定。
  • 生产集群和测试集群完全分离,生产集群的 kubeconfig 只存放于云效的私密配置里,研发本地不出现生产集群的凭证。

这里要提醒一个细节:不要把生产集群的 kubeconfig 放在代码仓库或配置中心里。我们之前有同事图方便,把 staging 集群的 kubeconfig 提交到了 Git 仓库,虽然仓库是私有的,但这属于习惯性问题,一旦仓库泄露,整个集群就暴露了。正确的做法是存在专门的密钥管理服务,或者交给云效这类平台的私有变量来托管。

关于权限还有一个容易被忽略的点:Kubernetes 的 RBAC 是按“用户/ServiceAccount + 角色绑定”来控制的,但如果你的云效账号体系没有和集群的认证打通,发布权限就还是会落到“谁有 kubeconfig 谁就能发布”的尴尬局面。所以我们后来通过云效的 Kubernetes 连接配置来统一管,不再依赖人肉分发 kubeconfig,权限回收和授予都在这一个入口上完成。

4.2 配置管理和敏感信息处理

环境之间的差异,应该用 ConfigMap 和 Secret 来表达,而不是在代码仓库里写死。我们的约定:

  • 非敏感的配置放 ConfigMap,例如日志级别、开关配置。
  • 敏感的凭据放 Secret,例如数据库密码、第三方 API Key。
  • 同一个服务在不同命名空间下有独立的 ConfigMap 和 Secret,环境切换时通过流水线参数选择。

使用过程中有两点实际经验:

第一,修改 ConfigMap 不会自动触发 Pod 滚动重启。有些团队改完配置以为服务已经生效,结果还是旧配置。我们自己后来写了一个小工具或直接在流水线里加了一段命令,发现 ConfigMap 变更后自动执行kubectl rollout restart deployment/xxx。如果你不想引入外部工具,最简单的做法是给 Deployment 加一个config-hash注解,配置变化时更新这个注解来触发滚动。

第二,Secret 不要直接明文写进 YAML 文件。云效的流水线支持变量引用,密码这些应该放在流水线的私密变量里,部署时通过envFromsecretRef注入到 Pod。我们早期图省事,把测试库密码写在部署 YAML 的注释旁边,当时没出事,但回头想想纯属侥幸。

4.3 优雅停机做不好,发布必伴随报错

很多人第一次做 Kubernetes 滚动发布,会误以为“新 Pod Ready 了,旧 Pod 被删掉,就万事大吉”。但实际生产中,连接池、消息队列消费者、HTTP Keep-Alive 连接这些“存量连接”不会瞬间消失。如果你的服务不做优雅停机,滚动发布期间就会持续出现“connect reset”之类的报错。

要做到比较优雅的停机,我们需要三件事配合:

  1. 就绪探针(readinessProbe):让新 Pod 真正能处理请求后才接入流量。
  2. preStop Hook:在 Pod 被终止前先停止接收新流量,再等存量请求处理完。
  3. terminationGracePeriodSeconds:给进程足够的清理时间。

示例配置:

readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 10 periodSeconds: 5 lifecycle: preStop: exec: command: - sh - -c - "sleep 10" terminationGracePeriodSeconds: 30

preStop 里的 sleep 10 是给负载均衡器和网关一个“摘除节点”的时间窗口。如果进程一收到 SIGTERM 就马上退出,接入层还没来得及把节点标记为不可用,流量依然会打过来,就会造成 502。这些细节看着小,但在发布频率高起来之后,对可用性的影响会被成倍放大。

4.4 镜像仓库只进不出,迟早出问题

镜像构建这条链路还有个很少有人提前规划的坑:ACR 上的镜像越堆越多,仓库越来越大,最终把构建和拉取速度拖慢,甚至超过限额

我们的做法:

  • 按环境设置镜像保留策略,dev/test 环境只保留最近 10 天或最近 50 个镜像。
  • release 和 master 的镜像不限制数量,因为这些是生产可追溯的交付物。
  • 流水线中选择“构建完成后顺手清理本地构建缓存”,避免节点磁盘被撑爆。

镜像清理一定要坚持执行。我们之前就因为测试环境镜像太多,导致拉取镜像时客户端反复重试,部署过程从五分钟拖到二十分钟。这属于典型的不注意卫生引发的潜伏问题。

5. 工作流跑通之后,还能往哪走

5.1 灰度发布与流量治理

Kubernetes 自带的滚动发布只能解决“分钟级可用性”,对于更精细的“把 5% 流量切到新版本,观察半小时再放量”这类需求,需要引入流量治理能力。

目前在 Kubernetes 生态里比较常见的是 Service Mesh 方案(比如 Istio)或者 Ingress 网关的灰度插件。云效本身也支持和金丝雀发布结合的应用发布方式,可以在发布策略里按比例调节新旧版本流量。如果团队刚起步,我会建议先别急着上 Service Mesh,用云效自带的能力做“按实例数量灰度”,比如先部署一个新版本 Pod,验证没问题后再逐步把旧版本缩容掉,至少能解决 90% 的场景。

5.2 GitOps 化的方向

工作流跑顺之后,下一步可以考虑把“部署状态”也纳入 Git 管理,也就是 GitOps 的思路。用 Git 仓库作为声明的唯一来源,集群内由 Operator 持续同步仓库里的期望状态到实际集群。

云效也支持把 Kubernetes 的部署配置放到代码仓库里,流水线通过改仓库里的 YAML 来触发同步。这样做的好处是,每一次生产变更都是一次代码提交,天然带审计记录,也天然可以回退。我们试过把部署配置迁移到 GitOps 风格之后,生产发布的可追溯性增强了一个量级,“谁在几点改了什么”清清楚楚。

5.3 可观测性建设是最后一块拼图

发布会了、回滚也自动了,但如果不解决“服务是否健康”的观测问题,一切还是白搭。我们最终的方案是四件套:

  • Prometheus 采集指标,Grafana 展示面板。
  • Loki 或同类工具做日志聚合,按应用、环境、TraceID 检索。
  • 微服务调用链用 OpenTelemetry 接入,配合云效的发布记录可以做到“发布后实时对比错误率、延迟变化”。
  • 钉钉群告警:发布成功、发布失败、发布后错误率超过阈值、服务不可用,分别推送到不同的群。

可观测性建好之后,Kubernetes + DevOps 工作流才算闭环。发布不再是一个“执行动作”,而是“验证假设的过程”,每次发版都有数据告诉你这次变更到底是变好了还是变坏了。

从我自己踩过的那场事故到现在,团队发布这件事的变化是脱胎换骨的。最大的体会是,DevOps 工作流设计的核心不是工具堆得有多高,而是把每一次变更都变成“可控、可观测、可回退”的常态化操作。如果你也在折腾 Kubernetes 和云效,建议别急着把所有功能铺开,先把一条核心服务的链路走通,把权限、清理、优雅停机这些容易忽略的细节补上,再逐步铺到整个团队。这套流程真正稳定跑起来之后,你会发现发布这件事,终于可以不用再靠祈祷了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询