华为云应用服务网格最佳实践之从 Spring Cloud 到 Istio
摘要:在全球首届社区峰会 IstioCon 2021 中,华为云应用服务网格首席架构师张超盟发表了《Best practice:from Spring Cloud to Istio》主题演讲,分享了 Istio 在生产中使用的实际案例。
在全球首届社区峰会 IstioCon 2021 中,华为云应用服务网格首席架构师张超盟发表了《Best practice:from Spring Cloud to Istio》主题演讲,分享了 Istio 在生产中使用的实际案例。
点击链接观看演讲:https://events.istio.io/istiocon-2021/sessions/best-practice%EF%BC%9Afrom-spring-cloud-to-istio/
以下为演讲全文
大家好,我是来自华为云的工程师。很荣幸有机会和大家分享 Istio 在生产中使用的实际案例。
华为云应用服务网格从 2018 年在公有云上线,作为全球最早的几个网格服务之一,经历和见证了从早期对网格的了解、尝试到当前大规模使用的过程。服务的客户越来越多,场景也越来越复杂。这其中的通用功能作为 feature 大都贡献到 Istio 社区,解决方案层面的实践也希望通过这样的机会和大家交流。
本次我选取的主题是 Spring Cloud to Istio。来自我们客户的 Spring cloud 的项目和 Istio 的结合与迁移案例。
演讲主要包含四部分的内容:
1)背景介绍
2)使用 Spring cloud 微服务框架遇到的问题
3)解决方案
4)通过示例来描述方案的实践细节
还是以微服务为切入点,微服务的诸多优势非常明显,但相应给整个系统带来的复杂度也非常显著。单体的系统变成了分布式后,网络问题,服务如何找到并访问到对端的服务发现问题,网络访问的容错保护问题等。连当年最简单的通过日志中的调用栈就能实现的问题定位,微服务化后必须要通过分布式调用链才能支持。怎样解决微服务带来的这些挑战?
微服务 SDK 曾经是一个常用的解决方案。将微服务化后通用的能力封装在一个开发框架中,开发者使用这个框架开发写自己的业务代码,生成的微服务自然就内置了这些能力。在很长的一段时间内,这种形态是微服务治理的标配,以至于初学者以为只有这些 SDK 才是微服务。
服务网格则通过另一种形态提供治理能力。不同于 SDK 方式,服务治理的能力在一个独立的代理进程中提供,完全和开发解耦。虽然从图上看两者差异非常小,后面我们将会从架构和实际案例来分析两者在设计理念上的差异,来体会前者是一个开发框架,而后者是一个基础设施。
SDK 形态中 Springcloud 是最有影响力的代表项目。Spring cloud 提供了构建分布式应用的开发工具集,如列表所示。其中被大部分开发者熟知的是微服务相关项目,如:服务注册发现 eureka、配置管理 config、负载均衡 ribbon、熔断容错 Hystrix、调用链埋点 sleuth、网关 zuul 或 Springcloud gateway 等项目。在本次分享中提到的 Spring cloud 也特指 Spring cloud 的微服务开发套件。
而网格形态中,最有影响力的项目当属 Istio。Istio 的这张架构图在这次演讲中会高频出现。作为本次分享的背景,我们只要知道架构上由控制面和数据面组成,控制面管理网格里面的服务和对服务配置的各种规则。数据面上每个服务间的出流量和入流量都会被和服务同 POD 的数据面代理拦截和执行流量管理的动作。
除了架构外,作为背景的另外一个部分,我们挑两个基础功能稍微打开看下两者的设计和实现上的相同和不同。首先是服务发现和负载均衡。
左边是 Spring cloud,所有的微服务都会先注册中心,一般是 Eureka 进行服务注册,然后在服务访问时,consumer 去注册中心进行服务发现得到待访问的目标服务的实例列表,使用客户端负载均衡 ribbon 选择一个服务实例发起访问。
右边 Istio 不需要服务注册的过程,只需要从运行平台 k8s 中获取服务和实例的关系,在服务访问时,数据面代理 Envoy 拦截到流量,选择一个目标实例发送请求。可以看到都是基于服务发现数据进行客户端负载均衡,差别是服务发现数据来源不同,负载均衡的执行体不同。
下面比较下熔断:
左边为经典的 Hystrix 的状态迁移图。一段时间内实例连续的错误次数超过阈值则进入熔断开启状态,不接受请求;隔离一段时间后,会从熔断状态迁移到半熔断状态,如果正常则进入熔断关闭状态,可以接收请求;如果不正常则还是进入熔断开启状态。
Istio 中虽然没有显示的提供这样一个状态图,但是大家熟悉 Istio 规则和行为应该会发现,Istio 中 OutlierDection 的阈值规则也都是这样设计的。两者的不同是 Spring cloud 的熔断是在 SDK 中 Hystrix 执行,Istio 中是数据面 proxy 执行。Hystrix 因为在业务代码中,允许用户通过编程做一些控制。
以上分析可以看到服务发现、负载均衡和熔断,能力和机制都是类似的。如果忽略图上的某些细节,粗的看框图模型都是完全一样的,对比表格中也一般只有一项就是执行位置不同,这一点不同在实际应用中带来非常大的差异。
使用 Springcloud 微服务框架遇到的问题
本次演讲的重点是实践。以下是我们客户找到我们 TOP 的几个的问题,剖析下用户使用传统微服务框架碰到了哪些问题,这些大部分也是他们选择网格的最大动力。
1) 多语言问题
在企业应用开发下,一个业务使用统一的开发框架是非常合理常见的,很多开发团队为了提升效率,经常还会维护有自己公司或者团队的通用开发框架。当然因为大部分业务系统都是基于 Java 开发,所以 Spring cloud 开发框架,或者衍生于 Spring cloud 的各种开发框架使用的尤其广泛。
但是在云原生场景下,业务一般更加复杂多样,特别是涉及到很多即存的老系统。我们不能要求为了微服务化将在用的一组成熟服务用 Spring cloud 重写下。用户非常希望有一种方式不重写原来的系统也能对其进行应用层服务访问管理。
2)将 Spring cloud 的微服务运行在 K8s 上会有很大的概率出现服务发现不及时
前面介绍过 Spring cloud 服务发现是基于各个微服务先向注册中心进行服务注册的数据来实现的,在传统 Spring cloud 场景下,当微服务部署在 VM 上,服务动态变化要求没有那么高,顶多个别实例运行不正常,通过服务发现的健康检查就足够了。但是在 k8s 场景下,服务实例动态迁移是非常正常场景。如图示,producer 的某个 Pod 已经从一个节点迁移到另外一个节点了,这时需要新的 pod2 的 producer 实例向 eureka 注册,老实例 Pod1 要去注册。
如果该情况频繁发生,会出现注册中心数据维护不及时,导致服务发现和负载均衡到旧的实例 pod1 上,从而引起访问失败的情况。
3)升级所有应用以应对服务管理需求变化
第三个问题是一个比较典型的问题。客户有一个公共团队专门维护了一套基于 Spring cloud 的自有开发框架,在每次升级开发框架时,不得不求着业务团队来升级自己的服务。经常会 SDK 自身修改测试工作量不大,但却要制定很长周期的升级计划,来对上千个基于这个 SDK 开发的服务分组重新编译,打包,升级,而且经常要陪着业务团队在夜间变更。业务团队因为自身没有什么改动,考虑到这个升级带来的工作量和线上风险,一般也没有什么动力。
4)从单体式架构向微服务架构迁移
这是一个比较普遍的问题,就是渐进的微服务化。马丁福勒在著名的文章单体到微服务的拆分中(https://martinfowler.com/articles/break-monolith-into-microservices.html)也提到了对渐进微服务化的倡议,如何能从业务上将一个大的业务分割,解耦,然后逐步微服务化。马丁福勒强调 “解耦的是业务能力不是代码” ,大神将代码的解耦留给了开发者。
但是站在开发者的角度讲渐进的微服务不是一个容易的事情。以基于 Springcloud 框架进行微服务开发为例,为了所有的微服务间进行统一的服务发现、负载均衡,消费和执行同样的治理策略,必须要求所有的微服务基于同样的,甚至是统一版本的 SDK 来开发。
当然我们客户在这种情况下也有基于 API 层面做适配,将原有的未微服务化的和已微服务化的并存,使用这种类似于灰度方式,实际操作非常麻烦。
曾经有客户问过有没有不用费劲搞两套,是否可以直接有些大的单体微服务化,另外一些单体很长时间内完全不动,直到有时间或者认为安全想动它的时候去动。
解决方案
对于客户实际碰到的 4 个典型的微服务框架的问题,我们推荐的解决方案都是服务网格。下面我们分别看下 Istio 如何解决上面的几个问题。
首先,多语言问题。基于服务网格,业务和治理的数据面无需运行在同一个进程里,也无需一起编译,因此也没有语言和框架上的绑定。无论什么语言开发的服务,只要有一个对外可以被访问和管理的一定应用协议上的端口,都可以被网格进行管理。通过统一的网格控制面,下发统一的治理规则给统一的网格数据面执行,进行统一的治理动作,包括前面介绍到的灰度、流量、安全、可观察性等等。
关于 Spring cloud 服务在 Kubernetes 运行时,关于原有的服务注册和发现不及时的问题。根本原因是两套服务发现导致的不一致问题,那么解决办法也比较简单,统一服务发现即可。既然 K8s 已经在 Pod 调度的同时维护有服务和实例间的数据,那么就没有必要再单独搞一套名字服务的机制,还要费劲的进行服务注册,然后再发现。
比较之前 Spring cloud 注册发现那张图,注册中心没了,服务基于注册中心的服务注册和服务发现的动作也不需要了,Istio 直接使用 k8s 的服务发现数据,但从架构上看也简洁很多。
我们也总结过,大部分碰到这个问题的场景,都是将微服务框架从 VM 迁移到 k8s 时候碰到的,有点把容器当作之前的 VM 使用,只使用了 k8s 作为容器部署运行的平台,并没有用到 k8s 的 service。
对于 SDK 自身升级导致业务全部重新升级的问题,解决办法就是把服务治理的公共能力和业务解耦。在网格中,将治理能力下沉到基础设施后,业务的开发、部署、升级都和服务治理的基础设施解耦了。业务开发者专注自己的业务部分。只要没有修改业务代码,就无需重新编译和上线变更。
当治理能力升级只需基础设施升级即可,基础设施的升级对用户业务完全没有影响。像华为云 ASM 这样大部分网格服务的服务提供商都能做到一键升级,用户完全感知不到这个过程。
关于渐进微服务化的问题,使用 Isito 服务网格可以非常完美的解决。Istio 治理的是服务间的访问,只要一个服务被其他服务访问,就可以通过 Istio 来进行管理,不管是微服务还是单体。Istio 接管了服务的流量后,单体和微服务都可以接收统一的规则进行统一的管理。
如图中,在微服务化的过程中,可以对某个单体应用 svc1 根据业务拆分优先进行微服务化,拆分成三个微服务 svc11、svc12 和 svc13,svc1 服务依赖的另外一个单体应用 svc2 不用做任何变更,在网格中运行起来就可以和另外三个微服务一样的被管理。同样在运行一段时间后,svc2 服务可以根据自身的业务需要再进行微服务化。从而尽量避免一次大的重构带来的工作量和业务迁移的风险,真正做到马丁富勒倡导的渐进微服务化的实践。
实践
以上是对实际工作中客户的几个典型问题的解决方案。在实践中,怎么把这些解决方案落地呢?下面基于实际客户案例总结,分享具体的实践。
我们的主要是思路是解耦和卸载。卸载原有 SDK 中非开发的功能,SDK 只提供代码框架、应用协议等开发功能。涉及到微服务治理的内容都卸载到基础设施去做。
从图上可以看到开发人员接触到开发框架变薄了,开发人员的学习、使用和维护成本也相应的降低了。而基础设施变得厚重了,除了完成之前需要做的服务运行的基础能力外,还包括非侵入的服务治理能力。即将越来越多的之前认为是业务的能力提炼成通用能力,交给基础设施去做,交给云厂商去做,客户摆脱这些繁琐的非业务的事务,更多的时间和精力投入到业务的创新和开发上。在这种分工下,SDK 才真的回归到开发框架的根本职能。
要使用网格的能力,前提是微服务出来的流量能走到网格的数据面来。主要的迁移工作在微服务的服务调用方。我们推荐 3 个步骤:
第一步:废弃原有的微服务注册中心,使用 K8S 的 Service。
第二步:短路 SDK 中服务发现和负载均衡等逻辑,直接使用 k8s 的服务名和端口访问目标服务;
第三步:结合自身项目需要,逐步使用网格中的治理能力替换原有 SDK 中提供的对应功能,当然这步是可选的,如调用链埋点等原有功能使用满足要求,也可以作为应用自身功能保留。
为了达成以上迁移,我们有两种方式,供不同的用户场景采用。
一种是只修改配置的方式:Spring cloud 本身除了支持基于 Eureka 的服务端的服务发现外,还可以给 Ribbon 配置静态服务实例地址。利用这种机制给对应微服务的后端实例地址中配置服务的 Kubernetes 服务名和端口。
当 Spring cloud 框架中还是访问原有的服务端微服务名时,会将请求转发到 k8s 的服务和端口上。这样访问会被网格数据面拦截,从而走到网格数据面上来。服务发现负载均衡和各种流量规则都可以应用网格的能力。
这种方式其实是用最小的修改将 SDK 的访问链路和网格数据面的访问链路串接起来。在平台中使用时,可以借助流水线工具辅助,减少直接修改配置文件的工作量和操作错误。可以看到我这个实际项目中,只是修改了项目的 application.yaml 配置文件,其他代码都是 0 修改。当然对于基于 annotation 的方式的配置也是同样的语义修改即可。
前面一种方式对原有项目的修改比较少,但是 Springcloud 的项目依赖都还在。
我们有些客户选择了另外一种更简单直接的方式,既然原有 SDK 中服务发现负载均衡包括各种服务治理能力都不需要了,干脆这些依赖也全部干掉。从最终的镜像大小看,整个项目的体量也得到了极大的瘦身。这种方式客户根据自己的实际使用方式,进行各种裁剪,最终大部分是把 Spring cloud 退化成 Spring boot。
迁移中还有另外一部分比较特殊,就是微服务外部访问的 Gateway。
Spring cloud 有两种功能类似的 Gateway,Zuul 和 Springcloud Gateway。基于 Eureka 的服务发现,将内部微服务映射成外部服务,并且在入口处提供安全、分流等能力。在切换到 k8s 和 Istio 上来时,和内部服务一样,将入口各个服务的服务发现迁移到 k8s 上来。
差别在于对于用户如果在 Gateway 上开发了很多私有的业务强相关的 filter 时,这时候 Gateway 其实是微服务的门面服务,为了业务延续性,方案上可以直接将其当成普通的微服务部署在网格中进行管理。
但是大多数情况下我们推荐使用 Istio 的 Ingress Gateway 直接替换这个微服务网关,以非侵入的方式提供外部 TLS 终止、限流、流量切分等能力。
经过以上的简单改造,各种不同语言、各种不同开发框架开发的服务,只要业务协议相通,彼此可以互相访问,访问协议可以被网格管理,就都可以通过 Istio 进行统一的管理。
控制面上可以配置统一的服务管理规则。数据面上,统一使用 Envoy 进行服务发现、负载均衡和其他流量、安全、可观察性等相关能力。数据面上的服务即可以运行在容器里,也可以运行在虚机上。并且可以运行在多个 k8s 集群中。
当然在迁移过程中间,我们也支持阶段性的保留原有微服务框架的注册中心,使 Istio 和其他的服务发现结合使用的中间状态,让网格中的服务可以访问到微服务注册中心的服务。
这里是一个 Spring cloud 开发的服务运行在 Istio 服务网格上进行灰度发布的示例。上面的日志是服务调用方 Sidecar 的日志,可以看到网格将流量分发到不同的服务后端上。下面的截图是使用了华为云 ASM 服务的灰度功能,可以看到这个 Spring cloud 服务通过网格配置的分流策略,将 30%的流量分发到灰度版本上。
下面这个示例是 Spring cloud 开发的服务使用 Istio 的熔断功能。这个过程就是就是前面原理一节 Hystrix 的状态迁移图的实践,不同在于这个实现是基于 Istio 来实现的。基于服务网格不管这里的服务是什么语言或者框架开发的,都可以对访问进行熔断保护。
这里的效果截图是来自华为云应用服务网格 ASM 的应用拓扑,可以非常清新的看到服务级别、服务实例级别流量变化情况,服务和服务实例的健康状态,从而展示故障的 Spring cloud 实例被隔离的全过程。从拓扑图上可以看到有个实例异常满足熔断阈值,触发了熔断,网格数据面向这个故障实例上分发的流量逐渐减少,直到完全没有流量,即故障实例被隔离。通过这种熔断保护保障服务整体访问的成功率。
下面三个流量拓扑演示了故障恢复的过程。
可以看到:
初始状态这个故障实例被隔离中,没有流量;
当实例自身正常后,网格数据面在将其隔离配置的间隔后,重新尝试给其分配流量,当满足阈值要求则该实例会被认为是正常实例,可以和其他两个实例一样接收请求。
最终可以看到三个实例上均衡的处理请求。
即实现了故障恢复。
最后,通过微服务、容器、k8s 和 Istio 的关系图来总结今天的内容:
1)微服务和容器都有轻量和敏捷的共同特点,容器是微服务非常适合的一个运行环境;
2)在云原生场景下,在微服务场景下,容器从来都不是独立存在的,使用 k8s 来编排容器已经是一个事实标准;
3)Istio 和 k8s 在架构和应用场景上的紧密结合,一起提供了一个端到端的微服务运行和治理的平台。
4)也是我们推荐的方案,使用 Istio 进行微服务治理正在成为越来越多用户的技术选择。
以上四个关系顺时针结合在一起为我们的解决方案构造一个完整的闭环。
附
华为云早在 2018 年就率先发布全球首个 Istio 商用服务——应用服务网格(Application Service Mesh,简称 ASM),华为云应用服务网格是一种高性能、高可靠性和易用性的全托管的服务网格,支持虚拟机、容器等多种基础设施,支持跨区域多云多集群服务的统一治理。以基础设施的方式为用户提供服务流量管理、服务运行监控、服务访问安全以及服务发布能力。
目前,华为云应用服务网格已服务于互联网、汽车、制造、物流、政府等多个行业的近千家客户,满足不同行业客户的业务诉求。华为云将在此过程中积累的丰富经验,转化为代码贡献给 Istio 社区,极大的推动了 Istio 技术的成熟和社区的快速发展。同时,华为云还大力投入服务网格的技术布道,通过社区论坛、技术会议、视频直播、专业书籍等各种方式,推动服务网格技术传播和普及。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/6f244e741d3714b6e1648b141】。文章转载请联系作者。
评论