monitoring

June 25, 2016

Overview

做监控两年了,总结梳理下自己做监控的历程。从 nagios 迁移 zabbix 开始,步入监控领域。维护 statsd + collectd + graphite,接着,基于 graphite,写报警系统 Halo,到现在,Halo 已经发展成为一个具有 APM (Application Performance Management)能力的监控平台。这不是一个纯回顾历史的文章,我希望把现在我对监控的理解贯穿到历史的回顾中去。

Nagios

最开始的监控总是主机层面的监控,例如当主机的 CPU load 过高、空闲 Memory 过低、硬盘空闲空间过低时发送短信给运维,运维及时处理,以防故障。在这一领域,最古老的(之一)是 Nagios。

Nagios 的基本原理是在每台机器部署一个 agent (NRPE 插件),采集机器数据并判断阈值,把结果传送给中心的 server,server 负责在 Web 展示,并且把消息发送给对应的人。

Nagios 在主机监控方面运行的很好(除了配置非常复杂外),但当时逐渐出现一些业务监控的需求,例如 5xx 数量,异常数量,当这些过多时希望报警给开发,开发去处理(我们的运维不管业务)。于是当时搭了 statsd, collectd, graphite,开发想添加什么报警,由运维负责采集指标,然后配置 nagios,从 graphite 取数据进行报警。这个让运维负担很大,这时就想让开发能够自己添加报警,而这一点,nagios 是不合适的:

Zabbix

在想支持开发自定义报警的目标下,我开始了新的搜寻,最后选择了 Zabbix。这个选择并没有解决自定义报警的问题,因为 Zabbix 界面也不直观,开发还是需要学一堆概念,于是后来有 Halo。但是引入 Zabbix 后,的确有很多好处,这点后面会讲,这里最想讲的是,学习 Zabbix,以及把 nagios 的报警项迁移到 Zabbix 的经历,使我对监控得到了一个系统性的理解。

要做一个监控系统,首先得采集数据,在 Zabbix,这叫 Item,采集数据的方式有很多种:调用一个 API、读 /proc 文件系统,或者解析日志,或通过 TCP 被动读进来,这个概念,在 heka (mozilla 出的数据采集和处理的系统) 里叫 input。input 负责的事情就是把数据弄进来,然后一般需要转换成一个统一的格式,对于 Item 来说,就是一个 Key,和一个 Value,Key 用来表明怎么采集,Value,则是采集得到的值。

采集到数据后要判断这个值是不是正常,这叫 Trigger,trigger 就是一个表达式,也是 bosun (stack exchange 出的报警系统) 里的 alert 定义。

值不正常了需要做一些事情,这个叫 action。最常见的 action 是发送报警短信给对应的人。这里有两个概念,一个是做什么(发送短信),另一个是发送短信给谁。

Zabbix 主要是对主机进行监控,所以有 Host 的概念,为了对 Host 进行组织,有了 Host Group,用来把 Host 分组,如果对机器的 cpu load 进行监控需要给每一台机器都添加一个报警项,那么这个报警项维护起来就会特别麻烦,例如,改一个阈值,需要改一堆报警项,但实际是,例如用来运行 MySQL 的机器,其阈值一般是一样的,这时就可以建一个 Host GroupMysql-servers,把它组织起来,然后定义 Template,应用到这个 Host Group,这样,如果需要改阈值,只需要改这个 Template 就好了。可以看出,Template 是对报警项的一种组织形式,它包含 Item + TriggerApplication 也是对 Item 的组织方式,它只体现在 Web 页面的展示上(属于一个 Application 会放在一起展示)。

有了报警需要发送给对应的人,所以必须有 User,为了对 User 进行组织,有了 User GroupUser Group 也是权限管理的单位,只能声明 User GroupHost Group 的权限。在 Zabbix,如果一个 User 对一个 Host 没有权限,那么这个 Host 触发的 Trigger 是不会发送信息给这个 User 的。

在以上理解的基础上,我是这么用 Zabbix 的:

这种配置的好处是当需要给一个人加报警时,只需要把报警挂载到它的 Host Group 所属的一台 Host 上,然后配置 ItemTrigger,报警就生效了。当然,为了维护这个系统,需要把上面的过程自动化,例如,当一个 User 入职时利用 Zabbix API 自动创建上述实体,离职时需要把对应报警项转移。

当然这个配置有个最大的缺点,是每个人对应一个 User GroupHost Group,实际上应该定义一些稳定的 User GroupHost Group。例如,MySQL-maintainerMySQL-servers。这样,当人离职时只需要把它从对应 User Group 移走,再把接盘人移入对应 User Group,就可以,整个配置不需要别的变动。

因为 Zabbix 通过 Web 配置所有事情,所以在理解了其概念后,报警项的维护负担也大大减少。有了 Zabbix API,实际上就拥有了系统报警自动化的基础。然而,时间和人手并不允许我做这些事情。

Halo

在把 Zabbix 推向开发后,发现开发反应很低迷。当初是以为 Zabbix 界面太不直观,于是转而自己开发报警系统 Halo。然而,当我做出 Halo,并推向开发之后,才发现,即使是只有一个表单,开发创建报警的情况也非常少。我现在认为,实际上我遇到的是开发和运维割裂的问题。

开发是不会自己去创建报警的。大家概念里的报警是怎么样的呢:没有这个意识。开发需要什么,他们需要自动创建好报警,运维帮忙改阈值,甚至不需要报警。通过报警,通过监控来对系统进行把握的这种意识根本没有。我当时应该做的其实不是让开发自定义报警,而是自动创建报警,来自动使这个监控起到作用,来让大家意识到监控的重要性。应该是完善指标的采集,让大家看到更多的图表。我应该做的是一个完整的默认可用的系统,而不只是一个报警系统,没有采集好指标,开发是几乎无事可做的。

接下来我会简要介绍下 Halo 的报警,然后重点介绍 Halo 成长为一个完整 APM 系统的过程。

做为报警系统的 Halo

Zabbix 页面不直观,API 不好用。我们需要有一个具有良好 API,方便开发自定义报警的报警系统,这就是做 Halo 的原因。

在了解 Zabbix 的基本概念后,就有了写报警系统的基本思路。因为主要是业务数据,采集通过 statsd 进入指标存储系统 graphite,并不需要 Zabbix Agent 这种东西来帮忙采集,所以,采集就省去了。那么报警系统就从 graphite 取数据,我的思路很简单:

整个系统对外提供 API,并提供一个 Web 界面供开发者自定义报警。只需填一个表单:

image

表单的设计受 librato 影响,整个表单读起来是一句话。

关于症状监控

Prometheus 的 Best Practices 文档,Google 的 Site Reliability Engineering 以及其他很多场合,都提到 google 的一个监控理念 symptom based monitoring,我也非常乐于布道这种优秀的理念,然而布道,在很多时候是没有用的,你需要自己去实现它,别人才会相信你认为正确的东西,在实现前,别人都是轻视的。就像大 9 神,在进入游戏时说的话,是没人听的,然而,只有当他用行动证明了自己后,别人才会尊敬他,对他说的话才听得进去。所以,在做事情的时候,需要想想如何才能让自己的想法落地,变成看得见的东西,这是比较重要的。

报警系统的演化

Halo 推出后,主要的使用方不是开发,而是我们的各种平台,例如 Redis 平台,docker 平台,haproxy 平台。它们采集指标,调用 Halo API,自动创建和删除检查项。它们的使用,促进了 Halo 的演化。

做为 APM 的 Halo

只做报警的 Halo,路也越走越窄,效果也不是特别好,开发反应不是特别热情,这时,急需拓宽我的视野,于是我花了一至两周的时间,调研了一圈业界的监控产品。这一圈,给我影响特别大的是三个产品 CATnew-relic,和 Appdynamics

这三个产品,我不时会去回顾它们,仔细地推敲这个有什么用,那么有什么用,反复理解它们提出的一个个概念,重塑我对监控的理解。通过它们,我对开发者自己都说不清楚的那些需求,监控产品应该长什么样,都开始有了概念。

做应用监控的整体思路

接口第一

做应用监控,得自顶向下。之前已经讲过,监控包括采集指标,存储指标,对指标作图,对指标报警。那么从采集指标来讲,首先需要采集的指标是技术栈最上层的指标,对于应用来说,是应用接口的指标。对于 RPC 服务,这个接口就是 RPC 方法;对于 Web 应用,这些接口就是 URL 对应的 Handler。这些接口,在 new-relic 等产品中,叫做 Transaction。接口指标只采集三个 请求量错误数响应时间

这是一切监控的起点,也是很多监控页面的入口视角。有了接口的数据,把他绘图,加上报警,就已经是一个比较好的监控起点,能起非常大的作用。

依赖第二

有了接口指标,那么在接口响应时间变慢,错误数变多的时候,自然就想知道为什么。这时有两个需要做的东西,一个是整个请求调用栈的追踪,俗称 distributed tracing,例如 Zipkin,这个可以分析出微服务环境下,接口响应时间为什么慢,根本原因在哪;第二个需要做的事情的是异常栈收集和展示。这里异常栈需要具有这种能力,能过滤出只属于这个接口的异常,并且最好能像 tracing 那样,把一个请求所关联的所有异常栈都弄到一起展示,如果异常栈能像 sentry 那样,带有丰富的本地变量的值等上下文信息,那么这个异常系统就是非常好的。

上面所说都是针对单个请求响应时间为什么慢,为什么出错,那么还需要一个统计性的东西,这个统计就是统计服务的外部依赖的 请求量响应时间错误数。外部依赖指的是跨越网络边界的调用,例如 RPCMySQLRedisHTTP。外部依赖为什么需要重视,因为这是外部环境,对于开发者来说,这是不可控的,也是故障的最重要的来源之一。要使它变得可控,那么就必须对它进行监控,采集它的指标,并展示出来。

基础设施监控

包括 MySQLRedis 等服务端采集的指标。

一体化

关于监控还有很多方面的维度,例如应用的变更,应用的报警,可用性报表,数据库报表,慢查询,应用地图等,这里就不一一讲了,这里讲一个一体化的概念。一体化是什么意思呢?简单说来,就是整个系统是一个比较完整,富有导航,互相之间有逻辑关系的系统,而不是 slow log 要去一个网站看,错误日志又要去另一个地方看,这种零散的系统对于开发是不友好的,开发记不了那么多网址,这种零碎的系统也没法发挥整体优势。我想举更多例子来说明一体化的重要性。

例如 Zabbix 的报警。必须弄到 Halo 来。不能说我要看当前报警,要在 Halo 看一遍,还要去 Zabbix 看一遍。

报警可以和接口数据进行联动。系统能够理解检查项是某接口的错误数的报警,那么就可以在报警信息里附带上到该接口错误页的链接,那么开发收到报警,就可以立刻进入这个页面,就加快了调查。

依赖和基础设施页面的联动。看到接口的依赖 MySQL 慢了,这时想查看服务端的一些指标,想查看 Slow log,那么在依赖页对应位置放一个链接,就比较直观。

接口和依赖的联动。看到这个接口慢了,很自然的想看下这个接口对应的依赖,抓几个请求看看,所以得有对应依赖页的链接,对应请求追踪的链接。

比较完备的收集各种监控数据,放进一个系统里,分为各个页面,各页面互相理解,互相链接,形成一个完整的,有逻辑性的系统,就叫做一体化。

实现

有了思路,其实实现起来并不复杂,这里要提到《谷歌和亚马逊如何做产品》,这本书非常好,我们就是按这里面讲的,从产品定义到最后运营,一步步来做。

说下团队分工。前面的 nagios 迁移 Zabbix,一直到做 Halo 报警时,只有我一个在做。做了一年半左右,那真是摸着石头过河,一步一个脚印。无经验,无见识,无视野,只有壮志凌云,孤单闯荡监控世界。然后我调研,思路清晰了一大半,在组会上提出了一体化的想法,提出要把基础设施的指标弄到 Halo 里展示,要做一个完整地一体化的系统。然后是我们老大 (dcc) 精明地安排,把指标存储从我身上分出去给团队里另一个人维护,把报警从我身上分出去给另一个人做,然后再让另一个人做基础设施,我和我们老大做应用监控。就这样,三条线,我们全组人都投入进监控上来。

于是我们开始做应用监控,我负责出线框图,采集指标,提供 API,dcc 负责写前端。我这方面的工作,如开头所说,实现并不复杂,相比于 CAT 专门写了一个系统,Zipkin 的服务依赖图也是通过 Hadoop job 来分析出来的,而我发现,接口分析和依赖分析,只需要用开源技术 statsd 就可以实现,利用之前我写的 Zipkin 客户端 tracing,用 Monkey patch (我们应用程序 Python 为主)捕获 RPCMySQLRedis 等外部请求和接口请求,就可以统计出接口的三个指标和依赖的三个指标,并且维护好当前接口是什么,就可以统计出这个接口下发生的外部请求的请求量响应时间错误数。指标打到 statsd,trace 打到 zipkin,这样,就用一套客户端,采集了指标和 trace 数据。

这个第一版,我们从提出到上线,做了一个月。上线后,开发者反应良好,从 GA 看,PV 等数据健康稳定,故障能在更早地时间发现,能比之前更快地查到原因,算是取得了初步地成功。