监控内容复杂化来源于以下几点,第一个是复杂的应用依赖。第二个是在高度分布式环境下,我们需要非常复杂,细颗粒度的指标才能描绘整个系统状态。
随着 Prometheus 逐渐成为云原生时代的可观测事实标准,那么今天为大家带来在微服务架构下基于 Prometheus 构建一体化监控平台的最佳实践和一些相关的思考,内容主要包括以下几个部分:
微服务、容器化技术演进的监控之痛
云原生时代,为什么是 Prometheus
阿里云 Prometheus 在微服务场景的落地实践
大规模落地实践挑战和解决方案
云原生可观测性的发展趋势和展望
一、微服务、容器化技术演进的监控之痛
1.第一个挑战:监控对象动态化
容器化部署使得我们的监控对象变得动态化。随着 K8s 这种服务编排框架大规模落地,应用部署单元从原来的主机变成一个 Pod。Pod 在每次发布时销毁重建,IP 也发生变化。在微服务体系下,我们讲究快速迭代、持续集成,这使得发布变得愈发频繁,Pod 生命周期变得非常短暂。据统计,平均每个 Pod 的生命周期只有两三天,然后就会被销毁,再去重建。而且随着 DevOps 普及,负责应用发布的角色发生变化,应用发布变得弱管控和更加敏捷,一个应用会不断的进行滚动发布,从而达成快速迭代的目标。
所以说,随着软件生产流程的变化和相关技术的成熟,我们的监控对象处于一个不断频繁变化的状态之中。
2.第二个挑战:监控层次/对象多样化
首先,Kubernetes 相关的 kube 组件以及容器层是我们必须要监控的新对象。其次,微服务拆分之后,以及行业在中间件,DB 等领域的精细化发展使我们发现依赖的 PaaS 层组件越来越多样化,对这些应用强依赖的 PaaS 组件也需要进行监控。最后就是多语言。当微服务拆分之后,每个团队都可以选择自己擅长的语言去进行应用开发。这就造成一个问题,即这些应用的监控指标需要支持各种语言的 client library 才能去生产和暴露。
3.第三个挑战:监控内容复杂化
监控内容复杂化来源于以下几点,第一个是复杂的应用依赖。第二个是在高度分布式环境下,我们需要非常复杂,细颗粒度的指标才能描绘整个系统状态。
上面这张图我们可以更直观的感受到以上挑战是如何产生的,左边是传统单体应用的部署架构,右边是微服务部署的架构。原来只需监控一个应用对象,现在变成了几十、上百个且不停的动态的发布,IP 地址不停变化。传统监控工具可能会采用静态配置的方式去发现这些监控目标。但在微服务场景下,这种方式已经无法实施。原来单体应用可能只需要依赖 MySQL 就可以了。但现在依赖的组件越来越多。传统监控工具是没有办法全面支持这种庞大的监控需求,且传统监控工具缺乏在容器层的监控能力。
为了解决上述问题,我们发现 Prometheus 或许是一个理想的解决方案。
二、云原生时代,为什么是 Prometheus
动态化:Prometheus 具有先发优势。在 Kubernetes 诞生之初,标配监控工具就是 Prometheus,天然契合 Kubernetes 的架构与技术特征,可以去自动发现监控目标。在大规模、监控目标不停变化的监控场景下,根据实践经验,主动拉取采集是一种比较好的实现方式,可以避免监控目标指标漏采,监控目标需要解决维护采集点配置以及 push 模式实现成本较大等一系列问题。其次,动态化的容器指标通过 Kubernetes 的 Kubelet/VK 组件采集,它们天然采用 Prometheus 格式生产和暴露指标数据。
多样化:因为 Kubernetes 有很多的控制面组件,比如 API server 等组件,也是天然通过 Prometheus 数据格式来暴露监控指标,这使得 Prometheus 采集这些组件的监控指标非常标准和简单。其次,Prometheus 是一个开放性社区,有 100+ 个官方或非官方 exporter 可以供大家使用。比如,你想监控数据库、消息队列、分布式存储、注册中心、网关,各种各样的 exporter 开箱即用,可以把原组件非 Prometheus 标准的数据格式转化成 Prometheus 的数据格式供采集器进行采集。再者,Prometheus 支持 go、Python、Java 等 20 多种语言,可以非常简单的为应用生成和暴露监控的 metric。最后,Prometheus 可扩展性非常强,如果上面都满足不了应用需求,它也有强大的工具可以帮助业务方轻松的写出自己的 exporter。
复杂化:Prometheus 定义了一个多维模型。多维模型可以简单理解为我可以给任何事情都打上标签,通过标签的方式来描述对象的系统状态。多维模型听起来比较简单,但很多监控工具最开始无法用这种方式去描述它的监控目标。通过多维模型,我们可以很容易刻画出整个监控目标的复杂状态,还可以刻画出应用之间的依赖关系。
其次,Prometheus 实现了一种称为 PromQL 的查询语言,非常强大,可以将复杂的指标进行过滤、聚合、计算。它内置有 20-30 种计算函数和算子,包括常见的累加,差值,平均值、最大最小值,P99,TopK 等,可以基于这些计算能力直接绘制指标视图和配置告警,这可以省去原本需要的代码开发工作,非常容易的得到想要的业务结果。
可以看到上图中一个真实的 PromQL 语句,http_request_duration_seconds_bucket 是一个 Histogram 类型的指标,它有多个 bucket,通过上面的 PromQL 语句,不用编写任何代码就可以计算出 RT 超过 500ms 和 1200ms 的请求占比,从而得出 Apdex Score,评价某个接口的服务能力。
三、Prometheus 落地实践方案
接下来,我们看一个完整的落地实践方案。其核心就是如何围绕 Prometheus 来构建可观测平台,也就是如何把描述系统状态的各个层次的指标数据都汇聚到 Prometheus 数据监控数据平台上。
之前我们几乎是不可能完成这种将各类指标进行汇聚的工作的。因为每个监控工具专注的领域不一样,数据格式不一样,工具之间的数据无法打通,即使汇聚在一起也无法产生 1+1>2 的效果。
但通过 Prometheus 可以把 IaaS、PaaS 层的各种组件的监控指标都汇聚在一起。如果需要采集一些业务指标、应用的健康状态、云上应用所依赖的云产品是否正常,Kubernetes 组件有没有在正常运行,容器的 CPU,内存水位是否符合预期,Node 节点 tcp 连接数分配有没有风险,或者需要将 tracing 转成 metric,log 转成 metric,甚至一些 CI/CD 事件想要和核心监控视图关联起来,从而快速发现到底监控视图的数据异常是不是由某次变更引起的。我们都有成熟的工具以将以上描述我们系统运行状态的指标采集到 Prometheus 平台。
另外,我们还可以把一些把无意义的计算单元,通过标记的方式标记成有意义的业务语义,然后把这些指标也汇聚到 Prometheus 上面来。
所以这里的核心思想就是打破数据边界,把所有能够真实反映我们当前运行系统状态的指标汇聚在一起,无代码编写成本,就可以将这些数据关联,生产出有效的监控视图和告警。在具体实施方面我们总结出三个层次,以便更好的实施。
第一个层次:从业务视角定义核心目标
首先,我们需要定义核心目标,这些监控的指标一定是为业务服务,指标本身并无实际意义,只是一个数值,只有确定的业务目标才能赋予这些指标生命力。
第二个层次:聚焦核心指标&提供角色视图
在制定核心目标后,我们需要确定核心目标可以被量化。需要特别注意,核心指标一定是动态变化的,因为微服务的特点就是要不停快速迭代。今天可能还没有依赖某组件,可能下个迭代就依赖了,如果你没有被依赖组件的指标,会非常痛苦。因为没有办法通过这些核心指标去完整映射核心目标到底有没有异常。另外,需要根据角色去提供视图,一个组织里不同角色关心的视图是不一样的,当出现问题时,角色视图可以帮助更快地排查问题。
第三个层次:全量收集&提前定义&提前聚合/过滤
想要实现上面两层的核心基础其实就是是全量收集,即能采集的指标一定要应采尽采。全量采集的技术基础是 metric 指标的存储成本是相对于 log 还有 trace 而言最小,即使全量采集也不会让成本膨胀太多,却能让你的核心目标度量效果即使在快速迭代的过程也不受损。
在全量采集前提下,我们要尽早去聚合或过滤掉高基数的 label。高基数问题是时序场景常遇到的问题,我们会看到采集的容器层指标带一些 Pod ID,但这种 label 是没有实际业务意义,再比如 URL path 会发散,带上了 uid 或者 order id 之类的业务 id,我们可能关心的是整个接口的健康状态,而不是某一个 path 的,这时就需要把 path 聚合一下,通过这种聚合可以减少存储成本,提升存储稳定性,也不会影响核心目标的达成。
另外,在我们采集指标时,尽量找出多层之间有关联关系的 label。比如在采集一些应用指标时,我们可以通过 Prometheus relabel 的功能把 pod name 一起采集过来,这样就可以直接建立应用和容器层的关联视图,在排查问题时通过关联分析减少 MTTD 时间。
接下来,我们讲一下如何利用 Tag 细化监控范围。Pod 本身是没有实际的业务语义的,但打上一些标签后,比如 某个 Pod 属于登录服务还是支付服务,属于生产环境还是测试、预发环境。就使得 Pod 这个计算单元有了实际业务语义。当有了业务语义之后,就可以配置出低噪音的告警。比如当我们支付成功率低于三个 9 的时候,认为核心目标已经受损了,需要马上告警出来。告警之后通过 Tag,就可以迅速定位到底是生产环境还是测试环境的问题。
如果是生产环境就要马上去处理,可以定位到底在北京哪个可用区,告警的原因到底是因为哪个服务的接口出现了异常。所以说告警的有效性也非常重要,因为我们在实践中都知道,如果告警一直是处于流量轰炸状态,告警最后就会变得没有意义。
通过 Tag,我们可以提供场景化的视图,假设老板想要去捞取生产环境下支付服务 CPU 利用率 Top5 的 Pod 列表,通过 Tag 加上 PromQL 语言,我们一条语句就可以把这个视图马上捞取出来。
讲一个真实的电商场景如何通过 Prometheus 构建统一监控平台的。从图中看到,电商系统的主体已经迁移到阿里云上,分两部分,一部分是在 Kubernetes 集群,另一部分是在 ECS 的虚机集群。每个服务依赖不同的中间件或 DB 组件,有些依赖云产品,还有一些依赖原有自建 DB 或组件。
我们可以看到使用之前的监控方案,很难实现全面的监控指标采集。应用层的同学通常只关心应用层正不正常,有哪些指标可以反映健康状态,他们可能会选择一些 APM 工具,或者其使用的开发语言相关的特定监控工具,比如使用 Spring Boot 的开发同学会通过 Actuator 监控应用状态。而负责 SRE 的同学通常会关心基础设施正不正常,怎么监控 Kubernetes 组件,容器的黄金指标水位是否正常。他们可能是不同的部门,会通过不同监控工具实现不同的监控系统,这些系统之间是割裂的,所以你去排查一个问题,我们都有一个体感,很多时候可能是网络问题,有些时候可能是某一台主机有问题,影响了应用性能,如果只看应用层,会觉得应用代码没有问题,但是有了这样一个全局的视图,就会很快排查到影响你应用的问题点到底在哪。
我们可以通过 node exporter 去采集 VM 层面 CPU、内存、 IO 黄金三指标,也可以通过云监控的 exporter,监控应用依赖的云服务健康状态。当然 Kubernetes 和容器这一层 Prometheus 提供的能力更加全面,比如 kube-state-metrics 可以监控 Kubernetes 的元信息,通过 cadvisor 可以采集容器运行时指标,还有各种 kube 组件的监控,动动手指头配置几个采集 job,或者直接用开源或者云产品,开箱即用。
另外我们团队提供了 ARMS APM,以无侵入的方式去生产,暴露应用的指标,全面监控应用健康状态。如果不能满足需求的话,你也可以使用 Prometheus 官方的多语言 client library 或者三方提供的一些 client library 很方便的去生产和暴露你的指标。还有很多官方或者三方的 exporter 可以用来监控 mysql,redis,nginx,kafka 等 DB 和中间件。除此之外,针对特定语言开发,比如 JVM,还有 JMX exporter 可以使用,查看堆内存有使用正不正常,GC 是不是在频繁的发生。
通过 Prometheus 及其生态,规范化,统一的指标可以很容易的汇聚在一起,接下来我们就可以定义 SLO。在电商系统场景下,以支付成功率为例,这是很要命的一个指标,如果你的支付成功率低了,可能今天 gaap 就会损失很大。通过 SLI 去准确衡量核心目标是否受损。比如 SLI 是应用层面的接口 error 这种指标,可能还需要关注应用运行的容器,其内存、CPU 是否在健康水位,如果超出健康水位,这可能就是预警,在接下来某段时间就会发生故障。有了这些指标,使用 Grafana 和 AlertManager,就可以轻松完成可视化和告警配置,在应用异常不符合预期时及时告警,快速定位问题范围。
如图,我们可以看到 Grafana 全面的展示了应用层,依赖的中间件、容器层、以及主机层的全量视图。
基于 Prometheus,我们还可以衍生出非常多的应用。举个例子,Kubernetes 上的网络拓扑是很难刻画出来的,但基于 epbf 技术可以采集 Kubernetes 工作负载间的各种关联关系,可以把这种关联关系转换成 metrics,结合 Prometheus 采集 Kubernetes 集群元信息指标,可以非常方便的刻画出整个网络拓扑,方便定位集群内的网络问题。我们已经提供了相关的云产品,并且由于基于 ebpf 实现,也是多语言适用且完全无侵入的。
还有一个例子和资源使用成本相关,由于云原生架构的弹性和动态化,我们很难计量各个应用消耗了多少资源,付出多少成本。但通过 Prometheus 加上自己的账单系统,定义好每个资源的计费,很容易去刻画出来一个部门和各个应用的成本视图。当应用出现资源消耗不合理时,我们还可以给出优化建议。
四、大规模落地实践挑战和解决方案
接下来我们讨论下,落地 Prometheus 有哪些技术上的挑战以及相应的解决方案,这其中包括:
多云、多租场景
规模化运维
可用性降低,MTTD 和 MTTR 时间长
大数据量、大时间跨度查询性能差
GlobalView
高基数问题
为了应对以上挑战,我们对采集和存储进行了分离。这样的好处就是采集端做的尽量轻,存储端可用性做的足够强,这样就可以支持公有云、混合云、边缘或者多元环境。在分离之后,我们针对采集、存储分别进行可用性优化,并保持与开源一致的使用方式。
接下来,可以看到部署拓扑图,采集端就是部署在业务方的集群里面,所以天然就是多租的,存储端我们用超大规模 Kubernetes 集群进行多租部署,计算存储分离,这些租户共享资源池,在容器层物理隔离,通过云盘和 NAS 存储索引和指标数值文件,可以保证一定弹性和单租户水平扩容能力,同时我们对每个租户使用的资源又有限制,避免某个租户对资源的消耗影响到其他租户。
最后为了解决多租问题,我们做了中心化的元信息管理,保证租户数据的最终一致性。在进行故障调度时,可以通过中心化的元信息管理,非常方便进行故障转移。改造之后,当 node 发生故障,在一分钟之内就可以恢复,Pod 发生故障,10s 可以恢复。
我们对采集侧进行了可用性改造,因为开源 Prometheus 是一个单体应用,单副本是无法保障高可用的。我们把采集端改造成一个多副本模型,共享同样的采集配置,根据采集量将采集目标调度到不同的副本上。当采集量发生变化或有新增的采集目标时,会计算副本的采集水位,进行动态扩容,保证采集可用性。
在存储侧,我们也做了一些可用性保障措施。在写的时候,可以根据时间线的数量来动态扩容存储节点,也可以根据索引所使用的 pagecache 有没有超限来进行扩容。在读的时候,也会根据时间线数量,还有时间点的数量进行限制,保证查询和存储节点的可用性。另外我们知道时序数据库都会做压缩,如果集中压缩的话,IO 抖动非常厉害,所以我们做了一个调度算法把节点进行分批压缩,这样就可以减少抖动。
在大数据量查询性能方面,我们可以看一个比较典型的 PromQL 查询案例。总数据量有 6 亿个时间点,600 万个时间线。如果使用开源系统进行查询,要占用 25G 的带宽,查询耗时可能是三分钟。但我们做了一些优化,比如 DAG 执行优化,简单讲就是对执行语句进行解析,如果发现有重复的子查询,就去重,然后并行化查询降低 RT。
还有算子下推,将一些算子计算逻辑从查询节点下推到存储节点实现,可以减少原始指标数据的传输,大幅度降低 RT。
针对大促场景,应用开发者或 SRE 在大促之前频繁查询大盘,执行的 PromQL 除了起止时间有些微差别,其他都是一样的,因此,我们做了场景化设计,将计算结果缓存,对超出起止时间缓存范围的部分进行增量查询。
最后通过 Gorilla 压缩算法结合流式响应,避免批量一次性的加载到内存里面进行计算。经过优化之后,对大规模、大数据量的查询性能优化到 8~10 秒,并对 70% 场景都可以提升 10 倍以上性能。
这一部分简单聊一下安全问题。云上应用是非常注重安全的,有些指标数据比较敏感,可能不希望被无关业务方抓取到。因此,我们设计了租户级别的鉴权机制,会对生成 Token 的密钥进行租户级别加密,加密流程是企业级安全的。如果出现 Token 泄露,可以收敛影响范围到租户级别,只需要受影响的租户换一下加密密钥生成新的 Token,废弃掉旧 Token 就可以消除安全风险。
除了以上部分,我们也做了一些其他技术优化,下面简单介绍一下。
高基数问题
通过预聚合,把发散指标进行收敛,在减少存储成本的同时,一定程度上缓解高基数问题。另外我们做了全局索引的优化,将时间线索引拆分到 shard 级别,当 shard 过期之后,索引也会随之删除,减少了短时间跨度查询时需要加载的时间线数量。
大时间跨度查询
实现 Downsampling,牺牲一定精度来换取查询性能与可用性。
采集能力
提升单副本采集能力,可以减少 agent 用户侧的资源消耗。
最后是阿里云 Proemtheus 监控与开源版本的对比。
在可用性方面,开源版本到了百万级时间线,内存消耗会出现暴涨,基本上是不可用的。而且因为是单副本,如果出现一些网络异常或者所在的宿主机出现问题,整个监控系统就是不可用的。
虽然开源的 Thanos、Cortex 做了一些可用性增强,但总体来讲他们并没有完全解决可用性问题。我们做了采集存储架构分离,采集存储端理论上可以无限水平扩容,可用性比较高。而且存储时长理论上也没有上限,而开源版本存储一个月指标,时间线就会膨胀得非常厉害,查询和写入基本上都是不可用的。
五、云原生可观测性的发展趋势和展望
最后聊一聊云原生可观测性的发展趋势,个人认为将来可观测性一定是标准化且由开源驱动的。现在整个软件架构体系变得越来越复杂,我们要监控的对象越来越多,场景也越来越广。封闭的单一厂商很难面面俱到的去实现全局可观测能力,需要社区生态共同参与,用开放、标准的方法来构建云原生可观测性。
我们可以看一下 metric、log、tracing 的关系,这三者在不同维度上从低到高,各有特长。
在告警有效性上来说,metric 是最有效的,因为 metric 最能真实反映系统状态,不会因为偶发抖动造成告警轰炸,告警平台完全失效的问题。但在排查问题的深度上,肯定还是需要去看 tracing 和 log。另外在单条记录存储成本上,metric 远低于 tracing 和 log。所以基于此,个人认为将来会以 metric 为接入点,再去关联 tracing 和 log,tracing 和 log 只在 metric 判定系统异常时才需要采集存储。这样就可以既保证问题抛出的有效性,又能降低资源使用成本,这样的形态是比较理想合理,符合未来发展趋势的。