Skip to content

3、Istio流量管理

Istio流量管理

image-20231110102205352

目录

[toc]

本节实战

实战名称
🚩 实战:路由到版本1-2023.11.11(测试成功)
🚩 实战:基于用户身份的路由-2023.11.11(测试成功)
🚩 实战:注入 HTTP 延迟故障-2023.11.12(测试成功)
🚩 实战:注入 HTTP abort 故障-2023.11.12(测试成功)
🚩 实战:流量拆分-2023.11.12(测试成功)
🚩 实战:流量镜像-2023.11.12(测试成功)
🚩 实战:熔断示例-2023.11.13(测试成功)
🚩 实战:TCP 流量拆分-2023.11.15(测试成功)

流量管理概述

上面我们了解了 GatewayVirtualService资源对象的作用,以及它们是如何影响 Envoy 的配置的,那么这些资源对象又是如何影响流量的呢?通过 Istio 如何实现流量管理的呢?

Istio 的流量路由规则可以很容易的控制服务之间的流量和 API 调用。Istio 简化了服务级别属性的配置,比如熔断器、超时和重试,并且能轻松的设置重要的任务,如 A/B 测试、金丝雀发布、基于流量百分比切分的分阶段发布等。它还提供了开箱即用的故障恢复特性, 有助于增强应用的健壮性,从而更好地应对被依赖的服务或网络发生故障的情况。

为了在网格中路由,Istio 需要知道所有的 endpoint 在哪以及它们属于哪些服务。为了定位到 service registry(服务注册中心),Istio 会连接到一个服务发现系统。如果在 Kubernetes 集群上安装了 Istio,那么它将自动检测该集群中的服务和 endpoint。

使用此服务注册中心,Envoy 代理可以将流量定向到相关服务。大多数基于微服务的应用程序,每个服务的工作负载都有多个实例来处理流量,称为负载均衡池。默认情况下,Envoy 代理基于轮询调度模型在服务的负载均衡池内分发流量,按顺序将请求发送给池中每个成员, 一旦所有服务实例均接收过一次请求后,就重新回到第一个池成员。

Istio 基本的服务发现和负载均衡能力提供了一个可用的服务网格,但它能做到的远比这多的多。在许多情况下我们可能希望对网格的流量情况进行更细粒度的控制。作为 A/B 测试的一部分,可能想将特定百分比的流量定向到新版本的服务,或者为特定的服务实例子集应用不同的负载均衡策略。可能还想对进出网格的流量应用特殊的规则,或者将网格的外部依赖项添加到服务注册中心。通过使用 Istio 的流量管理 API 将流量配置添加到 Istio,就可以完成所有这些甚至更多的工作。

请求路由

首先我们来实现下最基本的流量请求路由的功能,这里我们将学习如何将请求动态路由到微服务的多个版本。

我们知道 Bookinfo示例包含四个独立的微服务,每个微服务都有多个版本。其中 reviews服务的三个不同版本已经部署并同时运行。我们可以在浏览器中访问 Bookinfo应用程序并刷新几次。正常会看到三种不同的 reviews服务版本的输出,有时书评的输出包含星级评分,有时则不包含。这是因为没有明确的默认服务版本可路由,Istio 将以循环方式将请求路由到所有可用版本。

我们首先来将所有流量路由到微服务的 v1版本,稍后,您将应用规则根据 HTTP 请求 header 的值路由流量。

1.路由到版本1

==🚩 实战:路由到版本1-2023.11.11(测试成功)==

实验环境:

bash
k8sv1.27.6(containerd:[root@master1 ~]#istioctl versionclientversion:1.19.3controlplaneversion:1.19.3dataplaneversion:1.19.3(8 proxies)

实验软件:

链接:https:A[实战步骤] -->B(1️⃣ 部署VirtualService)A[实战步骤] -->C(2️⃣ DestinationRule)A[实战步骤] -->D(3️⃣ 验证)

要只路由到一个版本,则需要为微服务设置默认版本的 VirtualService

🍀

应用规则

Istio 使用 VirtualService来定义路由规则,只需要应用下面的资源对象即可:

该资源清单中定义了四个 VirtualService对象,分别是 productpagereviewsratingsdetails,它们分别对应着 Bookinfo应用中的四个微服务,完整的清单如下所示:

我们可以看到这里的 VirtualService对象中都定义了 subset字段,这个字段就是用来指定微服务的版本的,这里我们将所有的微服务都指定为 v1版本,这样所有的流量都会被路由到 v1版本的微服务中,包括 reviews服务,这样我们就不会再看到星级评分了。

🍀

但是如果我们现在直接去访问 Bookinfo应用的话,是不能正常访问的,因为我们压根就还没指定这些 v1版本的微服务到底在哪里。

image-20231111144249758

🍀

这个时候就需要用到另外一个资源对象 DestinationRule了,我们需要为每个微服务创建一个 DestinationRule对象,用来指定这些微服务的实际地址,这样 VirtualService对象才能将流量路由到这些微服务中。Istio 在 DestinationRule目标规则中使用 subsets定义服务的版本,运行以下命令为 Bookinfo服务创建默认的目标规则即可:

该资源清单中定义了四个 DestinationRule对象,分别是 productpagereviewsratingsdetails几个服务的目标规则,它们分别对应着 Bookinfo应用中的四个微服务,完整的清单如下所示:

🍀

现在我们就可以正常访问 Bookinfo 应用了,并且无论刷新多少次,页面的评论部分都不会显示评级星标,这是因为我们将 Istio 配置为将 reviews 服务的所有流量路由到版本 reviews:v1,而此版本的服务不访问星级评分服务。

img

这样我们就成功将流量路由到服务的某一个版本上了。

原理分析

🍀

前面章节中我们只定义了一个名为 bookinfoVirtualService资源对象就可以正常访问了:

很明显上面这个虚拟服务对象是我们访问 Bookinfo 应用的入口路由规则,所以这个虚拟服务对象实际上是为 istio-ingressgateway入口网关服务定义的。 它将所有的流量都路由到了 productpage这个服务上,而 productpage这个服务又会去调用其他的服务来获取数据,在 productpage服务中调用其他微服务其实就是直接通过服务名称来调用的,比如调用 reviews服务就是直接通过 reviews:9080这个服务来调用的,我们可以查看 productpage的代码来验证这一点:

img

🍀

我们可以再次查看 Bookinfo 在网格内的请求架构图:

img

当我们在浏览器中访问 http:kind:VirtualServicemetadata:name:reviewsspec:hosts:- reviewshttp:- route:- destination:host:reviewssubset:v1---apiVersion:networking.istio.io/v1alpha3kind:DestinationRulemetadata:name:reviewsspec:host:reviewssubsets:- name:v1labels:version:v1- name:v2labels:version:v2- name:v3labels:version:v3

那么这两个对象是如何来影响 Envoy Sidecar 的呢?前面我们已经分析了流量从 istio-ingressgateway进来后被路由到了 productpage服务,那么 productpage又该如何去访问其他微服务呢?同样我们可以使用 istioctl proxy-config来查看 productpage服务的 Envoy 配置。

🍀

每个 Envoy Sidecar 都有一个绑定到 0.0.0.0:15001的监听器,然后利用 IP tables 将 pod 的所有入站和出站流量路由到这里,此监听器会配置一个 useOriginalDst:true,这意味着它将请求交给最符合请求原始目标的监听器。如果找不到任何匹配的虚拟监听器,它会将请求发送给返回 404 的 BlackHoleCluster,我们可以查看下 15001 端口的监听器配置:

yaml
[root@master1 istio-1.19.3]#istioctl proxy-config listeners productpage-v1-564d4686f-7vhks --port 15001 -oyaml- accessLog:- filter:responseFlagFilter:flags:- NRname:envoy.access_loggers.filetypedConfig:'@type':type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLoglogFormat:textFormatSource:inlineString:|[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"%RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%"%BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%""%REQ(USER-AGENT)%""%REQ(X-REQUEST-ID)%""%REQ(:AUTHORITY)%""%UPSTREAM_HOST%"%UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%path:/dev/stdoutaddress:socketAddress:address:0.0.0.0portValue:15001filterChains:- filterChainMatch:destinationPort:15001filters:- name:istio.statstypedConfig:'@type':type.googleapis.com/stats.PluginConfig- name:envoy.filters.network.tcp_proxytypedConfig:'@type':type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxycluster:BlackHoleClusterstatPrefix:BlackHoleClustername:virtualOutbound-blackhole- filters:- name:istio.statstypedConfig:'@type':type.googleapis.com/stats.PluginConfig- name:envoy.filters.network.tcp_proxytypedConfig:'@type':type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxyaccessLog:- name:envoy.access_loggers.filetypedConfig:'@type':type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLoglogFormat:textFormatSource:inlineString:|[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"%RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% "%UPSTREAM_TRANSPORT_FAILURE_REASON%"%BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%""%REQ(USER-AGENT)%""%REQ(X-REQUEST-ID)%""%REQ(:AUTHORITY)%""%UPSTREAM_HOST%"%UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%path:/dev/stdoutcluster:PassthroughClusterstatPrefix:PassthroughClustername:virtualOutbound-catchall-tcpname:virtualOutboundtrafficDirection:OUTBOUNDuseOriginalDst:true[root@master1 istio-1.19.3]#

🍀

实际上我们的请求是到 9080 端口(productpage 服务绑定 9080 端口)的 HTTP 出站请求,这意味着它被切换到 0.0.0.0:9080虚拟监听器。所以我们查看下 9080 端口的监听器配置:

bash
# productpage 默认访问其他服务的 9080 端口$istioctlproxy-configlistenersproductpage-v1-564d4686f-wwqqf--port9080-oyaml-address:socketAddress:address:0.0.0.0portValue:9080# ......rds:configSource:ads:{}initialFetchTimeout:0sresourceApiVersion:V3routeConfigName:"9080"# RDS的路由配置名称# ......name:0.0.0.0_9080trafficDirection:OUTBOUND# 出流量

可以看到此监听器在其配置的 RDS 中查找名为 9080的路由配置,我们可以使用 istioctl proxy-config routes命令来查看这个路由配置的详细信息:

bash
# 查看 9080 这个路由配置$istioctlproxy-configroutesproductpage-v1-564d4686f-wwqqf--name9080-oyaml-name:"9080"virtualHosts:-domains:-details.default.svc.cluster.local-details-details.default.svc-details.default-10.111.83.224name:details.default.svc.cluster.local:9080routes:-decorator:operation:details.default.svc.cluster.local:9080/*match:prefix:/metadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/detailsroute:cluster:outbound|9080|v1|details.default.svc.cluster.local# ......-domains:-productpage.default.svc.cluster.local-productpage-productpage.default.svc-productpage.default-10.97.120.23name:productpage.default.svc.cluster.local:9080routes:-decorator:operation:productpage.default.svc.cluster.local:9080/*match:prefix:/name:defaultroute:cluster:outbound|9080||productpage.default.svc.cluster.local# ......-domains:-ratings.default.svc.cluster.local-ratings-ratings.default.svc-ratings.default-10.101.184.235name:ratings.default.svc.cluster.local:9080routes:-decorator:operation:ratings.default.svc.cluster.local:9080/*match:prefix:/metadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratingsroute:cluster:outbound|9080|v1|ratings.default.svc.cluster.local# ......-domains:-reviews.default.svc.cluster.local-reviews-reviews.default.svc-reviews.default-10.97.120.56name:reviews.default.svc.cluster.local:9080routes:-decorator:operation:reviews.default.svc.cluster.local:9080/*match:prefix:/metadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviewsroute:cluster:outbound|9080|v1|reviews.default.svc.cluster.local# ......-domains:-'*'name:allow_anyroutes:-match:prefix:/name:allow_anyroute:cluster:PassthroughCluster# ......

这个路由配置中其实包含了 K8s Service 对象中监听 9080 端口的所有服务,如果没有创建对应的 VirtualService对象,对应的路由配置就没有 metadata.filterMetadata.istio.config这个属性。比如现在我们正在通过 productpage请求前往 reviews服务,因此 Envoy 将选择我们的请求与域匹配的虚拟主机。一旦在域上匹配,Envoy 会查找与请求匹配的第一条路径,我们这里没有任何高级路由,因此只有一条路由匹配所有内容。这条路由告诉 Envoy 将请求发送到 outbound|9080|v1|reviews.default.svc.cluster.local集群,因为前面我们创建的 reviews这个 VirtualService对象配置了的 destination.subset:v1,所以这里的集群命名上多了一个 subset

需要注意的是我们在 VirtualService对象里面配置了 destination.subset:v1,那么必须要有对应的 subset存在才行,否则不会生成对应的 Envoy 集群配置,那么就不能正常访问该服务了,而该 subset就是通过前面的 DestinationRule对象来定义的,现在我们就可以来查看这个集群配置了:

bash
$istioctlproxy-configclusterproductpage-v1-564d4686f-wwqqf--fqdnreviews.default.svc.cluster.local-oyaml-edsClusterConfig:edsConfig:ads:{}initialFetchTimeout:0sresourceApiVersion:V3serviceName:outbound|9080||reviews.default.svc.cluster.locallbPolicy:LEAST_REQUESTmetadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviewsservices:-host:reviews.default.svc.cluster.localname:reviewsnamespace:default# ......name:outbound|9080||reviews.default.svc.cluster.localtype:EDS-edsClusterConfig:edsConfig:ads:{}initialFetchTimeout:0sresourceApiVersion:V3serviceName:outbound|9080|v1|reviews.default.svc.cluster.locallbPolicy:LEAST_REQUESTmetadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviewsservices:-host:reviews.default.svc.cluster.localname:reviewsnamespace:defaultsubset:v1name:outbound|9080|v1|reviews.default.svc.cluster.local# ......type:EDS-edsClusterConfig:edsConfig:ads:{}initialFetchTimeout:0sresourceApiVersion:V3serviceName:outbound|9080|v2|reviews.default.svc.cluster.localfilters:-name:istio.metadata_exchangetypedConfig:'@type':type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchangeprotocol:istio-peer-exchangelbPolicy:LEAST_REQUESTmetadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviewsservices:-host:reviews.default.svc.cluster.localname:reviewsnamespace:defaultsubset:v2name:outbound|9080|v2|reviews.default.svc.cluster.local# ......type:EDS-edsClusterConfig:edsConfig:ads:{}initialFetchTimeout:0sresourceApiVersion:V3serviceName:outbound|9080|v3|reviews.default.svc.cluster.localfilters:-name:istio.metadata_exchangetypedConfig:'@type':type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchangeprotocol:istio-peer-exchangelbPolicy:LEAST_REQUESTmetadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviewsservices:-host:reviews.default.svc.cluster.localname:reviewsnamespace:defaultsubset:v3name:outbound|9080|v3|reviews.default.svc.cluster.local# ......type:EDS

从上面配置可以看到里面一共包含了 4 个 reviews相关的集群,一个是原始的不包含 subset 的,而另外三个就是前面我们在 DestinationRule对象中配置的 3 个 subset,所以其实 DestinationRule 映射到 Envoy 的配置文件中就是 Cluster

🍀

最后我们同样还可以查看每个集群下面包含的 endpoint 有哪些:

bash
$istioctlproxy-configendpointproductpage-v1-564d4686f-wwqqf--cluster"outbound|9080||reviews.default.svc.cluster.local"-oyaml-edsServiceName:outbound|9080||reviews.default.svc.cluster.local-address:socketAddress:address:10.244.2.84portValue:9080# ......weight:1-address:socketAddress:address:10.244.2.83portValue:9080# ......weight:1-address:socketAddress:address:10.244.2.88portValue:9080# ......weight:1name:outbound|9080||reviews.default.svc.cluster.localobservabilityName:outbound|9080||reviews.default.svc.cluster.local$istioctlproxy-configendpointproductpage-v1-564d4686f-wwqqf--cluster"outbound|9080|v1|reviews.default.svc.cluster.local"-oyaml-edsServiceName:outbound|9080|v1|reviews.default.svc.cluster.localhostStatuses:-address:socketAddress:address:10.244.2.84portValue:9080weight:1name:outbound|9080|v1|reviews.default.svc.cluster.localobservabilityName:outbound|9080|v1|reviews.default.svc.cluster.local# 过滤 version=v1 的 reviews pod$kubectlgetpod-lapp=reviews,version=v1-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESreviews-v1-86896b7648-zjh2n2/2Running4(5h18m ago) 6d17h 10.244.2.84 node2 <none><none>

可以看到不包含 subset 的集群下面的 endpoint 其实就是 reviews这个 Service 对象的 endpoint 集合,包含 subset 就只有和该子集匹配的后端实例了。到了这一步,一切皆明了,后面的事情就跟之前的套路一样了,具体的 Endpoint 对应打了标签 version=v1的 Pod。

🍀

到这里我们是不是就实现了通过 VirtualServiceDestinationRule对象将流量路由到了指定的版本上面了,上面的整个过程就是请求从 productpage 到 reviews 的过程,从 reviews 到网格内其他应用的流量与上面类似,就不展开讨论了。

2.基于用户身份的路由

==🚩 实战:基于用户身份的路由-2023.11.11(测试成功)==

实验环境:

bash
k8sv1.27.6(containerd:[root@master1 ~]#istioctl versionclientversion:1.19.3controlplaneversion:1.19.3dataplaneversion:1.19.3(8 proxies)

实验软件:

链接:https:A[实战步骤] -->B(1️⃣ 更新VirtualService)A[实战步骤] -->C(2️⃣ 测试)

接下来我们继续更改路由配置,将来自特定用户的所有流量路由到特定服务版本。我们这里将配置来自名为 Jason的用户的所有流量被路由到服务 reviews:v2

注意 Istio 对用户身份没有任何特殊的内置机制,productpage服务在所有到 reviews服务的 HTTP 请求中都增加了一个自定义的 end-user请求头来实现该效果:headers['end-user'] =session['user']

🍀

要实现该功能,只需要创建下面的资源对象即可:

该资源清单文件创建了一个如下所示的 VirtualService资源对象:

该对象设置了一条路由规则,它会根据 productpage 服务发起的请求的 end-user自定义请求头内容进行匹配,如果有该内容且为 jason则会将流量路由到 reviews服务的 v2版本,其余的还是被路由到 v1版本去。

🍀

现在我们可以前往浏览器访问 Bookinfo 应用,多刷新几次可以看到评论始终访问到的是 v1版本的服务,即没有星标的:

img

🍀

然后我们点击页面右上角的 Sign in按钮,使用 jason进行登录,登录后页面就会出现带有黑色星标的 v2版本的评论服务,即使多刷新几次依然如此:

img

如果我们选择使用其他用户进行登录或者注销则星标就会消失,这是因为除了 Jason之外,所有用户的流量都被路由到 reviews:v1

🍀

同样的我们可以去查看下对应的 Envoy Sidecar 配置的变化,因为这里我们只更新了一个 VirtualService对象,所以只会对 Envoy 的路由表产生影响,查看对应的路由配置即可:

从配置上我们可以看到现在的 Envoy 配置中新增了一条路由规则,如下所示:

当请求头中包含 end-user:jason的时候请求会被路由到 outbound|9080|v2|reviews.default.svc.cluster.local这个 Envoy Cluster 集群,这个集群就是前面我们通过 DestinationRule创建的 v2这个子集,所以最后请求会被路由到带有黑色星标的评论服务去。

img

🍀

到这里我们就明白了要通过 Istio 实现服务的流量管理,需要用到 GatewayVirtualServiceDestinationRule三个 CRD 对象,这些对象其实最终都是去拼凑 Envoy 的配置,每个对象管理 Envoy 配置的一部分,把这个关系搞清楚我们就能更好的掌握 Istio 的使用了。

故障注入

前面我们讲解了在 Istio 中如何进行请求路由管理,这里我们来讲解下如何在 Istio 中进行故障注入,故障注入是指在服务调用过程中,故意制造一些故障,来验证服务的容错能力。

1.注入 HTTP 延迟故障

==🚩 实战:注入 HTTP 延迟故障-2023.11.12(测试成功)==

测试环境:

本次测试是在前面几次环境基础上传测试的,因此需要先具备前几次测试环境。

https:[root@master1 ~]#istioctl versionclientversion:1.19.3controlplaneversion:1.19.3dataplaneversion:1.19.3(8 proxies)

实验软件:

链接:https:A[实战步骤] -->B(1️⃣ 更新VirtualService)A[实战步骤] -->C(2️⃣ 验证)

首先保留前面的请求路由规则,即 jason用户的流量被路由到 reviews:v2,其他用户的流量被路由到 reviews:v1。然后我们将为用户 jasonreviews:v2ratings服务之间注入一个 7 秒的延迟。这个测试可以发现一个故意引入 Bookinfo 应用程序中的 bug。

reviews:v2服务对 ratings服务的调用具有 10 秒的硬编码连接超时。所以尽管引入了 7 秒的延迟,我们仍然期望端到端的流程是没有任何错误的。

🍀

创建故障注入规则以延迟来自测试用户 jason的流量,应用下面的资源对象即可:

该资源对象的完整清单如下所示:

其实就是在 ratings这个服务的 VirtualService对象中增加了一个 fault字段,用来指定故障注入的规则,这里我们指定了 delay故障注入规则,即延迟 100% 的流量 7 秒钟,这样我们就可以在 jason用户的流量中看到延迟的效果了。

🍀

应用上面的规则后,我们可以通过浏览器打开 Bookinfo 应用,以用户 jason登录到 /productpage页面。

我们的期望是 Bookinfo 主页在大约 7 秒钟加载完成并且没有错误(因为硬编码是 10s 超时),但是会出现如下所示的问题:

img

在 Chrome 浏览器中我们可以右键打开审查元素,打开网络标签,重新加载页面,我们会看到页面加载实际上用了大约 6 秒。

img

按照预期,我们引入的 7 秒延迟不会影响到 reviews服务,因为 reviewsratings服务间的超时被硬编码为 10 秒。但实际上在 productpagereviews服务之间也有一个 3 秒的硬编码的超时,再加 1 次重试,一共 6 秒。结果 productpagereviews的调用在 6 秒后提前超时并抛出错误了。

img

这种类型的错误可能发生在由不同的团队独立开发不同的微服务的场景中。Istio 的故障注入规则可以帮助识别此类异常,而不会影响最终用户。

image-20231112143726467

🍀

那么我们应该如何来修复这个问题呢?这种问题通常可以这样来解决:

  • 增加 productpagereviews服务之间的超时或降低 reviewsratings的超时
  • 终止并重启修复后的微服务
  • 确认 /productpage页面正常响应且没有任何错误

reviews服务的 v3版本实际上已经修复了这个问题。reviews:v3服务已将 reviewsratings的超时时间从 10 秒降低为 2.5 秒,因此它可以兼容(小于)下游 productpage请求的超时时间。

如果您按照流量转移任务所述将所有流量转移到 reviews:v3,可以尝试修改延迟规则为任何低于 2.5 秒的数值,例如 2 秒,然后确认端到端的流程没有任何错误。

🍀

我们这里是为 ratings服务配置的 VirtualService规则,而且是 reviews:v2服务去请求这个服务的,所以我们可以通过 istioctl proxy-config routes命令来查看 reviews:v2版本服务对应的 Envoy 路由表配置信息来验证下这个延迟故障:

bash
$istioctlproxy-configroutesreviews-v2-b7dcd98fb-gccfh--name9080-oyaml-name:"9080"virtualHosts:# ......-domains:-ratings.default.svc.cluster.local-ratings-ratings.default.svc-ratings.default-10.101.184.235includeRequestAttemptCount:truename:ratings.default.svc.cluster.local:9080routes:-decorator:operation:ratings.default.svc.cluster.local:9080/*match:caseSensitive:trueheaders:-name:end-userstringMatch:exact:jasonprefix:/metadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratingsroute:cluster:outbound|9080|v1|ratings.default.svc.cluster.local# ......typedPerFilterConfig:envoy.filters.http.fault:'@type':type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFaultdelay:fixedDelay:7spercentage:denominator:MILLIONnumerator:1000000-decorator:operation:ratings.default.svc.cluster.local:9080/*match:prefix:/metadata:filterMetadata:istio:config:/apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratingsroute:cluster:outbound|9080|v1|ratings.default.svc.cluster.local# ......

可以看到上面的路由规则中有一个匹配头为 end-user的规则:

yaml
match:caseSensitive:trueheaders:- name:end-userstringMatch:exact:jasonprefix:/

可以看到这个规则中还包含了一个 envoy.filters.http.fault的配置,这个就是我们前面配置的故障注入规则,可以看到这个规则中的延迟时间为 7 秒,这就是我们前面配置的故障注入规则。

yaml
envoy.filters.http.fault:"@type":type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFaultdelay:fixedDelay:7spercentage:denominator:MILLIONnumerator:1000000

🍀

reviews服务的 v3版本实际上已经修复了这个问题。reviews:v3服务已将 reviewsratings的超时时间从 10 秒降低为 2.5 秒,因此它可以兼容(小于)下游 productpage请求的超时时间。

如果您按照流量转移任务所述将所有流量转移到 reviews:v3,可以尝试修改延迟规则为任何低于 2.5 秒的数值,例如 2 秒,然后确认端到端的流程没有任何错误。

编辑文件:

bash
[root@master1 istio-1.19.3]#kubectl get vsNAMEGATEWAYSHOSTSAGEbookinfo["bookinfo-gateway"] ["*"] 42mdetails["details"] 40mproductpage["productpage"] 40mratings["ratings"] 40mreviews["reviews"] 40m[root@master1 istio-1.19.3]#kubectl edit vs reviews [root@master1 istio-1.19.3]#kubectl edit vs ratings

image-20231112150717665

image-20231112150546724

验证:

image-20231112150744014

符合预期,可以看到有2s的延迟。

测试结束。😘

2.注入 HTTP abort 故障

==🚩 实战:注入 HTTP abort 故障-2023.11.12(测试成功)==

测试环境:

本次测试是在前面几次环境基础上传测试的,因此需要先具备前几次测试环境。

https:[root@master1 ~]#istioctl versionclientversion:1.19.3controlplaneversion:1.19.3dataplaneversion:1.19.3(8 proxies)

实验软件:

链接:https:A[实战步骤] -->B(1️⃣ 更新VirtualService)A[实战步骤] -->C(2️⃣ 验证)

image-20231112151242441

测试微服务弹性的另一种方法是引入 HTTP abort 故障。这里同样我们针对测试用户 jason,将给 ratings微服务引入一个 HTTP abort。在这种情况下,我们希望页面能够立即加载,同时显示 Ratings service is currently unavailable这样的消息。

🍀

为用户 jason创建一个发送 HTTP abort 的故障注入规则,执行下面的命令即可:

这里创建的资源对象如下所示:

可以看到这里我们是将 fault下面的 delay延迟更改成了 abort,并且将 httpStatus设置为 500,percentage也设置为了 100,也就是 jason用户访问 ratings服务的所有请求都会变成 500 状态码的错误请求,这样我们就可以在页面上看到 Ratings service is currently unavailable这样的消息了。

🍀

应用上面的规则后,我们可以通过浏览器打开 Bookinfo 应用,以用户 jason登录到 /productpage页面,正常就可以看到如下所示的页面:

img

🍀

当然如果注销用户 jason打开 Bookinfo 应用程序,我们会看到 /productpage为除 jason以外的其他用户调用了 reviews:v1(完全不调用 ratings),因此不会看到任何错误消息。

同样我们可以去查看下 Envoy 的路由配置,看下这个故障注入规则是如何映射到 Envoy 的路由配置中的:

可以看到上面的路由表中也包含了一个匹配头为 end-user的规则,同时也包含了一个 envoy.filters.http.fault的配置,这个就是我们前面配置的故障注入规则,现在这个故障注入配置下面是 abort了,httpStatus为 500,这就是我们前面配置的故障注入规则。

🍀

到这里我们就学习了如何通过 VirtualService对象来实现故障注入的功能,包括延迟请求和请求中断,当然本质上这个功能是通过 Envoy 的 fault过滤器来实现的。此外我们还可以为 HTTP 请求配置超时,只需要通过路由规则中的 timeout字段来指定即可,如下所示:

测试结束。😘

流量拆分

==🚩 实战:流量拆分-2023.11.12(测试成功)==

测试环境:

本次测试是在前面几次环境基础上传测试的,因此需要先具备前几次测试环境。

https:[root@master1 ~]#istioctl versionclientversion:1.19.3controlplaneversion:1.19.3dataplaneversion:1.19.3(8 proxies)

实验软件:

链接:https:A[实战步骤] -->B(1️⃣ 更新VirtualService)A[实战步骤] -->C(2️⃣ 验证)

本节我们将了解如何将流量从微服务的一个版本逐步迁移到另一个版本,比如在 A/B 测试、金丝雀发布等场景中,我们需要将流量从一个版本逐步迁移到另一个版本,这个时候就需要用到流量拆分功能。

这里我们将会把 50% 的流量发送到 reviews:v1,另外,50% 的流量发送到 reviews:v3,接着,再把 100% 的流量发送到 reviews:v3来完成流量转移。

🍀

首先运行下面的命令将所有流量路由到各个微服务的 v1版本。

bash
kubectlapply-fsamples/bookinfo/networking/virtual-service-all-v1.yamlkubectlapply-fsamples/bookinfo/networking/destination-rule-all.yaml

这是因为前面我们做了其他测试,所以我们需要将流量转移的状态重置一下,这样才能保证我们的测试是正确的。

现在当我们在浏览器中访问 Bookinfo 应用的时候,不管刷新多少次,页面的 Reviews 部分都不会显示带评价星级的内容。这是因为 Istio 被配置为将星级评价的服务的所有流量都路由到了 reviews:v1版本,而该版本的服务不访问带评价星级的服务,也就是最初的版本。

🍀

接下来我们使用下面的命令把 50% 的流量路由到 reviews:v1,50% 的流量路由到 reviews:v3

bash
$kubectlapply-fsamples/bookinfo/networking/virtual-service-reviews-50-v3.yaml

该资源清单文件的内容如下所示:

yaml
apiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:reviewsspec:hosts:- reviewshttp:- route:- destination:host:reviewssubset:v1weight:50- destination:host:reviewssubset:v3weight:50

在这个 VirtualService对象中我们为 reviews服务的路由规则增加了一个 weight字段,这个字段的值为 50,表示将 50% 的流量路由到 reviews:v1,另外 50% 的流量路由到 reviews:v3

🍀

应用该规则后我们可以前往浏览器访问 Bookinfo 应用,刷新 /productpage页面,大约有 50% 的几率会看到页面中带红色星级的评价内容。

img

🍀

同样我们可以去查看下 Envoy 的路由配置,看下这个流量转移规则是如何映射到 Envoy 的路由配置中的:

yaml
$ istioctl proxy-config routes productpage-v1-564d4686f-wwqqf --name 9080 -oyaml- name:"9080"virtualHosts:- domains:- reviews.default.svc.cluster.local- reviews- reviews.default.svc- reviews.default- 10.97.120.56name:reviews.default.svc.cluster.local:9080routes:- decorator:operation:reviews:9080*","Host":"httpbin:8000","User-Agent":"curl/7.81.0-DEV","X-B3-Parentspanid":"623917d026166bc1","X-B3-Sampled":"1","X-B3-Spanid":"8ef9bb2eceeceec5","X-B3-Traceid":"b586a087a8c2219a623917d026166bc1","X-Envoy-Attempt-Count":"1","X-Forwarded-Client-Cert":"By=spiffe:}}

🍀

然后分别查看 httpbin Pod 的 v1v2两个版本的日志,可以看到 v1版本的访问日志条目,而 v2版本没有日志:

bash
$exportV1_POD=$(kubectlgetpod-lapp=httpbin,version=v1-ojsonpath={.items..metadata.name})$kubectllogs"$V1_POD"-chttpbin127.0.0.6--[10/Nov/2023:07:38:53 +0000]"GET /headers HTTP/1.1"200529"-""curl/7.81.0-DEV"$exportV2_POD=$(kubectlgetpod-lapp=httpbin,version=v2-ojsonpath={.items..metadata.name})$kubectllogs"$V2_POD"-chttpbin

🍀

接下来我们将创建一个新的规则,将 100% 的流量发送到 v1版本,但是同样将 100% 的相同流量镜像到 httpbin:v2服务去:

yaml
#mirror2.yamlapiVersion:networking.istio.io/v1alpha3kind:VirtualServicemetadata:name:httpbinspec:hosts:- httpbinhttp:- route:- destination:host:httpbinsubset:v1weight:100mirror:host:httpbinsubset:v2mirrorPercentage:value:100.0

当流量被镜像时,请求将发送到镜像服务中,并在 headers中的 Host/Authority属性值上追加 -shadow,例如 cluster-1变为 cluster-1-shadow

此外这些被镜像的流量是**『即发即弃』**的,就是说镜像请求的响应会被丢弃,前面在 Envoy 中我们也实验过该特性。

当然我们也可以使用 mirrorPercentage属性下的 value字段来设置镜像流量的百分比,而不是镜像所有请求。如果没有这个属性,将镜像所有流量。

🍀

bash
[root@master1 ~]#kubectl apply -f mirror2.yaml virtualservice.networking.istio.io/httpbinconfigured

直接应用该规则后,我们可以向 httpbin服务发送请求来验证:

bash
$kubectlexec"${SLEEP_POD}"-csleep--curl-sShttp:{"headers":{"Accept":"*