替换 Spring Cloud,使用基于 Cloud Native 的服务治理
本文整理自「火山引擎开发者社区」首次 Meetup 中的同名演讲,主要介绍了 Spring Cloud 技术体系和云原生技术体系的关联和区别,以及如何借助云原生能力构建微服务系统。
作者|夏岩,火山引擎高级研发工程师
大家好,我是来自火山引擎的高级研发工程师夏岩,我今天的分享主题是替换 Spring Cloud,使用基于 Cloud Native 的服务治理。
关于 Spring Cloud 技术体系
我们通过时间线展开整个项目背景:
在我刚开始工作的时候(2010 年以前),可能还没有云原生社区,当时 Java 体系是企业级开发的首选。
2010 年, Netflix 推出了 Move to Cloud 计划,将绝大部分的服务迁到了 AWS 上。
2012 年,Netflix 推出了 Open Source Software Center(开源软件中心仓库),类似于 Apahce Maven,提供了一些在上云过程中沉淀下来的开源项目。
2014 年,Martin Fowler 发表了一篇非常知名的博客,名叫 Microservices (https://martinfowler.com/articles/microservices.html),把当时一些公司的架构风格称为“微服务”。文章中指出微服务架构有以下一些特点:高可维护性和可测试性;服务之间松耦合;服务可独立部署;服务围绕业务组织;被一些小团队使用。
(现在,不管是前沿互联网公司还是传统 IT 企业都已经逐渐接受了微服务。在日益复杂的业务压力下,只有微服务架构才能保持企业的活力和软件开发的迭代速度。)
2015 年,Spring 社区围绕之前 Netflix 沉淀的一些组件以及 Martin 提出的微服务理念,推出了 Spring Cloud v1.0.0,直到现在 Spring Cloud 还被广泛使用。Spring Cloud v1.0.0 包含的组件较少,只有服务发现、配置管理等几个核心组件。
所以微服务架构的发展历程并不是从论文走向产业化,而是从工程师的实践中抽象出特点,最后形成完整的生态。到今天,Spring Cloud 组件已经比较的完善了,包含配置、服务解藕、服务发现、熔断、路由、消息传递、API 网关、tracing、CI 管道和测试等。这些构成了整个 Spring Cloud 的生态。
Spring Cloud 是基于 Java 构建的微服务体系,在 Spring 和 Java 社区不停迭代的过程中,出现了一股全新的力量。2014 年 6 月 7 日,Kubernetes 首次发布,当时还有 Docker Swarm、Mesos 这些调度平台互相竞争。
从时间线可以看出来,Kubernetes 和 Spring Cloud 的发展是同时期的。
微服务的一些关键组件包括配置管理、服务发现、Load Balance、API 网关、中心化日志、Metrics 等,Spring Cloud 这套体系和 Kubernetes 体系还是有一些交叠的。举例来说,Spring Cloud 有 Config Server(类似的有阿里开源的 Nacos、携程开源的 Apollo),Kubernetes 则有 ConfigMap、Secret 等,它本身也有配置能力,但是比较弱。Kubernetes 的优势在于它的组件和整个系统之间的交融度比较高,但在 Spring Cloud 里可能是所有组件都要去兼容 Spring Cloud,以 Java 社区为主,和其他语言的交互比较少。
上图展示了软件的各种能力。可以看到 Kubernetes 包含的能力范围比 Spring Cloud 更大。比较突出的有 Auto Scaling、DevOps、进程隔离,这些是 Spring Cloud 不能管辖到的。
在当时,一些新兴客户会面临一个问题:对于基于 Java 的业务应用,开发的时候选择哪种模式更好?
对于这个问题,现在我们更推荐使用 Kubernetes,因为 Kubernetes 是一个语言无关的平台。Spring Cloud 虽然是 JVM 体系,但是离开了 JVM 很多事情都做不了,因此不得不逼迫客户随着一起做变动,这个体验其实不太好。所以我们后面也说服了同公司的一些团队一起参与到 CNCF 云原生技术架构的建设。
Spring Cloud 基础能力替换
配置中心
Spring Cloud 的 Config Server 具有较多的能力:
Git 作为配置仓库;
JDBC 和 Redis 提供了统一的配置抽象层。
但不太好用。一些个性化的需求比如配置中心的权限管理和热加载,Spring Cloud Config Server 本身不支持,需要做二次开发。
对于 Kubernetes,可以通过 ConfigMap 或者 Secret 按照更加原生的方式以环境变量、文件或启动参数的方式注入到应用中去,就像敲 Linux 命令一样方便。
我们会发现 Spring Cloud Config Server 更像是一个独立的软件,Kubernetes 的 ConfigMap 更像是软件内的功能,这就是两者之间的区别。
配置管理
Kubernetes 的配置管理比较简单,只需要在最终的启动声明里增加 Environment,或者是将 ConfigMap 以 Volume 的方式加载进去就可以了。
有时候会有同事问,Sping Cloud 虽然原生没有热加载能力,但是基于 SpringEventBus,甚至用一些第三方厂商的开源工具,也可以实现所谓的热加载,Kubernetes 可以做到吗?
其实 Kubernetes 也是可以做到的。环境变量当然是 immutable 挂进去,但是我们可以将一些可变的属性以文件的方式挂载到宿主机容器化应用程序的 YMAL 文件里去。随着 ConfigMap 的变动,YMAL 也会同时变动,这时只需要让应用能 watch 配置文件的变化,进行自动从加载就可以了。而热加载本来就应该由应用自身实现。
Kubernetes 本身也有 reload 能力,尤其是在扩展到其他语言的时候。字节内部使用 Go 语言比较多,大家只要能够 reload 某一个文件或远程地址,应用就可以将自己的行为进行变化。
服务发现
Spring Cloud 和 Kubernetes 最大的不同在于服务发现。我们绝大部分的功能都需要基于服务发现去做二次扩展,这时就会面临服务发现的选择问题。
Spring Cloud 的服务发现是基于 Eureka 的(后期也可以基于 Consul 进行),提供了自上报的机制和客户端负载均衡,是一个 AP 系统。
Kubernetes 则更像传统的云厂商,可帮助用户创建机器/容器。平台自然知道应用在哪里,就可以通过 DNS 以及服务端负载均衡帮助导流。这样的体验是截然不同的。
Spring Cloud 这套体系如果是 Eureka Client,永远是要嵌入业务内部的,因为在启动的那一刻才知道应用在哪里,通过 Utils 组件去获取当前的 IP 地址。而 Kubernetes 并不需要由应用进行感知,这是非常大的区别。
接入 Kubernetes 的服务发现也是比较简单的。只要创建一个 service 的资源(resource),定义其对应的 Label 即可。我认为服务发现是 Kubernetes 的一个很大的优点。
Auto Scaling & Self Healing
Auto Scaling 和 Self Healing 是 Spring Cloud 不具备的。在 Spring Cloud 里,Eureka 会做一些健康检查。其逻辑比较简单:Eureka 不停地发请求,看心跳有没有定时上报上来。但 Spring Cloud 只能知道服务是否健康,无法阻止访问不健康的服务。如果要扩容或自恢复不健康的服务,需要在 Spring Cloud 里做很多扩展。
Kubernetes 这方面做得好一点。它本身提供 readless 的检测,检测完之后,如果调用失败了,平台就会帮助进行自动扩展和调度。要实现这样的功能也很简单,只要在应用或容器内开通一个端口,能够检测服务当前是否运行正常,可以比如说有延迟的参数,或者是间隔周期,在恰当时候进行一次请求,就可以知道应用是否就绪/健康。
这样会更符合所谓的微服务原子要素,因为我们不光要能检测系统是否健康,更希望能够自动扩展。Kubernetes 社区还在做 HPA,甚至可以监测某些指标,随着指标变化进行自动扩缩容。这些在 Spring Cloud 里面都是非常难实现的。
Spring Cloud 流量治理能力替换
API Gateway
绝大部分用户都会面临一个很重要的问题:已经基于 Spring Cloud 做了很多流量治理工作,这个工作如何迁移到云上?比如 API gateway,Spring Cloud Gateway 提供了很多能力,允许通过 filter 做很多开发,以完成自己的业务逻辑。
Kubernetes 是怎么实现的呢?它选择了更大的一个场景,把整个生态开源出来了。这个开源生态下有很多工具, 比如 API Gateway 就有 Kong、Tyk、Gloo 等工具。
举一个现在比较火的产品 Ambassador Edge。它原生提供了身份验证、分布式追踪、多协议、rate limit 等功能。但在 Spring Cloud 体系里实现这些功能就要做很多事情。Spring Cloud Gateway 的成本相对 Ambassador 等开源的网关成本要更高一些。
这里举一个例子。比如要用 Ambassador 构建一个 Keyclock 的鉴权体系。只要声明几个 YMAL 文件,就可以快速把整个流程走通。对比起来使用 Spring Cloud gateway 构建时,要花很多时间去研究 Keyclock 有没有 API 接口,Spring Cloud 要如何接入等。类似这种很通用的功能,可以考虑使用开源产品来直接替换。
Service Mesh
Service Mesh 是另外一个更激动人心的话题,也是现在大家都在研究的前沿方向。
传统应用之间的通讯一直是很复杂的问题。比如 Spring Cloud Ribbon 做了很多安全、分流的工作,而这些工作其实跟业务本身相关度非常低。那么这些能力可以提取出来吗?社区给出了一个全新的答卷:Service Mesh。
Service Mesh 所做的事情是在节点之间通过一个 Proxy 代理层截获所有流量,节点之间通过特定的网关进行转发。因为所有流量都被劫持了,可以做很多工作,包括 load balance、根据 lable 做灰度发布等。
Spring Cloud 原生的默认设置无法实现全链路灰度,需要改 load balance 策略,这样会导致同源数据里的开发工作量增加。但是在云原生体系里, Istio 直接配一个 virtualservice 就能完成。虽然 Istio 有一些功能还在开发过程中,但使用 Istio 会更加容易,因为它把跟业务不相关的属性全部剥离出去,不再跟应用绑得那么紧密了。
双向 TLS
举例来说,如果要提供双向 TLS,在传统的应用里如果是应用自己来解决这个问题,就要先发个邮件通知要升级双向 TLS 了,然后每个人都得配一下自己的证书,这个过程非常痛苦。现在有了 Service Mesh,只要通过一行声明式就可以在不同的 Proxy 之间强制打开双向验证,保证整个网络安全,至于服务跟 Proxy 之间的安全则由隔离性来保证。
熔断
对于 Spring 社区主打的熔断器功能,也可以直接使用 Istio 提供的 destinationRule 的能力,只需要简单配置一些参数即可,比如访问的最大可连接数、错误多少次之后会被拒绝、进行 Half-Open 重试的间隔等。
Centralized metircs
Metrics 可以通过 sidecar 获取,无需像传统架构由应用获取。如果应用本身还暴露出来一些业务的 metrics,通过 Promerthus 的定制抓取可获得这些数据。但如果只是想知道一些网络吞吐 metrics,现在应用本身也不需要直接提供,这样又减少了应用业务的负担。
总结与展望
Service Mesh 的出现提出了一个全新的思考方向:我们真的要将那么多中间件功能放在应用本身吗?恰好社区也在思考这个问题。CNCF 社区最近有一些新的博文,提出了一个叫做多运行时的架构体系(multi-runtime microservice architecture),这是一种全新的理念,认为围绕业务我们需要提供四种能力:
生命周期管理:管理应用什么时候启动,什么时候关闭等。包含打包、健康检查、部署、扩展、配置等功能。
网络管理:包括服务发现、A/B test、灰度发布、熔断、点对点通讯、pub-sub 等。
状态管理:包括 workflow 管理、缓存、应用状态等。
绑定:包含数据传输,协议转换等。
有了这些能力,开发人员只需关注业务逻辑,研发效率将会极大提高。
这些能力基于云原生体系也可以做到。比如生命周期可以基于 Kubernetes 去做,网络可以基于 Istio 去做,状态管理可以基于 Cloud State 去做,绑定可以基于 Camel 去做。将这些东西组合在一起,业务单元就无需再关注这些事情。而 Spring Cloud 为了解决复杂的依赖问题,需要 maven 依赖,要依赖很多组件。当然这些事情慢慢都可以去掉,我们只要关心业务单元最核心的部分——业务逻辑,因为只有这个部分才是真正动态的逻辑。
有了多运行时架构,会发现一个很大的变化:一旦业务单元变得足够小,我们只需要去写业务本身,进行数据的读取和存储就可以了。这时如果想获得额外的服务发现、熔断、配置管理等服务,只需要在外围添加这些能力即可。
大家可能会问,多运行时跟 FaaS 有什么关系?可以看一下这张图:
单体架构的复杂度和规模化正相关,规模越大复杂度越高,中间件越复杂。FaaS 在复杂度提升的过程中,提高了拓展度但是复杂度却背道而驰,Mecha 的设计是希望随着规模化,可以将复杂度控制在一个较低的水平之内。
最后展望得有点长远,其实现在 Istio 还在开发的过程中。Istio v1.9 之后一直在提速,要一切为了生产。整个社区在积极推动 sidecar 模式,要将很多非业务单元的东西移出来,比如负载均衡、服务发现、弹性伸缩。至于未来我们是否能走到多运行时路线上来,也是我们的展望,希望大家能够跟我们一起来探讨整个云原生架构的未来。
Q&A
Q1:Istio 有好用的控制器吗?目前还没有看到成熟的控制器,使用 Istio 的难度比较大。
A:Istio 确实没有好用的控制器,因为现在看起来还是一个比较前沿的方向。已知的像 solo.io,他们做了一些产品,是直接基于 Envoy,没有基于 Istio。但是我觉得社区是不断发展的,产品也会不断推陈出新,可能要交给后面的人来解决这个问题。当前的确是没有好用的控制器。
Q2:新的云原生平台和微服务能力是否是语言无关的。有关选的是哪些?
A:整个 CNCF 社区比 Spring Cloud 社区能打的原因就是因为语言无关。如果将语言绑定,很多平台可以自己自洽,就比较简单。但是我们要接受现实:现在的互联网体系,或者整个软件体系,异构已经成为了必然的趋势。我们不可能要求所有人只会一门语言,这个时候可以用不同的语言去实现不同的事情,不同生态会有不同的构成。Kubernetes 以及 CNCF 社区就在做这件事情。
Q3:kuber-proxy 能否完全替代 Spring Cloud Zuul 或 Gateway?
A:这个问题其实还蛮有意思。kube-proxy 现在还是基于 iptables 和 IPVS 做转发的工作,当然像 Cillium 基于 kube-proxy 的 eBPF 做了很多的工作。至于未来会不会融到 kubu 上去,从我的经验上来看,主要是因为 Service Mesh 融入了很多业务属性,可能并不是 kube-proxy 想要去支撑的。
Q4:Kubernetes 环境下,开发人员如何比较方便地调试本地代码?
A:现在还有一些单机版的 Kubernetes,比如 Minikube 或者一些云厂商,都会提供比较合理的本地直接访问云端服务的特性。个人更建议开发者尝试一下 Minikube/K3s, 就在本地运行,进行一些调试是非常方便的。
版权声明: 本文为 InfoQ 作者【火山引擎开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/9a0b501610465d8e64a511c1b】。文章转载请联系作者。
评论