作者:王建伟(正己)
12 月 11 日,OpenAI 旗下 AI 聊天机器人平台 ChatGPT、视频生成工具 Sora 及其面向开发人员的 API 自太平洋时间下午 3 点左右起发生严重中断,耗费约三个小时才顺利恢复所有服务。
OpenAI 在事后报告中写道,“该问题源自新部署的遥测服务,此项服务无意间压垮了 Kubernetes 控制平面,导致关键系统发生连锁故障。引发事故的根本原因就是新的遥测服务配置意外在大规模集群中产生了大量 Kubernetes API 负载,导致控制平面不堪重负并破坏了基于 DNS 的服务发现能力。”
可见,即使如实力强大的 OpenAI,面对复杂 Kubernetes 架构,也不能很好处理 Kubernetes 服务发现和控制面解耦的问题。造成这个问题的关键原因在于容器调度和业务关键服务发现链路耦合在一起,互相干扰,Kubernetes 控制面故障影响了业务服务发现链路。那么,Kubernetes 体系下应如何选择服务发现系统,进一步提升业务稳定性呢?
笔者认为,大型业务的服务发现系统应该具备高可靠性,高可伸缩性,高性能及高可维护性等特点,采用独立服务发现系统是一种相对较好的方案。本文以社区主流服务发现系统 Nacos 为例,从可靠性、可伸缩性、高性能、可维护性等 4 个方面探讨如何提升 Kubernetes 中微服务应用的稳定性。
01 如何提升系统可靠性
产品、系统在规定的条件下,规定的时间内,完成规定功能的能力称为可靠性。
解耦控制面与运行时
众所周知,Kubernetes 主要工作资源调度,更偏向运维系统一些。从架构合理性上讲,运行时与控制面的系统不应耦合在一起,甚至即使运维系统挂了也不会影响运行时。服务发现是运行时需求,而 Kubernetes 服务发现与运维系统绑定,一旦 Kubernetes 故障,上层运行态应用会受到致命影响。
Kubernetes 服务发现依赖 API Server,而 API Server 被非常多的组件调用,任何组件异常调用都有可能把 API Server 打挂,进而影响服务发现。以 OpenAI 这次故障为例,本来是要增强系统的可观测性, 但因可观测组件的大量调用把 API Server 打挂了,导致系统 DNS 解析发生故障。如果服务发现体系相对独立,不依赖或弱依赖 Kubernetes 控制面,本次故障是可以避免的。
Nacos 作为独立注册配置中心,可以不依赖 Kubernetes 独立部署,面向运行时设计,且有遵循多项面向失败原则,帮助业务服务发现与底层运维系统结构隔离,做到运维态系统故障不影响运行态系统,大大提高系统可靠性。
提升系统容灾能力
首先,Nacos 可以帮助提升部署在 Kubernetes 上微服务体系的容灾能力。先讲一个实际案例,2023 年 11 月,国内某头部出行服务公司的 app 突然出现大面积报错,导致长达几十个小时的服务不可用,影响大量用户正常出行。据官方发布通过,此次故障原因是底层软件异常导致(据推测为 Kubernetes 升级出现异常)。
对于大规模微服务系统,Kubernetes 集群容灾是系统稳定性非常重要的一环。通常,为了实现 Kubernetes 集群级别容灾,我们通常会把应用部署在多个 Kubernetes 上。这样,即使一个 Kubernetes 集群出现问题,还有另一个集群可以提供服务。但因 Kubernetes 服务发现是面向本集群内的,多 Kubernetes 部署之后,应用间的服务发现,尤其是跨 Kubernetes 集群服务发现会变得比较困难。
Nacos 作为独立的注册配置中心,可以不依赖 Kubernetes 独立部署。因此,如果在多 Kubernetes 引入 Nacos 作为注册配置中心,跨 Kubernetes 的服务发现问题就迎刃而解了。下图给出了 Nacos 作为独立注册配置中心的一个架构示意图。
- Nacos 独立于业务应用部署,可以部署在独立 Kubernetes 中也可以部署在其他平台上;
- 业务应用部署在两个 Kubernetes 集群上,服务提供者向 Nacos 注册服务;
- 服务订阅者从 Nacos 订阅服务,发起服务调用。
从上图可知,任何一个 Kubernetes 集群出现故障都不会影响整个系统的服务。因此,Nacos 能大幅提升 K8s 体系微服务系统的可靠性。
面向失败设计
针对微服务系统常见的问题,Nacos 做了多项面向失败的设计,帮助提升系统可靠性。本节重点介绍其中两个:客户端缓存和推空保护。
客户端缓存提高极端情况下系统可靠性
在 Kubernetes 系统中,CoreDNS 是服务发现的核心组件,所有 DNS 请求都经过CoreDNS。一旦 CoreDNS 故障,所有服务调用都会受到影响。跟 Kubernetes 服务发现不同,Nacos 客户端保存了缓存数据,在服务端故障无法更新服务 IP 列表的情况下,可以继续使用缓存提供服务,不会影响运行时服务调用。
推空保护防止服务实例被误下线
当 Nacos 服务端发现某个服务下的实例全部下线时,可以自动触发保护逻辑,不会给客户端推送空 IP 列表。推空保护策略能预防因网络抖动或运维失误等导致的服务实例全部异常下线问题,保障业务运行可靠性。
02 如何提升系统可伸缩性
信息系统不需要对本身进行大量修改,只需要通过增加软硬件资源使服务容量产生线性(理想情况下)增长的特性称为可伸缩性。
在微服务架构下,传统单体应用被切分为多个小应用提供某些独立功能。随着业务发展,服务数量可能会出现爆发式增长。以淘宝为例,仅仅 3 年时间微服务实例规模就从十万级别暴涨到了百万级别。微服务数量爆发式增长对注册配置中心的可伸缩性提出了很高的要求。
Kubernetes 服务发现的核心组件之一是 etcd,系统所有微服务相关数据均存储于其中。但 etcd 是基于 raft 一致性协议开发的系统,Raft 协议本身的特性决定所有写操作必须由 Leader 执行。因此,etcd 可伸缩性较差,无法通过水平扩容解决微服务大规模增长带来的压力。
那如何提升系统的可伸缩性呢?一种方案是业务拆分,即把业务按照一定的逻辑拆分到多个 Kubernetes 集群,每个 Kubernetes 内部业务封闭,但成本很高,可能会改变整个业务架构;另一种方案是引入可伸缩性好的注册配置中心。
与 etcd 不同,Nacos 服务发现能力基于自研的 Distro 协议构建,每个服务端均可提供写服务,其性能随着系统的水平扩展而提高。因此,Nacos 作为 Kubernetes 集群的注册配置中心,可以大幅提高整个系统的可伸缩能力。
03 如何提升系统性能
Kubernetes 系统内服务发现主要有两种方式:环境变量和 DNS。默认情况下,Kubernetes 会为每个服务自动生成一个环境变量,并注入到 Pod 中。如果业务规模很大,环境变量过多的问题就不可避免。环境变量过多会导致 Pod 启动过程很慢,笔者就多次遇到环境变量过多导致 Pod 无法启动的问题。
DNS 服务发现方式允许开发者像调用普通域名一样调用 Kubernetes 内的服务,为多语言技术栈开发带来了很大便利。但频繁的 DNS 解析一方面会导致请求响应时间变慢,另一方面也会有一定的资源消耗。笔者曾做过一个简单的实验,对比直接以 DNS 域名方式调用和以 IP 直连方式调用的响应时间。结果显示,平均每次调用DNS 方式的 RT 比直连慢 3%-5%。3%-5% 的延迟看起来微不足道,复杂业务可能都会有多次甚至几十次的调用,累积起来的延迟对终端用户的体验影响还是比较大的。
而 Nacos 服务发现方式,通常和微服务框架(SpringCloud/Dubbo 等)结合,推送 IP 列表给框架,然后框架用IP直连的方式发起调用,省去了 DNS 解析的消耗。
下图简要画出了 DNS 解析和 IP 直连方式的区别。
因此,对于技术栈统一的微服务架构,使用 Nacos 作为注册配置中心,可以进一步缩短响应时间提升系统吞吐量。
04 如何提升系统可维护性
系统发生故障后能够排除(或抑制)故障予以修复,并返回到原来正常运行状态的可能性称之为可维护性。
简化服务发现链路,降低维护成本
K8s 服务发现依赖组件众多,下图给出了其典型架构。可以看到整个链路很复杂,涉及到 api-server、etcd、kube-dns/coredns、kubelet、kube-proxy、iptables、ipvs 等组件。一个 Pod 从扩容到最终接收到请求大概需要 10 步。
1. 创建 Pod
2. 创建 Service
3. kubelet 检测 Pod 健康状态,并上报给 api-server
4. api-server 更新数据到 etcd
5. kube-proxy从api-server 收到 service 变更
6. kube-proxy 调用 iptables/ipvs 设置转发规则
7. kube-dns/coredns 从 api-server 监听到服务变化,更新 dns 解析记录
8. 调用方 pod 从 kube-dns/coredns 解析 service,得到 cluster ip
9. 调用方 pod 用拿到的 cluster ip 发起调用
10. 请求经过 iptables/ipvs 转发链到达服务提供方 pod
而 Nacos 的服务发现工作原理,如下图所示,涉及组件更少,只有 Nacos Sdk 和 Nacos Server;一个 Pod 从创建到最终接收到请求大概只需要 5 步:
1. 创建 Pod
2. 服务提供方 Pod 注册服务到 Nacos,并自动持续汇报心跳
3. 服务调用方 Pod 从 Nacos 订阅服务,拿到服务 IP 列表
4. 服务调用方使用 Pod IP 发起调用
5. 请求打到服务提供方 Pod
可以看出,相比 Kubernetes,Nacos 注册中心服务发现链路更短,涉及组件少。对于大规模微服务系统,采用 Nacos 作为注册配置中心,可以大幅提升系统的可维护性。
Kubernetes 与非 Kubernetes 服务互相发现,提升架构兼容性
随着 Kubernetes 的普及,新增系统通常选择直接部署在 Kubernetes 中。但仍有很多存量应用部署在传统虚拟机或物理机上。这两类应用经常互相调用,因此它们能互相发现变得十分必要。然而 Kubernetes 服务发现与 K8s 运维系统绑定,Kubernetes 服务要发现外部服务或被外部服务发现比较困难。如前所述,Nacos 是独立系统,对接入应用的部署平台没有限制,支持 Kubernetes 应用与非 Kubernetes 应用互相发现,下图是使用 Nacos 后 Kubernetes 与非 Kubernetes 应用互相发现的示意图。
05 Nacos 服务发现与 Kubernetes 服务发现特性对比
最后,下面表格中给出了 Nacos 服务发现与 Kubernetes 服务发现特性对比,方便大家选出更适合自己业务的微服务注册配置中心。
特性 | Nacos | Kubernetes |
多K8s或非K8s服务互相发现 |
|
|
控制面与运行时耦合 |
|
|
注册方式 |
|
|
发现方式 |
|
|
健康检查 |
|
|
负载均衡 |
|
|
性能与可伸缩性 |
|
|
可维护性 |
|
|
服务治理 |
|
|
生态集成 |
|
|
06 总结
Kubernetes 体系基于 DNS 的服务发现为开发者提供了很大的便利,但其高度复杂的架构往往带来更高的稳定性风险。以 Nacos 为代表的独立服务发现系统架构简单,在 Kubernetes 中选择独立服务发现系统可以帮助增强业务可靠性、可伸缩性、性能及可维护性,对于规模大、增长快、稳定性要求高的业务来说是一个较理想的服务发现方案。希望大家都能找到适合自己业务的服务发现系统。