深度解析Istio系列之流量控制篇
在我们公众号的上一篇文章中,我们对比了当前较流行的几款服务网格产品,主要关注的是Istio。得益于良好的模块化设计,Istio的各个组件设计清晰,分工明确,几个大的组件之间甚至可以独立工作,所以接下来我们将逐一深入分析Istio的各个组件。本文首先详细分析一下我们最常用的流量管理功能所对应的模块——Pilot和Envoy。
Istio基本架构
Istio基本架构图如下图所示,网格东西向及南北向的流量控制,核心思路是由Pilot维护管理策略,并通过标准接口下发到Envoy Proxy中,由Envoy最终实现流量的转发。
Istio服务网格逻辑上分为数据平面和控制平面:
数据平面:由一组以Sidecar方式部署的智能代理(Envoy)组成,这些代理可以调节和控制微服务之间所有的网络通信。
控制平面:服务管理和配置代理来路由流量。
其中与流量管理相关的主要包括控制层面的Pilot以及数据层面的Envoy Proxy。
控制平面组件——Pilot
Pilot的基本架构如下图所示。
Pilot功能
根据上图,Pilot主要实现下述功能:
1.统一服务模型
统一服务模型主要功能是从底层平台获取服务相关信息以及通过RuleAPI定义的服务间流量规则,以kubernetes为例,统一服务模型从平台上获取注册在Pod、Service、Node以及流量规则信息,获取到各种信息后转变成数据平面上可理解的数据格式存储在Abstract Model中。
2. 标准数据平面API
获取到各种服务信息以及流量规则之后,会通过该API下发到数据面的Sidecar中。
3. 业务DSL语言(Domain Specific Language)
SL语言提供了面向业务的高层抽象,可以被运维人员理解和使用。运维人员使用该DSL定义流量规则并下发到Pilot,这些规则被Pilot翻译成数据面的配置,再通过标准API分发到Envoy实例,可以在运行期对微服务的流量进行控制和调整。
Pilot的规则DSL是采用K8S API Server中的Custom Resource (CRD)实现的,因此和其他资源类型如Service Pod Deployment的创建和使用方法类似,都可以用Kubectl进行创建。
通过运用不同的流量规则,可以对网格中微服务进行精细化的流量控制,如按版本分流,断路器,故障注入,灰度发布等。
Pilot实现
与Pilot相关的服务主要有两个:
(1)Discovery Service
对应的docker为gcr.io/istio-release/pilot,进程为pilot-discovery,该组件的功能包括:
从Service Provider中获取服务信息,除了这里主要介绍的Kubernetes之外,Istio还支持Consul作为Service Provider。
从Kubernetes API Server中获取流量规则(Kubernetes CRD Resource)。
将服务信息和流量规则转化为数据面可以理解的格式,通过标准的数据面API下发到网格中的各个Sidecar中。
(2)Kubernetes API Server
提供Pilot相关的CRD Resource的增、删、改、查。和Pilot相关的CRD有以下几种:
Virtualservice:用于定义路由规则,如根据来源或 Header 制定规则,或在不同服务版本之间分拆流量。
DestinationRule:定义目的服务的配置策略以及可路由子集。策略包括断路器、负载均衡以及 TLS 等。
ServiceEntry:用 ServiceEntry 可以向Istio中加入附加的服务条目,以使网格内可以向Istio 服务网格之外的服务发出请求。
Gateway:为网格配置网关,以允许一个服务可以被网格外部访问。
EnvoyFilter:可以为Envoy配置过滤器。由于Envoy已经支持Lua过滤器,因此可以通过EnvoyFilter启用Lua过滤器,动态改变Envoy的过滤链行为。我之前一直在考虑如何才能动态扩展Envoy的能力,EnvoyFilter提供了很灵活的扩展性。
数据平面组件——Envoy
Envoy实现
Istio通过K8s的Admission Webhook机制实现了sidecar的自动注入,Istio集群中的每个微服务会被加入Envoy相关的容器。下面是官方示例productpage微服务的Pod内容,可见除productpage之外,Istio还在该Pod中注入了两个容器gcr.io/istio-release/proxy_init和gcr.io/istio-release/proxyv2。
1.初始化容器器proxy_init
Productpage的Pod中有一个InitContainer proxy_init,InitContrainer是K8S提供的机制,用于在Pod中执行一些初始化任务,在Initialcontainer执行完毕并退出后,才会启动Pod中的其它container。
在proxy_init中执行iptables.sh脚本,该脚本的作用是通过配置iptable来劫持Pod中的流量。
2.运行时Sidecar容器器proxyv2
Envoy的大部分配置都是dynamic resource,包括网格中服务相关的service cluster, listener, route规则等。这些dynamic resource是通过xDS接口从Istio控制面中动态获取的。但Envoy如何知道xDS server的地址呢?这是在Envoy初始化配置文件中以static resource的方式配置的。
在Proxyv2容器运行时,有两个进程pilot-agent和envoy。
1)pilot-agent进程
该进程根据K8S API Server中的配置信息生成Envoy的配置文件,并负责启动Envoy进程。注意Envoy的大部分配置信息都是通过xDS接口从Pilot中动态获取的,因此Agent生成的只是用于初始化Envoy的少量静态配置。
2)envoy进程
Envoy由Pilot-agent进程启动,启动后,Envoy读取Pilot-agent为它生成的配置文件,然后根据该文件的配置获取到Pilot的地址,通过数据面标准API的xDS接口从pilot拉取动态配置信息,包括路路由(route),监听器器(listener),服务集群(cluster)和服务端点(endpoint)。Envoy初始化完成后,就根据这些配置信息对微服务间的通信进行寻址和路由。
Envoy相关概念
数据面组件中涉及的概念比较多,这里列举如下。
Host:能够进行网络通信的实体(在手机或服务器等上的应用程序)。在 Envoy 中主机是指逻辑网络应用程序。只要每台主机都可以独立寻址,一块物理硬件上就运行多个主机。
Downstream:下游(downstream)主机连接到 Envoy,发送请求并或获得响应。
Upstream:上游(upstream)主机获取来自 Envoy 的链接请求和响应。
Cluster:集群(cluster)是 Envoy 连接到的一组逻辑上相似的上游主机。Envoy 通过服务发现发现集群中的成员。Envoy 可以通过主动运行状况检查来确定集群成员的健康状况。Envoy 如何将请求路由到集群成员由负载均衡策略确定。
Mesh:一组互相协调以提供一致网络拓扑的主机。Envoy mesh 是指一组 Envoy 代理,它们构成了由多种不同服务和应用程序平台组成的分布式系统的消息传递基础。
运行时配置:与 Envoy 一起部署的带外实时配置系统。可以在无需重启 Envoy 或更改 Envoy 主配置的情况下,通过更改设置来影响操作。
Listener: 侦听器(listener)是可以由下游客户端连接的命名网络位置(例如,端口、unix域套接字等)。Envoy 公开一个或多个下游主机连接的侦听器。一般是每台主机运行一个 Envoy,使用单进程运行,但是每个进程中可以启动任意数量的 Listener(监听器),目前只监听 TCP,每个监听器都独立配置一定数量的(L3/L4)网络过滤器。Listenter 也可以通过 Listener Discovery Service(LDS)动态获取。
Listener filter:Listener 使用 listener filter(监听器过滤器)来操作链接的元数据。它的作用是在不更改 Envoy 的核心功能的情况下添加更多的集成功能。Listener filter 的 API 相对简单,因为这些过滤器最终是在新接受的套接字上运行。在链中可以互相衔接以支持更复杂的场景,例如调用速率限制。
Envoy 已经包含了多个监听器过滤器。
Http Route Table:HTTP 的路由规则,例如请求的域名,Path 符合什么规则,转发给哪个 Cluster。
Health checking:健康检查会与SDS服务发现配合使用。但是,即使用其他服务发现方式,也有相应需要进行主动健康检查的情况。
xDS:xDS 是一个关键概念,它是一类发现服务的统称,通过对 xDS 的请求来动态更新 Envoy 配置,其包括如下几类:
CDS:Cluster Discovery Service
EDS:Endpoint Discovery Service
SDS:Service Discovery Service
RDS:Route Discovery Service
LDS:Listener Discovery Service
Envoy配置
Envoy的配置包含两部分,初始化配置和动态更新的配置。
1.Envoy初始配置
Pilot-agent进程根据启动参数和K8S API Server中的配置信息生成Envoy的初始配置文件,并负责启动Envoy进程。从ps命令输出可以看到Pilot-agent在启动Envoy进程时传入了pilot地址和zipkin地址,并为Envoy生成了一个初始化配置文件envoy-rev0.json。
2.Envoy动态更新配置
通过管理接口获取完整配置。从Envoy初始化配置文件中,我们可以大致看到Istio通过Envoy来实现服务发现和流量管理的基本原理。即控制面将xDS server信息通过static resource的方式配置到Envoy的初始化配置文件中,Envoy启动后通过xDS server获取到dynamic resource,包括网格中的service信息及路由规则。
详细流程如下:
Pilot-agent根据启动参数和K8S API Server中的配置信息生成Envoy的初始配置文件envoy-rev0.json,该文件告诉Envoy从xDS server中获取动态配置信息,并配置了了xDS server的地址信息,即控制面的Pilot。
Pilot-agent使用envoy-rev0.json启动Envoy进程。
Envoy根据初始配置获得Pilot地址,采用xDS接口从Pilot获取到Listener,Cluster,Route等d动态配置信息。
Envoy根据获取到的动态配置启动Listener,并根据Listener的配置,结合Route和Cluster对拦截到的流量进行处理。
服务间交互流程
下图表述了服务与服务之间通过sidecar进行交互的流程。
基本流程分析如下:
Productpage发起对Details的调用:http://details:9080/details/0 。
请求被Pod的iptable规则拦截,转发到15001端口。
Envoy的Virtual Listener在15001端口上监听,收到了该请求。
请求被Virtual Listener根据原目标IP(通配)和端口(9080)转发到0.0.0.0_900这个listener。
根据0.0.0.0_9080 listener的http_connection_manager filter配置,该请求采用"9080" route进行分发。
"9080"这个route的配置中,host name为details:9080的请求对应的cluster为outbound|9080||details.default.svc.cluster.local。
outbound|9080||details.default.svc.cluster.local cluster为动态资源,通过eds查询得到其endpoint为192.168.206.21:9080。
请求被转发到192.168.206.21,即Details服务所在的Pod,被iptable规则拦截,转发到15001端口。
Envoy的Virtual Listener在15001端口上监听,收到了该请求。
请求被Virtual Listener根据请求原目标地址IP(192.168.206.21)和端口(9080)转发到192.168.206.21_9080这个listener。
根据92.168.206.21_9080 listener的http_connection_manager filter配置,该请求对应的cluster为 inbound|9080||details.default.svc.cluster.local 。
inbound|9080||details.default.svc.cluster.local cluster配置的host为127.0.0.1:9080。
请求被转发到127.0.0.1:9080,即Details服务进行处理。
参考链接
https://zhaohuabing.com/post/2018-09-25-istio-traffic-management-impl-intro/
https://istio.io/zh/docs/concepts/what-is-istio/
https://www.cnblogs.com/xishuai/p/microservices-and-service-mesh.html