【阅读原文】戳:服务网格容灾系列场景(三):使用服务网格应对服务级故障容灾
服务级故障是云上云原生业务可能遭遇到的一类较为常见的故障类型,该类故障通常表现为:在Kubernetes集群中部署一个或多个服务、其部分或全部的工作负载遭遇到了不可用或服务降级的问题。通常来说,故障范围将小于可用区级别,但仍会因为服务单点故障造成业务不可用或降级问题。该类故障产生的原因多种多样,可能包括但不限于:
• 基础设施原因:
- 节点服务器操作系统崩溃、驱动程序错误、网络接口配置错误等。
- 节点CPU内存或磁盘空间不足。
- 持久化存储不可用或IO性能下降等。
- ……
• Kubernetes集群配置原因:
- 如不合理的Pod资源设置导致Pod资源达到上限、性能收到影响。
- 由于不合理的节点污点/反亲和性规则导致Pod调度到非预期的节点。
- 由于集群中的多个准入Webhook组件冲突使得Pod定义发生非预期的变化。
- 集群中的CNI问题导致Pod间通信中断或网络策略失效。
- ……
• 应用程序问题:
- 应用代码缺陷:代码中有死循环或计算密集型操作导致CPU资源过度占用、或代码中有内存泄漏问题等。
- 外部服务依赖问题:应用启动时依赖的数据库、缓存等服务不可用,导致启动失败。
- 消息队列故障:消息队列中的消息积压影响下游服务的处理能力。
- ……
当服务级故障发生时,其所影响的服务范围可能小于可用区级别或地域级别的故障,但由于该类故障成因之复杂与诡谲,其影响范围往往难以界定,可能涉及一个集群中部署的一个服务、一个可用区内的数个服务、或是分散在多个可用区的若干工作负载等等。
当故障发生时,可以参考地域级故障的思路,将业务对等部署在两个Kubernetes集群,通过入口DNS切换将所有流量转移至另一个单独部署的集群。然而此方法面临以下问题:
1. 全局切换意味着全部流量的转移,这意味着一个集群要瞬间开始承接远大于日常负载的流量,这对于扩容速度、缓存重建等都是极大的挑战,如果这些工作未能做好,可能将原本无故障的机房也打垮,以上因素使得全局切换往往是需要人工参与操作并决断的。
2. 无法应对复杂的故障场景:由于服务级故障发生的范围分散、故障的服务可能分别位于两个集群之中,导致全局切换到任何一个集群都无法缓解故障的状况。
由于服务级故障成因众多、频率较高,如何用心智负担更小、更加快速的手段应对服务级故障往往是。服务网格ASM支持打通多个集群的服务发现以及网络互访,结合多地域、多集群的服务对等部署模式,在任意的服务发生故障时,只要多集群中还存在可用的服务工作负载实例,服务网格都可以在秒级对流量目标实现无感切换,保证业务应用在全局的可用性。
容灾架构一览
在应对服务级这一类小规模的故障行为时,我们主要从以下两个方面考虑容灾架构的设计:
• 故障发现以及流量转移机制:当服务的部分或全部工作负载发生故障时,需要存在有效的机制检测到故障的发生、通知相关联系人、并将流量从故障的工作负载上转移到其它健康的工作负载。同时当流量转移发生时,还需要额外考虑流量转移目标的可达性、因为可能需要调用另外一个集群中的对等工作负载、而与该工作负载之间可能是无法直接可达的。
• 应用部署拓扑:在部署应用时,需要考虑将应用中的服务以怎样的拓扑部署在Kubernetes集群环境中。一般来说,更加复杂的拓扑可以应对更多的故障模式,比如,相比在单个集群中部署一套服务、在多个集群中对等部署多套服务可以更好地保证服务的可用性,因为后者可以应对一些因为Kubernetes集群配置原因导致的服务级故障。
1. 故障转移机制:服务网格基于地理位置的故障转移
服务网格支持基于地理位置的故障转移机制。通过在一段时间窗口内持续检测工作负载是否连续发生响应错误来判断工作负载的故障状态,并在工作负载故障时自动地将流量目标转移到其它可用的工作负载上。
我们以如下的应用部署拓扑为例:两个集群分别位于不同地域,其中对等部署着两套应用,在一个集群下 、每个服务的工作负载通过拓扑分布约束(Topology Spread Constraints)均匀分布在不同可用区。
1.1. 正常流量拓扑
在服务均为正常的情况下,服务网格会将流量保持在同一可用区,以最小化调用目标位置对于请求延迟的影响。
1.2. 工作负载异常时自动故障转移,按照地理位置规划转移优先级
服务网格ASM的网格代理会持续检测每个工作负载对请求的响应状况,判断是否工作负载连续出现响应错误的情况超过给定阈值,可能的响应错误情况包括:
• 服务返回5xx状态码
• 服务连接失败
• 服务连接断开
• 服务响应超时
当响应错误数量超过阈值时,服务网格ASM会将该服务剔除出负载均衡池,以将请求目标转移到其它可用的工作负载上。流量故障转移会按照如下的优先级顺序进行:
当有其他同可用区的工作负载可用时,其它同可用区的工作负载将被优先选择转移的流量目标,若没有同可用区的工作负载可用、则选择同地域不同可用区的工作负载作为流量目标。
同时,可以通过配置Prometheus实例采集服务网格相关指标,当故障转移发生时,可通过指标告警通知到相关业务联系人。
1.3. 服务网格实现跨地域流量转移
当同地域下任何可用区都没有可用的工作负载时,还可以将流量转移到另一个地域下其它集群中对等部署的相同服务。该过程由服务网格自动完成。
在这种情况下,需要依赖两个集群之间的路由打通:即从A集群的Pod可以连通B集群的Pod。对于阿里云多地域的场景,此时可以选择使用CEN(云企业网)打通集群之间的物理网络、实现集群互通,但需要付出较高的带宽成本。
CEN带宽成本较高,且对于阿里云集群+非阿里云集,非阿里云集群+非阿里云集群,云上集群+云下集群等复杂网络场景,往往存在无法将物理网络打通的限制。在这些场景下,ASM提供了通过公网打通的方式,通过使用ASM跨集群网络代理,可以利用公网在集群间通过公网建立一条mTLS的安全信道,用于必要的跨集群通信。通过这种方式,也可以实现安全的跨地域流量转移。
2. 应用部署拓扑:多种集群拓扑的支持方式及对比
服务网格ASM基于地理位置的故障转移机制可以无缝应用在多种应用部署的拓扑方式之中,不同的部署拓扑会有故障维度的支持范围以及资源成本、运维门槛上的区别。可根据实际资源及应用状况选择。
2.1. 单集群多可用区部署
单集群多可用区部署是考虑容灾主题时最为简单的部署方案。ACK支持多可用区(AZ)节点池,在节点池的创建和运行过程中,通过为节点池选择多个AZ的vSwitch,并在配置扩缩容策略时选择均衡分布策略,允许在伸缩组指定的多可用区(即指定多个专有网络交换机)之间均匀分配ECS实例。
Pros:
• 部署运维比较简单:只需要开通一个包含多可用区节点池的ACK集群、并通过拓扑分布约束(Topology Spread Constraints)在可用区间均匀部署应用即可。
• 节省云资源消耗:包括负载均衡、数据库、Kubernetes集群、服务网格在内的云资源只需要在同一地域内准备一份即可。
Cons:
• 对故障维度的覆盖不够:没有在很多关键基础设施和部署上做到防止单点。该部署拓扑对于可用区硬件故障、节点硬件故障等造成的工作负载故障可以进行处理,但对于Kuebernetes错误配置、应用程序依赖等造成的故障则无法处理,此外、也无法处理地域级别的故障。
2.2. 多集群、多可用区、单地域部署
在单集群多可用区部署的基础上,可以将单集群变为多集群,每个集群各自拥有各自的配置。由于每个集群拥有各自独立的配置,这样将能够有效解决由于Kubernetes集群配置导致的故障问题。当故障转移发生时,服务网格会优先寻找同可用区的其它工作负载、接着会在同地域不同可用区的其它可用工作负载中任选一个座位故障转移目标。
在通过此等部署拓扑进行应用部署时,建议为两个集群分别选择各自独立的可用区,如果两个集群的可用区重合,可能会造成同可用区的工作负载优于同集群工作负载的状况,这对业务来说有可能是非预期的。
Pros:
• 相比单集群部署来说,该拓扑拓展了覆盖的故障成因维度,进一步提高了系统整体的可用性。
• 集群互通成本低、质量好:当需要将流量故障转移到另一个集群中的服务时,此种部署拓扑具有较低的集群互通成本——只需要两个集群在同一VPC内部即可。
Cons:
• 部署和运维复杂度较高:需要将服务对等地部署在两个Kubernetes集群环境内。
• 云资源成本增加:负载均衡、Kubernetes集群等云资源基础设施需要部署多份。
• 无法应对更大规模的故障:当遇到地域级的故障、或者某个地域下的某服务工作负载全部不可用时,将无法处理这种故障情况。
在此种部署拓扑下,您需要对多个集群的网络配置进行规划、避免出现网络冲突的情况,具体请参考多集群网络规划[1] 。
一般来说,不建议将多集群多可用区单地域部署作为首选方案,因为其在显著增加部署运维复杂度的同时,没有带来与之相应的最高级别可用度。
2.3. 多集群、多地域部署
在多集群的基础上,将单地域部署变为多地域部署。通过这种方式,可以保证每个服务的部署在不同的维度上都是分散的:它们拥有不同的可用区、部署地域、部署集群、部署依赖。通过这种方式,可以尽可能地保证在发生任何可能原因导致的故障时,都有正常的工作负载在提供服务。
Pros:
• 能够保持极高级别的可用性:尽可能将同一服务的工作负载分散到不同地域、不同可用区、不同集群,以尽量保持服务的可用性。
Cons:
• 部署和运维复杂度较高:需要将服务对等地部署在两个Kubernetes集群环境内、且为了更好的推送延迟表现,服务网格实例也需要准备两份。
• 需要打通集群网络:跨地域网络互通较为复杂——可以通过CEN打通集群间物理网络、但带宽成本较高;也可以选择使用ASM跨集群网络代理打通,此时网络打通基于公网、可处理一般的故障转移情况、但不适用于大量的跨集群调用需求。
当您拥有高可用级别的需求时,推荐采用这种拓扑进行业务应用的部署。在跨集群网络打通场景下,可选用以下两种方式进行集群网络的打通:
• 使用云企业网直接打通跨地域集群底层网络。使用这种方式,您需要对多个集群的网络配置进行规划、避免出现网络冲突的情况,具体请参考多集群网络规划[1] 。接下来,需要使用云企业网打通跨地域网络,具体请参考跨地域连接[2] 。
• 使用ASM跨集群网络代理实现跨集群网络打通和故障转移,以优化资源成本并解决复杂网络互通问题。使用这种方式,两个集群的网络可以不直接互通、不需要进行集群网络规划,有关ASM跨集群网络代理的细节,请参考使用ASM跨集群网格代理实现多集群跨网络互通[3] 。
2.4. 多集群、多地域、多云部署
该部署拓扑是最高级别的部署拓扑方式。通过在不同云提供商之间、或是云上和云下提供两套Kubernete集群环境,以保证单个云厂商提供的任意基础设施在发生故障时仍可以由其它云厂商的基础设施提供服务。
ASM完全支持管理友商云的任意K8s集群,以及线下IDC集群,且ASM提供了跨集群网络代理用于打通集群通信,只要具备公网联通能力,即可实现基于任意多个K8s集群构建高可用系统。在多云部署拓扑下,基本环境和多地域多集群部署类似,但服务网格ASM无法在非阿里云环境提供托管控制面,可以使用ASM远程控制面来解决服务网格在跨云情况下的推送延迟问题(具体参考:使用ASM远程控制面降低推送延迟[4] )。
Pros:
• 能够保持极高级别的可用性:尽可能将同一服务的工作负载分散到不同地域、不同可用区、不同集群、不同云厂商,以尽量保持服务的可用性。
Cons:
• 部署和运维复杂度最高:需要将服务对等地部署在两个Kubernetes集群环境内。同时,由于云厂商之间基础架构、以及云产品能力的不同,在多云环境下的部署可能遭遇大量的兼容性以及其他未知问题。
• 需要打通集群网络:跨地域网络互通较为复杂——可以通过CEN打通集群间物理网络、但带宽成本较高、且可能面临网络冲突等导致无法打通的问题;此时可以选择使用ASM跨集群网络代理打通,网络打通基于公网、可处理故障转移跨集群调用的情况。
在多云部署拓扑下,您同样可以参照跨地域集群的网络打通方案,此处不再赘述。
容灾配置实践
本文以推荐采用的多集群、多地域部署方式为例,展示服务级容灾的实践流程。
步骤一:应用双活环境准备
1、在两个地域中分别部署多可用区Kubernetes集群
首先在两个地域下部署两个多可用区的Kubernetes集群。
创建两个Kubernetes集群,分别命名为cluster-1和cluster-2,都设置为开启使用EIP暴露API Server。创建集群时,分别为cluster-1和cluster-2指定两个不同的地域。
在具体创建每个集群时,还需要选择来自多个可用区的交换机以支持集群的多可用区。其它配置保持默认即可,节点池将默认使用均衡分布的扩缩容策略。
具体操作,请参见创建ACK托管集群[5] 。
2、基于两个集群构建多主控制面架构高可用服务网格
在集Kubernetes集群创建完成后,需要创建服务网格ASM实例,并将Kubernetes集群加入服务网格ASM进行管理。
多主控制面架构是一种使用服务网格管理多个Kubernetes集群的架构模式。在这种架构中,多个服务网格实例分别管理各自的Kubernetes集群的数据面组件,并为集群中的网格代理分发配置。同时,这些实例依靠共享的信任根证书,实现跨集群的服务发现和通信。
在跨地域的集群部署环境中,尽管可以将两个集群都通过统一的服务网格实例进行管理,但构建多主架构的服务网格仍然是跨地域容灾方案的最佳实践,这是由于以下原因:
1. 拥有更好的配置推送延迟表现:在跨地域部署的情况下,使用多个服务网格实例分别连接到就近的Kubernetes集群中的网格代理拥有更好的配置推送性能表现。
2. 稳定性表现更佳:遇到地域不可用的极端故障时,一个服务网格控制面连接所有集群的方式,可能会出现集群因为无法连接到控制面而无法同步配置或启动的问题;而在多主控制面架构下,正常地域中的网格代理仍可正常连接到控制面,不影响服务网格配置的下发与网格代理的正常启动。这保证了正常地域中的ASM网关和业务服务在故障地域中的服务网格控制面不可连接的情况下仍然可以正常扩容。
有关构建多主架构服务网格的具体操作,请参见通过ASM多主控制面架构实现多集群容灾[6] ,完成其中的步骤一、步骤二与步骤三。创建后,将得到两个名为mesh-1和mesh-2的服务网格实例,两个实例均对所有集群做了服务发现并进行了多集群网络配置、确保集群互访互通。
在创建每个服务网格实例时,为保证实例本身的可用区级高可用,也需要在创建服务网格实例时选择与集群相对应的多可用区:具体来说,在创建时,需要在交换机处选择两个不同可用区的交换机。
3、在两个集群中分别部署ASM网关和示例服务
1)参考在ASM入口网关中使用网络型负载均衡NLB[7] ,在ASM实例mesh-1、mesh-2中创建名为ingressgateway的ASM网关、并绑定一个网络型负载均衡NLB。NLB可用区选择和ASM实例与ACK集群同样的两个可用区。
本例中采用关联网络型负载均衡NLB的ASM网关作为流量入口。您也可以使用应用型负载均衡ALB作为应用的流量入口,ALB同样具备多可用区容灾能力。
创建好两个ASM网关后,记录两个ASM网关各自绑定的NLB域名。
2)在ASM实例mesh-1、mesh-2中,为命名空间default开启Sidecar自动注入。具体操作参考启用自动注入[8] 。
3)在两个ACK集群内对等部署示例应用。
使用kubectl连接到cluster-1集群(参考获取集群KubeConfig并通过kubectl工具连接集群[9] ),并执行以下命令来部署示例应用。
kubectl apply -f- <<EOF apiVersion: v1 kind: Service metadata: name: mocka labels: app: mocka service: mocka spec: ports: - port: 8000 name: http selector: app: mocka --- apiVersion: apps/v1 kind: Deployment metadata: name: mocka-cn-hangzhou-h labels: app: mocka spec: replicas: 1 selector: matchLabels: app: mocka template: metadata: labels: app: mocka locality: cn-hangzhou-h spec: nodeSelector: topology.kubernetes.io/zone: cn-hangzhou-h containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: cn-hangzhou-h - name: app value: mocka - name: upstream_url value: "http://mockb:8000/" ports: - containerPort: 8000 --- apiVersion: apps/v1 kind: Deployment metadata: name: mocka-cn-hangzhou-k labels: app: mocka spec: replicas: 1 selector: matchLabels: app: mocka template: metadata: labels: app: mocka locality: cn-hangzhou-k spec: nodeSelector: topology.kubernetes.io/zone: cn-hangzhou-k containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: cn-hangzhou-k - name: app value: mocka - name: upstream_url value: "http://mockb:8000/" ports: - containerPort: 8000 --- apiVersion: v1 kind: Service metadata: name: mockb labels: app: mockb service: mockb spec: ports: - port: 8000 name: http selector: app: mockb --- apiVersion: apps/v1 kind: Deployment metadata: name: mockb-cn-hangzhou-h labels: app: mockb spec: replicas: 1 selector: matchLabels: app: mockb template: metadata: labels: app: mockb locality: cn-hangzhou-h spec: nodeSelector: topology.kubernetes.io/zone: cn-hangzhou-h containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: cn-hangzhou-h - name: app value: mockb - name: upstream_url value: "http://mockc:8000/" ports: - containerPort: 8000 --- apiVersion: apps/v1 kind: Deployment metadata: name: mockb-cn-hangzhou-k labels: app: mockb spec: replicas: 1 selector: matchLabels: app: mockb template: metadata: labels: app: mockb locality: cn-hangzhou-k spec: nodeSelector: topology.kubernetes.io/zone: cn-hangzhou-k containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: cn-hangzhou-k - name: app value: mockb - name: upstream_url value: "http://mockc:8000/" ports: - containerPort: 8000 --- apiVersion: v1 kind: Service metadata: name: mockc labels: app: mockc service: mockc spec: ports: - port: 8000 name: http selector: app: mockc --- apiVersion: apps/v1 kind: Deployment metadata: name: mockc-cn-hangzhou-h labels: app: mockc spec: replicas: 1 selector: matchLabels: app: mockc template: metadata: labels: app: mockc locality: cn-hangzhou-h spec: nodeSelector: topology.kubernetes.io/zone: cn-hangzhou-h containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: cn-hangzhou-h - name: app value: mockc ports: - containerPort: 8000 --- apiVersion: apps/v1 kind: Deployment metadata: name: mockc-cn-hangzhou-k labels: app: mockc spec: replicas: 1 selector: matchLabels: app: mockc template: metadata: labels: app: mockc locality: cn-hangzhou-k spec: nodeSelector: topology.kubernetes.io/zone: cn-hangzhou-k containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: cn-hangzhou-k - name: app value: mockc ports: - containerPort: 8000 --- apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: mocka namespace: default spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: test number: 80 protocol: HTTP --- apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: demoapp-vs namespace: default spec: gateways: - mocka hosts: - '*' http: - name: test route: - destination: host: mocka port: number: 8000 EOF
使用kubectl连接到cluster-2集群,并执行以下命令来对等部署示例应用。
kubectl apply -f- <<EOF apiVersion: v1 kind: Service metadata: name: mocka labels: app: mocka service: mocka spec: ports: - port: 8000 name: http selector: app: mocka --- apiVersion: apps/v1 kind: Deployment metadata: name: mocka-ap-northeast-1a labels: app: mocka spec: replicas: 1 selector: matchLabels: app: mocka template: metadata: labels: app: mocka locality: ap-northeast-1a spec: nodeSelector: topology.kubernetes.io/zone: ap-northeast-1a containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: ap-northeast-1a - name: app value: mocka - name: upstream_url value: "http://mockb:8000/" ports: - containerPort: 8000 --- apiVersion: apps/v1 kind: Deployment metadata: name: mocka-ap-northeast-1b labels: app: mocka spec: replicas: 1 selector: matchLabels: app: mocka template: metadata: labels: app: mocka locality: ap-northeast-1b spec: nodeSelector: topology.kubernetes.io/zone: ap-northeast-1b containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: ap-northeast-1b - name: app value: mocka - name: upstream_url value: "http://mockb:8000/" ports: - containerPort: 8000 --- apiVersion: v1 kind: Service metadata: name: mockb labels: app: mockb service: mockb spec: ports: - port: 8000 name: http selector: app: mockb --- apiVersion: apps/v1 kind: Deployment metadata: name: mockb-ap-northeast-1a labels: app: mockb spec: replicas: 1 selector: matchLabels: app: mockb template: metadata: labels: app: mockb locality: ap-northeast-1a spec: nodeSelector: topology.kubernetes.io/zone: ap-northeast-1a containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: ap-northeast-1a - name: app value: mockb - name: upstream_url value: "http://mockc:8000/" ports: - containerPort: 8000 --- apiVersion: apps/v1 kind: Deployment metadata: name: mockb-ap-northeast-1b labels: app: mockb spec: replicas: 1 selector: matchLabels: app: mockb template: metadata: labels: app: mockb locality: ap-northeast-1b spec: nodeSelector: topology.kubernetes.io/zone: ap-northeast-1b containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: ap-northeast-1b - name: app value: mockb - name: upstream_url value: "http://mockc:8000/" ports: - containerPort: 8000 --- apiVersion: v1 kind: Service metadata: name: mockc labels: app: mockc service: mockc spec: ports: - port: 8000 name: http selector: app: mockc --- apiVersion: apps/v1 kind: Deployment metadata: name: mockc-ap-northeast-1a labels: app: mockc spec: replicas: 1 selector: matchLabels: app: mockc template: metadata: labels: app: mockc locality: ap-northeast-1a spec: nodeSelector: topology.kubernetes.io/zone: ap-northeast-1a containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: ap-northeast-1a - name: app value: mockc ports: - containerPort: 8000 --- apiVersion: apps/v1 kind: Deployment metadata: name: mockc-ap-northeast-1b labels: app: mockc spec: replicas: 1 selector: matchLabels: app: mockc template: metadata: labels: app: mockc locality: ap-northeast-1b spec: nodeSelector: topology.kubernetes.io/zone: ap-northeast-1b containers: - name: default image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/go-http-sample:tracing imagePullPolicy: IfNotPresent env: - name: version value: ap-northeast-1b - name: app value: mockc ports: - containerPort: 8000 --- apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: mocka namespace: default spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: test number: 80 protocol: HTTP --- apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: demoapp-vs namespace: default spec: gateways: - mocka hosts: - '*' http: - name: test route: - destination: host: mocka port: number: 8000 EOF
上述两个指令在两个集群中对等部署了一个包含名为mocka、mockb、mockc的服务的应用,每个服务下包含两个只有1副本的无状态部署,分别通过不同的nodeSelector分布到不同可用区的节点上,并通过配置环境变量来返回自己所在的可用区。在本示例中,工作负载分别部署在阿里云的两个地域、四个可用区,分别是cn-hangzhou-h、cn-hangzhou-k、ap-northeast-1a、ap-northeast-1b。
访问该应用时,将会响应应用的整条调用链路上的pod信息,例如:
-> mocka(version: cn-hangzhou-h, ip: 192.168.122.66)-> mockb(version: cn-hangzhou-k, ip: 192.168.0.47)-> mockc(version: cn-hangzhou-h, ip: 192.168.122.44)%
本示例中直接使用pod的nodeSelector字段来手动选择pod所在的可用区,这是出于示例演示直观的目的。在实际高可用环境的构建中,您应该配置拓扑分布约束,来保证pod尽可能分布在不同的可用区中。
具体配置方法请参考工作负载高可用配置[10]。
4、配置全局流量管理GTM实现基于双ASM入口网关的跨地域容灾
可以通过配置全局流量管理GTM、结合两个ASM网关绑定的NLB实例实现入口网关的多活容灾,在正常情况下,可以实现将应用域名通过CNAME域名解析1:1地解析到两个NLB实例域名上。具体操作,参见GTM如何实现多活负载并容灾[11]。
步骤二:开启基于地理位置的故障转移能力
服务网格ASM基于地理位置的故障转移能力默认开启,但需要结合目标规则中设置的主机级熔断规则才可以生效。
使用kubectl分别连接到cluster-1和cluster-2集群(参考获取集群KubeConfig并通过kubectl工具连接集群[9]),并执行以下命令来部署mocka、mockb、mockc服务的主机级熔断规则。
kubectl apply -f- <<EOF apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: mocka spec: host: mocka trafficPolicy: outlierDetection: splitExternalLocalOriginErrors: true consecutiveLocalOriginFailures: 1 baseEjectionTime: 5m consecutive5xxErrors: 1 interval: 30s maxEjectionPercent: 100 --- apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: mockb spec: host: mockb trafficPolicy: outlierDetection: splitExternalLocalOriginErrors: true consecutiveLocalOriginFailures: 1 baseEjectionTime: 5m consecutive5xxErrors: 1 interval: 30s maxEjectionPercent: 100 --- apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: mockc spec: host: mockc trafficPolicy: outlierDetection: splitExternalLocalOriginErrors: true consecutiveLocalOriginFailures: 1 baseEjectionTime: 5m consecutive5xxErrors: 1 interval: 30s maxEjectionPercent: 100 EOF
其中的outlierDetection是服务网格对于服务故障的检测及故障端点驱逐机制,解释如下:
部署目标规则实现主机级熔断的自动故障检测机制后,服务网格基于地理位置的故障转移机制将开始生效,此时默认的调用方式将是同可用区优先路由的。
尝试向服务域名发出请求,预期输出如下:
curl mock.asm-demo.work/mock -v * Host mock.asm-demo.work:80 was resolved. * IPv6: (none) * IPv4: 8.209.247.47, 8.221.138.42 * Trying 8.209.247.47:80... * Connected to mock.asm-demo.work (8.209.247.47) port 80 > GET /mock HTTP/1.1 > Host: mock.asm-demo.work > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < date: Wed, 11 Dec 2024 12:10:56 GMT < content-length: 153 < content-type: text/plain; charset=utf-8 < x-envoy-upstream-service-time: 3 < server: istio-envoy < * Connection #0 to host mock.asm-demo.work left intact -> mocka(version: ap-northeast-1b, ip: 10.1.225.40)-> mockb(version: ap-northeast-1b, ip: 10.1.225.31)-> mockc(version: ap-northeast-1b, ip: 10.1.225.32)%
可以看到服务调用链路始终保持在同一可用区的工作负载上。
步骤三:演练服务级故障情景
通过修改mockb服务某个可用区的容器镜像来模拟服务的部分工作负载故障的情况。
进入ACK控制台,找到任意集群中的任意mockb Deployment(本例中选取cluster-2中的mockb-ap-northeast-1a),单击操作列的编辑,并更换其镜像内容以及启动指令,以模拟开发者错误发布的情况。
将镜像名称修改为registry.cn-hangzhou.aliyuncs.com/acs/curl:8.1.2,该容器镜像启动后不会监听任何端口,因此对该容器的任何访问均会遭遇连接失败。
为容器加上启动指令sleep 36000让容器可以正常启动。
修改后,持续访问应用域名,以演练故障发生时的情况。
当请求发送到非ap-northeast-1a可用区时,可以看到请求仍然保持在正常可用区内:
curl mock.asm-demo.work/mock -v * Host mock.asm-demo.work:80 was resolved. * IPv6: (none) * IPv4: 8.209.247.47, 8.221.138.42 * Trying 8.209.247.47:80... * Connected to mock.asm-demo.work (8.209.247.47) port 80 > GET /mock HTTP/1.1 > Host: mock.asm-demo.work > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < date: Wed, 11 Dec 2024 12:10:56 GMT < content-length: 153 < content-type: text/plain; charset=utf-8 < x-envoy-upstream-service-time: 3 < server: istio-envoy < * Connection #0 to host mock.asm-demo.work left intact -> mocka(version: ap-northeast-1b, ip: 10.1.225.40)-> mockb(version: ap-northeast-1b, ip: 10.1.225.31)-> mockc(version: ap-northeast-1b, ip: 10.1.225.32)%
当请求发送到ap-northeast-1a可用区时,第一次请求将会发现请求到达mockb服务时发生连接被拒绝的情况。
curl mock.asm-demo.work/mock -v * Host mock.asm-demo.work:80 was resolved. * IPv6: (none) * IPv4: 112.124.65.120, 121.41.238.104 * Trying 112.124.65.120:80... * Connected to mock.asm-demo.work (112.124.65.120) port 80 > GET /mock HTTP/1.1 > Host: mock.asm-demo.work > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < date: Wed, 11 Dec 2024 12:08:45 GMT < content-length: 220 < content-type: text/plain; charset=utf-8 < x-envoy-upstream-service-time: 48 < server: istio-envoy < * Connection #0 to host mock.asm-demo.work left intact -> mocka(version: cn-hangzhou-h, ip: 192.168.122.135)upstream connect error or disconnect/reset before headers. reset reason: remote connection failure, transport failure reason: delayed connect error: Connection refused%
当再次请求到ap-northeast-1a可用区时,可以发现mocka后请求的服务已经发生了故障转移,优先转移到了同地域不同可用区的ap-northeast-1b。故障转移将通过主机级熔断对故障的检测而自动发生。
curl mock.asm-demo.work/mock -v * Host mock.asm-demo.work:80 was resolved. * IPv6: (none) * IPv4: 8.209.247.47, 8.221.138.42 * Trying 8.209.247.47:80... * Connected to mock.asm-demo.work (8.209.247.47) port 80 > GET /mock HTTP/1.1 > Host: mock.asm-demo.work > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/1.1 200 OK < date: Wed, 11 Dec 2024 12:10:59 GMT < content-length: 154 < content-type: text/plain; charset=utf-8 < x-envoy-upstream-service-time: 4 < server: istio-envoy < * Connection #0 to host mock.asm-demo.work left intact -> mocka(version: ap-northeast-1a, ip: 10.0.239.141)-> mockb(version: ap-northeast-1b, ip: 10.1.225.31)-> mockc(version: ap-northeast-1b, ip: 10.1.225.32)%
【可选】步骤四:配置服务级故障产生时的告警
主机级熔断可以产生一系列相关指标,以帮助判断熔断是否发生,部分指标的说明如下:
您可以通过配置Sidecar代理的proxyStatsMatcher使Sidecar代理上报相关指标,然后使用Prometheus采集并查看熔断相关指标。
1. 通过proxyStatsMatcher配置Sidecar代理上报熔断指标。在配置proxyStatsMatcher时,选中正则匹配,配置为.*outlier_detection.*。具体操作,请参见proxyStatsMatcher。
2. 重新部署httpbin无状态工作负载。具体操作,请参见重新部署工作负载。
配置主机级熔断指标采集及告警
配置完成上报主机级别的熔断指标后,您可以配置采集相关指标到Prometheus,并基于关键指标配置告警规则,实现熔断发生时的及时告警。通过这种方式,可以在服务级故障发生时及时通知到相关联系人,以在故障发生时及时监控到并定位服务故障根因。
以下以可观测监控Prometheus版为例说明如何配置主机级熔断指标采集和告警。
1. 在可观测监控Prometheus版中,为数据面集群接入阿里云ASM组件或升级至最新版,以保证可观测监控Prometheus版可以采集到暴露的熔断指标。有关更新接入组件的具体操作,参考接入组件管理。(如果参考集成自建Prometheus实现网格监控配置了通过自建Prometheus采集服务网格指标,则无需操作。)
2. 创建针对主机级熔断的告警规则。具体操作,请参见通过自定义PromQL创建Prometheus告警规则。配置告警规则的关键参数的填写示例如下,其余参数可参考上述文档根据自身需求填写。
相关链接:
[1] 多集群网络规划
https://help.aliyun.com/zh/asm/user-guide/plan-cidr-blocks-for-multiple-clusters-on-the-data-plane?
[2] 跨地域连接
https://help.aliyun.com/zh/cen/user-guide/manage-inter-region-connections
[3] 使用ASM跨集群网格代理实现多集群跨网络互通
[4] 使用ASM远程控制面降低推送延迟
https://help.aliyun.com/zh/asm/user-guide/reduce-push-latency-using-asm-remote-control-plane
[5] 创建ACK托管集群
[6] 通过ASM多主控制面架构实现多集群容灾
[7] 在ASM入口网关中使用网络型负载均衡NLB
[8] 启用自动注入
https://help.aliyun.com/zh/asm/user-guide/manage-global-namespaces#title-2ff-57z-yk9
[9] 获取集群KubeConfig并通过kubectl工具连接集群
[10] 工作负载高可用配置
[11] GTM如何实现多活负载并容灾
https://help.aliyun.com/zh/dns/how-does-gtm-implement-multi-live-load-and-disaster-tolerance
我们是阿里巴巴云计算和大数据技术幕后的核心技术输出者。
获取关于我们的更多信息~