3、Istio流量管理
Istio流量管理

目录
[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(测试成功) |
流量管理概述
上面我们了解了 Gateway 和 VirtualService 资源对象的作用,以及它们是如何影响 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(测试成功)==
实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 部署VirtualService)
3 A[实战步骤] -->C(2️⃣ DestinationRule)
4 A[实战步骤] -->D(3️⃣ 验证)
要只路由到一个版本,则需要为微服务设置默认版本的 VirtualService。
🍀
应用规则
Istio 使用 VirtualService 来定义路由规则,只需要应用下面的资源对象即可:
1$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
2virtualservice.networking.istio.io/productpage created
3virtualservice.networking.istio.io/reviews created
4virtualservice.networking.istio.io/ratings created
5virtualservice.networking.istio.io/details created
该资源清单中定义了四个 VirtualService 对象,分别是 productpage、reviews、ratings、details,它们分别对应着 Bookinfo 应用中的四个微服务,完整的清单如下所示:
1# virtual-service-all-v1.yaml
2apiVersion: networking.istio.io/v1alpha3
3kind: VirtualService
4metadata:
5 name: productpage
6spec:
7 hosts:
8 - productpage
9 http:
10 - route:
11 - destination:
12 host: productpage
13 subset: v1
14---
15apiVersion: networking.istio.io/v1alpha3
16kind: VirtualService
17metadata:
18 name: reviews
19spec:
20 hosts:
21 - reviews
22 http:
23 - route:
24 - destination:
25 host: reviews
26 subset: v1
27---
28apiVersion: networking.istio.io/v1alpha3
29kind: VirtualService
30metadata:
31 name: ratings
32spec:
33 hosts:
34 - ratings
35 http:
36 - route:
37 - destination:
38 host: ratings
39 subset: v1
40---
41apiVersion: networking.istio.io/v1alpha3
42kind: VirtualService
43metadata:
44 name: details
45spec:
46 hosts:
47 - details
48 http:
49 - route:
50 - destination:
51 host: details
52 subset: v1
53---
我们可以看到这里的 VirtualService 对象中都定义了 subset 字段,这个字段就是用来指定微服务的版本的,这里我们将所有的微服务都指定为 v1 版本,这样所有的流量都会被路由到 v1 版本的微服务中,包括 reviews 服务,这样我们就不会再看到星级评分了。
🍀
但是如果我们现在直接去访问 Bookinfo 应用的话,是不能正常访问的,因为我们压根就还没指定这些 v1 版本的微服务到底在哪里。

🍀
这个时候就需要用到另外一个资源对象 DestinationRule 了,我们需要为每个微服务创建一个 DestinationRule 对象,用来指定这些微服务的实际地址,这样 VirtualService 对象才能将流量路由到这些微服务中。Istio 在 DestinationRule 目标规则中使用 subsets 定义服务的版本,运行以下命令为 Bookinfo 服务创建默认的目标规则即可:
1$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml
2destinationrule.networking.istio.io/productpage created
3destinationrule.networking.istio.io/reviews created
4destinationrule.networking.istio.io/ratings created
5destinationrule.networking.istio.io/details created
该资源清单中定义了四个 DestinationRule 对象,分别是 productpage、reviews、ratings、details 几个服务的目标规则,它们分别对应着 Bookinfo 应用中的四个微服务,完整的清单如下所示:
1# destination-rule-all.yaml
2apiVersion: networking.istio.io/v1alpha3
3kind: DestinationRule
4metadata:
5 name: productpage
6spec:
7 host: productpage
8 subsets:
9 - name: v1
10 labels:
11 version: v1
12---
13apiVersion: networking.istio.io/v1alpha3
14kind: DestinationRule
15metadata:
16 name: reviews
17spec:
18 host: reviews
19 subsets:
20 - name: v1
21 labels:
22 version: v1
23 - name: v2
24 labels:
25 version: v2
26 - name: v3
27 labels:
28 version: v3
29---
30apiVersion: networking.istio.io/v1alpha3
31kind: DestinationRule
32metadata:
33 name: ratings
34spec:
35 host: ratings
36 subsets:
37 - name: v1
38 labels:
39 version: v1
40 - name: v2
41 labels:
42 version: v2
43 - name: v2-mysql
44 labels:
45 version: v2-mysql
46 - name: v2-mysql-vm
47 labels:
48 version: v2-mysql-vm
49---
50apiVersion: networking.istio.io/v1alpha3
51kind: DestinationRule
52metadata:
53 name: details
54spec:
55 host: details
56 subsets:
57 - name: v1
58 labels:
59 version: v1
60 - name: v2
61 labels:
62 version: v2
63---
🍀
现在我们就可以正常访问 Bookinfo 应用了,并且无论刷新多少次,页面的评论部分都不会显示评级星标,这是因为我们将 Istio 配置为将 reviews 服务的所有流量路由到版本 reviews:v1,而此版本的服务不访问星级评分服务。

这样我们就成功将流量路由到服务的某一个版本上了。
原理分析
🍀
前面章节中我们只定义了一个名为 bookinfo 的 VirtualService 资源对象就可以正常访问了:
1apiVersion: networking.istio.io/v1beta1
2kind: VirtualService
3metadata:
4 name: bookinfo
5 namespace: default
6spec:
7 gateways:
8 - bookinfo-gateway
9 hosts:
10 - "*"
11 http:
12 - match:
13 - uri:
14 exact: /productpage
15 - uri:
16 prefix: /static
17 - uri:
18 exact: /login
19 - uri:
20 exact: /logout
21 - uri:
22 prefix: /api/v1/products
23 route:
24 - destination:
25 host: productpage
26 port:
27 number: 9080
很明显上面这个虚拟服务对象是我们访问 Bookinfo 应用的入口路由规则,所以这个虚拟服务对象实际上是为 istio-ingressgateway 入口网关服务定义的。 它将所有的流量都路由到了 productpage 这个服务上,而 productpage 这个服务又会去调用其他的服务来获取数据,在 productpage 服务中调用其他微服务 其实就是直接通过服务名称来调用的,比如调用 reviews 服务就是直接通过 reviews:9080 这个服务来调用的,我们可以查看 productpage 的代码来验证这一点:

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

当我们在浏览器中访问 http://<gateway url>/productpage 时,请求将进入网格中的 istio-ingressgateway 服务,然后将请求转发到 productpage 服务。productpage 服务将调用 reviews 和 details 服务来填充页面的内容,然后将其返回给用户。(reviews 服务包括 3 个不同版本的应用,可以通过 version 标签区分)
🍀
现在我们只想将流量路由到 reviews:v1 版本去,按照传统的方法只需要将 reviews 的 Service 对象去强制关联 version: v1 这个标签即可,现在我们所有的服务都被注入了一个 Envoy 的 Sidecar 代理,通过 Envoy 很容易就可以实现这个路由功能,而相应的在 Istio 中我们只需要通过 VirtualService 和 DestinationRule 这两个资源对象就可以来实现了。上面我们创建的关于 reviews 服务的这两个对象如下所示:
1apiVersion: networking.istio.io/v1alpha3
2kind: VirtualService
3metadata:
4 name: reviews
5spec:
6 hosts:
7 - reviews
8 http:
9 - route:
10 - destination:
11 host: reviews
12 subset: v1
13---
14apiVersion: networking.istio.io/v1alpha3
15kind: DestinationRule
16metadata:
17 name: reviews
18spec:
19 host: reviews
20 subsets:
21 - name: v1
22 labels:
23 version: v1
24 - name: v2
25 labels:
26 version: v2
27 - name: v3
28 labels:
29 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 端口的监听器配置:
1[root@master1 istio-1.19.3]#istioctl proxy-config listeners productpage-v1-564d4686f-7vhks --port 15001 -oyaml
2- accessLog:
3 - filter:
4 responseFlagFilter:
5 flags:
6 - NR
7 name: envoy.access_loggers.file
8 typedConfig:
9 '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
10 logFormat:
11 textFormatSource:
12 inlineString: |
13 [%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%
14 path: /dev/stdout
15 address:
16 socketAddress:
17 address: 0.0.0.0
18 portValue: 15001
19 filterChains:
20 - filterChainMatch:
21 destinationPort: 15001
22 filters:
23 - name: istio.stats
24 typedConfig:
25 '@type': type.googleapis.com/stats.PluginConfig
26 - name: envoy.filters.network.tcp_proxy
27 typedConfig:
28 '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
29 cluster: BlackHoleCluster
30 statPrefix: BlackHoleCluster
31 name: virtualOutbound-blackhole
32 - filters:
33 - name: istio.stats
34 typedConfig:
35 '@type': type.googleapis.com/stats.PluginConfig
36 - name: envoy.filters.network.tcp_proxy
37 typedConfig:
38 '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
39 accessLog:
40 - name: envoy.access_loggers.file
41 typedConfig:
42 '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
43 logFormat:
44 textFormatSource:
45 inlineString: |
46 [%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%
47 path: /dev/stdout
48 cluster: PassthroughCluster
49 statPrefix: PassthroughCluster
50 name: virtualOutbound-catchall-tcp
51 name: virtualOutbound
52 trafficDirection: OUTBOUND
53 useOriginalDst: true
54
55[root@master1 istio-1.19.3]#
🍀
实际上我们的请求是到 9080 端口(productpage 服务绑定 9080 端口)的 HTTP 出站请求,这意味着它被切换到 0.0.0.0:9080 虚拟监听器。所以我们查看下 9080 端口的监听器配置:
1# productpage 默认访问其他服务的 9080 端口
2$ istioctl proxy-config listeners productpage-v1-564d4686f-wwqqf --port 9080 -oyaml
3- address:
4 socketAddress:
5 address: 0.0.0.0
6 portValue: 9080
7 # ......
8 rds:
9 configSource:
10 ads: {}
11 initialFetchTimeout: 0s
12 resourceApiVersion: V3
13 routeConfigName: "9080" # RDS的路由配置名称
14 # ......
15 name: 0.0.0.0_9080
16 trafficDirection: OUTBOUND # 出流量
可以看到此监听器在其配置的 RDS 中查找名为 9080 的路由配置,我们可以使用 istioctl proxy-config routes 命令来查看这个路由配置的详细信息:
1# 查看 9080 这个路由配置
2$ istioctl proxy-config routes productpage-v1-564d4686f-wwqqf --name 9080 -oyaml
3- name: "9080"
4 virtualHosts:
5 - domains:
6 - details.default.svc.cluster.local
7 - details
8 - details.default.svc
9 - details.default
10 - 10.111.83.224
11 name: details.default.svc.cluster.local:9080
12 routes:
13 - decorator:
14 operation: details.default.svc.cluster.local:9080/*
15 match:
16 prefix: /
17 metadata:
18 filterMetadata:
19 istio:
20 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/details
21 route:
22 cluster: outbound|9080|v1|details.default.svc.cluster.local
23 # ......
24 - domains:
25 - productpage.default.svc.cluster.local
26 - productpage
27 - productpage.default.svc
28 - productpage.default
29 - 10.97.120.23
30 name: productpage.default.svc.cluster.local:9080
31 routes:
32 - decorator:
33 operation: productpage.default.svc.cluster.local:9080/*
34 match:
35 prefix: /
36 name: default
37 route:
38 cluster: outbound|9080||productpage.default.svc.cluster.local
39 # ......
40 - domains:
41 - ratings.default.svc.cluster.local
42 - ratings
43 - ratings.default.svc
44 - ratings.default
45 - 10.101.184.235
46 name: ratings.default.svc.cluster.local:9080
47 routes:
48 - decorator:
49 operation: ratings.default.svc.cluster.local:9080/*
50 match:
51 prefix: /
52 metadata:
53 filterMetadata:
54 istio:
55 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratings
56 route:
57 cluster: outbound|9080|v1|ratings.default.svc.cluster.local
58 # ......
59 - domains:
60 - reviews.default.svc.cluster.local
61 - reviews
62 - reviews.default.svc
63 - reviews.default
64 - 10.97.120.56
65 name: reviews.default.svc.cluster.local:9080
66 routes:
67 - decorator:
68 operation: reviews.default.svc.cluster.local:9080/*
69 match:
70 prefix: /
71 metadata:
72 filterMetadata:
73 istio:
74 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews
75 route:
76 cluster: outbound|9080|v1|reviews.default.svc.cluster.local
77 # ......
78 - domains:
79 - '*'
80 name: allow_any
81 routes:
82 - match:
83 prefix: /
84 name: allow_any
85 route:
86 cluster: PassthroughCluster
87 # ......
这个路由配置中其实包含了 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 对象来定义的,现在我们就可以来查看这个集群配置了:
1$ istioctl proxy-config cluster productpage-v1-564d4686f-wwqqf --fqdn reviews.default.svc.cluster.local -o yaml
2- edsClusterConfig:
3 edsConfig:
4 ads: {}
5 initialFetchTimeout: 0s
6 resourceApiVersion: V3
7 serviceName: outbound|9080||reviews.default.svc.cluster.local
8 lbPolicy: LEAST_REQUEST
9 metadata:
10 filterMetadata:
11 istio:
12 config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews
13 services:
14 - host: reviews.default.svc.cluster.local
15 name: reviews
16 namespace: default
17 # ......
18 name: outbound|9080||reviews.default.svc.cluster.local
19 type: EDS
20- edsClusterConfig:
21 edsConfig:
22 ads: {}
23 initialFetchTimeout: 0s
24 resourceApiVersion: V3
25 serviceName: outbound|9080|v1|reviews.default.svc.cluster.local
26 lbPolicy: LEAST_REQUEST
27 metadata:
28 filterMetadata:
29 istio:
30 config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews
31 services:
32 - host: reviews.default.svc.cluster.local
33 name: reviews
34 namespace: default
35 subset: v1
36 name: outbound|9080|v1|reviews.default.svc.cluster.local
37 # ......
38 type: EDS
39- edsClusterConfig:
40 edsConfig:
41 ads: {}
42 initialFetchTimeout: 0s
43 resourceApiVersion: V3
44 serviceName: outbound|9080|v2|reviews.default.svc.cluster.local
45 filters:
46 - name: istio.metadata_exchange
47 typedConfig:
48 '@type': type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange
49 protocol: istio-peer-exchange
50 lbPolicy: LEAST_REQUEST
51 metadata:
52 filterMetadata:
53 istio:
54 config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews
55 services:
56 - host: reviews.default.svc.cluster.local
57 name: reviews
58 namespace: default
59 subset: v2
60 name: outbound|9080|v2|reviews.default.svc.cluster.local
61 # ......
62 type: EDS
63- edsClusterConfig:
64 edsConfig:
65 ads: {}
66 initialFetchTimeout: 0s
67 resourceApiVersion: V3
68 serviceName: outbound|9080|v3|reviews.default.svc.cluster.local
69 filters:
70 - name: istio.metadata_exchange
71 typedConfig:
72 '@type': type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange
73 protocol: istio-peer-exchange
74 lbPolicy: LEAST_REQUEST
75 metadata:
76 filterMetadata:
77 istio:
78 config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews
79 services:
80 - host: reviews.default.svc.cluster.local
81 name: reviews
82 namespace: default
83 subset: v3
84 name: outbound|9080|v3|reviews.default.svc.cluster.local
85 # ......
86 type: EDS
从上面配置可以看到里面一共包含了 4 个 reviews 相关的集群,一个是原始的不包含 subset 的,而另外三个就是前面我们在 DestinationRule 对象中配置的 3 个 subset,所以其实 DestinationRule 映射到 Envoy 的配置文件中就是 Cluster。
🍀
最后我们同样还可以查看每个集群下面包含的 endpoint 有哪些:
1$ istioctl proxy-config endpoint productpage-v1-564d4686f-wwqqf --cluster "outbound|9080||reviews.default.svc.cluster.local" -o yaml
2- edsServiceName: outbound|9080||reviews.default.svc.cluster.local
3 - address:
4 socketAddress:
5 address: 10.244.2.84
6 portValue: 9080
7 # ......
8 weight: 1
9 - address:
10 socketAddress:
11 address: 10.244.2.83
12 portValue: 9080
13 # ......
14 weight: 1
15 - address:
16 socketAddress:
17 address: 10.244.2.88
18 portValue: 9080
19 # ......
20 weight: 1
21 name: outbound|9080||reviews.default.svc.cluster.local
22 observabilityName: outbound|9080||reviews.default.svc.cluster.local
23
24$ istioctl proxy-config endpoint productpage-v1-564d4686f-wwqqf --cluster "outbound|9080|v1|reviews.default.svc.cluster.local" -o yaml
25- edsServiceName: outbound|9080|v1|reviews.default.svc.cluster.local
26 hostStatuses:
27 - address:
28 socketAddress:
29 address: 10.244.2.84
30 portValue: 9080
31 weight: 1
32 name: outbound|9080|v1|reviews.default.svc.cluster.local
33 observabilityName: outbound|9080|v1|reviews.default.svc.cluster.local
34
35# 过滤 version=v1 的 reviews pod
36$ kubectl get pod -l app=reviews,version=v1 -o wide
37NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
38reviews-v1-86896b7648-zjh2n 2/2 Running 4 (5h18m ago) 6d17h 10.244.2.84 node2 <none> <none>
可以看到不包含 subset 的集群下面的 endpoint 其实就是 reviews 这个 Service 对象的 endpoint 集合,包含 subset 就只有和该子集匹配的后端实例了。到了这一步,一切皆明了,后面的事情就跟之前的套路一样了,具体的 Endpoint 对应打了标签 version=v1 的 Pod。
🍀
到这里我们是不是就实现了通过 VirtualService 和 DestinationRule 对象将流量路由到了指定的版本上面了,上面的整个过程就是请求从 productpage 到 reviews 的过程,从 reviews 到网格内其他应用的流量与上面类似,就不展开讨论了。
2.基于用户身份的路由
==🚩 实战:基于用户身份的路由-2023.11.11(测试成功)==
实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 更新VirtualService)
3 A[实战步骤] -->C(2️⃣ 测试)
接下来我们继续更改路由配置,将来自特定用户的所有流量路由到特定服务版本。我们这里将配置来自名为 Jason 的用户的所有流量被路由到服务 reviews:v2。
注意 Istio 对用户身份没有任何特殊的内置机制,productpage 服务在所有到 reviews 服务的 HTTP 请求中都增加了一个自定义的 end-user 请求头来实现该效果:headers['end-user'] = session['user']。
🍀
要实现该功能,只需要创建下面的资源对象即可:
1$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
2virtualservice.networking.istio.io/reviews configured
该资源清单文件创建了一个如下所示的 VirtualService 资源对象:
1# virtual-service-reviews-test-v2.yaml
2apiVersion: networking.istio.io/v1alpha3
3kind: VirtualService
4metadata:
5 name: reviews
6spec:
7 hosts:
8 - reviews
9 http:
10 - match:
11 - headers:
12 end-user:
13 exact: jason
14 route:
15 - destination:
16 host: reviews
17 subset: v2
18 - route:
19 - destination:
20 host: reviews
21 subset: v1
该对象设置了一条路由规则,它会根据 productpage 服务发起的请求的 end-user 自定义请求头内容进行匹配,如果有该内容且为 jason 则会将流量路由到 reviews 服务的 v2 版本,其余的还是被路由到 v1 版本去。
🍀
现在我们可以前往浏览器访问 Bookinfo 应用,多刷新几次可以看到评论始终访问到的是 v1 版本的服务,即没有星标的:

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

如果我们选择使用其他用户进行登录或者注销则星标就会消失,这是因为除了 Jason 之外,所有用户的流量都被路由到 reviews:v1。
🍀
同样的我们可以去查看下对应的 Envoy Sidecar 配置的变化,因为这里我们只更新了一个 VirtualService 对象,所以只会对 Envoy 的路由表产生影响,查看对应的路由配置即可:
1$ istioctl proxy-config routes productpage-v1-564d4686f-wwqqf --name 9080 -oyaml
2- name: "9080"
3 validateClusters: false
4 virtualHosts:
5 # ......
6 - domains:
7 - reviews.default.svc.cluster.local
8 - reviews
9 - reviews.default.svc
10 - reviews.default
11 - 10.97.120.56
12 includeRequestAttemptCount: true
13 name: reviews.default.svc.cluster.local:9080
14 routes:
15 - decorator:
16 operation: reviews.default.svc.cluster.local:9080/*
17 match:
18 caseSensitive: true
19 headers:
20 - name: end-user
21 stringMatch:
22 exact: jason
23 prefix: /
24 metadata:
25 filterMetadata:
26 istio:
27 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews
28 route:
29 cluster: outbound|9080|v2|reviews.default.svc.cluster.local
30 maxGrpcTimeout: 0s
31 # ......
32 - decorator:
33 operation: reviews.default.svc.cluster.local:9080/*
34 match:
35 prefix: /
36 metadata:
37 filterMetadata:
38 istio:
39 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews
40 route:
41 cluster: outbound|9080|v1|reviews.default.svc.cluster.local
42 maxGrpcTimeout: 0s
43 # ......
从配置上我们可以看到现在的 Envoy 配置中新增了一条路由规则,如下所示:
1match:
2 caseSensitive: true
3 headers:
4 - name: end-user
5 stringMatch:
6 exact: jason
7 prefix: /
8route:
9 cluster: outbound|9080|v2|reviews.default.svc.cluster.local
当请求头中包含 end-user:jason 的时候请求会被路由到 outbound|9080|v2|reviews.default.svc.cluster.local 这个 Envoy Cluster 集群,这个集群就是前面我们通过 DestinationRule 创建的 v2 这个子集,所以最后请求会被路由到带有黑色星标的评论服务去。

🍀
到这里我们就明白了要通过 Istio 实现服务的流量管理,需要用到 Gateway、VirtualService、DestinationRule 三个 CRD 对象,这些对象其实最终都是去拼凑 Envoy 的配置,每个对象管理 Envoy 配置的一部分,把这个关系搞清楚我们就能更好的掌握 Istio 的使用了。
故障注入
前面我们讲解了在 Istio 中如何进行请求路由管理,这里我们来讲解下如何在 Istio 中进行故障注入,故障注入是指在服务调用过程中,故意制造一些故障,来验证服务的容错能力。
1.注入 HTTP 延迟故障
==🚩 实战:注入 HTTP 延迟故障-2023.11.12(测试成功)==
测试环境:
本次测试是在前面几次环境基础上传测试的,因此需要先具备前几次测试环境。

实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 更新VirtualService)
3 A[实战步骤] -->C(2️⃣ 验证)
首先保留前面的请求路由规则,即 jason 用户的流量被路由到 reviews:v2,其他用户的流量被路由到 reviews:v1。然后我们将为用户 jason 在 reviews:v2 和 ratings 服务之间注入一个 7 秒的延迟。这个测试可以发现一个故意引入 Bookinfo 应用程序中的 bug。
reviews:v2服务对ratings服务的调用具有 10 秒的硬编码连接超时。所以尽管引入了 7 秒的延迟,我们仍然期望端到端的流程是没有任何错误的。
🍀
创建故障注入规则以延迟来自测试用户 jason 的流量,应用下面的资源对象即可:
1kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
该资源对象的完整清单如下所示:
1piVersion: networking.istio.io/v1alpha3
2kind: VirtualService
3metadata:
4 name: ratings
5spec:
6 hosts:
7 - ratings
8 http:
9 - match:
10 - headers:
11 end-user:
12 exact: jason
13 fault:
14 delay:
15 percentage:
16 value: 100.0
17 fixedDelay: 7s
18 route:
19 - destination:
20 host: ratings
21 subset: v1
22 - route:
23 - destination:
24 host: ratings
25 subset: v1
其实就是在 ratings 这个服务的 VirtualService 对象中增加了一个 fault 字段,用来指定故障注入的规则,这里我们指定了 delay 故障注入规则,即延迟 100% 的流量 7 秒钟,这样我们就可以在 jason 用户的流量中看到延迟的效果了。
🍀
应用上面的规则后,我们可以通过浏览器打开 Bookinfo 应用,以用户 jason 登录到 /productpage 页面。
我们的期望是 Bookinfo 主页在大约 7 秒钟加载完成并且没有错误(因为硬编码是 10s 超时),但是会出现如下所示的问题:

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

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

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

🍀
那么我们应该如何来修复这个问题呢?这种问题通常可以这样来解决:
- 增加
productpage与reviews服务之间的超时或降低reviews与ratings的超时 - 终止并重启修复后的微服务
- 确认
/productpage页面正常响应且没有任何错误
reviews 服务的 v3 版本实际上已经修复了这个问题。reviews:v3 服务已将 reviews 与 ratings 的超时时间从 10 秒降低为 2.5 秒,因此它可以兼容(小于)下游 productpage 请求的超时时间。
如果您按照流量转移任务所述将所有流量转移到 reviews:v3,可以尝试修改延迟规则为任何低于 2.5 秒的数值,例如 2 秒,然后确认端到端的流程没有任何错误。
🍀
我们这里是为 ratings 服务配置的 VirtualService 规则,而且是 reviews:v2 服务去请求这个服务的,所以我们可以通过 istioctl proxy-config routes 命令来查看 reviews:v2 版本服务对应的 Envoy 路由表配置信息来验证下这个延迟故障:
1$ istioctl proxy-config routes reviews-v2-b7dcd98fb-gccfh --name 9080 -oyaml
2- name: "9080"
3 virtualHosts:
4 # ......
5 - domains:
6 - ratings.default.svc.cluster.local
7 - ratings
8 - ratings.default.svc
9 - ratings.default
10 - 10.101.184.235
11 includeRequestAttemptCount: true
12 name: ratings.default.svc.cluster.local:9080
13 routes:
14 - decorator:
15 operation: ratings.default.svc.cluster.local:9080/*
16 match:
17 caseSensitive: true
18 headers:
19 - name: end-user
20 stringMatch:
21 exact: jason
22 prefix: /
23 metadata:
24 filterMetadata:
25 istio:
26 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratings
27 route:
28 cluster: outbound|9080|v1|ratings.default.svc.cluster.local
29 # ......
30 typedPerFilterConfig:
31 envoy.filters.http.fault:
32 '@type': type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
33 delay:
34 fixedDelay: 7s
35 percentage:
36 denominator: MILLION
37 numerator: 1000000
38 - decorator:
39 operation: ratings.default.svc.cluster.local:9080/*
40 match:
41 prefix: /
42 metadata:
43 filterMetadata:
44 istio:
45 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratings
46 route:
47 cluster: outbound|9080|v1|ratings.default.svc.cluster.local
48 # ......
可以看到上面的路由规则中有一个匹配头为 end-user 的规则:
1match:
2 caseSensitive: true
3 headers:
4 - name: end-user
5 stringMatch:
6 exact: jason
7 prefix: /
可以看到这个规则中还包含了一个 envoy.filters.http.fault 的配置,这个就是我们前面配置的故障注入规则,可以看到这个规则中的延迟时间为 7 秒,这就是我们前面配置的故障注入规则。
1envoy.filters.http.fault:
2 "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
3 delay:
4 fixedDelay: 7s
5 percentage:
6 denominator: MILLION
7 numerator: 1000000
🍀
reviews 服务的 v3 版本实际上已经修复了这个问题。reviews:v3 服务已将 reviews 与 ratings 的超时时间从 10 秒降低为 2.5 秒,因此它可以兼容(小于)下游 productpage 请求的超时时间。
如果您按照流量转移任务所述将所有流量转移到 reviews:v3,可以尝试修改延迟规则为任何低于 2.5 秒的数值,例如 2 秒,然后确认端到端的流程没有任何错误。
编辑文件:
1[root@master1 istio-1.19.3]#kubectl get vs
2NAME GATEWAYS HOSTS AGE
3bookinfo ["bookinfo-gateway"] ["*"] 42m
4details ["details"] 40m
5productpage ["productpage"] 40m
6ratings ["ratings"] 40m
7reviews ["reviews"] 40m
8
9[root@master1 istio-1.19.3]#kubectl edit vs reviews
10
11[root@master1 istio-1.19.3]#kubectl edit vs ratings


验证:

符合预期,可以看到有2s的延迟。
测试结束。😘
2.注入 HTTP abort 故障
==🚩 实战:注入 HTTP abort 故障-2023.11.12(测试成功)==
测试环境:
本次测试是在前面几次环境基础上传测试的,因此需要先具备前几次测试环境。

实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 更新VirtualService)
3 A[实战步骤] -->C(2️⃣ 验证)

测试微服务弹性的另一种方法是引入 HTTP abort 故障。这里同样我们针对测试用户 jason,将给 ratings 微服务引入一个 HTTP abort。在这种情况下,我们希望页面能够立即加载,同时显示 Ratings service is currently unavailable 这样的消息。
🍀
为用户 jason 创建一个发送 HTTP abort 的故障注入规则,执行下面的命令即可:
1$ kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml
这里创建的资源对象如下所示:
1apiVersion: networking.istio.io/v1alpha3
2kind: VirtualService
3metadata:
4 name: ratings
5spec:
6 hosts:
7 - ratings
8 http:
9 - match:
10 - headers:
11 end-user:
12 exact: jason
13 fault:
14 abort:
15 percentage:
16 value: 100.0
17 httpStatus: 500
18 route:
19 - destination:
20 host: ratings
21 subset: v1
22 - route:
23 - destination:
24 host: ratings
25 subset: v1
可以看到这里我们是将 fault 下面的 delay 延迟更改成了 abort,并且将 httpStatus 设置为 500,percentage 也设置为了 100,也就是 jason 用户访问 ratings 服务的所有请求都会变成 500 状态码的错误请求,这样我们就可以在页面上看到 Ratings service is currently unavailable 这样的消息了。
🍀
应用上面的规则后,我们可以通过浏览器打开 Bookinfo 应用,以用户 jason 登录到 /productpage 页面,正常就可以看到如下所示的页面:

🍀
当然如果注销用户 jason打开 Bookinfo 应用程序,我们会看到 /productpage 为除 jason 以外的其他用户调用了 reviews:v1(完全不调用 ratings),因此不会看到任何错误消息。
同样我们可以去查看下 Envoy 的路由配置,看下这个故障注入规则是如何映射到 Envoy 的路由配置中的:
1$ istioctl proxy-config routes reviews-v2-b7dcd98fb-gccfh --name 9080 -oyaml
2- name: "9080"
3 virtualHosts:
4 # ......
5 - domains:
6 - ratings.default.svc.cluster.local
7 - ratings
8 - ratings.default.svc
9 - ratings.default
10 - 10.101.184.235
11 includeRequestAttemptCount: true
12 name: ratings.default.svc.cluster.local:9080
13 routes:
14 - decorator:
15 operation: ratings.default.svc.cluster.local:9080/*
16 match:
17 caseSensitive: true
18 headers:
19 - name: end-user
20 stringMatch:
21 exact: jason
22 prefix: /
23 metadata:
24 filterMetadata:
25 istio:
26 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratings
27 route:
28 cluster: outbound|9080|v1|ratings.default.svc.cluster.local
29 # ......
30 typedPerFilterConfig:
31 envoy.filters.http.fault:
32 '@type': type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
33 abort:
34 httpStatus: 500
35 percentage:
36 denominator: MILLION
37 numerator: 1000000
38 - decorator:
39 operation: ratings.default.svc.cluster.local:9080/*
40 match:
41 prefix: /
42 metadata:
43 filterMetadata:
44 istio:
45 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratings
46 route:
47 cluster: outbound|9080|v1|ratings.default.svc.cluster.local
48 # ......
可以看到上面的路由表中也包含了一个匹配头为 end-user 的规则,同时也包含了一个 envoy.filters.http.fault 的配置,这个就是我们前面配置的故障注入规则,现在这个故障注入配置下面是 abort 了,httpStatus 为 500,这就是我们前面配置的故障注入规则。
🍀
到这里我们就学习了如何通过 VirtualService 对象来实现故障注入的功能,包括延迟请求和请求中断,当然本质上这个功能是通过 Envoy 的 fault 过滤器来实现的。此外我们还可以为 HTTP 请求配置超时,只需要通过路由规则中的 timeout 字段来指定即可,如下所示:
1apiVersion: networking.istio.io/v1alpha3
2kind: VirtualService
3metadata:
4 name: reviews
5spec:
6 hosts:
7 - reviews
8 http:
9 - route:
10 - destination:
11 host: reviews
12 subset: v2
13 timeout: 0.5s
测试结束。😘
流量拆分
==🚩 实战:流量拆分-2023.11.12(测试成功)==
测试环境:
本次测试是在前面几次环境基础上传测试的,因此需要先具备前几次测试环境。

实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 更新VirtualService)
3 A[实战步骤] -->C(2️⃣ 验证)
本节我们将了解如何将流量从微服务的一个版本逐步迁移到另一个版本,比如在 A/B 测试、金丝雀发布等场景中,我们需要将流量从一个版本逐步迁移到另一个版本,这个时候就需要用到流量拆分功能。
这里我们将会把 50% 的流量发送到 reviews:v1,另外,50% 的流量发送到 reviews:v3,接着,再把 100% 的流量发送到 reviews:v3 来完成流量转移。
🍀
首先运行下面的命令将所有流量路由到各个微服务的 v1 版本。
1kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
2kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml
这是因为前面我们做了其他测试,所以我们需要将流量转移的状态重置一下,这样才能保证我们的测试是正确的。
现在当我们在浏览器中访问 Bookinfo 应用的时候,不管刷新多少次,页面的 Reviews 部分都不会显示带评价星级的内容。这是因为 Istio 被配置为将星级评价的服务的所有流量都路由到了 reviews:v1 版本,而该版本的服务不访问带评价星级的服务,也就是最初的版本。
🍀
接下来我们使用下面的命令把 50% 的流量路由到 reviews:v1,50% 的流量路由到 reviews:v3:
1$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
该资源清单文件的内容如下所示:
1apiVersion: networking.istio.io/v1alpha3
2kind: VirtualService
3metadata:
4 name: reviews
5spec:
6 hosts:
7 - reviews
8 http:
9 - route:
10 - destination:
11 host: reviews
12 subset: v1
13 weight: 50
14 - destination:
15 host: reviews
16 subset: v3
17 weight: 50
在这个 VirtualService 对象中我们为 reviews 服务的路由规则增加了一个 weight 字段,这个字段的值为 50,表示将 50% 的流量路由到 reviews:v1,另外 50% 的流量路由到 reviews:v3。
🍀
应用该规则后我们可以前往浏览器访问 Bookinfo 应用,刷新 /productpage 页面,大约有 50% 的几率会看到页面中带红色星级的评价内容。

🍀
同样我们可以去查看下 Envoy 的路由配置,看下这个流量转移规则是如何映射到 Envoy 的路由配置中的:
1$ istioctl proxy-config routes productpage-v1-564d4686f-wwqqf --name 9080 -oyaml
2- name: "9080"
3 virtualHosts:
4 - domains:
5 - reviews.default.svc.cluster.local
6 - reviews
7 - reviews.default.svc
8 - reviews.default
9 - 10.97.120.56
10 name: reviews.default.svc.cluster.local:9080
11 routes:
12 - decorator:
13 operation: reviews:9080/*
14 match:
15 prefix: /
16 metadata:
17 filterMetadata:
18 istio:
19 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews
20 route:
21 weightedClusters:
22 clusters:
23 - name: outbound|9080|v1|reviews.default.svc.cluster.local
24 weight: 50
25 - name: outbound|9080|v3|reviews.default.svc.cluster.local
26 weight: 50
27 # ......
可以看到在 Envoy 的配置路由表中,reviews 服务的路由规则中多了一个 weightedClusters 字段,这个字段就是我们前面配置的基于权重的流量转移规则,其中包含了两个集群,一个是 outbound|9080|v1|reviews.default.svc.cluster.local,另外一个是 outbound|9080|v3|reviews.default.svc.cluster.local,并且两个集群的权重都是 50,这样就实现了将 50% 的流量路由到 reviews:v1,另外 50% 的流量路由到 reviews:v3。
而且这里我们通过 Istio 来实现的流量拆分功能和直接使用 K8s 的部署功能来进行版本迁移完全不同,后者使用了实例扩容来对流量进行管理。使用 Istio,两个版本的
reviews服务可以独立地进行扩容和缩容,而不会影响这两个服务版本之间的流量分发。
🍀
如果 reviews:v3 微服务已经稳定了,那么可以通过应用 VirtualService 规则将 100% 的流量路由到 reviews:v3:
1$ cat samples/bookinfo/networking/virtual-service-reviews-v3.yaml
2apiVersion: networking.istio.io/v1alpha3
3kind: VirtualService
4metadata:
5 name: reviews
6spec:
7 hosts:
8 - reviews
9 http:
10 - route:
11 - destination:
12 host: reviews
13 subset: v3
14$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v3.yaml
现在,当刷新 /productpage 时,将始终看到带有红色星级评分的评论。
测试结束。😘
流量镜像
流量镜像,也称为影子流量,是一个以尽可能低的风险为生产带来变化的强大的功能,镜像会将实时流量的副本发送到镜像服务,不会对主流量产生影响,这样就可以在不影响生产流量的情况下,对新版本的服务进行测试。

==🚩 实战:流量镜像-2023.11.12(测试成功)==
实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1TvCU9yymMXSjVHNQnXnBYw?pwd=uns1
提取码:uns1
2023.11.12-实战:流量镜像-2023.11.12(测试成功)

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 部署应用)
3 A[实战步骤] -->C(2️⃣ 配置VirtualService和DestinationRule)
4 A[实战步骤] -->D(3️⃣ 验证)
🍀
这里我们将首先把流量全部路由到测试服务的 v1 版本,然后执行规则将一部分流量镜像到 v2 版本。
首先部署两个版本的 Httpbin 服务,并开启访问日志功能:
httpbin-v1:
1$ cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: httpbin-v1
6spec:
7 selector:
8 matchLabels:
9 app: httpbin
10 version: v1
11 template:
12 metadata:
13 labels:
14 app: httpbin
15 version: v1
16 spec:
17 containers:
18 - image: docker.io/kennethreitz/httpbin
19 imagePullPolicy: IfNotPresent
20 name: httpbin
21 command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
22 ports:
23 - containerPort: 80
24EOF
httpbin-v2:
1$ cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: httpbin-v2
6spec:
7 selector:
8 matchLabels:
9 app: httpbin
10 version: v2
11 template:
12 metadata:
13 labels:
14 app: httpbin
15 version: v2
16 spec:
17 containers:
18 - image: docker.io/kennethreitz/httpbin
19 imagePullPolicy: IfNotPresent
20 name: httpbin
21 command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
22 ports:
23 - containerPort: 80
24EOF
httpbin Kubernetes 服务:
1$ kubectl create -f - <<EOF
2apiVersion: v1
3kind: Service
4metadata:
5 name: httpbin
6 labels:
7 app: httpbin
8spec:
9 ports:
10 - name: http
11 port: 8000
12 targetPort: 80
13 selector:
14 app: httpbin
15EOF
🍀
然后启动一个如下所示的 sleep 服务,用来提供 curl 工具进行负载:
1$ cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5 name: sleep
6spec:
7 selector:
8 matchLabels:
9 app: sleep
10 template:
11 metadata:
12 labels:
13 app: sleep
14 spec:
15 containers:
16 - name: sleep
17 image: curlimages/curl
18 command: ["/bin/sleep","3650d"]
19 imagePullPolicy: IfNotPresent
20EOF
🍀
创建后的资源对象如下所示:
1$ kubectl get pod -l app=sleep
2NAME READY STATUS RESTARTS AGE
3sleep-6b56495b4c-rxf7j 2/2 Running 0 9s
4$ kubectl get pod -l app=httpbin
5NAME READY STATUS RESTARTS AGE
6httpbin-v1-65665cdc46-l8wfw 2/2 Running 0 2m
7httpbin-v2-66f744d46d-l52mb 2/2 Running 0 92s
8$ kubectl get svc httpbin
9NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
10httpbin ClusterIP 10.96.247.77 <none> 8000/TCP 102s
🍀
默认情况下,Kubernetes 在 httpbin 服务的两个版本之间进行负载均衡,接下来我们将创建如下所示的规则,把所有流量都路由到 v1 版本。
1#mirror.yaml
2apiVersion: networking.istio.io/v1alpha3
3kind: VirtualService
4metadata:
5 name: httpbin
6spec:
7 hosts:
8 - httpbin
9 http:
10 - route:
11 - destination:
12 host: httpbin
13 subset: v1
14 weight: 100
15---
16apiVersion: networking.istio.io/v1alpha3
17kind: DestinationRule
18metadata:
19 name: httpbin
20spec:
21 host: httpbin
22 subsets:
23 - name: v1
24 labels:
25 version: v1
26 - name: v2
27 labels:
28 version: v2
这里我们先创建一个 DestinationRule 对象,将 httpbin 服务分成 v1 和 v2 两个子集,分别匹配 version=v1 和 version=v2 的 Pod,然后再创建一个 VirtualService 对象,将所有流量都路由到 v1 子集。
1[root@master1 ~]#kubectl apply -f mirror.yaml
2virtualservice.networking.istio.io/httpbin created
3destinationrule.networking.istio.io/httpbin created
🍀
应用上面的资源对象后,现在所有流量都被转到 httpbin:v1 服务,我们可以向此服务发送请求来验证:
1$ export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
2$ kubectl exec "${SLEEP_POD}" -c sleep -- curl -sS http://httpbin:8000/headers
3{
4 "headers": {
5 "Accept": "*/*",
6 "Host": "httpbin:8000",
7 "User-Agent": "curl/7.81.0-DEV",
8 "X-B3-Parentspanid": "623917d026166bc1",
9 "X-B3-Sampled": "1",
10 "X-B3-Spanid": "8ef9bb2eceeceec5",
11 "X-B3-Traceid": "b586a087a8c2219a623917d026166bc1",
12 "X-Envoy-Attempt-Count": "1",
13 "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=4252b9bc6aa8f29690137269e51cd45b9441078c918e30f1380537bc77402b4d;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
14 }
15}
🍀
然后分别查看 httpbin Pod 的 v1 和 v2 两个版本的日志,可以看到 v1 版本的访问日志条目,而 v2 版本没有日志:
1$ export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
2$ kubectl logs "$V1_POD" -c httpbin
3127.0.0.6 - - [10/Nov/2023:07:38:53 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
4
5$ export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
6$ kubectl logs "$V2_POD" -c httpbin
🍀
接下来我们将创建一个新的规则,将 100% 的流量发送到 v1 版本,但是同样将 100% 的相同流量镜像到 httpbin:v2 服务去:
1#mirror2.yaml
2apiVersion: networking.istio.io/v1alpha3
3kind: VirtualService
4metadata:
5 name: httpbin
6spec:
7 hosts:
8 - httpbin
9 http:
10 - route:
11 - destination:
12 host: httpbin
13 subset: v1
14 weight: 100
15 mirror:
16 host: httpbin
17 subset: v2
18 mirrorPercentage:
19 value: 100.0
当流量被镜像时,请求将发送到镜像服务中,并在 headers 中的 Host/Authority 属性值上追加 -shadow,例如 cluster-1 变为 cluster-1-shadow。
此外这些被镜像的流量是**『即发即弃』**的,就是说镜像请求的响应会被丢弃,前面在 Envoy 中我们也实验过该特性。
当然我们也可以使用 mirrorPercentage 属性下的 value 字段来设置镜像流量的百分比,而不是镜像所有请求。如果没有这个属性,将镜像所有流量。
🍀
1[root@master1 ~]#kubectl apply -f mirror2.yaml
2virtualservice.networking.istio.io/httpbin configured
直接应用该规则后,我们可以向 httpbin 服务发送请求来验证:
1$ kubectl exec "${SLEEP_POD}" -c sleep -- curl -sS http://httpbin:8000/headers
2{
3 "headers": {
4 "Accept": "*/*",
5 "Host": "httpbin:8000",
6 "User-Agent": "curl/7.81.0-DEV",
7 "X-B3-Parentspanid": "6bab21955204ba63",
8 "X-B3-Sampled": "1",
9 "X-B3-Spanid": "ff009be6e25704dc",
10 "X-B3-Traceid": "2b52f289d76c3dbc6bab21955204ba63",
11 "X-Envoy-Attempt-Count": "1",
12 "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=4252b9bc6aa8f29690137269e51cd45b9441078c918e30f1380537bc77402b4d;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
13 }
14}
🍀
现在就可以看到 v1 和 v2 版本中都有了访问日志。v2 版本中的访问日志就是由镜像流量产生的,这些请求的实际目标是 v1 版本。
1$ kubectl logs "$V1_POD" -c httpbin
2127.0.0.6 - - [10/Nov/2023:07:38:53 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
3127.0.0.6 - - [10/Nov/2023:07:52:31 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
4
5$ kubectl logs "$V2_POD" -c httpbin
6127.0.0.6 - - [10/Nov/2023:07:52:31 +0000] "GET /headers HTTP/1.1" 200 569 "-" "curl/7.81.0-DEV"
🍀
同样我们可以去查看下 Envoy 里面的路由表配置来验证下这个流量镜像规则是如何映射到 Envoy 的路由配置中的:
1# 8000 端口
2$ istioctl proxy-config routes $SLEEP_POD --name 8000 -oyaml
3- name: "8000"
4 virtualHosts:
5 - domains:
6 - httpbin.default.svc.cluster.local
7 - httpbin
8 - httpbin.default.svc
9 - httpbin.default
10 - 10.96.247.77
11 name: httpbin.default.svc.cluster.local:8000
12 routes:
13 - decorator:
14 operation: httpbin.default.svc.cluster.local:8000/*
15 match:
16 prefix: /
17 metadata:
18 filterMetadata:
19 istio:
20 config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/httpbin
21 route:
22 cluster: outbound|8000|v1|httpbin.default.svc.cluster.local
23 requestMirrorPolicies:
24 - cluster: outbound|8000|v2|httpbin.default.svc.cluster.local
25 runtimeFraction:
26 defaultValue:
27 denominator: MILLION
28 numerator: 1000000
29 traceSampled: false
30 # ......
可以看到在 Envoy 的路由表中,httpbin 服务的路由规则中多了一个 requestMirrorPolicies 字段,这个字段就是我们前面配置的流量镜像规则,其中包含了一个集群,就是 outbound|8000|v2|httpbin.default.svc.cluster.local,并且配置的是 100%比例,这样就实现了将 100% 的流量镜像到 httpbin:v2,这其实和前面我们在 Envoy 中去实现的流量镜像功能是一致的。
🍀
最后如果测试后不再需要这些服务可以将其清理:
1kubectl delete virtualservice httpbin
2kubectl delete destinationrule httpbin
关闭 Httpbin 服务和客户端:
1kubectl delete deploy httpbin-v1 httpbin-v2 sleep
2kubectl delete svc httpbin
测试结束。😘
熔断
熔断是创建弹性微服务应用程序的重要模式,熔断能够使应用程序具备应对来自故障、潜在峰值和其他未知网络因素影响的能力。服务熔断是应对微服务雪崩效应的一种链路保护机制,类似股市、保险丝。微服务之间的数据交互是通过远程调用来完成的,服务 A 调用服务 B,服务 B 调用服务 C,某一时间链路上对服务 C 的调用响应时间过长或者服务 C 不可用,随着时间的增长,对服务 C 的调用也越来越多,然后服务 C 崩溃了,但是链路调用还在,对服务 B 的调用也在持续增多,然后服务 B 崩溃,随之 A 也崩溃,导致雪崩效应。
1.熔断概述
服务熔断是应对雪崩效应的一种微服务链路保护机制。例如在高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。同样,在微服务架构中,熔断机制也是起着类似的作用。当调用链路的某个微服务不可用或者响应时间太长时,会进行服务熔断,不再有该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。在 Spring Cloud 框架里,熔断机制通过 Hystrix实现,Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是 5 秒内 20 次调用失败,就会启动熔断机制。
Istio 也有对熔断的支持,无需对代码进行任何更改就可以为应用增加熔断和限流功能。Istio 中熔断和限流在 DestinationRule 的 CRD 资源的 TrafficPolicy 中设置,一般设置连接池(ConnectionPool)限流方式和异常检测(outlierDetection)熔断方式。两者各自配置部分参数,其中参数有可能存在对方的功能,并没有很严格的区分出来,如主要进行限流设置的 ConnectionPool 中的 maxPendingRequests 参数,最大等待请求数,如果超过则也会暂时的熔断。
连接池(ConnectionPool)设置
ConnectionPool 可以对上游服务的并发连接数和请求数进行限制,适用于 TCP 和 HTTP,所以 ConnectionPool 又称之为限流。

我们可以在 DestinationRule 中配置连接池:

TCP 连接池设置 http 和 tcp 上游连接的设置。
tcp 相关参数设置如下:

maxConnections:到目标主机的 HTTP1/TCP 最大连接数量,只作用于 http1.1,不作用于 http2,因为后者只建立一次连接。connectTimeout:TCP 连接超时时间,默认单位秒。也可以写其他单位,如 ms。tcpKeepalive:如果在套接字上设置SO_KEEPALIVE可以确保 TCP Keepalive。TCP 的 TcpKeepalive 设置:Probes:在确定连接已死之前,在没有响应的情况下发送的 keepalive 探测的最大数量。默认值是使用系统级别的配置(Linux 默认值为 9)。Time:发送 keep-alive 探测前连接存在的空闲时间。默认值是使用系统的配置(Linux 默认值为 7200s,即 2 小时。interval:探测活动之间的时间间隔。默认值是使用系统的配置(Linux 默认值为 75 秒)。
http 相关参数配置如下:

http 连接池设置用于 http1.1/HTTP2/GRPC 连接。
http1MaxPendingRequests:http 请求 pending 状态的最大请求数,从应用容器发来的 HTTP 请求的最大等待转发数,默认是2^32-1。http2MaxRequests:后端请求的最大数量,默认是2^32-1。maxRequestsPerConnection:在一定时间内限制对后端服务发起的最大请求数,如果超过了这个限制,就会开启限流。如果将这一参数设置为 1 则会禁止 keepalive 特性;idleTimeout:上游连接池连接的空闲超时。空闲超时被定义为没有活动请求的时间段。如果未设置,则没有空闲超时。当达到空闲超时时,连接将被关闭。注意,基于请求的超时意味着 HTTP/2ping 将无法保持有效连接。适用于 HTTP1.1 和 HTTP2 连接;maxRetries:在给定时间内,集群中所有主机都可以执行的最大重试次数。默认为 3。
异常检测(outlierDetection)
熔断策略对集群中压力过大的上游服务起到一定的保护作用,有一种情况是集群中的某些节点完全崩溃,这种情况我们并不知晓。istio 引入了异常检测来完成熔断的功能,通过周期性动态的异常检测来确定上游集群中的某些主机是否异常,如果发现异常,就将该主机从连接池中隔离出去,这就是异常检测。也是一种熔断的实现,用于跟踪上游服务的状态。适用于 HTTP 和 TCP 服务。对于 HTTP 服务,API 调用连续返回 5xx 错误,则在一定时间内连接池拒绝此服务。对于 TCP 服务,一个主机连接超时次数或者连接失败次数达到一定次数时就认为是连接错误。
异常检测的原理:
- 检测到了某个主机异常
- 如果到目前为止负载均衡池中还没有主机被隔离出去,将会立即隔离该异常主机;如果已经有主机被隔离出去,就会检查当前隔离的主机数是否低于设定的阈值(通过 envoy 中的
outlier_detection.max_ejection_percent指定),如果当前被隔离的主机数量不超过该阈值,就将该主机隔离出去,否则不隔离 - 隔离不是永久的,会有一个时间限制。当主机被隔离后,该主机就会被标记为不健康,并且不会被加入到负载均衡池中,除非负载均衡处于恐慌模式。隔离时间等于 envoy 中的
outlier_detection.base_ejection_time_ms的值乘以主机被隔离的次数。所以如果某个主机连续出现故障,会导致它被隔离的时间越来越长。 - 经过了规定的隔离时间之后,被隔离的主机将会自动恢复过来,重新接受调用方的远程调用。通常异常检测会与主动健康检查一起用于全面的健康检查解决方案。
异常检测类型:
连续 5xx 响应:如果上游主机连续返回一定数量的 5xx 响应,该主机就会被驱逐。这里的 5xx 响应不仅包括返回的 5xx 状态码,也包括 HTTP 路由返回的事件(如连接超时和连接错误)。隔离主机所需的 5xx 响应数量由consecutive_5xx的值控制。连续网关故障:如果上游主机连续返回一定数量的gatewayerrors( 502、503 或 504 状态码),该主机就会被驱逐。这里同样也包括 HTTP 路由返回的一个事件(如连接超时和连接错误)。隔离主机所需的连续网关故障数量由consecutive_gateway_failure的值控制。调用成功率:基于调用成功率的异常检测类型会聚合集群中每个主机的调用成功率,然后根据统计的数据以给定的周期来隔离主机。如果该主机的请求数量小于success_rate_request_volume指定的值,则不会为该主机计算调用成功率,因此聚合的统计数据中不会包括该主机的调用成功率。如果在给定的周期内具有最小所需请求量的主机数小于success_rate_minimum_hosts指定的值,则不会对该集群执行调用成功率检测。
我们可以在 DestinationRule 中配置异常检测:

consecutiveErrors:从连接池开始拒绝连接,已经连接失败的次数。当通过 HTTP 访问时,返回代码是 502、503 或 504 则视为错误。当访问不透明的 TCP 连接时,连接超时和连接错误/失败也会都视为错误。即将实例从负载均衡池中剔除,需要连续的错误(HTTP5XX 或者 TCP 断开/超时)次数。默认是 5。interval:拒绝访问扫描的时间间隔,即在 interval 内连续发生 1 个consecutiveErrors错误,则触发服务熔断,格式是 1h/1m/1s/1ms,但必须大于等于 1ms。即分析是否需要剔除的频率,多久分析一次,默认 10 秒。baseEjectionTime:最短拒绝访问时长,这个时间主机将保持拒绝访问,且如果拒绝访问达到一定的次数。这允许自动增加不健康服务的拒绝访问时间,时间为baseEjectionTime*驱逐次数。格式:1h/1m/1s/1ms,但必须大于等于 1ms。实例被剔除后,至少多久不得返回负载均衡池,默认是 30 秒。maxEjectionPercent:服务在负载均衡池中被拒绝访问(被移除)的最大百分比,负载均衡池中最多有多大比例被剔除,默认是 10%。
上面例子是设置 TCP 的连接池大小为 100 个连接,可以有 1000 个并发 HTTP2 请求,reviews 服务的请求连接比不大于 10。此外,配置拒绝访问的时间间隔是 5 分钟,同时,任何连续 7 次返回 5XX 码的主机,将会拒绝访问 15 分钟。
2.熔断示例
下面我们将来演示如何为连接、请求以及异常检测配置熔断。
==🚩 实战:熔断示例-2023.11.13(测试成功)==
实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1️⃣ 部署应用)
3 A[实战步骤] -->C(2️⃣ 配置熔断)
4 A[实战步骤] -->D(3️⃣ 验证)
🍀
首先启动一个 Httpbin 示例服务,如果您启用了 Sidecar 自动注入,通过以下命令部署 httpbin 服务:
1$ kubectl apply -f samples/httpbin/httpbin.yaml
否则,必须在部署 httpbin 应用程序前进行手动注入:
1$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml)
🍀
应用部署完成后首先我们需要创建一个 DestinationRule 对象,用来在调用 httpbin 服务时应用熔断设置:
1$ kubectl apply -f - <<EOF
2apiVersion: networking.istio.io/v1alpha3
3kind: DestinationRule
4metadata:
5 name: httpbin
6spec:
7 host: httpbin
8 trafficPolicy:
9 connectionPool:
10 tcp:
11 maxConnections: 1 # 最大连接数
12 http:
13 http1MaxPendingRequests: 1 # 最大挂起请求数
14 maxRequestsPerConnection: 1 # 每个连接到后端的最大请求数
15 outlierDetection:
16 consecutive5xxErrors: 1 # host从连接池中被拒绝之前的连续 5xx 错误数
17 interval: 1s # 检测间隔
18 baseEjectionTime: 3m # 基础驱逐时间
19 maxEjectionPercent: 100 # 最大驱逐百分比
20EOF
这里我们在 httpbin 服务的 DestinationRule 对象中配置了 trafficPolicy 字段,用来配置熔断限流的相关参数。
这些参数的具体含义可以参考官方文档。
🍀
我们说了 DestinationRule 对象其实就是配置 Envoy 里面的 Cluster 集群,那么我们可以通过 istioctl proxy-config cluster 命令来查看下 httpbin 服务的 Cluster 集群配置信息:
1$ istioctl proxy-config cluster httpbin-86869bccff-pkks7 --fqdn httpbin.default.svc.cluster.local -o yaml
2- circuitBreakers:
3 thresholds:
4 - maxConnections: 1
5 maxPendingRequests: 1
6 maxRequests: 4294967295
7 maxRetries: 4294967295
8 trackRemaining: true
9 connectTimeout: 10s
10 edsClusterConfig:
11 edsConfig:
12 ads: {}
13 initialFetchTimeout: 0s
14 resourceApiVersion: V3
15 serviceName: outbound|8000||httpbin.default.svc.cluster.local
16 lbPolicy: LEAST_REQUEST
17 name: outbound|8000||httpbin.default.svc.cluster.local
18 outlierDetection:
19 baseEjectionTime: 180s
20 consecutive5xx: 1
21 enforcingConsecutive5xx: 100
22 enforcingSuccessRate: 0
23 interval: 1s
24 maxEjectionPercent: 100
25 type: EDS
可以看到上面的配置中包含了我们前面配置的熔断参数,这些参数都是 Envoy 的 Cluster 集群的配置参数,比如 circuitBreakers、outlierDetection 等参数,也和我们前面在 DestinationRule 对象中配置的参数是一致的。
🍀
接下来我们创建一个 Fortio 的客户端程序来发送流量到 httpbin 服务,**Fortio 是一个开源的 HTTP 负载测试工具,它可以控制连接数、并发数及发送 HTTP 请求的延迟。**通过 Fortio 能够有效的触发前面在 DestinationRule 中设置的熔断策略。
1$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/sample-client/fortio-deploy.yaml)
2$ kubectl get pods -l app=fortio
3NAME READY STATUS RESTARTS AGE
4fortio-deploy-5cd456bbdb-5xjrz 2/2 Running 0 39s
🍀
创建成功后我们可以登入 Fortio 客户端 Pod,然后使用 Fortio 工具调用 httpbin 服务:
1$ export FORTIO_POD=$(kubectl get pods -l app=fortio -o 'jsonpath={.items[0].metadata.name}')
2# c=1 表示并发数为 1,n=20 表示总请求数为 20
3$ kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 1 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
4# ......
5Code 200 : 20 (100.0 %)
6Response Header Sizes : count 20 avg 230 +/- 0 min 230 max 230 sum 4600
7Response Body/Total Sizes : count 20 avg 824 +/- 0 min 824 max 824 sum 16480
8All done 20 calls (plus 0 warmup) 3.399 ms avg, 294.1 qps
可以看出总共发送了 20 个 HTTP 连接,也就是 20 个请求,响应码均为 200。接下来我们就可以测试熔断功能了。
1[root@master1 istio-1.19.3]#kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 1 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
222:23:10 I logger.go:127> Log level is now 3 Warning (was 2 Info)
3Fortio 1.17.1 running at 0 queries per second, 2->2 procs, for 20 calls: http://httpbin:8000/get
4Starting at max qps with 1 thread(s) [gomax 2] for exactly 20 calls (20 per thread + 0)
5Ended after 109.05115ms : 20 calls. qps=183.4
6Aggregated Function Time : count 20 avg 0.0054483557 +/- 0.006368 min 0.002985644 max 0.03283692 sum 0.108967113
7# range, mid point, percentile, count
8>= 0.00298564 <= 0.003 , 0.00299282 , 5.00, 1
9> 0.003 <= 0.004 , 0.0035 , 65.00, 12
10> 0.004 <= 0.005 , 0.0045 , 85.00, 4
11> 0.006 <= 0.007 , 0.0065 , 90.00, 1
12> 0.007 <= 0.008 , 0.0075 , 95.00, 1
13> 0.03 <= 0.0328369 , 0.0314185 , 100.00, 1
14# target 50% 0.00375
15# target 75% 0.0045
16# target 90% 0.007
17# target 99% 0.0322695
18# target 99.9% 0.0327802
19Sockets used: 1 (for perfect keepalive, would be 1)
20Jitter: false
21Code 200 : 20 (100.0 %)
22Response Header Sizes : count 20 avg 230.05 +/- 0.2179 min 230 max 231 sum 4601
23Response Body/Total Sizes : count 20 avg 824.05 +/- 0.2179 min 824 max 825 sum 16481
24All done 20 calls (plus 0 warmup) 5.448 ms avg, 183.4 qps
25[root@master1 istio-1.19.3]#
在 DestinationRule 配置中,我们定义了 maxConnections: 1 和 http1MaxPendingRequests: 1,表示如果并发的连接和请求数超过一个,在 istio-proxy 进行进一步的请求和连接时,后续请求或连接将被阻止。
🍀
这里我们来发送并发数为 2 的连接(-c 2),请求 20 次(-n 20):
1$ kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
2# ......
3Code 200 : 16 (80.0 %)
4Code 503 : 4 (20.0 %)
5Response Header Sizes : count 20 avg 184.15 +/- 92.08 min 0 max 231 sum 3683
6Response Body/Total Sizes : count 20 avg 707.55 +/- 233.3 min 241 max 825 sum 14151
7All done 20 calls (plus 0 warmup) 11.073 ms avg, 178.6 qps
可以看到总共发送了 20 个 HTTP 连接,其中 16 个请求成功,4 个请求被熔断了,响应码为 503。
🍀
可以通过查看 istio-proxy 状态来验证:
1$ kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending
2cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.remaining_pending: 1
3cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0
4cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0
5cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_active: 0
6cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_failure_eject: 0
7cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_overflow: 4
8cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_total: 36
其中 upstream_rq_pending_overflow 的值为 4,表明 maxConnections 断路器起作用了。istio-proxy 允许一定的冗余,我们可以将线程数提高到 3,限流的效果会更明显。通过限制待处理请求队列的长度,可以对恶意请求、DoS 和系统中的级联错误起到一定的缓解作用。
🍀
我们将并发连接数提高到 3 个:
1$ kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 30 -loglevel Warning http://httpbin:8000/get
2# ......
3Code 200 : 8 (26.7 %)
4Code 503 : 22 (73.3 %)
5Response Header Sizes : count 30 avg 61.5 +/- 102 min 0 max 231 sum 1845
6Response Body/Total Sizes : count 30 avg 396.63333 +/- 258.1 min 241 max 825 sum 11899
7All done 30 calls (plus 0 warmup) 12.773 ms avg, 208.3 qps
现在可以看到只有 26.7 % 的请求成功,其余的均被熔断器拦截了:
1$ kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending
2cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.remaining_pending: 1
3cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0
4cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0
5cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_active: 0
6cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_failure_eject: 0
7cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_overflow: 26
8cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_total: 44
可以看到 upstream_rq_pending_overflow 值 为 26,这意味着,目前为止已有 26 个调用被标记为熔断了。
测试结束。😘
TCP 流量拆分
==🚩 实战:TCP 流量拆分-2023.11.15(测试成功)==
实验环境:
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2
3[root@master1 ~]#istioctl version
4client version: 1.19.3
5control plane version: 1.19.3
6data plane version: 1.19.3 (8 proxies)
实验软件:
链接:https://pan.baidu.com/s/1pMnJxgL63oTlGFlhrfnXsA?pwd=7yqb
提取码:7yqb
2023.11.5-实战:BookInfo 示例应用-2023.11.5(测试成功) –本节测试yaml在此目录里

实验步骤:

1graph LR
2 A[实战步骤] -->B(1、 部署应用)
3 A[实战步骤] -->C(2、 创建Gatewy,vs,rs)
4 A[实战步骤] -->D(3、 配置TCP 流量拆分)
5 A[实战步骤] -->E(4、 验证)
前面我们试验了 HTTP 的流量拆分,那么 TCP 流量拆分如何实现呢?TCP 流量拆分是指将来自客户端的 TCP 流量分发到多个后端服务的过程。在 Istio 中,TCP 流量拆分是通过 VirtualService 和 DestinationRule 对象来实现的。
这里我们来演示如何将 TCP 流量从微服务的一个版本迁移到另一个版本。我们将会把 100% 的 TCP 流量分配到 tcp-echo:v1,接着,再通过配置 Istio 路由权重把 20% 的 TCP 流量分配到 tcp-echo:v2。
- 首先部署
tcp-echo服务的v1和v2两个版本:
1kubectl apply -f samples/tcp-echo/tcp-echo-services.yaml
查看:
1[root@master1 istio-1.19.3]#kubectl get po -l app=tcp-echo
2NAME READY STATUS RESTARTS AGE
3tcp-echo-v1-64c9b4bc95-hc5z7 2/2 Running 0 69s
4tcp-echo-v2-5b5657b486-dlsqf 2/2 Running 0 69s
5[root@master1 istio-1.19.3]#kubectl get svc tcp-echo
6NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
7tcp-echo ClusterIP 10.99.200.47 <none> 9000/TCP,9001/TCP 71s
该资源对象中包含 tcp-echo 的两个 Deployment 对象,包含 9000、9001、9002 三个端口,但是通过 Service 只暴露了 9000 和 9001 两个端口,省略了 9002 端口是为了测试透传能力,内容如下所示:
1apiVersion: v1
2kind: Service
3metadata:
4 name: tcp-echo
5 labels:
6 app: tcp-echo
7 service: tcp-echo
8spec:
9 ports:
10 - name: tcp
11 port: 9000
12 - name: tcp-other
13 port: 9001
14 # Port 9002 is omitted intentionally for testing the pass through filter chain.
15 selector:
16 app: tcp-echo
17---
18apiVersion: apps/v1
19kind: Deployment
20metadata:
21 name: tcp-echo-v1
22 labels:
23 app: tcp-echo
24 version: v1
25spec:
26 selector:
27 matchLabels:
28 app: tcp-echo
29 version: v1
30 template:
31 metadata:
32 labels:
33 app: tcp-echo
34 version: v1
35 spec:
36 containers:
37 - name: tcp-echo
38 image: docker.io/istio/tcp-echo-server:1.2
39 imagePullPolicy: IfNotPresent
40 args: ["9000,9001,9002", "one"]
41 ports:
42 - containerPort: 9000
43 - containerPort: 9001
44---
45apiVersion: apps/v1
46kind: Deployment
47metadata:
48 name: tcp-echo-v2
49 labels:
50 app: tcp-echo
51 version: v2
52spec:
53 selector:
54 matchLabels:
55 app: tcp-echo
56 version: v2
57 template:
58 metadata:
59 labels:
60 app: tcp-echo
61 version: v2
62 spec:
63 containers:
64 - name: tcp-echo
65 image: docker.io/istio/tcp-echo-server:1.2
66 imagePullPolicy: IfNotPresent
67 args: ["9000,9001,9002", "two"]
68 ports:
69 - containerPort: 9000
70 - containerPort: 9001
- 然后同样再部署一个 sleep 示例应用,作为发送请求的测试源:
1kubectl apply -f samples/sleep/sleep.yaml
对应的 Pod 如下所示:
1[root@master1 istio-1.19.3]#kubectl get pods
2NAME READY STATUS RESTARTS AGE
3……
4sleep-9454cc476-nx668 2/2 Running 0 11s
5tcp-echo-v1-64c9b4bc95-hc5z7 2/2 Running 0 27m
6tcp-echo-v2-5b5657b486-dlsqf 2/2 Running 0 27m
- 接下来我们将所有 TCP 流量路由到微服务
tcp-echo的v1版本,应用下面的资源清单即可:
1$ kubectl apply -f samples/tcp-echo/tcp-echo-all-v1.yaml
查看:
1[root@master1 istio-1.19.3]#kubectl get gateway
2NAME AGE
3bookinfo-gateway 2d16h
4tcp-echo-gateway 41s
5[root@master1 istio-1.19.3]#kubectl get vs
6NAME GATEWAYS HOSTS AGE
7bookinfo ["bookinfo-gateway"] ["*"] 2d16h
8……
9tcp-echo ["tcp-echo-gateway"] ["*"] 46s
10[root@master1 istio-1.19.3]#kubectl get dr
11NAME HOST AGE
12
13tcp-echo-destination tcp-echo 57s
该资源对象中包含了 tcp-echo 服务的 Gateway、DestinationRule 和 VirtualService 对象,内容如下所示:
1apiVersion: networking.istio.io/v1alpha3
2kind: Gateway
3metadata:
4 name: tcp-echo-gateway
5spec:
6 selector:
7 istio: ingressgateway
8 servers:
9 - port:
10 number: 31400 # istio-ingressgateway 包含该端口
11 name: tcp
12 protocol: TCP
13 hosts:
14 - "*"
15---
16apiVersion: networking.istio.io/v1alpha3
17kind: DestinationRule
18metadata:
19 name: tcp-echo-destination
20spec:
21 host: tcp-echo
22 subsets:
23 - name: v1
24 labels:
25 version: v1 # 匹配 tcp-echo-v1 的标签
26 - name: v2
27 labels:
28 version: v2 # 匹配 tcp-echo-v2 的标签
29---
30apiVersion: networking.istio.io/v1alpha3
31kind: VirtualService
32metadata:
33 name: tcp-echo
34spec:
35 hosts:
36 - "*"
37 gateways:
38 - tcp-echo-gateway # 关联上面的 Gateway
39 tcp:
40 - match:
41 - port: 31400 # 匹配上面 Gateway 的端口
42 route:
43 - destination: # 路由导 tcp-echo 服务
44 host: tcp-echo
45 port:
46 number: 9000
47 subset: v1 # 全部路由到 v1 子集
上面的资源清单中单独定义了一个 Gateway 对象,用来暴露 TCP 流量的端口,然后通过 VirtualService 对象将 TCP 流量路由到 tcp-echo 服务的 v1 版本,这样所有的流量都会被路由到 tcp-echo:v1,需要注意的是 VirtualService 对象中配置的是 tcp 协议,而不是 http 协议了,因为我们这里要处理的是一个 TCP 服务,其他方式方法都是一样的。
查看下istio-ingressgateway内容:
1[root@master1 istio-1.19.3]#kubectl get svc istio-ingressgateway -nistio-system -oyaml
2……
3 ports:
4 - name: status-port
5 nodePort: 31410
6 port: 15021
7 protocol: TCP
8 targetPort: 15021
9 - name: http2
10 nodePort: 31666
11 port: 80
12 protocol: TCP
13 targetPort: 8080
14 - name: https
15 nodePort: 32213
16 port: 443
17 protocol: TCP
18 targetPort: 8443
19 - name: tcp
20 nodePort: 30291 ##
21 port: 31400 ##
22 protocol: TCP
23 targetPort: 31400 ##证明容器里也有一个应用监听31400端口
24 - name: tls
25 nodePort: 31787
26 port: 15443
27 protocol: TCP
28 targetPort: 15443
29……
30[root@master1 istio-1.19.3]#kubectl get po istio-ingressgateway-9c8b9b586-vj44l -nistio-system -oyaml
31……
32image: docker.io/istio/proxyv2:1.19.3
33 imagePullPolicy: IfNotPresent
34 name: istio-proxy
35 ports:
36 - containerPort: 15021
37 protocol: TCP
38 - containerPort: 8080 #http
39 protocol: TCP
40 - containerPort: 8443 #https
41 protocol: TCP
42 - containerPort: 31400 #TCP流量
43 protocol: TCP
44 - containerPort: 15443
45 protocol: TCP
46 - containerPort: 15090
47 name: http-envoy-prom
48 protocol: TCP
49……
- 我们可以用下面的方式来测试,首先获得 Ingress Gateway 的访问入口地址和端口:
1export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="tcp")].nodePort}')
2export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
查看:
1echo $TCP_INGRESS_PORT #istio-ingressgateway svc的nodePort(监听器端口),因为这里的SVC是LoadBalancer类型的,因为我们通过NodePort来访问就行。
2echo $INGRESS_HOST
3
4[root@master1 istio-1.19.3]#echo $TCP_INGRESS_PORT #svc的nodePort
530291
6[root@master1 istio-1.19.3]#echo $INGRESS_HOST
7172.29.9.63
- 然后通过 sleep 容器中的
nc命令来发送 TCP 流量:
1[root@master1 istio-1.19.3]#export SLEEP=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
2[root@master1 istio-1.19.3]#echo $SLEEP
3sleep-9454cc476-nx668
4
5$ for i in {1..20}; do
6 kubectl exec "$SLEEP" -c sleep -- sh -c "(date; sleep 1) | nc $INGRESS_HOST $TCP_INGRESS_PORT";
7done
8one Mon Nov 13 03:23:59 UTC 2023
9one Mon Nov 13 03:24:00 UTC 2023
10one Mon Nov 13 03:24:01 UTC 2023
11# ......
可以看到所有时间戳都有一个前缀 one,说明所有流量都被路由到 tcp-echo 的 v1 版本,当然我们也可以在 v1 版本的 Pod 中查看到相应的日志。

查看v1 版本的 Pod 中查看到相应的日志:
1[root@master1 istio-1.19.3]#kubectl logs -f tcp-echo-v1-64c9b4bc95-hc5z7
2listening on [::]:9002, prefix: one
3listening on [::]:9000, prefix: one
4listening on [::]:9001, prefix: one
5
6
7request: Tue Nov 14 23:44:32 UTC 2023
8response: one Tue Nov 14 23:44:32 UTC 2023
9request: Tue Nov 14 23:44:34 UTC 2023
10response: one Tue Nov 14 23:44:34 UTC 2023
11request: Tue Nov 14 23:44:35 UTC 2023
12response: one Tue Nov 14 23:44:35 UTC 2023
13request: Tue Nov 14 23:44:36 UTC 2023
14response: one Tue Nov 14 23:44:36 UTC 2023
15request: Tue Nov 14 23:44:37 UTC 2023
16……
- 接下来我们将 20% 的 TCP 流量路由到
tcp-echo:v2版本,应用下面的资源清单即可:
1kubectl apply -f samples/tcp-echo/tcp-echo-20-v2.yaml
该清单文件内容如下所示:
1apiVersion: networking.istio.io/v1alpha3
2kind: VirtualService
3metadata:
4 name: tcp-echo
5spec:
6 hosts:
7 - "*"
8 gateways:
9 - tcp-echo-gateway
10 tcp:
11 - match:
12 - port: 31400
13 route:
14 - destination:
15 host: tcp-echo
16 port:
17 number: 9000
18 subset: v1
19 weight: 80 # 80% 的流量路由到 v1
20 - destination:
21 host: tcp-echo
22 port:
23 number: 9000
24 subset: v2
25 weight: 20 # 20% 的流量路由到 v2
这里我们更新了 VirtualService 对象,将 tcp-echo 服务的 v1 版本和 v2 版本的权重分别设置为 80% 和 20%,这样 20% 的流量就会被路由到 tcp-echo:v2 版本了,这和前面的 HTTP 基于权重的流量拆分是一样的方式。
- 应用成功后,我们再次发送 TCP 流量来验证:
1$ export SLEEP=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
2$ for i in {1..20}; do
3 kubectl exec "$SLEEP" -c sleep -- sh -c "(date; sleep 1) | nc $INGRESS_HOST $TCP_INGRESS_PORT";
4done
5one Mon Nov 13 03:29:32 UTC 2023
6one Mon Nov 13 03:29:33 UTC 2023
7one Mon Nov 13 03:29:34 UTC 2023
8one Mon Nov 13 03:29:36 UTC 2023
9one Mon Nov 13 03:29:37 UTC 2023
10one Mon Nov 13 03:29:38 UTC 2023
11one Mon Nov 13 03:29:39 UTC 2023
12two Mon Nov 13 03:29:40 UTC 2023
13one Mon Nov 13 03:29:41 UTC 2023
14one Mon Nov 13 03:29:42 UTC 2023
15two Mon Nov 13 03:29:43 UTC 2023
16# ......

输出结果中大约有 20% 的时间戳带有前缀 two,说明 80% 的 TCP 流量被路由到 tcp-echo 的 v1 版本,而 20% 的流量被路由到 v2 版本,证明我们的 TCP 服务基于权重的流量拆分配置成功了。
- 当然最后我们同样可以去查看下 Envoy Sidecar 的具体配置,注意我们这里是通过 Istio Ingress Gateway 来暴露 TCP 流量的端口的,所以我们这里直接查看
31400端口的监听器配置即可:
1$ istioctl proxy-config listeners istio-ingressgateway-9c8b9b586-s6s48 -n istio-system --port 31400 -o yaml
2- address:
3 socketAddress:
4 address: 0.0.0.0
5 portValue: 31400
6 # ......
7 filterChains:
8 - filters:
9 # ......
10 - name: envoy.filters.network.tcp_proxy
11 typedConfig:
12 '@type': type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
13 # ......
14 statPrefix: tcp-echo.default
15 weightedClusters:
16 clusters:
17 - name: outbound|9000|v1|tcp-echo.default.svc.cluster.local
18 weight: 80
19 - name: outbound|9000|v2|tcp-echo.default.svc.cluster.local
20 weight: 20
21 name: 0.0.0.0_31400
22 trafficDirection: OUTBOUND
我们可以发现上面的监听器配置下面的过滤器链中直接使用的是一个 envoy.filters.network.tcp_proxy 过滤器,用来处理 TCP 流量,而不是 HTTP 流量了,而且直接在该过滤器下面配置了 weightedClusters 权重集群,用来实现基于权重的流量拆分,可以看到 v1 和 v2 版本的权重分别为 80 和 20,符合我们的预期。
- 最后测试完后可以清理下资源:
1kubectl delete -f samples/tcp-echo/tcp-echo-all-v1.yaml
2kubectl delete -f samples/sleep/sleep.yaml
3kubectl delete -f samples/tcp-echo/tcp-echo-services.yaml
测试结束。😘

