我的新课
《C2C 电商系统微服务架构120天实战训练营》
在公众号
儒猿技术窝
上线了,感兴趣的同学,可以长按扫描下方二维码了解课程详情:
在云环境下,技术栈可谓是多种多样,通过不同的技术生成不同的应用。如何能将这些异构的服务或者应用有机地串联起来,成为了服务治理的重大课题,在这样的大背景下
Istio架构为这样应用场景提供了
服务治理的功能,
Istio提供的流量治理、策略、遥测、访问安全等功能至今都被人津津乐道。
今天就围绕Istio架构的实现原理为大家介绍如下内容:
-
为什么选择
Istio
-
什么是
Istio
-
Istio架构原理
-
Istio服务治理功能介绍
随着业务的复杂度提高,为了应对高并发、大流量,系统架构对服务/应用进行拆分。拆分以后的服务/应用可以进行分布式部署,来应对高并发带来的系统压力以及处理复杂的业务逻辑。
这样的做法会造成系统中存在大量独立的服务或者应用,它们分布在不同的进程、主机上面,在它们之间互相调用的时候就存在服务治理的问题。
微服务就是一个典型的例子,微服务的开发和运维对程序员来说是一个挑战。
分而治之的思想使得业务本身的规模和复杂度不降反增。
在分布式系统中,网络可靠性、通信安全、网络时延、网络拓扑变化等都成了关注的焦点,同时服务注册、服务发现、负载均衡、服务间通讯、分布式调用链追踪都是要解决的问题。
为了解决这个问题,把服务治理部分抽象成公共库,让所有微服务都使用这个公共库。如图1所示,在Node 1 和 Node 2 上分别用Service 1 和Service 2两个服务,它们分别针对自己的业务逻辑都有对应的服务治理的SDK,通过这个SDK完成服务治理的服务发现、服务注册等功能。
如果将图1中的SDK包含到开发框架中(例如:Spring Cloud),当运用这种开发框架后就拥有服务治理的能力了。SDK的模式虽然解耦了业务逻辑和服务治理,由于在一个开发框架中,因此业务逻辑需要和 服务治理的SDK一起编译,发布以后业务逻辑和服务治理的代码在一个进程中运行。这会导致业务代码和 SDK 基于同一种语言,无法兼容其他语言开发的服务。同时,在服务治理升级时,需要升级整个服务,即使业务逻辑没有改变。如果说图1的模式,SDK和业务代码在同一进程,因此需要对其进行解耦,把服务治理从业务代码中剥离出来。如图2所示,红色的部分代替了原来的SDK,其使用了Sidecar模式。在这种形态下,业务逻辑和服务治理在独立的进程下运行。
图2
Sidecar解耦业务逻辑和服务治理
Sidecar的模式使两者代码和运行无耦合。如图3所示,业务逻辑就好像绿色的方块,再其右边的蓝色方块就是Istio提供的Sidecar(边车),也就是通过这个Sidecar与网络中其他服务的Sidecar进行链接,从而实现服务之间的通信。
这样业务逻辑可以使用不同的语言进行开发,升级也相互独立,而其他的服务治理的工作,例如:服务注册、服务发现、负载均衡、通讯等都由Sidecar来完成。
图3
Istio的Sidecar模式
这里通过业务逻辑与服务治理的角度,将使用Istio之前和之后的微服务做区分,从以下三个维度进行对比。
因此在使用Istio架构以后,会将业务逻辑与服务治理在运行进程、技术栈和服务升级三个方面进行完全解耦,通过Sidecar模式打造分布式系统的最佳实践,接下来就来看看Istio包括哪些内容。
众所周知
Istio
是一个
Service Mesh
形态的,用于服务治理的开放平台。这里的服务
“治理”不仅限于“微服务”,可以推广到任何服务。只要存在服务或者应用,在它们之间存在访问,也存在对服务与应用的管理,都可以使用到 Istio
。
如图
4
所示,在
Istio
官方介绍中,其功能包括:连接(
Connect
)、安全(
Secure)、控制(Control)和观察(Observe)
图4
Istio 官方功能介绍
-
连接:
通过流量规则控制服务间的流量和调用,实现负载均衡、熔断、故障注入、重试、重定向等功能。
-
安全:
提供认证机制、通道加密、服务访问授权等安全能力,增强访问的安全性。
-
控制:
通过可动态插拔、可扩展的策略实现访问控制、速率限制、配额管理、服务计费等能力。
-
观察:
获取服务运行数据和输出,提供调用链监控和日志收集能力。
在微服务时代,
Kubernetes
提供了服务的部署、升级、扩容等运行管理能力,但在服务治理方面,如服务的熔断、限流、动态路由、调用链追踪显得能力不足。
Istio作为服务治理的架构刚好在这一点上弥补了Kubernetes的不足,成为了Kubernetes的好搭档。
既然把
Istio
吹上了天,就来看看
Istio 在服务访问的过程中
有哪些建树吧。如图
5
所示,有两个
Pod容器,分别存放两个不同的服务,Service A和Service B。其中Service A
由
Java进行开发,而Service B由Python进行开发。
-
Service A通过
服务发现获取
Service B
服务实例列表,如果
Service B存在多个水平扩展(Service B集群),还需要根
据负载均衡策略选择一个具体的
Service B
实例。
-
为了保证安全性,服务之间的请求和响应需要启用双向认证和通道加密。
-
在一段时间内,
Service A在访问Service B不断出现错误,需要进行熔断处理,停止对Service B的请求动作。
-
针对
Service B的处理能力,
设置最大连接的请求数、访问超时等参数,从而对其进行服务保护。
-
如果有需要可以将
Service A对Service B发起的请求重定向到其他服务上。
-
如果
Service B
有新、老两个版本,在执行灰度发布的时候,将
Service A请求的部分流量(20%
)导入到
Service B的新版本中,其他的流量(80%
)导入到
Service B的老版本上。随着Service B
新版本的逐步稳定,再将剩下的
80%
流量导入到新版本上。
-
对
Service A调用 Service B
的调用链进行追踪,为提升服务之间的调用效率提供数据依据。
Istio 针对服务治理的功能
在上一节中介绍了什么是Istio,是针对其功能进行的描述,看上去比较抽象,这里从Istio的工作机制和架构进一步进行描述。如图4所示,Istio整个架构可以分为控制面和数据面两部分,控制面主要包括Pilot、Mixer、Galley、Citadel等组件;数据面由伴随服务部署的代理Envoy组成,Envoy针对服务完成服务治理的逻辑。这里我们按照Istio的运行机制将每个步骤标上序号,逐个介绍。序号并不表示执行的顺序,只是为了方便标注,为的是讲解功能。在数据面中的交互通过带箭头的实线表示,数据面和控制面的交互通过虚线标注。其资源是通过Kubernetes进行部署的,在Node 1 和Node 2 通过Pod容器部署了服务A和服务B,其中服务B有两个版本V1 和V2,分别部署在Node 2 的两个Pod中。通过描述服务A调用服务B不同的版本,以及外部请求访问服务A的过程给大家讲述Istio各个组件的工作流程。
图4
Istio的控制面和数据面
由于
Istio使用了Sidecar代理的模式,将业务逻辑和服务治理进行了解耦。因此在 Kubernetes
场景下创建
Pod
时,同时创建
Sidecar
容器。实际上是注入并创建了
istio-proxy和istio-init两个容器。其中istio-proxy包含了Pilot-agent和Envoy两个进程。Envoy作为处理服务之间请求流量的进程在服务调用中起到重要的作用,因此在图4中特别标注出来。
在注入Envoy以后,假设服务A调用服务B,因此需要通过Envoy向服务B发起请求。服务A如何得知服务B的访问地址能,就需要通过服务发现的方式完成。此时,Envoy 需要调用管理面组件 Pilot 的服务发现接口,获取服务B的实例列表。Pilot 直接从运行平台提取数据并将其转换成 Istio 的服务发现模型,这种服务发现的方式还支持Kubernetes、Consul等平台。
当服务A得知服务B的地址以后,就会通过服务A向Envoy发送请求流量。发出的流量成为Outbound,在服务B端会接收到这个流量成为Inbound。图4中从服务A流出的流量(Outbound)会被服务A侧的 Envoy拦截,而当流量作为流入的流量(Inbound)到达服务B时,会被服务B侧的Envoy拦截。这里拦截的目的是对流量进行控制,特别是在高并发的情况下会对某些服务进行流量的限制。
服务A作为请求的发起方,Envoy根据配置的负载均衡策略选择服务实例,并连接对应的实例地址。这些负载均衡的策略是通过Pilot以配置文件的形式下发到Envoy上实现的,具体策略如RANDOM和ROUND_ROBIN。图4中访问服务B,V2版本的时候,发现该版本有多个服务B,此时就需要使用负载均衡策略访问其中某个服务B了。
Envoy 从 Pilot 中获取配置的流量规则,在拦截到 Inbound 流量和Outbound 流量时执行治理逻辑。和流量拦截不同的是,其目的是为了访问同一服务的不同版本。服务A通过Envoy获取规则,通过规则判断将流量分发到服务B的V1或V2版本。
在服务A和服务B之间建立双向认证和通道加密,并基于服务的身份进行授权管理。同样由Pilot下发安全配置,在服务A和服务B对应的Envoy上加载证书和密钥来实现双向认证,证书和密钥由管理面的Citadel组件维护。
在服务间通信时,通信双方的Envoy会连接管理面的Mixer组件上报访问数据。例如:监控指标、日志和调用链都可以通过这种方式进行收集。
在左下角有个“外部请求访问”,其作为这个网格之外的请求访问网格内的服务A。因此在入口处有一个Envoy扮演入口网关的角色。外部服务通过Gateway访问服务A。如果需要负载均衡以及流量治理的策略,都在这个Gateway的Envoy进行设置。
最后是管理面的galley组件。它不面向数据面提供服务,而是在控制面上向其他组件提供支持。主要负责验证控制面的配置信息格式和内容的正确性,并将配置信息提供给 Pilot和 Mixer组件使用。
通过上面Istio架构原理的介绍,把控制面和数据面的组件给大家过了一遍。由于篇幅问题不能在这里逐个展开介绍,由于
Istio
的主要功能是服务治理,这里选取几个服务治理中经常使用的功能给大家介绍,也算是窥豹一斑吧。
服务路由
服务路由在实际场景中比较常见,如图
5
所示
Service
A
根据不同的路由
:Test
.com/ServiceB,
Test
.com/ServiceC
, Test
.com/ServiceD
,分别访问
Service
B
、
C
和
D
。
图
5
服务路由
正如在
“
Istio
架构原理”章节中提到的,
Istio
配置规则从
Pilot
发起传送到
Envoy
上执行。其配置文件格式基本与
Kubernetes
相似。具体到图
5
的配置文件会使用到
VirtualService
类型的配置。
VirtualService
定义了对特定目标服务的流量规则。它在表示一个虚拟服务,功能是将满足条件的流量转发到对应的服务(一个或者多个)。
如代码段
1
所示,在
Istio
的配置文件中
,
按照红色数字描述如下
:
-
在
kind中定义VirtualService的类型
-
定义要访问的入口服务的名称,这里
ServiceA作为入口服务,通过它访问后面的三个服务。
-
在
hosts中定义主机的url地址作为路由的一部分,因为这里的地址访问按照“Test.com/ServiceB”的方式进行访问,因此定义为“Test.com”。
-
这里针对
http请求进行路由,因此在http下面的match(匹配)中的uri中定义prefix,显然如果要访问“Test.com/ServiceB”,这里的prefix需要定义为“/ServiceB”。实际上就是具体服务路由的地址。
-
最后,针对
uri地址制定对应的route,在destination(目标)的host中定义服务的名称:“ServiceB”。ServiceC、D的定义和B的基本一致,不再赘述。
流量切分
上面描述了简单路由的规则设置,如果遇到需要更具访问内容进行流量切分的情况,或者需要按照比例切分流量的情况配置文件的内容就需要修改了。如图
6
所示,
Service
A
要访问
Service
B
的三个不同版本
V
1
、
V
2
、
V
3
。当
URI
为
”Test.com/status”
和
”Test.com/
data
”
的时候会请求
Service
B
的
V
2
和
V
3
版本,导入的流量分别是
2
0%
和
8
0%
(红线标注的部分)。其他
URI
路由到
Service
B
的
V
1
版本(绿线标注的部分)。
照旧看看配置文件的每个配置项的内容,按照红色数字描述如下:
-
在
http
-
match
的部分用来匹配
URI
,这里有两个
prefix
分别是:
“
data
”和“
status
”,当请求
URI
满足这个两个条件中的一个时进入下面的路由选择。
-
在路由选择
route
的
destination
中对应了
ServiceB
服务的,
subset
:
V
2
也就是
V
2
版本。
W
eight
设置是
2
0
,意思是
2
0%
的流量,流入
ServiceB
的
V
2
版本。
-
在路由选择
route
的
destination
中对应了
ServiceB
服务的,
subset
:
V
3
也就是
V
3
版本。
W
eight
设置是
80
,意思是
80%
的流量,流入
ServiceB
的
V
3
版本。
-
最后,如果在没有命中上述两个
prefix
的情况下,流量会流入
ServiceB
的
V
1
版本。
负载均衡
负载均衡是服务治理中经常遇到的功能,来看看在
Istio
中是如何实现的。如图
7
所示,
Service
A
会访问
Service
B
V
2
版本的集群以及
Service
B
V
1
版本的集群。针对同一个服务的两个不同版本的集群,需要使用两种不同的负载均衡策略,分别是
ROUND_ROBIN
和
RANDOM
。
图
7
负载均衡
在看完负载均衡的需求之后再来看看如何通过配置文件实现它,在介绍配置文件之前先来介绍一下
DestinationRule
的规则描述。
如果说
VirtualService
是一个虚拟
Service
,
其描述的
内容是
“从服务流出的请求
被哪个服务处理
”,
那么
DestinationRule
描述的是
“流入的
请求到达
服务之后如何处理
”
,从字面意思理解就是
目标规则,
如果落到负载均衡的这个例子上来说,也就是流量到达
Service
B
的两个版本(
V
1
、
V
2
)以后如何进行访问。
-
规则配置定义为
DestinationRule
,表示处理服务流入的请求。
-
这里请求流入的服务是
Service
B
,其从在两个版本,每个版本都是以集群的方式存在的。
-
针对
Service
B
版本
V
2
的情况,在
trafficPolicy
(流量规则)的
loadBalancer
(负载均衡)中使用了
ROUND
_ROBIN