先说我自己的结论:对于一个跑了两年的 Kubernetes 集群,我们最后没有选择“继续优化 this cluster”的路线,而是把 80% 的服务迁到了 Serverless 体系上。账单从迁移前平均每月两万多降到了六千左右,CPU 和内存利用率也基本贴到了实际用量上。最直接的感受是,以前每天至少有两个小时在处理集群和基础设施的事,现在基本只剩下看监控和偶尔调调并发参数。这篇文章把这套动作拆开讲:钱省在哪、运维工作量省在哪、迁移过程里哪些坑值得留意,以及什么样的服务千万别盲目迁出去。
我不会把所有东西打包成一条“方案”,因为不同场景差别很大。我只把我们实际做的事情、拿到手的数据、踩过的坑,以及最后形成的取舍原则整理出来。你要是正在 K8s 集群里被 Node 升级、Ingress 权限、容量规划搞得心烦,这篇文章应该能给你一个比较清晰的参考。
1. 先算清楚“成本”和“运维”到底是什么
1.1 账单为什么那么贵
我们原来的 K8s 集群是三台 8C16G 的云主机,加一台 4C8G 的 Node 做系统组件和 Ingress Controller,这套固定资源每个月大约 8800 块。再加上云盘、负载均衡、公网 IP、NAT 网关、容器镜像仓库、日志存储,杂七杂八下来就上万了。可实际上,我们大多数服务在深夜和周末根本没多少流量,CPU 长期在 5% 到 15% 之间波动。也就是说,钱花在了“随时待命”的物理资源上,而不是真正处理的请求上。
成本问题不只是“资源买多了”这么简单,K8s 集群天然会把很多隐性成本绑在一起:节点必须跨可用区部署、每台 Node 要留系统预留资源、各类 DaemonSet 要把 CPU 吃掉一截、一旦节点数不够还得再加一台物理机。你为了高可用买三台,结果每台上面 40% 的资源都在跑基础组件和空转。想只按业务请求量付费?传统 K8s 做不到,因为你买的是 Node,不是请求。
1.2 运维工作量到底在哪
我有意识地记录过一周的运维时间。不算业务开发,只算基础设施,这周大概花在下面这些事上:
- 证书还有两周到期,更新 Ingress TLS Secret 并重新加载
- 集群有节点内存水位到了 85%,查是哪个 Pod 在内存泄漏
- 登录到某台 Node,把系统磁盘占满的日志文件清掉
- 升级集群小版本,结果 Node 上的容器运行时版本和集群版本不匹配,重新排空节点
- 处理监控面板里持续报的
CrashLoopBackOff,发现是存活探针时间太短,业务日志和探针冲突 - 新服务要接入,改 Ingress 规则、配证书、配限流、配灰度发布标签
这些工作纯靠经验堆,但和业务本身一点关系都没有。它们不产生功能,只是为了维持集群的“正常状态”。更可怕的是,这类工作往往没有明确终点:你永远不知道下一个节点磁盘什么时候满,年久失修的 RBAC 什么时候又出问题。运维工作量的本质不是“操作多”,而是“不确定性高”。
1.3 我们定下的迁移目标
到了去年年中,我们决定不再继续“养”集群了。目标很简单:把无状态的业务服务迁到按调用计费的 Serverless 平台上,保留极少部分必须靠近状态或需要灵活调度的负载留在 K8s。这个目标不是拍脑袋,而是基于两个判断:
- 我们的核心业务指标是“每百万请求的固定成本”,不是“集群资源利用率”。只要请求高峰能被 Serverless 快速扩展,低峰期零实例也是可以的。
- 团队人数没有增长,继续维护一套 K8s 集群只会越来越吃力。我们希望基础设施的复杂度是“云厂商直接提供的”,而不是自己还要去搭一套调度系统。
换句话说,我们不是认为 Kubernetes 不好,而是认为“日常维护这套系统”这件事的收益已经不如改成 Serverless 的收益高。真正触发这次迁移的,是成本和运维两个维度同时亮红灯。
2. Serverless 方案选型:为什么是容器形态而不是函数计算
2.1 平台方案对比
Serverless 这个词很宽泛,市面上至少有两类形态。一类是函数计算,例如 AWS Lambda、阿里云函数计算;另一类是容器式 Serverless,例如 Knative、阿里云 SAE、Google Cloud Run、AWS Fargate。我们最终选的是容器式,核心原因是不想让团队改代码。
函数计算很有诱惑力,它把计费粒度切到了毫秒级,冷启动也可以控制在百毫秒级别。但问题在于:我们的项目里存在大量传统 Web 服务,依赖 Spring Boot 的整体启动方式,函数计算的运行模型要求把请求入口拆成 handler。要把几十个服务都改造成函数事件模型,工作量非常大。我们想要的不是用新技术重写业务,而是把原有容器镜像原封不动地跑起来。
Knative 的模式我很喜欢:它本质是一个安装在 Kubernetes 之上的服务层,但通过Revision、Service和自动伸缩把“容器实例“变成了可以按需拉起、缩到零的抽象。Knative 仍然依赖 K8s,但用户不需要管节点。如果云厂商直接提供兼容 Knative 的托管服务,那更省心。我们最后用的是兼容 Knative 的托管容器平台,不用自己装 K8s,也不用给 Node 打补丁。
2.2 为什么不用“托管版 Kubernetes”
有人会问,明明有托管版 K8s,为什么还要迁出去?我用过托管版,它确实帮你解决了 Master 节点和控制面的问题,但 Worker 节点还是要你自己准备、自己升级、自己设置自动伸缩。更关键的是,托管版 K8s 的计费模式依然按节点算钱,并不会因为服务没有请求就把节点资源“归零”。如果你有几十个 Deployment,且配置了replicas: 2,那么白天黑夜这些容器都会占着资源。托管版只是帮你省了一部分控制面运维,业务扩容和资源优化还是得自己动手。
Serverless 容器平台解决的是“业务实例生命周期”的问题。低峰期可以把副本数缩到 0,高峰期系统按并发自动扩容。我们不再需要提前估算峰值,不用维护节点的cluster-autoscaler配置,也不用关心节点加入集群后需要拉取多少镜像。实例是即时创建的,用完即销毁。
2.3 目标架构和能力要求
我们给目标平台列了一份需求清单:
- 支持原有容器镜像,不强制重写代码
- 支持实例数缩到 0,能按 HTTP 请求并发自动扩容
- 提供灰度发布和快速回滚
- 提供域名接入和 HTTPS
- 支持日志从标准输出采集,保留原有字段解析
- 可以配置并发上限,防止单实例被打爆
- 计费粒度至少到秒,最好按“vCPU/GB 秒”计费,而不是按整个实例存在时间计费
最终确定用容器式 Serverless,是因为它同时满足以上全部要求。云端底层是否在跑 K8s 其实无所谓,我们关注的是它的 API 是不是足够的声明式。在实际操作中,我们甚至可以把原有的 K8s Deployment YAML 稍作字段调整,直接映射到 Serverless Service 上。
2.4 哪些服务适合迁,哪些不适合迁
我们做了一个迁移适合度评估,主要看四个维度:是否无状态、是否对冷启动敏感、是否持有长连接、是否依赖固定 IP 或主机名。
| 评估维度 | 适合迁 | 不适合迁 |
|---|---|---|
| 状态性 | 完全无状态,文件系统可随时丢弃 | 依赖本地磁盘缓存,或写入本地文件 |
| 冷启动 | 允许请求在 1~3 秒内响应 | 要求极低延迟,必须常驻实例 |
| 长连接 | HTTP 短连接为主,无 WebSocket | 大量 WebSocket 或私有协议长连接 |
| 网络 | 能接受实例 IP 变化 | 被运营商/合作方要求固定 IP 白名单 |
| 依赖 | 数据库/缓存都在外部 | 需要直接访问集群内服务发现 |
最后只有少量服务不适合迁:一个自建的 WebSocket 推送网关、一个需要固定出口 IP 对接银行接口的后台任务、一个需要在 GPU 上跑批处理模块。它们继续留在原 K8s 集群里,不过我们把 Node 数量压缩到了两台,同时把资源预留调低。这个决定很值,它让我们不用为了极少服务去扛整集群的复杂度。
3. 实操过程:Nginx / Spring Boot 服务平迁到 Serverless
3.1 从 K8s Deployment 映射到 Serverless Service
以我们一个 Nginx 静态资源服务和两个 Java 服务为例,迁移过程不用改代码,但需要把 YAML 重新梳理一遍。原来 Deployment 大概长这样:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-static spec: replicas: 3 selector: matchLabels: app: nginx-static template: metadata: labels: app: nginx-static spec: containers: - name: nginx image: registry.example.com/nginx-static:v1.2.0 ports: - containerPort: 80 env: - name: LOG_LEVEL value: "info"迁移到 Serverless Service 后,很多字段可以保留,但几个关键字段需要换掉:
- 去掉
replicas,不再手动指定副本数 - 去掉
selector和template嵌套,服务编排逻辑交给平台 - 增加
minReplicas和maxReplicas配置 - 增加
containerConcurrency参数,控制单个实例的最大并发数
改造后的配置大致是这样:
apiVersion: serving.example.com/v1 kind: Service metadata: name: nginx-static spec: template: spec: containerConcurrency: 20 timeoutSeconds: 300 containers: - name: nginx image: registry.example.com/nginx-static:v1.2.0 env: - name: LOG_LEVEL value: "info" resources: requests: cpu: 0.25 memory: 256Mi limits: cpu: 1 memory: 512MicontainerConcurrency: 20是我们要特别说明的一个参数。它不是 QPS 上限,而是“单实例同时处理的请求数上限”。比如 Nginx 服务每个请求平均耗时 50ms,单实例并发 20 时,理论上可以支撑大约 20 / 0.05 = 400 QPS。如果流量到了 800 QPS,平台会自动扩展到至少 2 个实例。设置这个值的核心目的不是限制系统上限,而是防止单实例因为太多并发请求导致内存或 CPU 被压垮。
3.2 自动伸缩参数的实际调整过程
第一次迁移时,我们把大多数服务直接设成minReplicas: 0,想体验一把“不跑就零成本”的爽感。结果凌晨有大促活动,流量像潮水一样涌过来,系统在几秒内从 0 扩展到 20 个实例。那几十秒里,部分请求因为实例还没 Ready 返回了 503。
那之后我们学会了一个原则:线上核心服务不要从 0 开始,至少设置minReplicas: 1或2。如果你对成本敏感,可以把这些常驻实例的规格调低,比如requests.cpu=0.1。这里有一个计算技巧:
假设你的服务平均并发是 10,平台默认的扩容参考目标是containerConcurrency的 70%。如果containerConcurrency=20,那么当一个实例的并发达到 14 时,系统就会创建第二个实例。我们有时候希望扩展更敏感,就把这个系数调低,比如 50%,这样到了并发 10 就开始扩容。扩展的触发是“并发”,不是“CPU 使用率”,所以把 CPU 请求值设定得很低不会影响到扩容能力。
关于缩容,一定要对“缩到零”这件事保持敬畏。不是所有服务都适合 0 实例。内部管理后台、定时任务调度入口、对接第三方回调的服务,都不适合缩到 0。因为它们可能在一个没有请求的时间段里,突然收到一个回调或健康检查请求,冷启动那一两秒会造成明显延迟。我们最后的策略是:对外 API 和官网级静态服务允许缩到 0,内部服务一律保留 1 个常驻实例。
3.3 公网入口和 HTTPS 的改造
迁移后不需要再自己维护 Ingress Controller 了。平台一般会提供一个默认网关,你只需要创建一条域名映射,例如:
- 服务名:
nginx-static - 域名:
static.example.com - HTTPS 证书:在平台控制台上传或关联已有的证书
这里有一个容易忽略的坑:如果你在原来的 K8s 集群里配过nginx.ingress.kubernetes.io/proxy-body-size这类 Nginx 特有注解,迁移到 Serverless 平台后不一定生效。平台网关对请求体的默认限制通常较小(我们遇到的默认是 1MB)。如果你有上传文件接口,必须确认平台网关是否允许修改body size或挂载自定义网关配置。我们有一个接口需要上传 10MB 的附件,上线后才发现,好在这类问题可以通过平台的路由配置快速修复。
另外,原来在 K8s 里可以直接用 Service 暴露多个端口,比如 Nginx 同时监听 80 和 9090,其中 9090 是内部监控端口。Serverless 平台一般只支持一个主端口映射到公网域名。我们采用的办法是把监控接口单独发布成一个内部服务,不暴露到外网。这个调整虽然小,但在设计迁移方案的时候就得想好。
3.4 日志和监控怎么接
原集群里我们用 EFK 体系:Filebeat 采集日志到 Elasticsearch,Kibana 看日志。迁移到 Serverless 后,我们没有继续维护这套组件,而是直接用平台的标准输出采集能力。Java 服务需要把原本写到文件里的日志改为使用logback或log4j的标准输出 appender,否则平台边上的日志收集器只会抓到空日志。
不是所有服务都能随手改日志配置。我们有一个老服务是内部框架,日志框架封装得很深,临时加了一个启动时参数:logging.stdout.enabled=true,然后把原文件路径的日志也同时输出到标准输出。这样既不影响旧逻辑,也能在平台里看到日志。
监控方面的思路也变了。在 K8s 里我们习惯看 Node 的 CPU、内存、网络,然后围绕节点做告警。迁到 Serverless 后,更值得看的是:
请求错误率:4xx 和 5xx 的比例P95/P99 响应延迟实例并发数扩容次数和实例拉起耗时单实例冷启动频率
我们取消了大多数节点层面的告警,把重点放在服务等级指标上。如果某天到了 8 点,请求量突然上涨,但我们没有看到实例数跟着涨,那大概率是containerConcurrency设置过高,系统认为单实例还能扛更多请求。这种情况下要响应的是业务容量,而不是服务器故障。
3.5 成本估算怎么算
迁移前最怕的是算不明白账。我们当时做了一个表格,把约 20 个服务的“高峰期 CPU”和“高峰期内存”统计出来,然后用 Serverless 平台的单价推算。
假设一个服务需要 512MB 内存和 0.5 vCPU,平均每个请求占用 0.2 vCPU 秒,每天 100 万请求:
- 总 vCPU 秒 = 1,000,000 × 0.2 = 200,000 vCPU 秒
- 总内存 GB 秒 = 1,000,000 × 0.5 / 1024 × 1 = 约 488 GB 秒(这里以 512MB 约等于 0.5GB 算)
- 按平台的 vCPU 秒单价和 GB 秒单价,每百万请求的成本大约在 30 到 60 元之间
如果一天 100 万请求,一个月就是几块钱到几百块钱的浮动,这远比固定账号下买一台 4C8G 的机器便宜。对于低流量服务,minReplicas: 0时没有请求就等于没有费用。我们有一个定时任务服务,每天只在凌晨 2 点运行 10 分钟,迁移后基本可以视为接近零成本。
注意,成本估算必须包含公网流量费。Serverless 平台通常计算公网出流量,而原来在 K8s 集群里,公网 IP 和带宽可能已经包含在包月套餐中了。如果服务经常被外部调用并返回大量 JSON,流量费会占最终账单的 30% 以上,这个在选型时要单独测算。我们的解决方案是给高流量静态接口前面加了一层 CDN,缓存命中率高,回源流量大减,整体成本压下来不少。
3.6 灰度发布和快速回滚
在 K8s 里做灰度发布,我一般用 Deployment 多副本加 Service selector 切流或者用 Ingress 的权重路由。迁到 Serverless 平台后,灰度逻辑简单很多:每次发布产生一个新 Revision,平台支持按百分比把流量切到新版本。可以把 5% 流量切过去,观察几分钟,再逐步增加到 100%。
这里有一个很实用的技巧:如果新 Revision 有问题,回滚不是重新部署,也不是切换镜像 tag,而是把流量权重直接拨回旧 Revision。我们用这个能力快速处理过两次线上事故,整个过程不到一分钟,用户几乎无感。在原来的集群里,回滚至少要做一次镜像 tag 回退和滚动更新,有时还会因为拉取镜像太慢导致长时间 503。
不过灰度发布有一个生命周期问题:每个 Revision 以及它对应的实例会保留一段时间,如果发布特别频繁,旧 Revision 的常驻实例会造成成本浪费。我们后来没让平台无限保留历史版本,而是关闭掉超过一周的旧 Revision,哪怕有时想多留几天,也只能在镜像 tag 里找回来。
4. 踩坑实录与问题排查清单
4.1 冷启动不是只有时间问题
冷启动是最常见的坑。Nginx 镜像冷启动大概在 1 秒内,Java 服务冷启动可能要 3 到 8 秒。我们处理的方式有三个:一是把核心服务minReplicas设为 1 或 2;二是把健康检查接口调整到最轻量,减少启动时额外初始化;三是给平台配置“预热”,也就是在发布 Revision 后先发送几个探针请求,让 JIT 和连接池初始化完成。
冷启动还有一个副作用:短时间内大量实例同时拉取同一个镜像,镜像仓库的带宽会瞬间打满。我们在高峰期遇到过上百个实例同时拉镜像,导致仓库响应变慢。后来把所有镜像都推到平台自带加速的仓库,拉取时间从平均 20 秒降到 3 秒左右。如果你的镜像特别大,建议拆成基础镜像和业务层,或者用更小的镜像。我们现在 Nginx 静态服务镜像小于 30MB,Java 服务镜像控制在 200MB 上下。
4.2 长连接和 WebSocket 的“水土不服”
我们有一个服务维持着大量 WebSocket 长连接,在 K8s 里它能稳定运行,因为 Pod 生命周期较长,连接不会频繁断开。迁到 Serverless 后发现两个问题:一是实例在缩容时,平台会直接关闭旧实例上所有连接,客户端会瞬间断线;二是实例扩容后,新实例数量多,整个连接管理状态分散在各实例中,互相不感知。
这个服务最终没有迁出去。我们保留它在原来的 K8s 集群,但给集群加了一个暂停自动缩容的规则,让它始终保持至少 3 个副本。如果你有类似服务,建议不要做“无状态化改造”来强行适配 Serverless,除非你能把连接状态搬到 Redis 或 MQTT broker 中,否则改造成本会很高。
4.3 文件系统“像记忆一样短”
我们当时有一个图片处理组件,原设计是先把图片写到本地/tmp,处理完成后上传到对象存储。这个逻辑在 K8s 里基本正常,因为 Pod 生命周期较长。可到了 Serverless 平台,实例随时会被销毁,而且不同请求之间不一定落在同一个实例上,本地文件一丢就出问题。更尴尬的是有些平台实例的文件系统是只读的,只有/tmp目录可写,而且容量极小。
解决方式是统一改日志和文件输出,把临时文件全部放到对象存储或 Redis 里。如果确实需要本地临时文件,就只用/tmp并加清理逻辑,但不要依赖它做两小时以后的数据读取。这个属于迁移前必须梳理的“环境假设”,光靠改配置解决不了。
4.4 实例 IP 漂移带来的白名单问题
原来的 K8s 集群有几个稳定的公网 IP,合作方防火墙只需要把我们几台 Node 的 IP 加白名单。迁到 Serverless 后,出流量 IP 是动态的。第一次对接某银行接口时,对方连续拒绝了几十笔请求,排查到深夜才发现是 IP 不在白名单里。
办法有两种:一种是给 Serverless 服务单独配置一个 NAT 网关,让出口 IP 固定;另一种是改回 K8s,把需要固定 IP 的服务留在集群里。我们最后采用了“混合模式”:大部分 Serverless 服务走动态出口,只有三个对接支付的服务被强制绑定到 NAT 网关的固定 IP 上。注意,NAT 网关也是按时间计费的,保留它就意味着省不掉那部分固定成本。
4.5 并发限制和 429 风暴
某次大促前,我们为了防流量打爆服务,把containerConcurrency设成 10。结果实际流量远超预期,平台确实按 10 并发快速扩容了,但扩容需要时间,在流量瞬间暴涨阶段,很多请求直接返回了 429。客户端没有做重试处理,导致用户端一直报错。
那次之后,我们把平台网关的 429 响应策略改成客户端可重试,但业务侧也加了本地重试。更重要的收获是:containerConcurrency不要设置得太死,尤其是后端依赖数据库的服务。过低的并发反而会导致线程池频繁等待,服务端资源没打满,客户端感知到延迟却在飙升。合理的设计是:让单实例并发略高于实际平均并发,但请求超时时间设短一点,这样平台扩得足够快,同时不会让一个实例拖垮数据库连接池。
4.6 账单从“消失”到“反弹”
Serverless 计费确实可以降到很低,但不要天真地以为成本会一直这么低。有几次月底账单突然涨了 30%,排查后发现是有个服务被外部扫描器打到了,产生了大量请求。因为配置了minReplicas: 0,扫描流量也会触发冷启动和实例创建,账单上显示的就是按秒收费的实例费用。
这里必须有预算配额和告警。我们给每个团队加了每日账单变更告警,如果某天的费用比前七天均值翻了三倍,就立刻查日志。另外,还要记得清理那些“没人记得发布过”的旧 Revision,它们也会消耗持续实例费用。
以下是问题排查速查表,贴在我们团队文档里:
| 现象 | 大概率原因 | 建议处理 |
|---|---|---|
| 高峰时段大量 503 | 实例扩容不够快或镜像拉取时间长 | 提高 minReplicas,启用镜像预热 |
| P99 延迟高但实例数没涨 | containerConcurrency 设得过高 | 调低并发值,观察扩容行为 |
| 日志平台一直没数据 | 日志没有输出到标准输出 | 改日志 appender 配置 |
| 上传文件失败 | 网关 body size 限制 | 修改网关或走对象存储直传 |
| 凌晨费用不为零 | 存在定时任务/minReplicas 实例或旧 Revision | 清理旧版本,单独评估常驻实例 |
| 被外部服务白名单拦截 | 实例出口 IP 变化 | 绑定 NAT 网关或保留固定 IP 服务 |
| 某些请求响应特别慢 | 冷启动或 JIT 预热不充分 | 设置 minReplicas,增加预热请求 |
5. 迁移后的收益账与技术边界
5.1 成本账单对比
我们挑迁移前后的 30 天做了一组横评,统计口径排除人为变更造成的异常:
- 迁移前基础设施账单:约 2.3 万元/月
- 迁移后基础设施账单:约 0.65 万元/月
- 降幅约 71%
这个差距来自几个方面:按量的实例计费替代包月节点;大部分服务缩到 0 后夜间费用几乎为零;不再需要为临时扩容预留额外节点;日志和监控组件从自建改成了托管,节省了两台 4C8G 的固定开销。不过也要说实话,如果流量再涨 3 倍,Serverless 账单可能也会涨到原来的水平,但它带来的弹性是传统集群难以达到的。成本下降不是必然结果,而是我们流量特征恰好适合按量付费。
5.2 运维时间统计
我们记录过迁移前后的每周基础设施操作时长。迁移前那一周约 11 小时,主要包括节点维护、集群升级、排障、监控部署、Ingress 配置。迁移后稳定下来的那一周大约 1 小时,主要就是看新服务是否自动扩容、检查日志告警、偶尔处理证书到期。
运维工作量下降 90%,不是因为我们人变勤快了,而是很多“不确定性”消失了。节点宕机会自动由云厂商处理,实例故障会被平台重新拉起,节点升级不再需要排空 Pod。我们不再需要关心 Kubernetes 节点组,也不再维护 Ingress Controller 的配置。原来最占时间的“容器挂了但没挂明白”问题,变成平台自动重启并采集退出日志,我们只需要去看业务报错。
5.3 团队姿态和开发方式的改变
迁移后开发人员开始自己发布服务,不再得找基础设施的人审批。因为发布流程也接上了 CI,合到主干之后自动构建镜像,然后自动发布到测试环境的 Serverless Service。灰度发布和回滚的权限下放到服务负责人,不必等平台组介入。对团队来说,这更像一次工作流改革:技术人花在业务上的时间变多了。
不过也有新的纪律要求。以前在 K8s 里,你想保留几个副本,deploy 文件里就写几个,别人能看见。迁到 Serverless 后,如果minReplicas和maxReplicas不写清楚,别人不知道这个服务到底能容忍多少突发流量。我们要求每个服务必须写清containerConcurrency、minReplicas、maxReplicas和超时时间,并把这些字段当成服务契约的一部分,否则很难判断流量高峰是否会让系统被打爆。
5.4 最终留在 K8s 里的是什么
不是所有负载都能迁,也不是所有负载都值得迁。我们最终留在原集群的只有三类:
- 有状态服务:包括自建的 Redis 集群、某些任务队列、需要持久化本地数据的模块
- 长连接网关:一个 WebSocket 网关,保持常驻实例
- GPU 批处理任务:模型推理任务,需要明确的资源调度,而且单次执行时间很长
这一类服务加起来不到总量的 20%,但它们承载了核心链路的重要部分。我们没把它硬塞到 Serverless,而是把集群节点数压缩到最少,同时给 Node 打上标签,让这 20% 服务和原先的 80% 服务彻底隔离。这样既避免了暴力迁移的风险,又保住了迁移带来的收益。
如果你问“是不是 Serverless 更适合新项目”,我的看法是:新项目直接上 Serverless 很合理,因为它从第一天就不需要运维 Kubernetes;但老项目迁移前一定要先给服务分类,不要为每一个服务都走同一条路。
5.5 迁移前必须做的三件事
第一,量化收益。把你过去 90 天的真实账单拉出来,把“线下运维时间”也折算进去,再对比 Serverless 平台的价格,别凭感觉。很多朋友问我要不要迁,我第一句永远是先问:你现在的集群有多少 Node?请求量曲线是什么样?只有曲线的谷值和峰值落差大,或者服务数量多但每个流量都不高,Serverless 才最划算。
第二,确定迁移顺序。先把最简单的无状态静态服务迁过去,观察几天成本和稳定性,再把核心 API 迁过去。一定不要先迁核心支付链路,除非你已经很熟悉平台的限流和网络策略。
第三,准备好“回退开关”。Serverless 平台的接口和 K8s 不完全一致,如果迁移失败,至少保留原集群的备份配置。我们当时把 Deployment YAML 都留了一个 tag,成本不高,但给了我们随时退回的空间。
最后说几句体己话
每次有人听到我们把 Kubernetes 换掉了,都会觉得我们是在“开倒车”。我一开始也有这种错觉。但实际操作下来,我发现这里的分界线不是技术名称,而是团队规模和流量特征。小团队维护一套多节点的 K8s 集群,本质上是用人力和复杂度去换“主流的部署形态”;而 Serverless 是把这部分复杂度交给系统自动处理,让开发者专心写业务。对我个人来说,最大的成长不是学会了 Knative 和 Serverless 的新语法,而是终于把基础设施从“一个要持续维护的系统”看成了“一个按需求分配的计算资源池”。
如果你现在正在犹豫要不要做类似的迁移,我的建议是先从最不起眼的服务开始试试。不要问别人“该不该迁”,而是问自己:“如果需要我每周花半天去维护集群,我的业务功能到底比同行多做了多少?”答案出来的时候,多半你心里已经有数了。