通过拦截所有的网络通信,Istio 能够得到一些指标和数据,这些指标和数据能够用来实现整个应用的可观察性。 Kiali 是一个开源的项目,它能够利用这些数据回答这样的问题:微服务是如何成为 Istio 服务网格的一部分的,它们是如何连接在一起的?
在安装 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 的功能。
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 打头的服务,这样我们就会看到收集到的指标,如下图所示:
这是一个空的视图,一点也无法调动我们的兴致,管理层是不允许这样的。通过如下的命令,我们可以造一些负载出来:
复制代码
$whiletrue;do/ curl -i http://$EXTERNAL_IP/sentiment -H"Content-type: application/json"/ -d'{"sentence": "I love yogobella"}'/ sleep .8;done
现在,我们的图表漂亮多了,除此之外,我们还有一些非常棒的工具,Prometheus 能够用来监控,Grafana 能用来对指标进行可视化,有了它们之后,我们就能知道随着时间的推移服务的性能、健康状况以及是否有升级和降级。
最后,我们将会研究一下如何跨服务进行跟踪。
我们需要跟踪系统的原因在于服务越多就越难以定位失败的原因。以下面图中这个简单的场景为例:
请求调用进来了,但是出现了失败,失败的原因是什么呢?是第一个服务失败还是第二个服务失败?两者都发生了异常,我们需要探查它们的日志。我们有多少次重复这样的经历了?在一定程度上讲,我们更像软件侦探,而不是开发人员。
这是微服务中一个非常普遍的问题,借助分布式的跟踪系统可以解决该问题,这种跟踪系统会在服务间传递一个唯一的 header 信息,然后这个信息会发送至分布式跟踪系统中,这样请求的跟踪信息就能汇总在一起了。样例如图 13 所示:
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 所示:
trace 展示了如下所述的信息:
注意:在第 4 步,我们的应用需要获取 Istio 生成的 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 能够提供为集群带来很多新功能:
在本文后面的内容中,我们将会在自己的应用中展示这些功能并在这个过程中介绍一些新的概念。我们要介绍的第一个概念就是 DestinationRules,并使用它们来实现 A/B 测试。
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 所示的问题。
这里有文件没有找到的错误,这是因为在应用的不同版本中它们的名称不相同。我们验证一下:
复制代码
$ 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 就能够让这一切变成现实。
在 VirtualService 将请求路由至正确的服务后,借助 DestinationRules 我们能够为面向该服务实例的流量指定策略,如图 17 所示。
注意:图 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)或镜像(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 所示:
但是,我们想要将请求路由至 version v1 的实例并镜像至 version v2,如图 19 所示:
这可以通过组合使用 VirtualService 和 DestinationRule 来实现,Destination Rule 声明子集,而 VirtualService 路由至特定的子集。
我们通过如下的配置定义子集:
复制代码
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
通过执行如下的命令应用该配置:
复制代码
$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets- destinationrule.yaml destinationrule.networking.istio.io/sa-logic created
在子集定义完之后,我们可以继续配置 VirtualService,让针对 sa-logic 的请求遵循如下的规则:
这可以通过如下的 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% 的请求失败,但是这些失败都不会影响终端用户,因为他们是由当前活跃的服务来提供响应的。
在本节中,我们第一次看到如何将 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
,其中随机的失败模拟了网络的不可靠性。
带有缺陷的服务版本会有三分之一的概率在生成响应时耗费过长的时间,三分之一的概率遇到服务器内部错误,其余的请求均能正常完成。
为了降低这些缺陷的影响并给用户带来更好的用户体验,我们会采取如下的措施:
这可以通过如下的资源定义来实现:
复制代码
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
这是一种优化:用户的等待不会超过 8 秒钟,如果失败的话,我们将会重试三次,这样的话增加了获取成功响应的概率。
通过如下命令应用更新后的配置:
复制代码
$ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts- vs.yaml virtualservice.networking.istio.io/sa-logic configured
查阅 Grafana 中的图表,看成功率是否有所提升(参见图 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 代理运行应用