吴佳兴 译 分布式实验室
在本系列的前面两篇文章,《WePay为什么选择Linkerd作为服务网格代理?》以及《Sidecar和DaemonSet,WePay是如何选择的?》中,我们深入探讨了一些服务网格的细节,分别是服务网格代理( Linkerd)和这些代理的容器模式。
在本系列的第三部分中,我们将站在一个更高层面来看待服务网格系统。具体来说,我们将会从监控和告警两个角度来查看服务网格系统的健康性,并讲述如何使用各组数据来定义WePay基础设施中服务网格架构的高可用。
全景图
和我们在本系列中前面讨论的服务网格设定一样,这里给出的例子是一个跑在Google Kubernetes Engine(GKE)的Kubernetes集群上的高可用及模块化的服务网格。
我们在此之前经历了几次服务网格架构设计的迭代,而支持模块化的那个最能满足我们的需求。在我们的模块化设计中,服务网格的数据面板或者说代理(Linkerd)以及服务发现或者说控制面板(Namerd)被分成两个独立的模块,以便维护和监控。在这样的服务网格架构中,服务之间可以使用Namerd相互发现,并且可以借助Linkerd将请求路由到其他服务,此外它还提供负载均衡和监控指标。
图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代理。
在可能的情况下,系统应当能够自愈。
监控系统必须能够上报系统中每个模块内部和外部的健康状态。
尽可能地提高系统的自愈能力,并在未能自愈或无法修复时发出告警。
服务发现,A-OK
服务发现是服务网络系统的重要组成部分,对我们来说,它是基础设施里的一项关键服务,它能给出在每个数据中心里发现了多少服务。服务发现如果中断可能会影响路由,如果中断延长到几分钟,那么可能会让所有通过服务网格发现彼此的服务的路由停止工作。因此,如何监控namerd的服务发现取决于Namerd本身的心跳,以及代理的运行状况(如图2所示),它会将服务端点的更改上报给Namerd。
发现服务的心跳
作为服务网格的发现服务,Namerd内部的健康状况通过监控它的每个实例实现。我们将Namerd迁移到Kubernetes的主要理由之一是为了得到更棒的自愈能力和监控,这和我们所监控和观察的跑在数据中心里的所有微服务一样。
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探测。
服务网格是一个技术栈,它将控制面板,数据流和负载平衡三块独立开来,而且通过一套高可用的设定,我们得以持续享受它所带来的所有好处,并且提供更好的保障。
在我们的高可用设定中,我们:
将控制面板和数据平面单独分开,从而最小化我们的监控范围,而且对于监控栈来说监控对象也变得更加具象。
从不同的维度实施健康检查,并确保容器操作和功能检查是分别单独监控的。
......并且,使用这些监控设定时,我们让可操作的监控事件变得更加清晰而且可以告警出来。