前面我们了解了 Gateway 和 VirtualService 资源对象的作用,以及它们是如何影响 Envoy 的配置的,那么这些资源对象又是如何影响流量的呢?通过 Istio 如何实现流量管理的呢?
Istio 的流量路由规则可以很容易的控制服务之间的流量和 API 调用。Istio 简化了服务级别属性的配置,比如熔断器、超时和重试,并且能轻松的设置重要的任务,如 A/B 测试、金丝雀发布、基于流量百分比切分的分阶段发布等。它还提供了开箱即用的故障恢复特性, 有助于增强应用的健壮性,从而更好地应对被依赖的服务或网络发生故障的情况。
为了在网格中路由,Istio 需要知道所有的 endpoint 在哪以及它们属于哪些服务。为了定位到 service registry(服务注册中心),Istio 会连接到一个服务发现系统。如果在 Kubernetes 集群上安装了 Istio,那么它将自动检测该集群中的服务和 endpoint。
首先我们来实现下最基本的流量请求路由的功能,这里我们将学习如何将请求动态路由到微服务的多个版本。
我们知道 Bookinfo 示例包含四个独立的微服务,每个微服务都有多个版本。其中 reviews 服务的三个不同版本已经部署并同时运行。我们可以在浏览器中访问 Bookinfo 应用程序并刷新几次。正常会看到三种不同的 reviews 服务版本的输出,有时书评的输出包含星级评分,有时则不包含。这是因为没有明确的默认服务版本可路由,Istio 将以循环方式将请求路由到所有可用版本。
我们首先来将所有流量路由到微服务的 v1 版本,稍后,您将应用规则根据 HTTP 请求 header 的值路由流量。
要只路由到一个版本,则需要为微服务设置默认版本的 VirtualService。
Istio 使用 VirtualService 来定义路由规则,只需要应用下面的资源对象即可:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yamlvirtualserviceworking.istio.io/productpage createdvirtualserviceworking.istio.io/reviews createdvirtualserviceworking.istio.io/ratings createdvirtualserviceworking.istio.io/details created
该资源清单中定义了四个 VirtualService 对象,分别是 productpage、reviews、ratings、details,它们分别对应着 Bookinfo 应用中的四个微服务,完整的清单如下所示:
# virtual-service-all-v1.yamlapiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: productpagespec: hosts: - productpage http: - route: - destination: host: productpage subset: v1---apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: reviewsspec: hosts: - reviews http: - route: - destination: host: reviews subset: v1---apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: ratingsspec: hosts: - ratings http: - route: - destination: host: ratings subset: v1---apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: detailsspec: hosts: - details http: - route: - destination: host: details subset: v1---
我们可以看到这里的 VirtualService 对象中都定义了 subset 字段,这个字段就是用来指定微服务的版本的,这里我们将所有的微服务都指定为 v1 版本,这样所有的流量都会被路由到 v1 版本的微服务中,包括 reviews 服务,这样我们就不会再看到星级评分了。
但是如果我们现在直接去访问 Bookinfo 应用的话,是不能正常访问的,因为我们压根就还没指定这些 v1 版本的微服务到底在哪里。
bookinfo error
这个时候就需要用到另外一个资源对象 DestinationRule 了,我们需要为每个微服务创建一个 DestinationRule 对象,用来指定这些微服务的实际地址,这样 VirtualService 对象才能将流量路由到这些微服务中。Istio 在 DestinationRule 目标规则中使用 subsets 定义服务的版本,运行以下命令为 Bookinfo 服务创建默认的目标规则即可:
$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yamldestinationruleworking.istio.io/productpage createddestinationruleworking.istio.io/reviews createddestinationruleworking.istio.io/ratings createddestinationruleworking.istio.io/details created
该资源清单中定义了四个 DestinationRule 对象,分别是 productpage、reviews、ratings、details 几个服务的目标规则,它们分别对应着 Bookinfo 应用中的四个微服务,完整的清单如下所示:
# destination-rule-all.yamlapiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: productpagespec: host: productpage subsets: - name: v1 labels: version: v1---apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: reviewsspec: host: reviews subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 - name: v3 labels: version: v3---apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: ratingsspec: host: ratings subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 - name: v2-mysql labels: version: v2-mysql - name: v2-mysql-vm labels: version: v2-mysql-vm---apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: detailsspec: host: details subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2---
现在我们就可以正常访问 Bookinfo 应用了,并且无论刷新多少次,页面的评论部分都不会显示评级星标,这是因为我们将 Istio 配置为将 reviews 服务的所有流量路由到版本 reviews:v1,而此版本的服务不访问星级评分服务。
v1版本review
这样我们就成功将流量路由到服务的某一个版本上了。
前面章节中我们只定义了一个名为 bookinfo 的 VirtualService 资源对象就可以正常访问了:
apiVersion: networking.istio.io/v1beta1kind: VirtualServicemetadata: name: bookinfo namespace: defaultspec: gateways: - bookinfo-gateway hosts: - "*" http: - match: - uri: exact: /productpage - uri: prefix: /static - uri: exact: /login - uri: exact: /logout - uri: prefix: /api/v1/products route: - destination: host: productpage port: number: 9080
很明显上面这个虚拟服务对象是我们访问 Bookinfo 应用的入口路由规则,所以这个虚拟服务对象实际上是为 istio-ingressgateway 入口网关服务定义的。 它将所有的流量都路由到了 productpage 这个服务上,而 productpage 这个服务又会去调用其他的服务来获取数据,在 productpage 服务中调用其他微服务 其实就是直接通过服务名称来调用的,比如调用 reviews 服务就是直接通过 reviews:9080 这个服务来调用的,我们可以查看 productpage 的代码来验证这一点:
productpage
我们可以再次查看 Bookinfo 在网格内的请求架构图:
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 服务的这两个对象如下所示:
apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: reviewsspec: hosts: - reviews http: - route: - destination: host: reviews subset: v1---apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: reviewsspec: host: reviews subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 - name: v3 labels: 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 端口的监听器配置:
$ istioctl proxy-config listeners productpage-v1-564d4686f-wwqqf --port 15001 -oyaml- address: socketAddress: address: 0.0.0.0 portValue: 15001 filterChains: - filterChainMatch: destinationPort: 15001 filters: - name: istio.stats typedConfig: '@type': type.googleapis.com/stats.PluginConfig - name: envoy.filterswork.tcp_proxy typedConfig: '@type': type.googleapis.com/envoy.extensions.filterswork.tcp_proxy.v3.TcpProxy cluster: BlackHoleCluster statPrefix: BlackHoleCluster name: virtualOutbound-blackhole - filters: - name: istio.stats typedConfig: '@type': type.googleapis.com/stats.PluginConfig - name: envoy.filterswork.tcp_proxy typedConfig: '@type': type.googleapis.com/envoy.extensions.filterswork.tcp_proxy.v3.TcpProxy # ...... cluster: PassthroughCluster statPrefix: PassthroughCluster name: virtualOutbound-catchall-tcp name: virtualOutbound trafficDirection: OUTBOUND useOriginalDst: true
实际上我们的请求是到 9080 端口(productpage 服务绑定 9080 端口)的 HTTP 出站请求,这意味着它被切换到 0.0.0.0:9080 虚拟监听器。所以我们查看下 9080 端口的监听器配置:
# productpage 默认访问其他服务的 9080 端口$ istioctl proxy-config listeners productpage-v1-564d4686f-wwqqf --port 9080 -oyaml- address: socketAddress: address: 0.0.0.0 portValue: 9080 # ...... rds: configSource: ads: {} initialFetchTimeout: 0s resourceApiVersion: V3 routeConfigName: "9080" # RDS的路由配置名称 # ...... name: 0.0.0.0_9080 trafficDirection: OUTBOUND # 出流量
可以看到此监听器在其配置的 RDS 中查找名为 9080 的路由配置,我们可以使用 istioctl proxy-config routes 命令来查看这个路由配置的详细信息:
# 查看 9080 这个路由配置$ istioctl proxy-config routes productpage-v1-564d4686f-wwqqf --name 9080 -oyaml- name: "9080" virtualHosts: - domains: - details.default.svc.cluster.local - details - details.default.svc - details.default - 10.111.83.224 name: details.default.svc.cluster.local:9080 routes: - decorator: operation: details.default.svc.cluster.local:9080/* match: prefix: / metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/details route: cluster: outbound|9080|v1|details.default.svc.cluster.local # ...... - domains: - productpage.default.svc.cluster.local - productpage - productpage.default.svc - productpage.default - 10.97.120.23 name: productpage.default.svc.cluster.local:9080 routes: - decorator: operation: productpage.default.svc.cluster.local:9080/* match: prefix: / name: default route: cluster: outbound|9080||productpage.default.svc.cluster.local # ...... - domains: - ratings.default.svc.cluster.local - ratings - ratings.default.svc - ratings.default - 10.101.184.235 name: ratings.default.svc.cluster.local:9080 routes: - decorator: operation: ratings.default.svc.cluster.local:9080/* match: prefix: / metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/ratings route: cluster: outbound|9080|v1|ratings.default.svc.cluster.local # ...... - domains: - reviews.default.svc.cluster.local - reviews - reviews.default.svc - reviews.default - 10.97.120.56 name: reviews.default.svc.cluster.local:9080 routes: - decorator: operation: reviews.default.svc.cluster.local:9080/* match: prefix: / metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews route: cluster: outbound|9080|v1|reviews.default.svc.cluster.local # ...... - domains: - '*' name: allow_any routes: - match: prefix: / name: allow_any route: 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 对象来定义的,现在我们就可以来查看这个集群配置了:
$ istioctl proxy-config cluster productpage-v1-564d4686f-wwqqf --fqdn reviews.default.svc.cluster.local -o yaml- edsClusterConfig: edsConfig: ads: {} initialFetchTimeout: 0s resourceApiVersion: V3 serviceName: outbound|9080||reviews.default.svc.cluster.local lbPolicy: LEAST_REQUEST metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews services: - host: reviews.default.svc.cluster.local name: reviews namespace: default # ...... name: outbound|9080||reviews.default.svc.cluster.local type: EDS- edsClusterConfig: edsConfig: ads: {} initialFetchTimeout: 0s resourceApiVersion: V3 serviceName: outbound|9080|v1|reviews.default.svc.cluster.local lbPolicy: LEAST_REQUEST metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews services: - host: reviews.default.svc.cluster.local name: reviews namespace: default subset: v1 name: outbound|9080|v1|reviews.default.svc.cluster.local # ...... type: EDS- edsClusterConfig: edsConfig: ads: {} initialFetchTimeout: 0s resourceApiVersion: V3 serviceName: outbound|9080|v2|reviews.default.svc.cluster.local filters: - name: istio.metadata_exchange typedConfig: '@type': type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange protocol: istio-peer-exchange lbPolicy: LEAST_REQUEST metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews services: - host: reviews.default.svc.cluster.local name: reviews namespace: default subset: v2 name: outbound|9080|v2|reviews.default.svc.cluster.local # ...... type: EDS- edsClusterConfig: edsConfig: ads: {} initialFetchTimeout: 0s resourceApiVersion: V3 serviceName: outbound|9080|v3|reviews.default.svc.cluster.local filters: - name: istio.metadata_exchange typedConfig: '@type': type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange protocol: istio-peer-exchange lbPolicy: LEAST_REQUEST metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/reviews services: - host: reviews.default.svc.cluster.local name: reviews namespace: default subset: v3 name: outbound|9080|v3|reviews.default.svc.cluster.local # ...... type: EDS
从上面配置可以看到里面一共包含了 4 个 reviews 相关的集群,一个是原始的不包含 subset 的,而另外三个就是前面我们在 DestinationRule 对象中配置的 3 个 subset,所以其实 DestinationRule 映射到 Envoy 的配置文件中就是 Cluster。
最后我们同样还可以查看每个集群下面包含的 endpoint 有哪些:
$ istioctl proxy-config endpoint productpage-v1-564d4686f-wwqqf --cluster "outbound|9080||reviews.default.svc.cluster.local" -o yaml- edsServiceName: outbound|9080||reviews.default.svc.cluster.local - address: socketAddress: address: 10.244.2.84 portValue: 9080 # ...... weight: 1 - address: socketAddress: address: 10.244.2.83 portValue: 9080 # ...... weight: 1 - address: socketAddress: address: 10.244.2.88 portValue: 9080 # ...... weight: 1 name: outbound|9080||reviews.default.svc.cluster.local observabilityName: outbound|9080||reviews.default.svc.cluster.local$ istioctl proxy-config endpoint productpage-v1-564d4686f-wwqqf --cluster "outbound|9080|v1|reviews.default.svc.cluster.local" -o yaml- edsServiceName: outbound|9080|v1|reviews.default.svc.cluster.local hostStatuses: - address: socketAddress: address: 10.244.2.84 portValue: 9080 weight: 1 name: outbound|9080|v1|reviews.default.svc.cluster.local observabilityName: outbound|9080|v1|reviews.default.svc.cluster.local# 过滤 versinotallow=v1 的 reviews pod$ kubectl get pod -l app=reviews,versinotallow=v1 -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATESreviews-v1-86896b7648-zjh2n 2/2 Running 4 (5h18m ago) 6d17h 10.244.2.84 node2 <none> <none>
可以看到不包含 subset 的集群下面的 endpoint 其实就是 reviews 这个 Service 对象的 endpoint 集合,包含 subset 就只有和该子集匹配的后端实例了。到了这一步,一切皆明了,后面的事情就跟之前的套路一样了,具体的 Endpoint 对应打了标签 versinotallow=v1 的 Pod。
到这里我们是不是就实现了通过 VirtualService 和 DestinationRule 对象将流量路由到了指定的版本上面了,上面的整个过程就是请求从 productpage 到 reviews 的过程,从 reviews 到网格内其他应用的流量与上面类似,就不展开讨论了。
接下来我们继续更改路由配置,将来自特定用户的所有流量路由到特定服务版本。我们这里将配置来自名为 Jason 的用户的所有流量被路由到服务 reviews:v2。
注意 Istio 对用户身份没有任何特殊的内置机制,productpage 服务在所有到 reviews 服务的 HTTP 请求中都增加了一个自定义的 end-user 请求头来实现该效果:headers['end-user'] = session['user']。
要实现该功能,只需要创建下面的资源对象即可:
$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yamlvirtualserviceworking.istio.io/reviews configured
该资源清单文件创建了一个如下所示的 VirtualService 资源对象:
# virtual-service-reviews-test-v2.yamlapiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: reviewsspec: hosts: - reviews http: - match: - headers: end-user: exact: jason route: - destination: host: reviews subset: v2 - route: - destination: host: reviews subset: v1
该对象设置了一条路由规则,它会根据 productpage 服务发起的请求的 end-user 自定义请求头内容进行匹配,如果有该内容且为 jason 则会将流量路由到 reviews 服务的 v2 版本,其余的还是被路由到 v1 版本去。
现在我们可以前往浏览器访问 Bookinfo 应用,多刷新几次可以看到评论始终访问到的是 v1 版本的服务,即没有星标的:
Bookinfo
然后我们点击页面右上角的 Sign in 按钮,使用 jason 进行登录,登录后页面就会出现带有黑色星标的 v2 版本的评论服务,即使多刷新几次依然如此:
Bookinfo jason
如果我们选择使用其他用户进行登录或者注销则星标就会消失,这是因为除了 Jason 之外,所有用户的流量都被路由到 reviews:v1。
同样的我们可以去查看下对应的 Envoy Sidecar 配置的变化,因为这里我们只更新了一个 VirtualService 对象,所以只会对 Envoy 的路由表产生影响,查看对应的路由配置即可:
$ istioctl proxy-config routes productpage-v1-564d4686f-wwqqf --name 9080 -oyaml- name: "9080" validateClusters: false virtualHosts: # ...... - domains: - reviews.default.svc.cluster.local - reviews - reviews.default.svc - reviews.default - 10.97.120.56 includeRequestAttemptCount: true name: reviews.default.svc.cluster.local:9080 routes: - decorator: operation: reviews.default.svc.cluster.local:9080/* match: caseSensitive: true headers: - name: end-user stringMatch: exact: jason prefix: / metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews route: cluster: outbound|9080|v2|reviews.default.svc.cluster.local maxGrpcTimeout: 0s # ...... - decorator: operation: reviews.default.svc.cluster.local:9080/* match: prefix: / metadata: filterMetadata: istio: config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews route: cluster: outbound|9080|v1|reviews.default.svc.cluster.local maxGrpcTimeout: 0s # ......
从配置上我们可以看到现在的 Envoy 配置中新增了一条路由规则,如下所示:
match: caseSensitive: true headers: - name: end-user stringMatch: exact: jason prefix: /route: cluster: outbound|9080|v2|reviews.default.svc.cluster.local
当请求头中包含 end-user:jason 的时候请求会被路由到 outbound|9080|v2|reviews.default.svc.cluster.local 这个 Envoy Cluster 集群,这个集群就是前面我们通过 DestinationRule 创建的 v2 这个子集,所以最后请求会被路由到带有黑色星标的评论服务去。
Kiali Dashboard
到这里我们就明白了要通过 Istio 实现服务的流量管理,需要用到 Gateway、VirtualService、DestinationRule 三个 CRD 对象,这些对象其实最终都是去拼凑 Envoy 的配置,每个对象管理 Envoy 配置的一部分,把这个关系搞清楚我们就能更好的掌握 Istio 的使用了。
本文链接:http://www.28at.com/showinfo-26-17904-0.htmlIstio流量管理之请求路由分析
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com