28

通过 Istio 重新实现微服务(四):跨服务跟踪和流量管理

 4 years ago
source link: https://www.tuicool.com/articles/f6vUzam
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

内置的特性

通过拦截所有的网络通信,Istio 能够得到一些指标和数据,这些指标和数据能够用来实现整个应用的可观察性。 Kiali 是一个开源的项目,它能够利用这些数据回答这样的问题:微服务是如何成为 Istio 服务网格的一部分的,它们是如何连接在一起的?

Kiali——可观察性

在安装 Istio 到我们的集群中之前,我们创建了一个用于 kiali 的 secret(还有另外一个用于 grafana),其中,我们将 user 和 password 都设置为 admin 。要访问 Kiali 的 Admin UI,需要执行如下的命令:

复制代码

$ kubectl port-forward \
$(kubectlgetpod -n istio-system -lapp=kiali \
-ojsonpath='{.items[0].metadata.name}') \
-n istio-system 20001

打开 http://localhost:20001/ 并使用“admin”(不含引号)作为 user 和 password 进行登录。这里很有多有用的特性,比如检查 Istio 组件的配置、拦截网络请求和响应所收集的信息构建的可视化服务,比如“谁调用了谁?”,“哪个版本的服务出现了故障?”等等。在进入下一步章节学习使用 Grafana 可视化指标之前,请花一些时间检验 Kiali 的功能。

y6neUf6.jpg!web

图 10 Kiali——服务可观察性

Grafana——指标可视化

Istio 收集到的指标被放到了 Prometheus 中,并使用 Grafana 进行可视化。要访问 Grafana 的 Admin UI,请执行如下的命令并打开 http://localhost:3000

复制代码

$ kubectl -n istio-system port-forward \
$(kubectl -n istio-systemgetpod -lapp=grafana \
-ojsonpath='{.items[0].metadata.name}') 3000

在左上角点击 Home 菜单,并选择 Istio Service Dashboard ,然后选择 sa-web-app 打头的服务,这样我们就会看到收集到的指标,如下图所示:

NBVJneV.jpg!web

图 11 Grafana 指标的可视化

这是一个空的视图,一点也无法调动我们的兴致,管理层是不允许这样的。通过如下的命令,我们可以造一些负载出来:

复制代码

$whiletrue;do\
curl -i http://$EXTERNAL_IP/sentiment -H"Content-type: application/json"\
-d'{"sentence": "I love yogobella"}'\
sleep .8;done

现在,我们的图表漂亮多了,除此之外,我们还有一些非常棒的工具,Prometheus 能够用来监控,Grafana 能用来对指标进行可视化,有了它们之后,我们就能知道随着时间的推移服务的性能、健康状况以及是否有升级和降级。

最后,我们将会研究一下如何跨服务进行跟踪。

Jaeger——跟踪

我们需要跟踪系统的原因在于服务越多就越难以定位失败的原因。以下面图中这个简单的场景为例:

juM3222.jpg!web

图 12 一个常见的请求失败

请求调用进来了,但是出现了失败,失败的原因是什么呢?是第一个服务失败还是第二个服务失败?两者都发生了异常,我们需要探查它们的日志。我们有多少次重复这样的经历了?在一定程度上讲,我们更像软件侦探,而不是开发人员。

这是微服务中一个非常普遍的问题,借助分布式的跟踪系统可以解决该问题,这种跟踪系统会在服务间传递一个唯一的 header 信息,然后这个信息会发送至分布式跟踪系统中,这样请求的跟踪信息就能汇总在一起了。样例如图 13 所示:

ieUb2aY.jpg!web

图 13 用于识别请求 span 的 TraceId

Istio 使用了 Jaeger Tracer,后者实现了 OpenTracing API,这是一个独立于供应商的框架。要访问 Jaegers UI,请执行如下的命令:

复制代码

$ kubectl port-forward -n istio-system \
$(kubectlgetpod -n istio-system -lapp=jaeger \
-ojsonpath='{.items[0].metadata.name}') 16686

然后通过 http://localhost:16686 打开 UI,选择 sa-web-app 服务,如果服务没有出现在下拉列表中的话,那么在页面进行一些活动,并点击刷新。随后点击 Find Traces 按钮,它只会展现最近的 trace,选择任意一条记录,所有的跟踪信息将会分别展示,如图 14 所示:

fyIjUvq.jpg!web

图 14 Jaeger- 一个请求的跟踪信息

trace 展示了如下所述的信息:

  1. 传入 istio-ingressgateway 的请求(这是第一次与某个服务产生关联,所以会生成 Trace ID),随后网关将请求转发至 sa-web-app 服务;
  2. sa-web-app 服务中,请求会被 Envoy 容器捕获并生成一个 span child(这就是我们能够在 trace 中看到它的原因),然后会被转发至 sa-web-app 应用。
  3. 在这里, sentimentAnalysis 方法会处理请求。这些 trace 是由应用生成的,这意味着需要对代码进行一些修改;
  4. 在此之后,会发起针对 sa-logic 的 POST 请求。sa-web-app 需要将 Trace ID 传播下去;
  5. ……

注意:在第 4 步,我们的应用需要获取 Istio 生成的 header 信息,并将其向下传递给下一个请求,如下面的图片所示。

MniYZvA.jpg!web

图 15 (A)Istio 传播 header 信息,(B)服务传播 header 信息

Istio 承担了主要的工作,它会为传入的请求生成 header 信息、为每个 sidecar 创建新的 span 并传递 span,但是,如果我们的服务不同时传播这些 header 信息的话,那么我们就会丢失请求的完整跟踪信息。

要传播的 header 信息如下所示:

复制代码

x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
x-ot-span-context

尽管这是一个很简单的任务,但依然有 很多的库 在简化这个过程,例如,在 sa-web-app 服务中, RestTemplate 客户端就进行了 instrument 操作,这样的话,我们只需要在 依赖 中添加 Jaeger 和 OpenTracing 就能传播 header 信息了。

在研究了开箱即用(以及部分开箱即用)的功能之后,我们看一下这里的主要话题,也就是细粒度路由、管理网络流量以及安全性等等。

流量管理

借助 Envoy,Istio 能够提供为集群带来很多新功能:

  • 动态请求路由 :金丝雀发布、A/B 测试
  • 负载均衡 :简单一致的 Hash 均衡
  • 故障恢复 :超时、重试、断路器
  • 故障注入 :延时、请求中断等。

在本文后面的内容中,我们将会在自己的应用中展示这些功能并在这个过程中介绍一些新的概念。我们要介绍的第一个概念就是 DestinationRules,并使用它们来实现 A/B 测试。

A/B 测试——Destination Rule 实践

A/B 测试适用于应用有两个版本的场景中(通常这些版本在视觉上有所差异),我们无法 100% 确定哪个版本能够增加用户的交互,所以我们同时尝试这两个版本并收集指标。

为了阐述这种场景,我们部署前端的第二个版本(使用绿色按钮替换白色按钮)。执行如下的命令:

复制代码

$ kubectl apply -fresource-manifests/kube/ab-testing/sa-frontend-green- deployment.yaml
deployment.extensions/sa-frontend-green created

绿色版本的部署 manifest 有两点差异:

istio-green
version: green

因为两个 deployment 都有 app: sa-frontend 标记,所以 virtual service sa-external-services 在将请求路由至 sa-frontend 服务时,会转发到这两者上面,并且会使用 round robin 算法进行负载均衡,这样会导致图 16 所示的问题。

QbERBbb.jpg!web

图 16 请求的文件未找到

这里有文件没有找到的错误,这是因为在应用的不同版本中它们的名称不相同。我们验证一下:

复制代码

$ curl --silent http://$EXTERNAL_IP/ | tr '"' '\n' | grep main
/static/css/main.c7071b22.css
/static/js/main.059f8e9c.js
$ curl --silent http://$EXTERNAL_IP/ | tr '"' '\n' | grep main
/static/css/main.f87cd8c9.css
/static/js/main.f7659dbb.js

也就是说, index.html 在请求某个版本的静态文件时,被负载均衡到了另外一个版本的 pod 上了,这样的话,就会理解为其他文件不存在。这意味着,对我们的应用来说,如果想要正常运行的话,就要引入一种限制“ 为 index.html 提供服务的应用版本,必须也要为后续的请求提供服务 ”。

我们会使用一致性 Hash 负载均衡(Consistent Hash Loadbalancing)来达成这种效果, 这个过程会将同一个客户端的请求转发至相同的后端实例中 ,在实现时,会使用一个预先定义的属性,如 HTTP header 信息。使用 DestionatioRules 就能够让这一切变成现实。

DestinationRules

在 VirtualService 将请求路由至正确的服务后,借助 DestinationRules 我们能够为面向该服务实例的流量指定策略,如图 17 所示。

rq6VZnu.jpg!web

图 17 借助 Istio 资源进行流量管理

注意:图 17 用非常易于理解的方式可视化地展现了 Istio 资源是如何影响网络流量的。但是,精确决定请求要发送至哪个实例是 Ingress Gateway 的 Envoy 完成的,它需要使用 CRD 来进行配置。

借助 Destination Rules 我们可以配置负载均衡使用一致哈希算法,从而确保相同的用户会由同一个服务实例提供响应。我们可以通过如下的配置实现这一点:

复制代码

apiVersion:networking.istio.io/v1alpha3
kind:DestinationRule
metadata:
name:sa-frontend
spec:
host:sa-frontend
trafficPolicy:
loadBalancer:
consistentHash:
httpHeaderName:version# 1

1. 根据“version”头信息的内容生成 consistentHash

执行如下的命令应用该配置:

复制代码

$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa- frontend.yaml
destinationrule.networking.istio.io/sa-frontend created

执行如下命令并校验在指定 version header 信息时会得到相同的文件:

复制代码

$ curl --silent -H"version: yogo"http://$EXTERNAL_IP/ |tr'"''\n'|grepmain

注意:在浏览器中,你可以使用 chrome 扩展 为 version 设置不同的值。

DestinationRules 有很多其他负载均衡的功能,关于其细节,请参考 官方文档

在更详细地探索 VirtualService 之前,通过如下的命令移除应用的 green 版本和 DestinationRules:

复制代码

$ kubectl delete -f resource-manifests/kube/ab-testing/sa-frontend-green- deployment.yaml
deployment.extensions"sa-frontend-green"deleted
$ kubectl delete -f resource-manifests/istio/ab-testing/destinationrule-sa- frontend.yaml
destinationrule.networking.istio.io"sa-frontend"deleted

Shadowing———Virtual Services 服务实践

当我们想要在生产环境测试某项变更,但是不想影响终端用户的时候,可以使用影子(Shadowing)或镜像(Mirroring)技术,这样的话,我们能够将请求镜像至具有变更的第二个实例并对其进行评估。 或者说更简单的场景,你的同事解决了一个最重要的缺陷,并提交了包含大量内容的 Pull Request,没人能够对其进行真正的审查。

为了测试这项特性,我们通过如下的命令创建 SA-Logic 的第二个实例(它是有缺陷的):

复制代码

$ kubectl apply -f resource-manifests/kube/shadowing/sa-logic-service.buggy.yaml

执行下面的命令校验所有的版本除了 app=sa-logic 之外都带有对应版本的标记:

复制代码

$ kubectl get pods -lapp=sa-logic --show-labels
NAME READY LABELS
sa-logic-568498cb4d-2sjwj 2/2app=sa-logic,version=v1
sa-logic-568498cb4d-p4f8c 2/2app=sa-logic,version=v1
sa-logic-buggy-76dff55847-2fl66 2/2app=sa-logic,version=v2
sa-logic-buggy-76dff55847-kx8zz 2/2app=sa-logic,version=v2

因为 sa-logic 服务的目标是带有 app=sa-logic 标记的 pod,所以传入的请求会在所有的实例间进行负载均衡,如图 18 所示:

3Qveu2z.jpg!web

图 18 Round Robin 负载均衡

但是,我们想要将请求路由至 version v1 的实例并镜像至 version v2,如图 19 所示:

22mQVvq.jpg!web

图 19 路由至 v1 并镜像至 v2

这可以通过组合使用 VirtualService 和 DestinationRule 来实现,Destination Rule 声明子集,而 VirtualService 路由至特定的子集。

通过 Destination Rule 声明子集

我们通过如下的配置定义子集:

复制代码

apiVersion:networking.istio.io/v1alpha3
kind:DestinationRule
metadata:
name:sa-logic
spec:
host:sa-logic# 1
subsets:
-name:v1# 2
labels:
version:v1# 3
-name:v2
labels:
version:v2

  1. host 定义这个规则只适用于路由至 sa-logic 服务的请求;
  2. 当路由至子集实例时所使用子集名称;
  3. 以键值对形式定义的标记,将实例定义为子集的一部分。

通过执行如下的命令应用该配置:

复制代码

$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets- destinationrule.yaml
destinationrule.networking.istio.io/sa-logic created

在子集定义完之后,我们可以继续配置 VirtualService,让针对 sa-logic 的请求遵循如下的规则:

  1. 路由至名为 v1 的子集;
  2. 镜像至名为 v2 的子集。

这可以通过如下的 manifest 实现:

复制代码

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: sa-logic
spec:
hosts:
- sa-logic
http:
-route:
-destination:
host: sa-logic
subset: v1
mirror:
host: sa-logic
subset: v2

所有的配置都很具有表述性,我们不再赘述,接下来看一下它的实际效果:

复制代码

$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing- vs.yaml
virtualservice.networking.istio.io/sa-logic created

通过执行如下命令生成一些负载:

复制代码

$whiletrue;docurl -v http://$EXTERNAL_IP/sentiment \
-H"Content-type: application/json"\
-d '{"sentence":"I love yogobella"}'; \
sleep.8;done

在 Grafana 检查结果,你会发现有缺陷的版本会出现 60% 的请求失败,但是这些失败都不会影响终端用户,因为他们是由当前活跃的服务来提供响应的。

zM3Qbyi.jpg!web

图 20 sa-logic 服务的成功率

在本节中,我们第一次看到如何将 VirtualService 用到服务的 envoy 上,当 sa-web-app 发起对 sa-logic 的请求时,会经过 sidecar envoy,借助我们配置的 VirtualService,将会路由至 sa-logic 的 v1 子集并镜像至 v2 子集。

金丝雀部署

金丝雀(Canary)部署指的是让少量用户使用应用新版本的一个过程,借助这一过程能够验证新版本是否存在问题,然后能够确保以更高的质量发布给更多的受众。

我们继续使用 sa-logic 带有缺陷的子集来阐述金丝雀发布。

首先,我们大胆地将 20% 的用户发送至带有缺陷的版本(这就是金丝雀发布),并将 80% 的用户发送至正常的服务,这可以通过如下的 VirtualService 来实现:

复制代码

apiVersion:networking.istio.io/v1alpha3
kind:VirtualService
metadata:
name:sa-logic
spec:
hosts:
-sa-logic
http:
- route:
-destination:
host:sa-logic
subset:v1
weight:80# 1
-destination:
host:sa-logic
subset:v2
weight:20# 1

1. 权重声明了请求要转发到目的地或目的地子集的百分比。

通过下面的命令,更新上述的 sa-logic virtual service:

复制代码

$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic configured

我们马上可以看到有些请求失败了:

复制代码

$ while true; do \
curl -i http://$EXTERNAL_IP/sentiment -H "Content-type: application/json" \
-d '{"sentence": "I love yogobella"}' \
--silent -w "Time: %{time_total}s \t Status: %{http_code}\n" -o /dev/null; \
sleep .1; done
Time:0.153075s Status: 200
Time:0.137581s Status: 200
Time:0.139345s Status: 200
Time:30.291806s Status: 500

VirtualServices 实现了金丝雀发布,借助这种方法,我们将潜在的损失降低到了用户群的 20%。非常好!现在,当我们对代码没有确切把握的时候,就可以使用 Shadowing 技术和金丝雀发布。

超时和重试

代码不仅仅会有缺陷,在“ 分布式计算的 8 个谬误 ”中,排名第一的就是“网络是可靠的”。网络实际上是不可靠的,这也是我们需要超时和重试的原因。

为了便于阐述,我们将会继续使用有缺陷版本的 sa-logic ,其中随机的失败模拟了网络的不可靠性。

带有缺陷的服务版本会有三分之一的概率在生成响应时耗费过长的时间,三分之一的概率遇到服务器内部错误,其余的请求均能正常完成。

为了降低这些缺陷的影响并给用户带来更好的用户体验,我们会采取如下的措施:

  • 如果服务耗时超过了 8 秒钟,将会超时;
  • 对于失败的请求进行重试。

这可以通过如下的资源定义来实现:

复制代码

apiVersion:networking.istio.io/v1alpha3
kind:VirtualService
metadata:
name:sa-logic
spec:
hosts:
-sa-logic
http:
-route:
-destination:
host:sa-logic
subset:v1
weight:50
-destination:
host:sa-logic
subset:v2
weight:50
timeout:8s# 1
retries:
attempts:3# 2
perTryTimeout:3s# 3

  1. 请求有 8 秒钟的超时;
  2. 它会尝试 3 次;
  3. 如果耗时超过 3 秒钟就将本次尝试标记为失败。

这是一种优化:用户的等待不会超过 8 秒钟,如果失败的话,我们将会重试三次,这样的话增加了获取成功响应的概率。

通过如下命令应用更新后的配置:

复制代码

$ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts- vs.yaml
virtualservice.networking.istio.io/sa-logic configured

查阅 Grafana 中的图表,看成功率是否有所提升(参见图 21)。

7nANJbn.jpg!web

图 21 在使用超时和重试功能之后,带来了成功率的提升

在进入下一节之前,通过如下命令移除掉 sa-logic-buggy 和 VirtualService:

复制代码

$ kubectl delete deployment sa-logic-buggy
deployment.extensions"sa-logic-buggy"deleted
$ kubectl delete virtualservice sa-logic
virtualservice.networking.istio.io"sa-logic"deleted

系列回顾

通过 Istio 重新实现微服务 (一):认识 Istio

通过 Istio 重新实现微服务 (二):Istio 实践

通过 Istio 重新实现微服务 (三):使用 Istio 代理运行应用


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK