Service Mesh - Istio流量控制篇(上)

语言: CN / TW / HK

动态路由:用Virtual Service和Destination Rule设置路由规则

路由这个功能是流量控制里面非常重要,也是最常用的一个功能。在Istio里一般通过Virtual Service(虚拟服务)以及Destination Rule(目标规则)这两个API资源进行动态路由的设置。

基本概念

虚拟服务(Virtual Service):

  • 定义路由规则,匹配请求
  • 描述满足条件的请求去哪里

目标规则(Destination Rule):

  • 定义子集、策略
  • 描述到达目标的请求怎么处理

Service Mesh - Istio流量控制篇(上)

实践动态路由

在上一篇Service Mesh - Istio安装与部署文章中,我们演示了BookInfo这个Demo应用的部署,并且可以发现其中的 reviews 服务共有三个不同的版本。现在我们的需求是将请求路由到 reviews 服务的指定版本上。例如,路由到版本1上,如下图:
Service Mesh - Istio流量控制篇(上)

实现该需求很简单,官方已经提供了配置清单文件,我们只需执行如下命令应用路由规则即可:

[[email protected] ~]# kubectl apply -f /usr/local/istio-1.8.1/samples/bookinfo/networking/virtual-service-all-v1.yaml    # 创建虚拟服务
virtualservice.networking.istio.io/productpage created
virtualservice.networking.istio.io/reviews created
virtualservice.networking.istio.io/ratings created
virtualservice.networking.istio.io/details created
[[email protected] ~]# kubectl apply -f /usr/local/istio-1.8.1/samples/bookinfo/networking/destination-rule-all.yaml   # 创建目标规则
destinationrule.networking.istio.io/productpage created
destinationrule.networking.istio.io/reviews created
destinationrule.networking.istio.io/ratings created
destinationrule.networking.istio.io/details created
[[email protected] ~]# 

然后回到应用页面上测试下规则是否生效,正常情况下,此时无论怎么刷新页面,访问的都是版本1的 reviews 服务:
Service Mesh - Istio流量控制篇(上)

那么配置是如何生效的呢?我们先来看看这两个API资源它们的一些具体配置项:
Service Mesh - Istio流量控制篇(上)

  • Virtual Service
    • hosts:对应 DestinationRule 所配置的host,可配置多个
    • gateways:用来和配置的网关进行匹配使用的,如果是服务网关内部的虚拟服务就不需要配置这一项
    • http:配置http请求的路由规则与 HTTPRoute 对应
    • tls:配置tls请求的路由规则
    • tcp:配置tcp请求的路由规则
    • exportTo:给虚拟服务设置它的可见性,例如设置为所有的Namspace都可见
  • HTTPRoute:
    • match:设置匹配满足什么样条件的请求,对应 HTTPMatchRequest 对象配置项
    • route:匹配到的请求经过route配置的规则进行路由
  • HTTPRouteDestination:
    • destination:通过该字段将虚拟服务与目标规则进行绑定
  • Destination Rule
    • host:最终路由到的具体目标地址
    • subset:子集,一般主要是给服务限定版本

virtual-service-all-v1.yaml 文件中针对 reviews 服务创建的虚拟服务配置内容如下:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService  # 资源类型
metadata:
  name: reviews   # 虚拟服务的名称
spec:
  hosts:
  - reviews   # 虚拟服务的主机名,可定义多个
  http:  # 没有定义match代表匹配任意的请求
  - route:
    - destination:
        host: reviews   # 服务名称,与目标规则中的host配置对应
        subset: v1      # 通过子集限定了服务版本

destination-rule-all.yaml 文件中针对 reviews 服务创建的目标规则配置内容如下:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule  # 资源类型
metadata:
  name: reviews   # 目标规则的名称
spec:
  host: reviews   # 指向k8s中的服务名称,k8s平台的dns机制可以解析出具体的服务地址
  subsets:   # 定义子集,对应该服务的三个版本,虚拟服务就是根据在这里定义的子集来路由对应的版本的
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

Virtual Service 和 Destination Rule 的应用场景:

  • 按服务版本路由
  • 按比例切分流量
  • 根据匹配规则进行路由
  • 定义各种策略(负载均衡、连接池等)

网关:用Gateway管理进入网格的流量

什么是网关(Gateway)

  • 一个运行在网格边缘的负载均衡器
  • 接收外部请求,转发给网格内的服务
  • 配置对外的端口、协议与内部服务的映射关系
  • Istio中的Ingress网关控制入口流量,Egress网关控制出口流量,在网关只定义入口点不定义具体的路由
  • 与k8s中的Ingress一样,Istio中的Gateway也只是一种资源,需要配合一个真正工作的组件使用,在k8s中通常是ingress-nginx,在Istio中则是基于envoy的istio-ingressgateway / istio-egressgateway
    Service Mesh - Istio流量控制篇(上)
  • 官方文档

实践创建网关

我们来创建一个入口网关,配合虚拟服务对外暴露一些服务接口:
Service Mesh - Istio流量控制篇(上)

Gateway资源的一些配置选项
Service Mesh - Istio流量控制篇(上)

  • Gateway:
    • servers:定义入口点列表
    • selector:选择器,用于通过label选择集群中Istio网关的Pod
  • Server:
    • port:暴露给外部访问的端口信息,包括端口号、名称、协议
    • hosts:暴露给外部可访问的host列表
  • VirtualService:
    • gateways:用于配置关联哪些Gateway资源,通过名称指定

创建 test-gateway.yaml 文件,内容如下:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway   # 资源类型为网关
metadata:
  name: test-gateway   # 网关的名称
spec:
  selector:   # 配置选择器,指向istio-ingressgateway的pod
    istio: ingressgateway
  servers:  # 定义对外暴露的入口点,主要配置哪些host和端口允许访问
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

---

# 由于在网关只定义入口点不定义具体的路由,所以我们需要定义虚拟服务来配置路由规则
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService   # 资源类型为虚拟服务
metadata:
  name: test-gateway    # 虚拟服务的名称
spec:
  hosts:
  - "*"
  gateways:   # 配置gateway的名称,用于关联具体的gateway
  - test-gateway
  http:
  - match:   # 定义路由匹配规则,即暴露哪些接口
    - uri:
        prefix: /details
    - uri:
        exact: /health
    route:
    - destination:   # 将请求转发到哪些目标
        host: details
        port:
          number: 9080

在应用该配置文件之前,我们先到浏览器上访问 details 接口,此时返回的状态码将会是404:
Service Mesh - Istio流量控制篇(上)

然后应用配置文件,创建我们所定义的网关和虚拟服务:

[[email protected] ~]# kubectl apply -f service-mash/test-gateway.yaml
gateway.networking.istio.io/test-gateway created
virtualservice.networking.istio.io/test-gateway created
[[email protected] ~]# 

通过网关和虚拟服务暴露接口后,再重新访问一下该接口,可以看到能正常访问了:
Service Mesh - Istio流量控制篇(上)

同样,health 接口也可以被访问到:
Service Mesh - Istio流量控制篇(上)

Gateway 的应用场景:

  • 暴露网格内服务给外界访问
  • 访问安全(HTTPS、mTLS 等)
  • 统一应用入口,API 聚合

服务入口:用Service Entry扩展你的网格服务

官方文档:

什么是服务入口(ServiceEntry):

  • 服务入口与网关正好是相反的概念,服务入口是用于添加外部服务到网格内
  • 管理到外部服务的请求,对外部服务进行抽象,使得可以像访问内部服务一样访问外部服务
  • 扩展网格,例如需要给多个集群共享同一个Mesh的场景
    Service Mesh - Istio流量控制篇(上)

接下来我们实践一下,将 httpbin.org 注册为网格内部的服务,并配置流控策略。httpbin.org 这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等,且支持 GET、POST 等多种方法,对 web 开发和测试很有帮助,我们可以将它作为一个外部的服务进行测试。

由于我们之前部署的Bookinfo应用的所有服务中都没有 curl 命令,因此我们想要模拟内部服务调用外部服务,就需要额外添加一个 sleep 服务。官方已经给我们准备好了配置文件,使用如下命令应用一下即可:

[[email protected] ~]# kubectl apply -f /usr/local/istio-1.8.1/samples/sleep/sleep.yaml
serviceaccount/sleep created
service/sleep created
deployment.apps/sleep created
[[email protected] ~]# 

通过 sleep 服务来访问一下 httpbin 这个外部服务:

[[email protected] ~]# kubectl exec -it sleep-854565cb79-gm6hj -c sleep -- curl http://httpbin.org/headers
{
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "0", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.69.1", 
    "X-Amzn-Trace-Id": "Root=1-5fe0b2c6-0940d09657bb9faf42e9f49e", 
    "X-B3-Sampled": "1", 
    "X-B3-Spanid": "c8f80494e0358f1f", 
    "X-B3-Traceid": "46b7447441931824c8f80494e0358f1f", 
    "X-Envoy-Attempt-Count": "1", 
    "X-Envoy-Peer-Metadata": "ChkKDkFQUF9DT05UQUlORVJTEgcaBXNsZWVwChoKCkNMVVNURVJfSUQSDBoKS3ViZXJuZXRlcwoYCg1JU1RJT19WRVJTSU9OEgcaBTEuOC4xCt8BCgZMQUJFTFMS1AEq0QEKDgoDYXBwEgcaBXNsZWVwChkKDGlzdGlvLmlvL3JldhIJGgdkZWZhdWx0CiEKEXBvZC10ZW1wbGF0ZS1oYXNoEgwaCjg1NDU2NWNiNzkKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwoqCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgcaBXNsZWVwCi8KI3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLXJldmlzaW9uEggaBmxhdGVzdAoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIAoETkFNRRIYGhZzbGVlcC04NTQ1NjVjYjc5LWdtNmhqChYKCU5BTUVTUEFDRRIJGgdkZWZhdWx0CkkKBU9XTkVSEkAaPmt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L2RlcGxveW1lbnRzL3NsZWVwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoYCg1XT1JLTE9BRF9OQU1FEgcaBXNsZWVw", 
    "X-Envoy-Peer-Metadata-Id": "sidecar~172.22.152.249~sleep-854565cb79-gm6hj.default~default.svc.cluster.local"
  }
}
[[email protected] ~]# 

从输出结果可以看到,从 sleep 服务的内部能够直接访问外部的 httpbin 服务,原因是Istio里默认允许所有的网格内的服务直接访问外部服务。所以为了测试服务入口这个API资源,我们需要把这种允许访问外部服务的行为给关掉,使得只有注册过的服务才能访问外部服务。

使用如下命令可关闭出流量可访问权限(outboundTrafficPolicy = REGISTRY_ONLY):

[[email protected] ~]# istioctl install --set profile=demo -y --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY

此时再通过 sleep 访问 httpbin 就访问不到了,没有任何输出:

[[email protected] ~]# kubectl exec -it sleep-854565cb79-gm6hj -c sleep -- curl http://httpbin.org/headers
[[email protected] ~]# 

现在我们来为外部服务(httpbin)配置 ServiceEntry ,让 sleep 服务通过服务入口来访问外部服务。具体命令如下:

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry   # 资源类型为服务入口
metadata:
  name: httpbin-ext   # 服务入口的名称 
spec:
  hosts:   # 外部服务的访问地址列表,域名或ip
  - httpbin.org
  ports:   # 配置外部服务的访问端口信息
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS   # 指定服务发现机制
  location: MESH_EXTERNAL  # 定义该服务位于网格内部还是网格外部
EOF

通过如下命令可查看已创建的服务入口:

[[email protected] ~]# kubectl get se
NAME          HOSTS             LOCATION        RESOLUTION   AGE
httpbin-ext   ["httpbin.org"]   MESH_EXTERNAL   DNS          97s
[[email protected] ~]# 

测试从 sleep 服务访问 httpbin :

[[email protected] ~]# kubectl exec -it sleep-854565cb79-gm6hj -c sleep -- curl http://httpbin.org/headers
{
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "0", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.69.1", 
    "X-Amzn-Trace-Id": "Root=1-5fe0bd82-51a06204013c7006305ac384", 
    "X-B3-Sampled": "1", 
    "X-B3-Spanid": "3cdd9036d7815b35", 
    "X-B3-Traceid": "08d1682631d190293cdd9036d7815b35", 
    "X-Envoy-Attempt-Count": "1", 
    "X-Envoy-Decorator-Operation": "httpbin.org:80/*", 
    "X-Envoy-Peer-Metadata": "ChkKDkFQUF9DT05UQUlORVJTEgcaBXNsZWVwChoKCkNMVVNURVJfSUQSDBoKS3ViZXJuZXRlcwoYCg1JU1RJT19WRVJTSU9OEgcaBTEuOC4xCt8BCgZMQUJFTFMS1AEq0QEKDgoDYXBwEgcaBXNsZWVwChkKDGlzdGlvLmlvL3JldhIJGgdkZWZhdWx0CiEKEXBvZC10ZW1wbGF0ZS1oYXNoEgwaCjg1NDU2NWNiNzkKJAoZc2VjdXJpdHkuaXN0aW8uaW8vdGxzTW9kZRIHGgVpc3RpbwoqCh9zZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1uYW1lEgcaBXNsZWVwCi8KI3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLXJldmlzaW9uEggaBmxhdGVzdAoaCgdNRVNIX0lEEg8aDWNsdXN0ZXIubG9jYWwKIAoETkFNRRIYGhZzbGVlcC04NTQ1NjVjYjc5LWdtNmhqChYKCU5BTUVTUEFDRRIJGgdkZWZhdWx0CkkKBU9XTkVSEkAaPmt1YmVybmV0ZXM6Ly9hcGlzL2FwcHMvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L2RlcGxveW1lbnRzL3NsZWVwChcKEVBMQVRGT1JNX01FVEFEQVRBEgIqAAoYCg1XT1JLTE9BRF9OQU1FEgcaBXNsZWVw", 
    "X-Envoy-Peer-Metadata-Id": "sidecar~172.22.152.249~sleep-854565cb79-gm6hj.default~default.svc.cluster.local"
  }
}
[[email protected] ~]# 

服务入口的配置选项
Service Mesh - Istio流量控制篇(上)


流量转移:灰度发布是如何实现的?

官方文档:

蓝绿部署

Service Mesh - Istio流量控制篇(上)

蓝绿部署简单理解就是当需要部署新版本时,不会去动老版本的服务,而是在另一个环境部署相同数量的新服务,然后当新的服务测试确认 OK 后,把流量切到新的服务这边来。

其实这种部署方式,就是我们说的预发环境。生产线上有两套相同的集群,一套是 Prod 是真实服务的,另一套是 Stage 是预发环境,发布发 Stage,然后把流量切到 Stage 这边,于是 Stage 就成了 Prod,而之前的 Prod 则成了 Stage。有点像换页似的。

这种方式的优点是没有停机,实时发布和升级,也避免有新旧版本同时在线的问题。但这种部署的问题就是有点浪费,因为需要使用双倍的资源(不过,这只是在物理机时代,在云计算时代没事,因为虚拟机部署完就可以释放了)。

另外,如果我们的服务中有状态,比如一些缓存什么的,停机部署和蓝绿部署都会有问题。

灰度发布(金丝雀发布)

Service Mesh - Istio流量控制篇(上)

金丝雀部署又叫灰度部署,其得名来源于矿井中的金丝雀。17 世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱。而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为 ” 瓦斯检测指标 “,以便在危险状况下紧急撤离。

灰度部署是指逐渐将生产环境流量从老版本切换到新版本。通常流量是按比例分配的。例如 90% 的请求流向老版本,10% 的请求流向新版本。然后没有发现问题,就逐步扩大新版本上的流量,减少老版本上的流量。

除了切流量外,对于多租户的平台,例如云计算平台,灰度部署也可以将一些新的版本先部署到一些用户上,如果没有问题,扩大部署,直到全部用户。一般的策略是,从内部用户开始,然后是一般用户,最后是大客户。

这个技术大多数用于缺少足够测试,或者缺少可靠测试,或者对新版本的稳定性缺乏信心的情况下。把一部分用户切到新版上来,然后看一下有没有问题。如果没有问题就继续扩大升级,直到全部升级完成。

A/B 测试

Service Mesh - Istio流量控制篇(上)

AB 测试和蓝绿部署或是金丝雀灰度部署完全是不一样的。AB 测试是同时上线两个版本,然后做相关的比较。它是用来测试应用功能表现的方法,例如可用性、受欢迎程度、可见性等。

蓝绿部署是为了不停机,灰度部署是对新版本的质量没信心。而 AB 测试是对新版的功能没信心。注意,一个是质量,一个是功能。

比如,网站 UI 大改版,推荐算法的更新,流程的改变,我们不知道新的版本否会得到用户青睐或是能得到更好的用户体验,我们需要收集一定的用户数据才能知道。

于是我们需要在生产线上发布两个版本,拉一部分用户过来当小白鼠,然后通过科学的观测得出来相关的结论。AB 测试旨在通过科学的实验设计、采样样本代表性、流量分割与小流量测试等方式来获得具有代表性的实验结论,并确信该结论在推广到全部流量时可信。

我们可以看到 AB 测试,其包含了灰度发布的功能。也就是说,我们的观测如果只是观测有没有 bug,那就是灰度发布了。当然,如果我们复杂一点,要观测用户的一些数据指标,这完全也可能做成自动化的,如果新版本数据好,就自动化地切一点流量过来,如果不行,就换一批用户(样本)再试试。

对于灰度发布或是 AB 测试可以使用下面的技术来选择用户:

  • 浏览器 cookie、查询参数、地理位置。技术支持,如浏览器版本、屏幕尺寸、操作系统等。客户端语言

实践基于权重的路由

在Istio中我们可以配置基于权重的路由,将请求按比例路由到对应的服务版本来实现灰度发布的效果。接下来我们利用 reviews 服务的多版本,模拟灰度发布。官方demo已经提供了该配置文件,执行如下命令应用即可:

[[email protected] ~]# kubectl apply -f /usr/local/istio-1.8.1/samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
virtualservice.networking.istio.io/reviews configured
[[email protected] ~]# 

virtual-service-reviews-50-v3.yaml 文件的内容如下,就是通过 Virtual Service 配置了权重:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 50   # 权重配置,表示50%的流量转发到v1版本
    - destination:
        host: reviews
        subset: v3
      weight: 50   # 表示50%的流量转发到v3版本

此时到应用页面上进行一下测试,可以发现请求基本按照50%的比例转发到v1和v3版本:
Service Mesh - Istio流量控制篇(上)
Service Mesh - Istio流量控制篇(上)


下篇:

分享到: