WePay服务网格系统的高可用


【编者的话】本文是WePay工程团队服务网格系列的第三篇,在这篇文章里,作者从监控和告警的角度深入探讨了该如何实现服务网格架构的高可用。

在本系列的前面两篇文章,《在Webpay中使用Linkerd作为服务网格代理》以及《Sidecar和DaemonSet:容器模式之争》中,我们深入探讨了一些服务网格的细节,分别是服务网格代理( Linkerd)和这些代理的容器模式。

在本系列的第三部分中,我们将站在一个更高层面来看待服务网格系统。具体来说,我们将会从监控和告警两个角度来查看服务网格系统的健康性,并讲述如何使用各组数据来定义WePay基础设施中服务网格架构的高可用。

全景图

和我们在本系列中前面讨论的服务网格设定一样,这里给出的例子是一个跑在Google Kubernetes Engine(GKE)的Kubernetes集群上的高可用及模块化的服务网格。

我们在此之前经历了几次服务网格架构设计的迭代,而支持模块化的那个最能满足我们的需求。在我们的模块化设计中,服务网格的数据面板或者说代理(Linkerd)以及服务发现或者说控制面板(Namerd)被分成两个独立的模块,以便维护和监控。在这样的服务网格架构中,服务之间可以使用Namerd相互发现,并且可以借助Linkerd将请求路由到其他服务,此外它还提供负载均衡和监控指标。
image_0.png

图1:服务网格模块,Namerd服务和Linkerd代理

图1展示了Namerd和Linkerd作为WePay基础设施中实现服务网格的模块,还有将WePay的Sensu设定当成核心,让我们能够在出现任何问题时查看服务网格系统的状态,并且在某些地方无法正常工作时发出告警。

正如前面所提到的那样,在这篇文章中,我们将会把重点放在如何通过监控工具简化并实现一个高可用的服务网格系统。我们还将详细介绍监控系统涉及到的一些内容,一起来看看在一个系统里什么操作被认为是正常的,什么则是异常。

服务网格监控的ABC

上一节介绍了我们是如何通过明确定义的模块来简化服务网格的架构。使用该架构时,我们把一些时间花在了确定系统中每个模块的监控方式,随后是该系统整体。我们只想简单的回答这样一个问题,“应该怎样监控系统才能在遇到问题时拿到第一手资料,还有,我们的告警对象是谁?”

通过解答这些问题,我们发现并改进了整个系统的各个部分。例如,在我们的POC阶段,我们最开始使用的一个解决方案,其中将Namerd当作Kubernetes中Linkerd的sidecar。一旦我们发觉两个服务各自拥有自己的生命周期和健康检查定义会是一个更好的做法时,我们将Namerd放到了HAProxy后面的集群,后端由Google Compute Engine的实例组成。随后,经历多次迭代后,我们把Namerd重构成了Kubernetes的service,在那里它会通过一个Kubernetes的load balancer暴露给Linkerd代理。
image_1.png

图2:通过Kubernetes的namer发现的服务网格栈

在图2中,Namerd会在Kubernetes集群中做复制同步,Linkerd代理通过它的Kubernetes load balancer与Namerd通信,它会以sidecar容器的形式运行Kubernetes proxy来监视每个Kubernetes集群的更改,而每个Kubernetes代理会向它的master上报集群更改的信息,并返回到Namerd。

这只是一个示例,其中明确每个模块的适用范围极大地简化了服务网格系统里服务的监控和生命周期管理。在确定了每个模块的适用范围后,我们根据系统中模块的高可用要求对它们进行了测试:
  • 在可能的情况下,系统应当能够自愈。
  • 监控系统必须能够上报系统中每个模块内部和外部的健康状态。
  • 尽可能地提高系统的自愈能力,并在未能自愈或无法修复时发出告警。


下面部分介绍了每个Namerd和Linkerd模块的一些重要的监控单元。我们还会详细介绍我们开发的一些工具,针对每个Google Cloud项目和Kubernetes集群,分别在监控和告警方面实现模块外部和内部的可见性。

服务发现,A-OK

服务发现是服务网络系统的重要组成部分,对我们来说,它是基础设施里的一项关键服务,它能给出在每个数据中心里发现了多少服务。服务发现如果中断可能会影响路由,如果中断延长到几分钟,那么可能会让所有通过服务网格发现彼此的服务的路由停止工作。因此,如何监控namerd的服务发现取决于Namerd本身的心跳,以及代理的运行状况(如图2所示),它会将服务端点的更改上报给Namerd。

发现服务的心跳

作为服务网格的发现服务,Namerd内部的健康状况通过监控它的每个实例实现。我们将Namerd迁移到Kubernetes的主要理由之一是为了得到更棒的自愈能力和监控,这和我们所监控和观察的跑在数据中心里的所有微服务一样。
image_2.png

图3:Namerd internals with Kubernetes health probe

我们再仔细研究一下运行在Kubernetes环境中的Namerd的副本,图3显示了集群中该服务的N个副本。Kubernetes的health scheduler会监视每个副本,以确保每时每刻均有N个实时的副本存在。此外,每个副本配置成在一个很短的时间间隔内使用Kubernetes的health scheduler做ping,比如每隔几秒钟,这样来衡量它的响应能力。如果这些检查中有任何一次不成功或是无法响应,那么Kubernetes会重启受影响的副本。这样一来,副本将会重新启动然后重新绑定之前配置的名称。

此外,我们可能会遇到所有副本同时受影响的情况。在这种情况下,我们会通过设置一个更高级别的心跳检测来获悉Namerd完整的停机时间。这个心跳检测配置在Kubernetes的外部,并且它会在Namerd的Kubernetes load balancer后面寻找至少一个健康且可用的后端实例。我们通过在数据中心里使用Sensu系统的check runner来实现这一目标。
image_3.png

图4:Namerd使用Sensu进行外部检查

Sensu的check会每隔一个很短的时间间隔(每10秒左右)运行一次,以确保始终至少有一个后端实例是可用的。这个Sensu的check即是针对Kubernetes中Namerd的load balancer做简单的HTTP ping。在图4中,Sensu服务端向其中一个check runner发出信号,让它去ping Namerd,然后它会向事先配置好的通知系统上报结果。如果心跳连续多次执行失败,它会向通知服务发送告警,便于运维团队进一步调查。

我们让每个副本彼此相对独立地运行,从而能够对Namerd服务进行一次完整的健康测试,这主要是通过将每个实例的名称唯一对应一份副本实现。在该模式下,每个副本都是服务本身的一个完整表达,而除了其配置之外没有其他的依赖项。此外,我们还解决了服务需要横向扩展来应对高交易量的可扩展性。

所有服务,随时发现

如今我们可以通过监控Namerd的操作来发现任何可能存在的运行时问题,我们可以把精力放在识别可能存在的服务发现问题,即图4中的Discovery check。这类监控的目的是用来测试Namerd使用的不同类型的代理或namer

我们尝试实现了一个自定义的Sensu check,它会针对被测试的发现类型设置对应的dtab配置。这些dtab配置定义在一个特定的范畴,也称为命名空间
GET /api/1/dtabs/<namespace> HTTP/1.1

HTTP/1.1 200 OK
{
{
    "prefix":"/srv/default",
    "dst":"/#/io.l5d.k8s/prefix/portName"
},
{
    "prefix":"/svc",
    "dst":"/srv"
}


代码示例1:从Namerd获取实时的dtab配置

命名空间的一个例子就是用于HTTP1协议的出向Linkerd代理的dtab配置。参考代码示例1中的一个命名空间配置,其中包含一个微服务名称和命名空间,要求Namerd解析该名称来发现该微服务的检查尝试次数:
POST /dtab/delegator.json HTTP/1.1
{
"dtab":<dtab_configuration>,
"namespace":<namespace_name>,
"path":"/prefix/service"
}

HTTP/1.1 200 OK
{
"type":"delegate",
"path":"/prefix/service",
"delegate":{
  "type":"alt",
  "path":"/srv/prefix/service",
  "dentry":{
     "prefix":"/svc",
     "dst":"/srv"
  },
  "alt":[
     {
        "type":"neg",
        ...
     },
     {
        "type":"leaf",
        "path":"/#/io.l5d.k8s/prefix/portName/service",
        "dentry":{
           "prefix":"/srv/default",
           "dst":"/#/io.l5d.k8s/prefix/portName"
        },
        "bound":{
           "addr":{
              "type":"bound",
              ...
           },
           "id":"/%/io.l5d.k8s.daemonset/mesh/...",
           "path":"/"
        }
     },
     ...
  ]
}


代码示例2:通过Namerd解析一个服务的名字

在代码示例2的API调用中可以看到,命名空间A中发现了Service X,因此在Namerd的响应主体中返回了“type”:“leaf”对象。在同一请求中,所有其他的发现路由都返回了“type”:“neg”,没有从API调用中的请求主体里识别出到Service X的路径。

该check用到的每个命名空间都和协议及路由器类型,入向/出向,设置等有关。比如,HTTP/1.1协议有一条用于发送方路由的dtab,以及用于接收路由的另一个dtab,而且为了简单起见,这还只是考虑了在一个可发现域范围的情况,其中不包括外部服务或实体。

由于服务发现是每个微服务环境的核心,对于全局服务发现的健康性而言,所有服务发现的检查都被视为非常关键的指标,如果问题在短时间内无法自己纠正,那么将会触发相应地告警。

探测监听的路由

就像监控Namerd的服务发现一样,监控和测试生产环境里使用Linkerd做代理的数据面板包含两个维度,每个维度从不同视角为我们讲述了服务网格代理是如何工作的。一个维度是使用一个外部的watcher监控代理运行的健康状况,在这里即是Kubernetes的health scheduler。另一个维度则是,如果在给定代理是健康的条件下,它们是否可以成功地将请求路由到同一集群或是跨集群的其他节点上的代理呢?

检查代理的运行健康状况的目标是为了发现那些可以通过重启有问题的容器解决的问题。因此,我们需要配置一个健康检查,确保经过代理的一个完整循环,它的响应码可以被转换成Kubernetes里的对象,即响应码是200表明状态是healthy,而非200的响应码则会将容器标记成unhealthy:
GET /admin/ping HTTP/1.1

HTTP/1.1 200 OK
pong

代码示例3:针对每个代理做简单的健康检查

这些健康检查可以根据基础设施环境自定义成任意的复杂度,但是在基础设施里,一般基本的健康检查就能发现潜在的问题,作为监控检查的一部分,代码示例3展示了对代理容器的简单ping探测。
image_4.png

图5:带有Kubernetes健康检查探测的Linkerd sidecar代理

图5展示了基础设施中每个代理在一个很短的时间间隔里发生的ping,无论每个代理组使用什么样的容器模式

当我们开始查看本节开头提到的第二个维度,即检查路由的内部运行情况时,代理的检查会变得更加有趣。换句话说,如果路由情况从外面看起来很好的话,我们就得看看代理是否健康并且是否能够路由到不同的域:
image_5.png

图6:两个Kubernetes集群之间内部和外部的探测

要实施更明确一点的探测测试的话,我们需要在每个域里种下一个服务,在这里即是两个不同的Kubernetes集群,这样一来便可以按需从内部探测任何微服务。在图6中,在集群A中启动探测,指示探测服务A向其代理发送探测目标为服务1的请求。想要在这一设定下实现全域覆盖的话,我们可以在集群B中使用探测服务B启动一个相同的探测任务,其中探测本身是从集群B中发起,但是最终是以集群A的服务1作为目标服务。相同的模式可以扩展到任意数量的集群,使用我们的探测服务按需进行任意数量的迭代。

此外,一套基础设施环境中存在多种协议的情况也是适用的,比如,同时有HTTP/1.1HTTP/2,可以将探测服务配置成在单次探测检查中使用这些协议中的任意一种来探测目标:
image_6.png

图7:使用探测服务探测REST及gRPC应用

就像服务发现的健康检查那样,任何路由方面的健康检查也是至关重要的,如果问题没有自愈,那么会触发通知或告警。如果在服务网格的环境中某些东西不能正常工作,我们可以通过这样端到端的监控形式,填补基础设施中可能出错的地方。

高可用,乃至更远

服务网格是一个技术栈,它将控制面板,数据流和负载平衡三块独立开来,而且通过一套高可用的设定,我们得以持续享受它所带来的所有好处,并且提供更好的保障。

在我们的高可用设定中,我们:
  • 将控制面板和数据平面单独分开,从而最小化我们的监控范围,而且对于监控栈来说监控对象也变得更加具象。
  • 从不同的维度实施健康检查,并确保容器操作和功能检查是分别单独监控的。
  • ......并且,使用这些监控设定时,我们让可操作的监控事件变得更加清晰而且可以告警出来。


鉴于从高可用的服务网格设定下得到的信心,我们将越来越多的微服务迁移到了服务网格,而且利用了服务网格附带的所有功能。因此,在本系列的下一篇文章中,我们将深入探讨我们的应用程序,REST或gRPC应用,是如何利用服务网格的,以及我们如何在WePay的基础架构中管理栈的生命周期。

原文链接:A Highly Available Service Mesh at WePay(译者:吴佳兴)

0 个评论

要回复文章请先登录注册