TH小破站

  • 首页
  • 容器
    • 认识Docker
    • DockerFile详解
  • k8s
    • 认识Kubernetes
    • Kubernetes部署--(kubeadm方式)
    • kubeasz部署k8s集群
    • Ansible-binary部署k8s集群-1.31.2
  • Ceph
    • 认识分布式存储
    • CPEH部署
  • CI/CD
    • DevOps认识
    • 部署--Gitlab
  • ELK
    • ELK简介
    • Elasticsearch
    • Kibana部署
    • Logstash
    • ELK--收集 tomcat 日志(实战)
  • HD
    • 认识--大数据
    • Hadoop-单机部署
    • Hadoop-伪分布部署
    • Hadoop-完全分布部署
    • Hadoop-(HA)完全分布部署
    • Ambari自动部署--Hadoop
  • Nginx
    • Nginx功能介绍
    • Nginx安全选项
    • Nginx配置文件简单介绍
    • 网站服务器部署
  • 监控
    • ZABBIX-部署(yum)
    • ZABBIX-编译安装
  • 云计算
    • KVM 概述
    • OpenStack企业级部署(T版)
    • OpenStack之dashboard部署
    • OpenStack-controller的高可用
    • openstack(Caracal版_ubuntu版本)部署
  • 数据库
    • mariadb与mysql
    • mysql主从
    • MySQL--冷备(实战)
    • MySQL--热备(dump表)
    • MySQL--热备(dump库)
    • 数据恢复实战案例
    • Xtrabackup实现MySql8.x全量备份
    • MyCat实现数据库读写分离
    • MHA实现数据库高可用
    • Percona XtraDB(PXC)实现数据库集群高可用
    • 数据库压力测试
    • PostgreSQL 基操
    • PostgreSQL备份恢复
    • PostgreSQL高可用
    • MongDB安装配置基础使用
    • MongoDB实现复制集
    • MongoDB备份还原
  • 自动化
    • ssh远程服务原理及配置
    • Ansible剧本文件应用
    • Ansible管理服务介绍
    • Ansible自动化部署node_exporter(包含升级,回滚)
    • Saltstack-部署安装
  • 脚本
    • 一键查看服务器资源利用率
    • 一键生成SSL证书
    • 保存指定数量的文件/目录
    • 初始化服务器
    • 扫描可用IP
    • 批量分发SSH密钥
    • 监控多主机磁盘脚本
    • 一键安装MySQL8.x脚本
    • Python脚本监控ES集群
    • 资源巡检脚本
    • 查看各用户使用的cpu占比
热衷于分享
架构师成长之路 !!!
  1. 首页
  2. 云原生
  3. 正文

istio之故障注入&流量拆分&流量镜像&熔断

2024年7月28日 1221点热度 1人点赞 0条评论

istio之故障注入&流量拆分&流量镜像&熔断

本节实战

实战名称
🚩 实战:注入 HTTP 延迟故障-2024.7.21(测试成功)
🚩 实战:注入 HTTP abort 故障-2024.7.21(测试成功)
🚩 实战:流量拆分-2024.7.21(测试成功)
🚩 实战:流量镜像-2024.7.21(测试成功)
🚩 实战:熔断示例-2024.7.28(测试成功)

1、故障注入

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

注入 HTTP 延迟故障

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

测试环境:

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

实验环境:

k8s v1.29.4(cri-dockerd)

~/istio# istioctl version
client version: 1.22.2
control plane version: 1.22.2
data plane version: 1.22.2 (4 proxies)

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

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

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

🍀

路由前准备

root@k8s-master01:~/istio# cat istio-1.22.2/samples/bookinfo/networking/destination-rule-all.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: ratings
spec:
  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/v1alpha3
kind: DestinationRule
metadata:
  name: details
spec:
  host: details
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---

root@k8s-master01:~/istio# kubectl apply -f  istio-1.22.2/samples/bookinfo/networking/destination-rule-all.yaml

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

kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml

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

root@k8s-master01:~/istio# cat dr-vir.sh 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
    - match:
      - headers:
          end-user:
            exact: stangj
      fault:
        delay:
          percentage:
            value: 100.0
          fixedDelay: 7s   # 7秒延迟访问
      route:
        - destination:
            host: reviews
            subset: v2
    - route:
        - destination:
            host: reviews
            subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
    - name: v3
      labels:
        version: v3

root@k8s-master01:~/istio# kubectl apply  -f dr-vir.sh

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

🍀

应用上面的规则后,我们可以通过浏览器打开 Bookinfo 应用,以用户 stangj 登录到 /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 路由表配置信息来验证下这个延迟故障:

$ istioctl proxy-config routes reviews-v2-b7dcd98fb-gccfh --name 9080 -oyaml
- name: "9080"
  virtualHosts:
  # ......
  - domains:
    - ratings.default.svc.cluster.local
    - ratings
    - ratings.default.svc
    - ratings.default
    - 10.101.184.235
    includeRequestAttemptCount: true
    name: ratings.default.svc.cluster.local:9080
    routes:
    - decorator:
        operation: ratings.default.svc.cluster.local:9080/*
      match:
        caseSensitive: true
        headers:
        - name: end-user
          stringMatch:
            exact: stangj
        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
        # ......
      typedPerFilterConfig:
        envoy.filters.http.fault:
          '@type': type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
          delay:
            fixedDelay: 7s
            percentage:
              denominator: MILLION
              numerator: 1000000
    - 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
        # ......

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

match:
  caseSensitive: true
  headers:
    - name: end-user
      stringMatch:
        exact: stangj
  prefix: /

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

envoy.filters.http.fault:
  "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
  delay:
    fixedDelay: 7s
    percentage:
      denominator: MILLION
      numerator: 1000000

🍀

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

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

编辑文件:

# kubectl get vs
NAME          GATEWAYS               HOSTS             AGE
bookinfo      ["bookinfo-gateway"]   ["*"]             42m
details                              ["details"]       40m
productpage                          ["productpage"]   40m
ratings                              ["ratings"]       40m
reviews                              ["reviews"]       40m

# kubectl edit vs reviews

验证:

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

测试结束。😘

注入 HTTP abort 故障

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

实验环境:

k8s v1.29.4(cri-dockerd)

~/istio# istioctl version
client version: 1.22.2
control plane version: 1.22.2
data plane version: 1.22.2 (4 proxies)

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

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

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

root@k8s-master01:~/istio# cat dr-vir.sh 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
    - match:
      - headers:
          end-user:
            exact: stangj
      fault:
        delay:
          percentage:
            value: 100.0
          fixedDelay: 2s
        abort:
          percentage:
            value: 100.0
          httpStatus: 500  # 只要是 stangj 用户则访问放回500错误
      route:
        - destination:
            host: reviews
            subset: v2
    - route:
        - destination:
            host: reviews
            subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
    - name: v3
      labels:
        version: v3
        
        
root@k8s-master01:~/istio# kubectl apply  -f dr-vir.sh

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

🍀

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

🍀

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

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

$ istioctl proxy-config routes reviews-v2-b7dcd98fb-gccfh --name 9080 -oyaml
- name: "9080"
  virtualHosts:
  # ......
  - domains:
    - ratings.default.svc.cluster.local
    - ratings
    - ratings.default.svc
    - ratings.default
    - 10.101.184.235
    includeRequestAttemptCount: true
    name: ratings.default.svc.cluster.local:9080
    routes:
    - decorator:
        operation: ratings.default.svc.cluster.local:9080/*
      match:
        caseSensitive: true
        headers:
        - name: end-user
          stringMatch:
            exact: stangj
        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
        # ......
      typedPerFilterConfig:
        envoy.filters.http.fault:
          '@type': type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault
          abort:
            httpStatus: 500
            percentage:
              denominator: MILLION
              numerator: 1000000
    - 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
        # ......

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

🍀

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

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
    - route:
        - destination:
            host: reviews
            subset: v2
      timeout: 0.5s

测试结束。😘

2、流量拆分

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

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

实验环境:

k8s v1.29.4(cri-dockerd)

~/istio# istioctl version
client version: 1.22.2
control plane version: 1.22.2
data plane version: 1.22.2 (4 proxies)

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

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

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

🍀

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

root@k8s-master01:~/istio# kubectl apply -f istio-1.22.2/samples/bookinfo/networking/virtual-service-all-v1.yaml
root@k8s-master01:~/istio# kubectl apply -f istio-1.22.2/samples/bookinfo/networking/destination-rule-all.yaml

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

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

🍀

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

root@k8s-master01:~/istio# kubectl apply -f istio-1.22.2/samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml

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

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
    - route:
        - destination:
            host: reviews
            subset: v1
          weight: 50
        - destination:
            host: reviews
            subset: v3
          weight: 50

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

🍀

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

🍀

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

root@k8s-master01:~/istio# istioctl proxy-config routes productpage-v1-87d54dd59-7wxtl --name 9080 -oyaml
- ignorePortInHostMatching: true
  maxDirectResponseBodySizeBytes: 1048576
  name: "9080"
  validateClusters: false
  virtualHosts:
  - domains:
    - details.default.svc.cluster.local
    - details
    - details.default.svc
    - details.default
    - 173.96.169.163
    includeRequestAttemptCount: true
    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
        maxGrpcTimeout: 0s
        retryPolicy:
          hostSelectionRetryMaxAttempts: "5"
          numRetries: 2
          retriableStatusCodes:
          - 503
          retryHostPredicate:
          - name: envoy.retry_host_predicates.previous_hosts
            typedConfig:
              '@type': type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
          retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
        timeout: 0s
  - domains:
    - productpage.default.svc.cluster.local
    - productpage
    - productpage.default.svc
    - productpage.default
    - 173.96.170.254
    includeRequestAttemptCount: true
    name: productpage.default.svc.cluster.local:9080
    routes:
    - decorator:
        operation: productpage.default.svc.cluster.local:9080/*
      match:
        prefix: /
      metadata:
        filterMetadata:
          istio:
            config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/productpage
      route:
        cluster: outbound|9080|v1|productpage.default.svc.cluster.local
        maxGrpcTimeout: 0s
        retryPolicy:
          hostSelectionRetryMaxAttempts: "5"
          numRetries: 2
          retriableStatusCodes:
          - 503
          retryHostPredicate:
          - name: envoy.retry_host_predicates.previous_hosts
            typedConfig:
              '@type': type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
          retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
        timeout: 0s
  - domains:
    - ratings.default.svc.cluster.local
    - ratings
    - ratings.default.svc
    - ratings.default
    - 173.96.114.75
    includeRequestAttemptCount: true
    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
        maxGrpcTimeout: 0s
        retryPolicy:
          hostSelectionRetryMaxAttempts: "5"
          numRetries: 2
          retriableStatusCodes:
          - 503
          retryHostPredicate:
          - name: envoy.retry_host_predicates.previous_hosts
            typedConfig:
              '@type': type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
          retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
        timeout: 0s
  - domains:
    - reviews.default.svc.cluster.local
    - reviews
    - reviews.default.svc
    - reviews.default
    - 173.96.36.214
    includeRequestAttemptCount: true
    name: reviews.default.svc.cluster.local:9080
    routes:
    - decorator:
        operation: reviews:9080/*
      match:
        prefix: /
      metadata:
        filterMetadata:
          istio:
            config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/reviews
      route:
        maxGrpcTimeout: 0s
        retryPolicy:
          hostSelectionRetryMaxAttempts: "5"
          numRetries: 2
          retriableStatusCodes:
          - 503
          retryHostPredicate:
          - name: envoy.retry_host_predicates.previous_hosts
            typedConfig:
              '@type': type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate
          retryOn: connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes
        timeout: 0s
        weightedClusters:  # 这个就是权重
          clusters:
          - name: outbound|9080|v1|reviews.default.svc.cluster.local
            weight: 50
          - name: outbound|9080|v3|reviews.default.svc.cluster.local
            weight: 50
  - domains:
    - '*'
    includeRequestAttemptCount: true
    name: allow_any
    routes:
    - match:
        prefix: /
      name: allow_any
      route:
        cluster: PassthroughCluster
        maxGrpcTimeout: 0s
        timeout: 0s

可以看到在 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:

root@k8s-master01:~/istio# cat istio-1.22.2/samples/bookinfo/networking/virtual-service-reviews-v3.yaml 
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v3
        
root@k8s-master01:~/istio# kubectl apply -f  istio-1.22.2/samples/bookinfo/networking/virtual-service-reviews-v3.yaml

现在,当刷新 /productpage 时,将始终看到带有红色星级评分的评论。

测试结束。😘

3、流量镜像

流量镜像,也称为影子流量,是一个以尽可能低的风险为生产带来变化的强大的功能,镜像会将实时流量的副本发送到镜像服务,不会对主流量产生影响,这样就可以在不影响生产流量的情况下,对新版本的服务进行测试。

🚩 实战:流量镜像-2024.7.21(测试成功)

实验环境:

k8s v1.29.4(cri-dockerd)

~/istio# istioctl version
client version: 1.22.2
control plane version: 1.22.2
data plane version: 1.22.2 (4 proxies)

graph LR
	A[实战步骤] -->B(1️⃣ 部署应用)
	A[实战步骤] -->C(2️⃣ 配置VirtualService和DestinationRule)
	A[实战步骤] -->D(3️⃣ 验证)

🍀

这里我们将首先把流量全部路由到测试服务的 v1 版本,然后执行规则将一部分流量镜像到 v2 版本。

首先部署两个版本的 Httpbin 服务,并开启访问日志功能:

httpbin-v1:

root@k8s-master01:~/istio# cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin-v1
spec:
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80
EOF

httpbin-v2:

root@k8s-master01:~/istio# cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin-v2
spec:
  selector:
    matchLabels:
      app: httpbin
      version: v2
  template:
    metadata:
      labels:
        app: httpbin
        version: v2
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80
EOF

httpbin Kubernetes 服务:

root@k8s-master01:~/istio# kubectl create -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
spec:
#  type: NodePort
  ports:
  - name: http 
    port: 8000
    targetPort: 80
#    nodePort: 31310
  selector:
    app: httpbin
EOF

🍀

然后启动一个如下所示的 sleep 服务,用来提供 curl 工具进行负载:

root@k8s-master01:~/istio# cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep","3650d"]
        imagePullPolicy: IfNotPresent
EOF

🍀

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

root@k8s-master01:~/istio# kubectl get pod -l app=sleep
NAME                     READY   STATUS    RESTARTS   AGE
sleep-6b56495b4c-rxf7j   2/2     Running   0          9s
root@k8s-master01:~/istio# kubectl get pod -l app=httpbin
NAME                         READY   STATUS    RESTARTS   AGE
httpbin-v1-f66cc5cbc-ckq26   2/2     Running   0          12m
httpbin-v2-556d9d49-4j2j7    2/2     Running   0          12m

root@k8s-master01:~/istio# kubectl get svc httpbin
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
httpbin   ClusterIP   173.96.252.5   <none>        8000/TCP   12m

🍀

默认情况下,Kubernetes 在 httpbin 服务的两个版本之间进行负载均衡,接下来我们将创建如下所示的规则,把所有流量都路由到 v1 版本。

root@k8s-master01:~/istio# cat mirror.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
    - httpbin
  http:
    - route:
        - destination:
            host: httpbin
            subset: v1
          weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

这里我们先创建一个 DestinationRule 对象,将 httpbin 服务分成 v1 和 v2 两个子集,分别匹配 version=v1 和 version=v2 的 Pod,然后再创建一个 VirtualService 对象,将所有流量都路由到 v1 子集。

root@k8s-master01:~/istio# kubectl apply -f mirror.yaml 
virtualservice.networking.istio.io/httpbin created
destinationrule.networking.istio.io/httpbin created

🍀

应用上面的资源对象后,现在所有流量都被转到 httpbin:v1 服务,我们可以向此服务发送请求来验证:

root@k8s-master01:~/istio# export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
root@k8s-master01:~/istio# kubectl exec "${SLEEP_POD}" -c sleep -- curl -sS http://httpbin:8000/headers
{
  "headers": {
    "Accept": "*/*",
    "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://cluster.local/ns/default/sa/default;Hash=4252b9bc6aa8f29690137269e51cd45b9441078c918e30f1380537bc77402b4d;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
  }
}

🍀

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

root@k8s-master01:~/istio# export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
root@k8s-master01:~/istio# kubectl logs "$V1_POD" -c httpbin
127.0.0.6 - - [10/Nov/2023:07:38:53 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"

root@k8s-master01:~/istio# export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
root@k8s-master01:~/istio# kubectl logs "$V2_POD" -c httpbin

🍀

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

# mirror2.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
    - httpbin
  http:
    - route:
        - destination:
            host: httpbin
            subset: v1
          weight: 100
      mirror:
        host: httpbin
        subset: v2
      mirrorPercentage:
        value: 100.0

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

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

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

🍀

root@k8s-master01:~/istio#  kubectl apply -f mirror2.yaml 
virtualservice.networking.istio.io/httpbin configured

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

root@k8s-master01:~/istio#  kubectl exec "${SLEEP_POD}" -c sleep -- curl -sS http://httpbin:8000/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin:8000",
    "User-Agent": "curl/7.81.0-DEV",
    "X-B3-Parentspanid": "6bab21955204ba63",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "ff009be6e25704dc",
    "X-B3-Traceid": "2b52f289d76c3dbc6bab21955204ba63",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=4252b9bc6aa8f29690137269e51cd45b9441078c918e30f1380537bc77402b4d;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
  }
}

🍀

现在就可以看到 v1 和 v2 版本中都有了访问日志。v2 版本中的访问日志就是由镜像流量产生的,这些请求的实际目标是 v1 版本。

root@k8s-master01:~/istio# kubectl logs "$V1_POD" -c httpbin
127.0.0.6 - - [10/Nov/2023:07:38:53 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"
127.0.0.6 - - [10/Nov/2023:07:52:31 +0000] "GET /headers HTTP/1.1" 200 529 "-" "curl/7.81.0-DEV"

root@k8s-master01:~/istio# kubectl logs "$V2_POD" -c httpbin
127.0.0.6 - - [10/Nov/2023:07:52:31 +0000] "GET /headers HTTP/1.1" 200 569 "-" "curl/7.81.0-DEV"

🍀

同样我们可以去查看下 Envoy 里面的路由表配置来验证下这个流量镜像规则是如何映射到 Envoy 的路由配置中的:

# 8000 端口
root@k8s-master01:~/istio# istioctl proxy-config routes $SLEEP_POD --name 8000 -oyaml
- name: "8000"
  virtualHosts:
  - domains:
    - httpbin.default.svc.cluster.local
    - httpbin
    - httpbin.default.svc
    - httpbin.default
    - 10.96.247.77
    name: httpbin.default.svc.cluster.local:8000
    routes:
    - decorator:
        operation: httpbin.default.svc.cluster.local:8000/*
      match:
        prefix: /
      metadata:
        filterMetadata:
          istio:
            config: /apis/networking.istio.io/v1alpha3/namespaces/default/virtual-service/httpbin
      route:
        cluster: outbound|8000|v1|httpbin.default.svc.cluster.local
        requestMirrorPolicies:
        - cluster: outbound|8000|v2|httpbin.default.svc.cluster.local
          runtimeFraction:
            defaultValue:
              denominator: MILLION
              numerator: 1000000
          traceSampled: false
        # ......

可以看到在 Envoy 的路由表中,httpbin 服务的路由规则中多了一个 requestMirrorPolicies 字段,这个字段就是我们前面配置的流量镜像规则,其中包含了一个集群,就是 outbound|8000|v2|httpbin.default.svc.cluster.local,并且配置的是 100%比例,这样就实现了将 100% 的流量镜像到 httpbin:v2,这其实和前面我们在 Envoy 中去实现的流量镜像功能是一致的。

🍀

最后如果测试后不再需要这些服务可以将其清理:

kubectl delete virtualservice httpbin
kubectl delete destinationrule httpbin

关闭 Httpbin 服务和客户端:

kubectl delete deploy httpbin-v1 httpbin-v2 sleep
kubectl delete svc httpbin

测试结束。😘

4、熔断

熔断是创建弹性微服务应用程序的重要模式,熔断能够使应用程序具备应对来自故障、潜在峰值和其他未知网络因素影响的能力。服务熔断是应对微服务雪崩效应的一种链路保护机制,类似股市、保险丝。微服务之间的数据交互是通过远程调用来完成的,服务 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.熔断示例

下面我们将来演示如何为连接、请求以及异常检测配置熔断。

🚩 实战:熔断示例-2024.7.28(测试成功

实验环境:

k8s v1.29.4(cri-dockerd)

~/istio# istioctl version
client version: 1.22.2
control plane version: 1.22.2
data plane version: 1.22.2 (4 proxies)
graph LR
	A[实战步骤] -->B(1️⃣ 部署应用)
	A[实战步骤] -->C(2️⃣ 配置熔断)
	A[实战步骤] -->D(3️⃣ 验证)

🍀

首先启动一个 Httpbin 示例服务,如果您启用了 Sidecar 自动注入,通过以下命令部署 httpbin 服务:

root@k8s-master01:~/istio# cat istio-1.22.2/samples/httpbin/httpbin.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: httpbin
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
    service: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 8080
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      serviceAccountName: httpbin
      containers:
      - image: docker.io/kong/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        # Same as found in Dockerfile's CMD but using an unprivileged port
        command:
        - gunicorn
        - -b
        - 0.0.0.0:8080
        - httpbin:app
        - -k
        - gevent
        env:
        # Tells pipenv to use a writable directory instead of $HOME
        - name: WORKON_HOME
          value: /tmp
        ports:
        - containerPort: 8080

root@k8s-master01:~/istio# kubectl apply -f istio-1.22.2/samples/httpbin/httpbin.yaml 
root@k8s-master01:~/istio# kubectl get pod -l app=httpbin
NAME                       READY   STATUS    RESTARTS   AGE
httpbin-86b8ffc5ff-bqtgb   2/2     Running   0          79s

否则,必须在部署 httpbin 应用程序前进行手动注入:

$ kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml)

🍀

应用部署完成后首先我们需要创建一个 DestinationRule 对象,用来在调用 httpbin 服务时应用熔断设置:

root@k8s-master01:~/istio# kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 1  # 最大连接数
      http:
        http1MaxPendingRequests: 1 # 最大挂起请求数
        maxRequestsPerConnection: 1 # 每个连接到后端的最大请求数
    outlierDetection:
      consecutive5xxErrors: 1 # host从连接池中被拒绝之前的连续 5xx 错误数
      interval: 1s # 检测间隔
      baseEjectionTime: 3m  # 基础驱逐时间
      maxEjectionPercent: 100 # 最大驱逐百分比
EOF

这里我们在 httpbin 服务的 DestinationRule 对象中配置了 trafficPolicy 字段,用来配置熔断限流的相关参数。

这些参数的具体含义可以参考官方文档。

🍀

我们说了 DestinationRule 对象其实就是配置 Envoy 里面的 Cluster 集群,那么我们可以通过 istioctl proxy-config cluster 命令来查看下 httpbin 服务的 Cluster 集群配置信息:

root@k8s-master01:~/istio# istioctl proxy-config cluster httpbin-86b8ffc5ff-bqtgb --fqdn httpbin.default.svc.cluster.local -o yaml
- circuitBreakers:
    thresholds:
    - maxConnections: 1
      maxPendingRequests: 1
      maxRequests: 4294967295
      maxRetries: 4294967295
      trackRemaining: true
  commonLbConfig:
    healthyPanicThreshold: {}
    localityWeightedLbConfig: {}
  connectTimeout: 10s
  edsClusterConfig:
    edsConfig:
      ads: {}
      initialFetchTimeout: 0s
      resourceApiVersion: V3
    serviceName: outbound|8000||httpbin.default.svc.cluster.local
  filters:
  - name: istio.metadata_exchange
    typedConfig:
      '@type': type.googleapis.com/udpa.type.v1.TypedStruct
      typeUrl: type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange
      value:
        enable_discovery: true
        protocol: istio-peer-exchange
  lbPolicy: LEAST_REQUEST
  metadata:
    filterMetadata:
      istio:
        config: /apis/networking.istio.io/v1alpha3/namespaces/default/destination-rule/httpbin
        services:
        - host: httpbin.default.svc.cluster.local
          name: httpbin
          namespace: default
  name: outbound|8000||httpbin.default.svc.cluster.local
  outlierDetection:
    baseEjectionTime: 180s
    consecutive5xx: 1
    enforcingConsecutive5xx: 100
    enforcingSuccessRate: 0
    interval: 1s
    maxEjectionPercent: 100
  transportSocketMatches:
  - match:
      tlsMode: istio
    name: tlsMode-istio
    transportSocket:
      name: envoy.transport_sockets.tls
      typedConfig:
        '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        commonTlsContext:
          alpnProtocols:
          - istio-peer-exchange
          - istio
          combinedValidationContext:
            defaultValidationContext:
              matchSubjectAltNames:
              - exact: spiffe://cluster.local/ns/default/sa/httpbin
            validationContextSdsSecretConfig:
              name: ROOTCA
              sdsConfig:
                apiConfigSource:
                  apiType: GRPC
                  grpcServices:
                  - envoyGrpc:
                      clusterName: sds-grpc
                  setNodeOnFirstMessageOnly: true
                  transportApiVersion: V3
                initialFetchTimeout: 0s
                resourceApiVersion: V3
          tlsCertificateSdsSecretConfigs:
          - name: default
            sdsConfig:
              apiConfigSource:
                apiType: GRPC
                grpcServices:
                - envoyGrpc:
                    clusterName: sds-grpc
                setNodeOnFirstMessageOnly: true
                transportApiVersion: V3
              initialFetchTimeout: 0s
              resourceApiVersion: V3
          tlsParams:
            tlsMaximumProtocolVersion: TLSv1_3
            tlsMinimumProtocolVersion: TLSv1_2
        sni: outbound_.8000_._.httpbin.default.svc.cluster.local
  - match: {}
    name: tlsMode-disabled
    transportSocket:
      name: envoy.transport_sockets.raw_buffer
      typedConfig:
        '@type': type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer
  type: EDS

可以看到上面的配置中包含了我们前面配置的熔断参数,这些参数都是 Envoy 的 Cluster 集群的配置参数,比如 circuitBreakers、outlierDetection 等参数,也和我们前面在 DestinationRule 对象中配置的参数是一致的。

🍀

接下来我们创建一个 Fortio 的客户端程序来发送流量到 httpbin 服务,Fortio 是一个开源的 HTTP 负载测试工具,它可以控制连接数、并发数及发送 HTTP 请求的延迟。通过 Fortio 能够有效的触发前面在 DestinationRule 中设置的熔断策略。

root@k8s-master01:~/istio# cat istio-1.22.2/samples/httpbin/sample-client/fortio-deploy.yaml 
apiVersion: v1
kind: Service
metadata:
  name: fortio
  labels:
    app: fortio
    service: fortio
spec:
  ports:
  - port: 8080
    name: http
  selector:
    app: fortio
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fortio-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fortio
  template:
    metadata:
      annotations:
        # This annotation causes Envoy to serve cluster.outbound statistics via 15000/stats
        # in addition to the stats normally served by Istio. The Circuit Breaking example task
        # gives an example of inspecting Envoy stats via proxy config.
        proxy.istio.io/config: |-
          proxyStatsMatcher:
            inclusionPrefixes:
            - "cluster.outbound"
            - "cluster_manager"
            - "listener_manager"
            - "server"
            - "cluster.xds-grpc"
      labels:
        app: fortio
    spec:
      containers:
      - name: fortio
        image: fortio/fortio:latest_release
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          name: http-fortio
        - containerPort: 8079
          name: grpc-ping

root@k8s-master01:~/istio#  kubectl apply -f istio-1.22.2/samples/httpbin/sample-client/fortio-deploy.yaml 
root@k8s-master01:~/istio# kubectl get pods -l app=fortio
NAME                             READY   STATUS    RESTARTS   AGE
fortio-deploy-689bd5969b-bht9r   2/2     Running   0          70s

🍀

创建成功后我们可以登入 Fortio 客户端 Pod,然后使用 Fortio 工具调用 httpbin 服务:

~/istio# export FORTIO_POD=$(kubectl get pods -l app=fortio -o 'jsonpath={.items[0].metadata.name}')

# c=1 表示并发数为 1,n=20 表示总请求数为 20
~/istio# kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 1 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
# ......
IP addresses distribution:
173.96.238.58:8000: 1
Code 200 : 20 (100.0 %)  # 所有请求都正常
Response Header Sizes : count 20 avg 230.05 +/- 0.2179 min 230 max 231 sum 4601
Response Body/Total Sizes : count 20 avg 655.05 +/- 0.2179 min 655 max 656 sum 13101
All done 20 calls (plus 0 warmup) 6.497 ms avg, 153.9 qps

可以看出总共发送了 20 个 HTTP 连接,也就是 20 个请求,响应码均为 200。接下来我们就可以测试熔断功能了。

在 DestinationRule 配置中,我们定义了 maxConnections: 1 和 http1MaxPendingRequests: 1,表示如果并发的连接和请求数超过一个,在 istio-proxy 进行进一步的请求和连接时,后续请求或连接将被阻止。

🍀

这里我们来发送并发数为 2 的连接(-c 2),请求 20 次(-n 20):

~/istio# kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
# ......
IP addresses distribution:
173.96.238.58:8000: 7
Code 200 : 14 (70.0 %)
Code 503 : 6 (30.0 %)   # 出现503 
Response Header Sizes : count 20 avg 161.1 +/- 105.5 min 0 max 231 sum 3222
Response Body/Total Sizes : count 20 avg 530.9 +/- 189.8 min 241 max 656 sum 10618
All done 20 calls (plus 0 warmup) 7.998 ms avg, 237.2 qps

可以看到总共发送了 20 个 HTTP 连接,其中 16 个请求成功,4 个请求被熔断了,响应码为 503。

🍀

可以通过查看 istio-proxy 状态来验证熔断次数:

~/istio# kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.remaining_pending: 1
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_active: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_failure_eject: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_overflow: 6   # 熔断了 6 次
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_total: 34

其中 upstream_rq_pending_overflow 的值为 6,表明 maxConnections 断路器起作用了。istio-proxy 允许一定的冗余,我们可以将线程数提高到 3,限流的效果会更明显。通过限制待处理请求队列的长度,可以对恶意请求、DoS 和系统中的级联错误起到一定的缓解作用。

🍀

我们将并发连接数提高到 3 个:

~/istio# kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 30 -loglevel Warning http://httpbin:8000/get
# ......
Code 200 : 8 (26.7 %)
Code 503 : 22 (73.3 %)
Response Header Sizes : count 30 avg 61.5 +/- 102 min 0 max 231 sum 1845
Response Body/Total Sizes : count 30 avg 396.63333 +/- 258.1 min 241 max 825 sum 11899
All done 30 calls (plus 0 warmup) 12.773 ms avg, 208.3 qps

现在可以看到只有 26.7 % 的请求成功,其余的均被熔断器拦截了:

~/istio# kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.remaining_pending: 1
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_active: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_failure_eject: 0
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_overflow: 26
cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_total: 44

可以看到 upstream_rq_pending_overflow 值 为 26,这意味着,目前为止已有 26 个调用被标记为熔断了。

测试结束。😘

标签: 暂无
最后更新:2024年7月28日

tanghao946

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • istio之故障注入&流量拆分&流量镜像&熔断
  • 本节实战
  • 1、故障注入
    • 注入 HTTP 延迟故障
    • 注入 HTTP abort 故障
  • 2、流量拆分
    • 🚩 实战:流量拆分-2024.7.21(测试成功)
  • 3、流量镜像
    • 🚩 实战:流量镜像-2024.7.21(测试成功)
  • 4、熔断
    • 1.熔断概述
    • 2.熔断示例

COPYRIGHT © 2025 TH小破站. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

渝ICP备2023013789号

渝公网安备50011702500926号