前 言
eBay的数据中心已经全面由k8s接管,目前有3个主数据中心,20个边缘数据中心,100+ k8s集群。然而,相对于容器运行时的迁移进度,云原生网络的迁移进度相对滞后。将数据中心基于硬件负载均衡的网络迁移到基于Istio的云原生网络遇到了不少的挑战,尤其是在高可用,流量管理方面。在虚拟机时代,eBay数据中心的生产环境应用采用了一种两层负载均衡的架构,我们称之为2-Tier。为了实现生产环境迁移到软件负载均衡,eBay Network team设计并实现了一种基于Istio的两层应用网关的架构,以实现高可用的流量管理。
流量管理现状
在过去,eBay数据边缘以及数据中心采用的是F5以及Citrix的硬件负载均衡,有数千对硬件负载均衡设备支撑着全站点的生产环境流量。近年来,公司内部通过启动了不同的项目来全面推广硬件负载均衡设备替换为软件负载均衡,并且也取得了一定的成功,如通过UFES项目将数据边缘/POP的硬件负载均衡替换成了基于IPVS以及Envoy的软件负载均衡,如数据边缘/POP的负载均衡已经通过UFES项目替换成基于IPVS/Envoy的软件负载均衡。
eBay在应用硬件负载均衡的时候引入了层(tier)的概念,主要是指负载均衡的层数,以提供更完善的流量管理能力。根据负载均衡设备所处的不同流量转发位置,我们称之为Web Tier以及App Tier。
目前数据中心生产环境全部采用2 tier的架构,也就是有两层LB。生产环境主要是通过两层硬件负载均衡设备将内网流量最终转发到服务器。之所以会有两层硬件负载均衡设备,主要出于以下几点考量:
防止客户端缓存VIP造成数据面影响。在一层LB的情况下,如果单个集群的VIP以及服务器整体宕机,由于客户端本地DNS缓存以及TTL以及DNS TTL,流量依旧可能转发到该VIP。在两层LB的情况下,我们能够容忍App VIP以及以下的服务端整体宕机,流量可以从Web VIP层转发到其它的数据中心。
精确控制流量。在一层LB的情况下,流量的控制只能通过GTM采用ratio的方式解析DNS,但同样因为DNS缓存的原因,GTM无法精确控制流量。而在两层LB的情况下,数据中心的流量可以在Web VIP层精确控制。
其架构图如下:
图2-1 eBay数据中心2-Tier架构
数据中心软件负载均衡遇到的挑战主要有以下几点:
超大规模。由于微服务架构,生产环境3000+应用,数万个VIP,数十万个Pod。
高安全性。由于合规要求,支付相关的应用必须全链路加密,并且集成软件防火墙。
Istio生产可用。由于我们采用的软件负载均衡方案是Istio,而Istio直到2019年才生成可用。
高可用流量管理架构
应用网关(Application Gateway)是对数据中心内部的软件负载均衡的统称,它本身包含了基于IPVS的L4负载均衡,基于Istio/Envoy的L7的负载均衡,而且我们对于单层网关多集群的架构已经有了大量的生产化实践。更多关于应用网关的细节可以参考【eBay基于Istio的应用网关的探索和实践】。
为了不损失两层硬件负载均衡的高可用以及精确的流量管理等特性,我们实现了2-Tier应用网关。其中L4采用IPVS,内部称为TLB。为了实现2-Tier架构,这里最核心的一点是我们用不同的VIP端口表示不同的tier,比如443端口的VIP类似为原来的Web Tier 而7443类似为App Tier。
当然2 Tier还能够有其它的实现方式,比如我们可以直接将L4/L7部署翻倍,或者在App Tier加一层L4等等,但是综合考虑资源使用率,运行维护的成本,以及数据面的延迟,我们最终采用了基于端口的2层方案,其架构图如下:
图3-1 基于Istio的2-Tier架构
2-Tier流量管理功能
网关VIP配置在GTM,通过DNS解析返回不同的IP给客户端
GTM会对后端的VIP做TCP的建卡检查
支持有跨数据中心的流量
支持在Web Tier层将流量切到其它数据中心
支持数据面全链路加密
2-Tier故障转移:
Web Tier层依靠GTM的健康检查,将不健康的Web VIP标志为不可用,客户端的DNS缓存会导致数据面的影响
App Tier层依靠Web Tier层的主动/被动健康检查,App Tier层的故障可以快速将流量切到其它数据中心
App Tier层依靠被动健康检查,会将故障Pod标志为不可用
如果应用在一个数据中心的服务器全宕机,最终会在Web Tier层将App VIP标志为不可用,进而可速将流量切到其它数据中心,避免客户端的DNS缓存导致数据面的影响
在2-Tier的应用网关架构中,由于采用了DSR(Direct Server Return),IPIP tunnel, 同时还涉及到同数据中心的数据流以及跨数据中心的数据流。其中的配置信息以及TCP连接都相对来说比较复杂,总体的数据流如下图所示:
图3-2 基于Istio的2-Tier架构数据流
同数据中心数据流
同数据中心的数据流相对来说比较简单,Web Tier和 App Tier中间的流量转发本质上是一次本地转发,因为ingressgateway同时监听了该VIP的两个端口,并不需要出容器网络,但是需要多走两次网络协议栈。总体上数据面的性能会很接近1-Tier的网关架构,下面详细介绍一下同数据中心数据面转发。
假设client访问https://abc.ebay.com,abc.ebay.com后端绑定了两个VIP, VIP1和VIP2
DNS解析返回VIP1给该client,client尝试与VIP1:443建立TCP连接,此时TCP数据包为:
Source: CIP,CPort, Destination:VIP1, 443
由于L4通过BGP宣告了VIP网段的路由,最终数据包会被路由到L4集群,也就是某一个TLB Pod。
L4上配置了相应的IPVS规则,会将VIP1转发到后端的目的地址,也就是IngressGateway pod中的一个,但此时的转发是通过IPIP隧道模式,此时的数据包为:
内层包:Source: CIP,CPort, Destination:VIP1, 443
外层包:Source: L4 Pod IP, L4 Pod Port, Destination:IngressGateway Pod IP, 443
IPIP数据包转发到IngressGateway Pod会先由IngressGateway Pod上的tun0 interface解封外层包,解包之后会得到内层包。此时内层包的目的地址是VIP1,为了能够让IngressGateway继续处理该数据包而不是转发或者丢弃,所以这里会将VIP1的地址也配置到IngressGateway Pod的tun0 interface,并且停用了ARP,这样IngressGateway Pod能够继续处理该数据包,最终通过网络协议栈将数据包传到应用层,也就是Envoy。
由于Envoy运行在None Prevelidge模式,无法监听1024以下的端口,所以Envoy实际监听在8443,这里还涉及到443到8443的端口的装换。这里的转换是通过netd,也就是我们的CNI在创建VIP时会在iptables的PREROUTING链里面创建一个目的端口转换的规则,所以在Envoy里面可以认为443端口等同于8443。
由于Ingress Gateway Envoy监听在8443,所以该数据包会经过Envoy的8443的filter chain,包括 listener filter, tls inspect以及http connection manager,由于给8443网关配置了一条路由将全部流量转发到相应的7443 VIP,最终会将8443的请求以99%的概率转发到该VIP的7443端口。
由于Ingress Gateway Envoy同时也在7443监听了该VIP,所以这里是一次本地转发,同样的数据包会经过一次Envoy的7443的filter chain。到了这里数据面就和原来1-Tier的架构一样了。
Envoy的7443 filter chain会将数据包转发到upstream pod,也就是最终后端的应用服务器。应用处理完请求后将回报给Ingress Gateway Envoy,最终通过DSR直接将请求返回到客户端,请求不需要经过L4。自此本数据中心一次完整的请求和响应结束。
同数据中心TCP连接如下,我们可以看到这里一共有4个TCP连接,如下图所示:
图3-3 2-Tier架构同数据中心TCP连接
从IngresGateway角度看2-Tier本地转发的数据面,可以看到Envoy本身同时监听在VIP:8443以及VIP:7443,所以对于本地转发需要两次完整的经过linux网络协议栈。
图3-3 同数据中心linux网络协议栈
跨数据中心数据流
跨数据中心的流量1-6步与同数据中心数据流相同,从第7步开始有所差异:
由于Ingress Gateway Envoy监听在8443,所以该数据包会经过Envoy的8443的filter chain,包括 listener filter, tls inspect以及http connection manager,由于给8443网关配置了一条路由将全部流量转发到相应的7443 VIP,这里会有1%的概率将请求转发到远端的7443 VIP,也就是跨数据中心的流量。
对于跨数据中心的流量而言,其客户端是本数据中心的IngressGateway, 目的地址是远端的VIP2的7443端口,其TCP数据包如下:
Source: Local IngressGateway Pod IP,CPort, Destination:VIP2, 7443
由于远端数据中心的L4宣告了VIP2的地址,所以最终数据包会被路由到VIP2所在的TLB,然后经过IPIP tunnel转发到远端的IngressGateway Pod
数据包经过远端的IngressGateway Pod的tunnel解包之后会进入Envoy的7443 filter chain,这一步与上面第9步相同。
最终请求转发到远端的后端应用服务器,服务器处理完请求后将响应转发到远端的IngressGatway Pod,然后远端的IngressGatway Pod DSR到本数据中心的IngressGatway Pod,再进过一次DSR到真实的客户端。自此一次跨数据中心完整的请求和响应结束。
可以看到,对于跨数据中心的请求来说,需要经过远端的L4以及L7,会产生额外的跳数(hop),请求到了远端网关后就相当于1-Tier的数据面,相应的响应也需要经过两次DSR才能最终回到客户端。但是由于远端流量的权重通常会是1%,在正常情况下不会影响整体的延时,只有在应急状态,比如集群服务器出现大规模的故障,或者需要进行数据中心流量退出时才会将流量整体转到远端。
跨数据中心TCP连接:
图3-4 2-Tier架构跨数据中心TCP连接
从IngresGateway角度看2-Tier远端转发的数据面,请求会先被转发到本地Envoy VIP1:8443,经过一次网络协议栈。之后再转发到远端Envoy监听的VIP2:7443,这种数据面和基于硬件负载均的2层架构一致。
图3-5 跨数据中心linux网络协议栈
应用网关是由基于IPVS的L4以及基于Envoy的L7所组成。IPVS/L4的配置相对来说比较简单和直观,其主要作用就是为一组workload暴露服务,本质上和kube-proxy一样。只不过这里的workload是ingressgateway,并且暴露的是External IP,并且这里external IP会通过BGP路由宣告。
应用网关的L7配置都在Envoy,7层的配置相对来说也是最复杂的,因为涉及到TLS证书、L7路由规则以及上游集群的配置等等,社区有一个很详细的文档介绍Life of a Request,其中包括数据面和控制面。
下面是2-Tier应用网Envoy的示例的静态配置,实际环境中是通过XDS配置listener/route/secret/cluster/endpoints等等。但不管是静态配置还是XDS配置,最终生成的配置信息是相同的。这里最核心的一点是每一个VIP/port的组合都有独立的listener filter chain,我们用同一个VIP的不同端口代表不同的tier。
listeners:
- name: 10.0.0.1_8443
address:
socket_address:
protocol: TCP
address: 10.0.0.1
port_value: 8443
# A single listener filter exists for TLS inspector.
listener_filters:
- name: "envoy.filters.listener.tls_inspector"
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
# On the listener, there is a single filter chain that matches SNI for sample2tier.ebay.com.
filter_chains:
- filter_chain_match:
# This will match the SNI extracted by the TLS Inspector filter.
server_names: ["sample2tier.ebay.com"]
# Downstream TLS configuration.
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificate_sds_secret_configs:
name: "kubernetes://cert-system/credentialname"
filters:
# The HTTP connection manager is the only network filter.
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
route_config:
name: https.443.https-443.sampleroute.ns.10.0.0.1
virtual_hosts:
- name: webtier_service
domains: ["sample2tier.ebay.com"]
routes:
- match:
path: "/"
route:
cluster: sample2tier-443.appgw.ebay.com
# CustomFilter and the HTTP router filter are the HTTP filter chain.
http_filters:
# - name: some.customer.filter
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: outbound|7443||sample2tier-443.appgw.ebay.com
# Upstream TLS configuration.
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
load_assignment:
cluster_name: outbound|7443||sample2tier-443.appgw.ebay.com
# Static endpoint assignment.
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.0.0.1
port_value: 7443
---
listeners:
- name: 10.0.0.1_7443
address:
socket_address:
protocol: TCP
address: 10.0.0.1
port_value: 7443
…
同数据中心配置
同数据中心的请求经过Envoy的流程如下:
用户请求经过L4 IPIP tunnel到达ingressgateway,在解包之由于Envoy监听在VIP:8443,linux网络协议栈会将数据包转发到应用,也就是Envoy。
Envoy的8443 listener配置了listener_filters,其中有一个listener filter 叫TLS inspector,该linstener filter会根据检查初始的TLS握手并且并提取服务器名称(SNI),该SNI会被用来后续的过滤器匹配。
根据SNI匹配到的TLS transport socket会负责将TCP连接的数据进行解密,将解密后的数据交给network filter进一步处理。
这里最重要的network filter就是HCM(HttpConnectionManager), 它负责 创建HTTP/2 codec并且管理HTTP filter chain。HTTP/2 codec会将TCP连接中的数据流进行HTTP解码。
HCM实例化一个HTTP过滤器链,会进一步进行路由处理。这里8443的route会将请求转发到下一跳,也就是由7443的VIP所组成的ServiceEntry的上游集群中,而我们会为7443的VIP创建相应的WorkloadEntry,最终SE/WLE会被Isito的CDS/EDS所维护,并将endpoint信息动态推送到Envoy。
请求经过8443的listener以及filter chain后会被转发到local的7443 VIP,相当于是Envoy的VIP:8443将请求转发到了VIP:7443,这里也就是一次本地转发。由于配置了DestinationRule TLS simple,8443到7443的请求是simple TLS。
请求经过网络协议栈到达Envoy 7443的listener以后又会经过一次与8443类似的filter chain处理,最终请求会以mTLS方式转发到local的upstream Pod。
图3-6 同数据中心的请求经过Envoy的流程
跨数据中心数据流
在我们的设计架构里,不同的集群是不同的mesh,跨集群的调用都会经过网关,走南北向流量。
跨数据中心Envoy的配置都很类似,相当于local的7443/8443 filter chain在远端复制了一份,但是监听在不同的VIP。其架构图如下:
图3-7 跨数据中心的请求经过Envoy的流程
流量管理模型
在eBay的生产环境中,同时存在多种类型的流量,包括公网流量,内网流量以及七层转发规则的流量。多数据中心以及多层软件负载均衡的架构也增加了流量管理的复杂的,因此我们需要一套统一的模型来管理各种类型的流量。
在公网流量管理的实现中,我们引入了统一的流量管理模型,更多细节可以参考【eBay数据边缘和数据中心统一流量管理模型实践】。从spec的角度来说,公网和内网,1-Tier,2-Tier,3-Tier都可以用相同的CRD描述,也就是AccessPoint。
作为一种联邦资源,AccessPoint封装了Istio的CRD以及k8s Service, 在复用社区CRD的语义的基础上能够灵活组合,实现对不同架构的统一描述。
对于2-Tier网关架构,这里我们主要复用了社区的CRD ServiceEntry以及WorkloadEntry。这两者的关系有点类似于k8s中的Service以及Pod,本质上是将非k8s集群的服务或者Workload引入到mesh。比如我们可以定义如下ServiceEntry:
serviceEntries:
- apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
creationTimestamp: null
name: se-foo
spec:
hosts:
- foo.appgw.ebay.com
ports:
- name: https-foo
number: 7443
protocol: HTTPS
resolution: DNS
workloadSelector:
labels:
app: foo-7443
再定义一组WorkloadEntry如下:
workloadEntries:
- apiVersion: networking.istio.io/v1beta1
kind: WorkloadEntry
metadata:
creationTimestamp: null
name: https-foo-cluster-1
spec:
address: 10.0.0.1
labels:
app: foo-7443
weight: 50
status: {}
- apiVersion: networking.istio.io/v1beta1
kind: WorkloadEntry
metadata:
creationTimestamp: null
name: https-foo-cluster-2
spec:
address: 10.0.0.2
labels:
app: foo-7443
weight: 50
status: {}
通过这组配置,ServiceEntry se-foo,最终会被Istiod的服务发现成为一个Envoy的Cluster以及对应的endpoints。我们可以通过istioctl查看相关的配置:
istioctl pc cluster -n istio-system istio-ingressgateway-66f4b4f786-56wg8 | grep foo.appgw.ebay.com
foo.appgw.ebay.com 7443 - outbound STRICT_DNS
下面是 cluster foo.appgw.ebay.com 对应的endpoint:
istioctl pc ep -n istio-system istio-ingressgateway-66f4b4f786-56wg8 | grep foo.appgw.ebay.com
10.0.0.1:7443 HEALTHY OK outbound|7443||foo.appgw.ebay.com
10.0.0.1:7443 HEALTHY OK outbound|7443||fooappgw.ebay.com
下面是2-Tier主要的配置,我们可以看到其实都是使用的社区的spec以及原生的语义,
virtualServices:
- apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
creationTimestamp: null
name: foo
spec:
gateways:
- foo-gw
hosts:
- foo.ebay.com
http:
- match:
- port: 443
uri:
prefix: /
route:
- destination:
host: foo.appgw.ebay.com
port:
number: 7443
- match:
- port: 7443
uri:
prefix: /
route:
- destination:
host: foo-svc
port:
number: 8082
status: {}
gateways:
- apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: foo-gw
spec:
selector:
istio: ingressgateway-system
servers:
- hosts:
- foo.ebay.com
port:
name: foo-7443
number: 7443
protocol: HTTPS
- hosts:
- foo.ebay.com
port:
name: foo-443
number: 443
protocol: HTTPS
services:
- apiVersion: v1
kind: Service
metadata:
name: foo-svc
spec:
ports:
- name: http-8082
port: 8082
protocol: TCP
targetPort: 8082
selector:
app: foo
type: ClusterIP
status:
loadBalancer: {}
其中关键的几点是:
Gateway定义了两个端口,分别是443和7443。其中443相当于是Web Tier,通过配置virutalservice会将流量443的流量全部转发到下一层,也就是在ServiceEtnry中定义的foo.appgw.ebay.com。
转发到ServiceEntry中的请求其实还是会回到7443的gateway,可能是本地也可能是远端的。
7443的请求转发至gateway后会再做一次tls终结,由于virtualservice中也定义了7443的规则,最终会将请求转发至k8s的service,该service最终选择了应用的pod,这也是virtualservice原生支持的转发的目的。
2-Tier网关的挑战
通过统一的流量管理模型,我们已经能够在控制面对公网流量以及内网流量进行统一的管理,并且屏蔽了不同层的软件负载均衡控制面的差异。然而在公网与内网的流量对接中,我们仍然会遇到一些数据面的挑战,比如Istio社区默认强制使用SNI以及如何对不同层级的软件负载均衡进行流量退出。
内网流量分为数据中心网关以及应用网关,流量退出的接口依旧是ATC,但是实现的机制是通过将下游AccessPoint中对应的上游AccessPoint服务的WorkloadEntry的状态设置为False来实现停止流量,需要指出的是同一套机制适用于Contour以及Istio。
应用网关流量退出
对于1层软件负载均衡的网络拓扑,通过将内网NameService对应的VIP状态标志位False以停止DNS解析返回该VIP。
对于2层软件负载均衡的网络拓扑,同样是通过将WorkloadEntry健康状态标志为False的方式停止将流量转发到上游的VIP。
二层架构的流量退出涉及到App Tier层,所以需要patch全站的TFAP中需要做流量退出的可用区(AZ)所对应的workloadentry,并且考虑到我们的3层控制面架构,假设我们有5000个TFAP,每个AP有20个AZ,每个AZ中有一个k8s集群,那么我们需要总共更新的TFAP个数为:5000(fcp) + 5000*20(az) + 5000*20(cluster)。TFAP的3层架构图如下:
图5-1 TFAP 3层架构图
我们定下的目标在40秒钟内需要支持5000个TFAP以及20个AZ的流量退出,尽管做了很多代码优化以及性能调优,比如增加worker数量,通过patch减少spec更新带来的冲突,增加apiserver的max-requests-inflight以及max-mutating-requests-inflight参数到5000以减少apiserver的throttling等等,我们取得的最好成绩是是5000个TFAP,6个k8s集群在27秒钟内完成流量退出,这离我们的目标还有比较远的距离。
另外还有一个挑战是,由于引入2-Tier,相应的IngressGateway上的xds配置也翻倍了,由此也会带来xds转换延迟的增加,我们测试下来500个两端口(HTTP/HTTPS)AP的2-Tier转换成IngresGateway的延迟是20秒,而在1-Tier的架构下20s延迟对应的是6000个AP。
为了实现网络架构的高可用,很重要的一点就是能及时将网络拓扑中有问题的节点及时摘除或者停止下发流量。在2-Tier的架构中,出问题的节点可能是Web Tier VIP,也有App Tier VIP,也有能是Pod,因此需要分别在不同层面进行健康检查。
2-Tier的健康检查分为3个层面:
GTM对Web Tier进行健康检查
Web Tier 对App Tier进行健康检查
App Tier对Pod进行健康检查
在硬件负载均衡时代,每一层都健康检查都是主动探测的。GTM会对Web Tier进行TCP检查(理论上也能进行HTTPS检查,但是会消耗更多CPU,所以从来没有启用),Web Tier会对App Tier层进行TCP检查,App Tier层会对Pod进行HTTPS检查,这些健康检查能够自动将VIP或者Pod标志为不可用,从而不会接受请求,所以总体上硬件负载均衡提供了完善的健康检查机制。
图5-2 2-Tier健康检查流程
在软件负载均衡的实现中由于数据面采用的是Envoy,并且Envoy本身支持主动和被动的健康检查。其中的主动健康检查基本提供了硬件负载均衡类似的功能,而被动检查则是通过正常的数据面流量获取到的观测指标结果判断上游是否健康。由于2-tier的架构,不同的层需要检查的目标有所不同,而且这两种检查方式在实际使用过程中会有不同的挑战。
主动健康检查(Active health check)
Istio 社区并没有提供原生的主动健康检查功能,主要的考虑在于Envoy的性能。假设mesh中有1万个pod,这种健康检查带来的开销可能会影响Envoy本身的数据面,因为IngressGateway中的每个Envoy Pod都需要对整个mesh中的Pod做全量的健康检查。对于小规模的mesh,社区的建议是通过EnvoyFilter来配置,然而考虑到我们生产环境mesh的规模,这种方案显然不适合做workload级别的健康检查。
但是对于2-tier架构,主动健康检查能够作为web tier向app tier的健康检查方式,因为VIP相对于Pod的数量来说会少很多,我们可以通过给每个VIP配置一份EnvoyFilter做主动的健康检查,其EnvoyFilter配置如下。
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: health-check-7443
namespace: istio-pre-production
spec:
configPatches:
- applyTo: CLUSTER
match:
cluster:
service: private-virtual-two-tier-443.appgw.ebay.com
context: GATEWAY
patch:
operation: MERGE
value:
health_checks:
- always_log_health_check_failures: true
event_log_path: /var/log/proxy/cluster_health_check.log
healthy_threshold: 1
http_health_check:
expected_statuses:
- end: 499
start: 200
host: appgw.ebay.com
path: /
interval: 10s
no_traffic_interval: 10s
reuse_connection: false
timeout: 2s
tls_options: {}
unhealthy_threshold: 2
然而即使只对VIP做健康检查,我们仍然需要对控制面以及数据面做一些压力测试,以确保这些主动健康检查带来的开销不会对数据面造成影响。而实际上EnvoyFitler本身也是一个很重的配置,因为它是在istiod生成好mesh的配置以后再单独遍历所有的EnvoyFitler去patch已经生成好的xds配置,这里会带来比较大的时间开销。
被动健康检查(Passive health check)
被动检查的原理是通过周期性的动态的异常检测来确定上游集群中的某些主机是否异常,如果发现异常,就将该主机从连接池中隔离出去,这其实也是一种熔断的实现,用于跟踪上游服务的状态。对于HTTP服务,如果上游服务连续返回5xx错误,则可以设置在一定时间内驱逐掉这个节点,新的请求将不会被发往改节点。
Istio 社区通过DestinationRule 能够支持原生配置被动健康检查,也即trafficPolicy中的outlierDetection。然而驱逐条件中的consecutiveGatewayErrors或者consecutive5xxErrors并不适用我们的生产环境,因为无法区分5xx是应用本身返回的还是gateway返回的,而在我们生产环境中有一些网关类型的应用本身也会返回一些5xx响应码,但是对于这些节点我们并不希望驱逐。不过Envoy/Istio社区也有人提到了同样的顾虑,并且提供了相应的功能即consecutiveLocalOriginFailures。该配置重点关注从gateway发起的到上游节点的网络连接错误,比如连接超时,连接重置等等,通过该配置项我们能区分应用本身返回的5xx与基础设施层本身的网络连接问题,从而能更安全的将异常点从上游服务中驱逐掉。而且由于是DestinationRule原生支持的配置,不会给CDS带来太多开销,这也是我们最终选择的对于workload的健康检查方案。
小结
2-Tier的架构从可用性以及流量管理的角度来说,是优于1-Tier架构的,但是最终这种架构没有在生产环境全面推广。目前只有30几个平台相关的应用采用了该部署架构,主要的挑战是在第5节中提到的流量退出的性能问题以及健康检查的实现。由于短期内彻底解决这些问题的风险很高,并且有可能影响到业务:因为我们已经停止购买硬件负载均衡,生成环境应用必须启动软件负载均衡的迁移,否则在购物季可能会遇到硬件负载均衡过载的问题。所以最终在VP层面做下了这个决策:生产环境从原来的两层硬件负载均衡架构迁移到一层的应用网关软件负载。
由此而带来的反思是,技术架构需要在稳定性,可用性,复杂度以及按期交付之间做一些取舍,而最重要的是不能影响业务。另一个层面的取舍是,原本我们在实施硬件负载均衡到软件复杂均衡中必须要考虑的一个问题是功能对等(feature parity),也就是说硬件负载均衡上所有目前被用到的功能,我们在软件负载均衡的实现上也要提供一致的功能。然而从2-Tier到1-Tier的调整也同时改变了这种约束,由此带来的一系列功能调整包括但不限于以下几点:
2-Tier时代的L7流量的目的地址是App Tier VIP,我们可以精确控制L7流量的权重。但是在1-Tier的架构下,L7流量目的地址是DNS。而其中的逻辑是,既然默认的流量可以直接使用DNS作为流量入口,而不需要考虑DNS缓存以及流量权重的控制,那么L7的流量也不需要。
2-Tier时代数据中心流量的退出可以不用考虑DNS缓存的问题,尽管我们将DNS服务端TTL以及客户端DNS 缓存都设置为60s,我们仍能够在60s之内将一个数据中心的流量退出,因为在Web Tier层能够将流量切到远端数据中心而不需要调整DNS。但是在1-Tier架构中,尽管我们尝试将DNS TTL减小到30s,客户端DNS缓存减小到30s,由于客户端长连接以及TTL的原因,目前的测试结果仍然需要2min以上的时间实现一个数据中心流量退出。
2-Tier会对VIP进行主动健康检查,Web Tier层实际上是检查App Tier VIP,通过roundrobin的方式只需要连续探测下层的App Tier VIP是否健康即可。但是在1-Tier的架构下,VIP roundrobin探测的是pod的健康状态,会出现在pod之间状态跳转的问题,更重要的一点是现在对于应用网关的Pod以及VIP我们还没有实现主动健康检查。
以上这些问题都是从2-Tier转换到1-Tier的一些代价,虽然2-Tier的整体架构设计以及实现已经变成一种沉没成本,但是这种架构其实仍然隐含在我们内网L7规则的实现中。因为本质上L7的数据面就是一个VIP to VIP的2-Tier,只是相关控制面的spec有所不同,而且长远的看如果我们考虑公网内网流量统一用Istio,而公网目前是3-Tier。也许到时候我们又会回过头看重新审视并且解决这些问题。
还有很重要的一点是,目前我们还无法量化这种从2-Tier转成1-Tier所带来的风险,比如这种转变会对整个eBay数据中心的可用性带来多少百分比的影响,但是这种大规模的技术设施转型本身就充满了很多未知的技术风险和挑战,哪怕我们即使严格的实现了功能对等也可能会出现一些问题。所以目前我们只能基于历史数据。所谓的历史数据就是PCI/RNPCI那些与支付相关的的重要的应用也是采用1-Tier架构,并且没有因为这种架构导致重大网络事故,这也是支持我们从2-Tier转成1-Tier最重要的依据。或许随着越来越多的生产环境迁移到1-Tier的架构会有新的问题暴露出来,但是相信这些问题最终都会得到解决。