Istio系列文章之(四)流量管理


上一节已经讲过使用基本的虚拟服务(VirtualService)和目标规则(DestinationRule)进行流量管理的示例,本节将继续讲解几个高级用法。

创建目标规则

执行以下命令创建默认目标规则

kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

基于用户身份的路由

创建路由规则

本示例将修改路由配置,将来自特定用户的所有流量路由到特定服务版本。在这种情况下, 来自名为Jason的用户的所有流量将被路由到服务reviews:v2。

productpage服务在所有到reviews服务的HTTP请求中都增加了一个自定义的end-user请求头,从而达到了本例子的效果。

注意:reviews:v2是包含星级评分功能的版本。

  1. 运行以下命令以启用基于用户的路由
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
apiVersion: networking.istio.io/v1beta1 kind: VirtualService ... spec: hosts: - reviews http: - match: - headers: end-user: exact: jason route: - destination: host: reviews subset: v2 - route: - destination: host: reviews subset: v1
  1. 在Bookinfo的页面上,以用户jason登录(密码也是jason),这将会在从productpage发往reviews的HTTP请求上增加header:end-user=jason,从而触发第一步设置的路由规则。刷新页面,星级评分将显示在每个评论旁边。

  2. 登出jason用户(或以其他用户名登录),再刷新页面,星级评分没有了。这是因为除了Jason之外,所有用户的流量都被路由到reviews:v1。

清除环境

kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml

基于权重的路由

创建路由规则

  1. 首先,运行此命令将所有流量路由到各个微服务的 v1 版本。
kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
  1. 在浏览器中打开Bookinfo页面,不管刷新多少次,页面的评论部分都不会显示评价星级的内容。 这是因为Istio被配置为将星级评价的服务的所有流量都路由到了reviews:v1版本,而该版本的服务不访问带评价星级的服务。

  2. 使用下面的命令把50%的流量从reviews:v1转移到reviews:v3:

kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
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
  1. 刷新浏览器,大约有50%的几率会看到页面中带红色星级的评价内容。这是因为reviews的v3版本可以访问带星级评价,但v1版本不能。

  2. 如果认为reviews:v3微服务已经稳定,我们可以通过应用Virtual Service规则将100%的流量路由reviews:v3:

kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v3.yaml
  1. 现在新页面时,将始终看到带有红色星级评分的书评。

清除环境

kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml

故障注入

创建路由规则

kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml

经过上面的流程,下面是请求的流程:

  • productpage → reviews:v2 → ratings (针对 jason 用户)
  • productpage → reviews:v1 (其他用户)

注入HTTP延迟故障

为了测试微服务应用程序Bookinfo的弹性,我们将为用户jason在reviews:v2和ratings服务之间注入一个2秒的延迟。这个测试将会发现一个故意引入Bookinfo应用程序中的bug。

注意reviews:v2服务对ratings服务的调用具有10秒的硬编码连接超时。而在productpage和reviews服务之间也有一个3秒的硬编码的超时,再加1次重试,一共6秒。因此,reviews和rating之间延迟不能超过3s,否则productpage就会直接超时重试了。尽管引入了2秒的延迟,我们仍然期望端到端的流程是没有任何错误的。

  1. 创建故障注入规则以延迟来自测试用户jason的流量:
kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - match: - headers: end-user: exact: jason fault: delay: percentage: value: 100.0 fixedDelay: 2s route: - destination: host: ratings subset: v1 - route: - destination: host: ratings subset: v1
  1. 测试故障,刷新页面,用jason登录,发现页面需要2s才能响应。打开F12,看到productpage的响应时间为2s左右

注入HTTP abort故障(模拟500)

测试微服务弹性的另一种方法是引入HTTP abort故障。 在这个任务中,针对测试用户jason,将给ratings微服务引入一个HTTP abort。

在这种情况下,我们希望页面能够立即加载,同时显示 Ratings service is currently unavailable 这样的消息。

  1. 为用户 jason 创建一个发送 HTTP abort 的故障注入规则:
kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml
samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - match: - headers: end-user: exact: jason fault: abort: percentage: value: 100.0 httpStatus: 500 route: - destination: host: ratings subset: v1 - route: - destination: host: ratings subset: v1
  1. 浏览器打开Bookinfo应用,以jason用户登录,刷新页面将会立刻返回Ratings service is currently unavailable消息。

  2. 此时打开kiali,也可以看到链路调用图中有一条红色的线标识返回500的请求。

  3. 如果注销用户jason或用其他用户打开Bookinfo页面,将看到/productpage为除jason以外的其他用户调用了reviews:v1(完全不调用ratings)。因此,页面不会看到任何错误消息。

清除环境

kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml

设置请求超时

HTTP请求的超时可以通过路由规则中的timeout字段来指定 默认情况下,超时是禁用的,本任务中,会把reviews服务的超时设置为半秒。为了观察效果,还需要在对ratings服务的调用上人为引入2秒的延迟。

创建路由规则

  1. 将请求路由到reviews服务的v2版本,它会发起对ratings服务的调用:
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v2
EOF
  1. 给对ratings服务的调用添加2秒的延迟:
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - fault:
      delay:
        percent: 100
        fixedDelay: 2s
    route:
    - destination:
        host: ratings
        subset: v1
EOF
  1. 在浏览器中打开Bookinfo,刷新页面每次会有2s延迟。

  2. 现在给对reviews服务的调用增加一个半秒的请求超时:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v2
    timeout: 0.5s
EOF
  1. 这时候应该看到1秒钟就会返回,而不是之前的2秒钟,但reviews是不可用的。

即使超时配置为半秒,响应仍需要1秒,是因为productpage服务中存在硬编码重试,因此它在返回之前调用reviews服务超时两次。

可以观察到,Bookinfo的页面(调用reviews服务来生成页面)没显示评论,而是显示了消息:Sorry, product reviews are currently unavailable for this book.这就是它收到了来自 reviews 服务的超时错误信息。

清除环境

kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml

熔断

本任务展示如何为连接、请求以及异常检测配置熔断。

熔断,是创建弹性微服务应用程序的重要模式。熔断能够使应用程序具备应对来自故障、潜在峰值和其他未知网络因素影响的能力。

这个任务中,本文将配置熔断规则,然后通过有意的使熔断器“跳闸”来测试配置。

部署httpbin服务

kubectl apply -f samples/httpbin/httpbin.yaml

配置熔断器

创建一个目标规则,在调用httpbin服务时应用熔断设置

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
      interval: 1s
      baseEjectionTime: 3m
      maxEjectionPercent: 100
EOF

部署客户端

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

  1. 部署fortio应用
kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml
  1. 登入客户端 Pod 并使用 Fortio 工具调用 httpbin 服务。-curl 参数表明发送一次调用:
## export FORTIO_POD=$(kubectl get pods -l app=fortio -o 'jsonpath={.items[0].metadata.name}')
## kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio curl -quiet http://httpbin:8000/get
HTTP/1.1 200 OK
server: envoy
date: Wed, 29 Nov 2023 02:29:55 GMT
content-type: application/json
content-length: 604
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 37

{
  "args": {},
  "headers": {
    "Host": "httpbin:8000",
    "User-Agent": "fortio.org/fortio-1.17.1",
    "X-B3-Parentspanid": "4fe31703ae3e116c",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "309260e8f043215f",
    "X-B3-Traceid": "eb54b298e3a056414fe31703ae3e116c",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/ficc-ofa-dev/sa/httpbin;Hash=c76311278aaa076b14ab1497090ed974a9d79a70e6c9b4e8373d4a9570a5b044;Subject=\"\";URI=spiffe://cluster.local/ns/ficc-ofa-dev/sa/default"
  },
  "origin": "127.0.0.6",
  "url": "http://httpbin:8000/get"
}

可以看到调用后端服务的请求已经成功!接下来,可以测试熔断。

触发熔断器

在DestinationRule配置中定义 maxConnections: 1和http1MaxPendingRequests: 1。这些规则意味着,如果并发的连接和请求数超过一个,在istio-proxy进行进一步的请求和连接时,后续请求或连接将被阻止。

  1. 发送并发数为2的连接(-c 2),请求20次(-n 20):
## kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
level warning http://httpbin:8000/get
02:36:45 I logger.go:127> Log level is now 3 Warning (was 2 Info)
Fortio 1.17.1 running at 0 queries per second, 2->2 procs, for 20 calls: http://httpbin:8000/get
Starting at max qps with 2 thread(s) [gomax 2] for exactly 20 calls (10 per thread + 0)
02:36:45 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
02:36:45 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
02:36:45 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:36:45 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:36:45 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
02:36:45 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
Ended after 225.072865ms : 20 calls. qps=88.86
Aggregated Function Time : count 20 avg 0.022105607 +/- 0.01845 min 0.001268616 max 0.074273631 sum 0.442112137
## range, mid point, percentile, count
>= 0.00126862 <= 0.002 , 0.00163431 , 10.00, 2
> 0.003 <= 0.004 , 0.0035 , 15.00, 1
> 0.007 <= 0.008 , 0.0075 , 20.00, 1
> 0.011 <= 0.012 , 0.0115 , 25.00, 1
> 0.012 <= 0.014 , 0.013 , 35.00, 2
> 0.014 <= 0.016 , 0.015 , 55.00, 4
> 0.016 <= 0.018 , 0.017 , 60.00, 1
> 0.018 <= 0.02 , 0.019 , 70.00, 2
> 0.03 <= 0.035 , 0.0325 , 80.00, 2
> 0.035 <= 0.04 , 0.0375 , 85.00, 1
> 0.045 <= 0.05 , 0.0475 , 90.00, 1
> 0.05 <= 0.06 , 0.055 , 95.00, 1
> 0.07 <= 0.0742736 , 0.0721368 , 100.00, 1
## target 50% 0.0155
## target 75% 0.0325
## target 90% 0.05
## target 99% 0.0734189
## target 99.9% 0.0741882
Sockets used: 8 (for perfect keepalive, would be 2)
Jitter: false
Code 200 : 14 (70.0 %)
Code 503 : 6 (30.0 %)
Response Header Sizes : count 20 avg 161.6 +/- 105.8 min 0 max 231 sum 3232
Response Body/Total Sizes : count 20 avg 656.7 +/- 272.1 min 241 max 835 sum 13134
All done 20 calls (plus 0 warmup) 22.106 ms avg, 88.9 qps

有14个请求成功了,6个失败了

Code 200 : 14 (70.0 %)
Code 503 : 6 (30.0 %)
  1. 将并发连接数提高到 3 个:
## kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 30 -loglevel Warning http://httpbin:8000/get
02:55:56 I logger.go:127> Log level is now 3 Warning (was 2 Info)
Fortio 1.17.1 running at 0 queries per second, 2->2 procs, for 30 calls: http://httpbin:8000/get
Starting at max qps with 3 thread(s) [gomax 2] for exactly 30 calls (10 per thread + 0)
02:55:56 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [1] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:56 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:57 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:57 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:57 W http_client.go:806> [2] Non ok http code 503 (HTTP/1.1 503)
02:55:57 W http_client.go:806> [0] Non ok http code 503 (HTTP/1.1 503)
Ended after 1.10855232s : 30 calls. qps=27.062
Aggregated Function Time : count 30 avg 0.091949974 +/- 0.1241 min 0.002178128 max 0.468933898 sum 2.75849922
## range, mid point, percentile, count
>= 0.00217813 <= 0.003 , 0.00258906 , 3.33, 1
> 0.004 <= 0.005 , 0.0045 , 6.67, 1
> 0.007 <= 0.008 , 0.0075 , 10.00, 1
> 0.008 <= 0.009 , 0.0085 , 13.33, 1
> 0.009 <= 0.01 , 0.0095 , 16.67, 1
> 0.012 <= 0.014 , 0.013 , 33.33, 5
> 0.014 <= 0.016 , 0.015 , 36.67, 1
> 0.018 <= 0.02 , 0.019 , 40.00, 1
> 0.02 <= 0.025 , 0.0225 , 50.00, 3
> 0.035 <= 0.04 , 0.0375 , 53.33, 1
> 0.04 <= 0.045 , 0.0425 , 56.67, 1
> 0.045 <= 0.05 , 0.0475 , 60.00, 1
> 0.05 <= 0.06 , 0.055 , 66.67, 2
> 0.06 <= 0.07 , 0.065 , 70.00, 1
> 0.07 <= 0.08 , 0.075 , 73.33, 1
> 0.1 <= 0.12 , 0.11 , 76.67, 1
> 0.2 <= 0.25 , 0.225 , 86.67, 3
> 0.25 <= 0.3 , 0.275 , 90.00, 1
> 0.35 <= 0.4 , 0.375 , 96.67, 2
> 0.45 <= 0.468934 , 0.459467 , 100.00, 1
## target 50% 0.025
## target 75% 0.11
## target 90% 0.3
## target 99% 0.463254
## target 99.9% 0.468366
Sockets used: 20 (for perfect keepalive, would be 3)
Jitter: false
Code 200 : 11 (36.7 %)
Code 503 : 19 (63.3 %)
Response Header Sizes : count 30 avg 84.866667 +/- 111.5 min 0 max 232 sum 2546
Response Body/Total Sizes : count 30 avg 458.96667 +/- 286.5 min 241 max 836 sum 13769
All done 30 calls (plus 0 warmup) 91.950 ms avg, 27.1 qps

现在能看到预期的熔断行为,只有36.7%的请求成功,其余的均被熔断器拦截:

Code 200 : 11 (36.7 %)
Code 503 : 19 (63.3 %)
  1. 查询istio-proxy状态以了解更多熔断详情:
## kubectl exec "$FORTIO_POD" -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep pending
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.circuit_breakers.default.remaining_pending: 1
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.circuit_breakers.default.rq_pending_open: 0
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.circuit_breakers.high.rq_pending_open: 0
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.upstream_rq_pending_active: 0
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.upstream_rq_pending_failure_eject: 0
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.upstream_rq_pending_overflow: 31
cluster.outbound|8000||httpbin.ficc-ofa-dev.svc.cluster.local.upstream_rq_pending_total: 40

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

清除环境

kubectl delete destinationrule httpbin
kubectl delete -f samples/httpbin/sample-client/fortio-deploy.yaml
kubectl delete -f samples/httpbin/httpbin.yaml

流量镜像

流量镜像,也称为影子流量,是一个以尽可能低的风险为生产带来变化的强大的功能。镜像会将实时流量的副本发送到镜像服务。镜像流量发生在主服务的关键请求路径之外。

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

首先部署httpbin的v1和v2两个deployment,过程略。再部署httpbin的service,过程略。此时请求httpbin将会在httpbin服务的两个版本之间进行负载均衡。接下来把所有流量都路由到v1版本。

  1. 创建一个默认路由规则,将所有流量路由到服务的v1版本:
## kubectl apply -f - <<EOF
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
EOF
  1. 现在所有流量都转到httpbin:v 服务,并向此服务发送请求:
export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
kubectl exec "${SLEEP_POD}" -c sleep -- curl -sS http://httpbin:8000/headers
  1. 分别查看httpbin Pod的v1和v2两个版本的日志。您可以看到v1版本的访问日志条目,而v2版本没有日志

镜像流量到v2

  1. 改变流量规则将流量镜像到v2:
## kubectl apply -f - <<EOF
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
EOF

这个路由规则发送100%流量到v1版本。mirrorPercentage表示将100%的相同流量镜像(即发送)到httpbin:v2服务。当流量被镜像时,请求将发送到镜像服务中,并在headers中的Host/Authority属性值上追加-shadow。 例如cluster-1变为cluster-1-shadow
此外,重点注意这些被镜像的流量是『即发即弃』的,就是说镜像请求的响应会被丢弃。
可以使用mirrorPercentage属性下的value字段来设置镜像流量的百分比,而不是镜像所有请求。如果没有这个属性,将镜像所有流量。

  1. 发送流量:
kubectl exec "${SLEEP_POD}" -c sleep -- curl -sS http://httpbin:8000/headers

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


文章作者: 洪宇轩
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 洪宇轩 !
评论
  目录