云原生大数据是大数据平台新一代架构和运行形态。随着字节跳动内部业务的快速增长,传统大数据运维平台的劣势开始逐渐暴露,如组件繁多,安装运维复杂,与底层环境过度耦合;对业务方来说缺少开箱即用的日志、监控、告警功能等。在此背景下,我们进行了一系列云原生大数据运维管理实践。通过云原生的方式进行运维管理,最终达到弱化业务方对状态的感知,屏蔽环境的差异,统一不同环境下的使用体验。
业务现状与背景介绍 背景介绍
字节跳动过去几年在支撑自身业务的过程中积累了很多大数据领域的引擎工具,目前也在探索将这些引擎工具的能力进行标准化、产品化的输出。在此过程中主要有以下几个难点:
随着近几年云原生概念的兴起,我们也尝试将这些工具进行云原生改造来解决以上问题。
云原生场景特性
以上这三个特性会相互促进,形成一个良性的循环。
云原生演进方向
对于上述所说的云原生化改造,主要归纳总结了以下几个大的演进方向:
架构演进 云原生大数据简介
云原生大数据主要是构建在容器上的,这里的容器可以是公有云的容器服务,也可以是私有云的容器底座,私有云的容器底座可以是开源的 K8s/基于 K8s 改造的底座,整个云原生大数据可以分为三大平台和一大支撑体系,三大平台分别是调度层、引擎层和平台层。在容器之上是资源调度层,负责统一管理调度整个集群的计算、存储和网络等资源。调度层上面的核心引擎层主要是是字节自研的统一大数据存储系统,兼容 HDFS 语义的同时支持对接标准的 S3 对象存储。存储层的上一层是 Flink、Spark 等各类字节自研或优化的计算引擎、消息中间件、日志搜索及实时分析引擎等工具。最上面即是平台服务层,负责将这些引擎能力封装整合成一个对外输出的产品。
本次介绍的运维管理平台支撑了上述的三大平台,提供日常组件运维的管理功能,为了更好地适应整个大数据云原生的改造,我们对运维管理模块也做了云原生的改进。
云原生上的运维实践
所以为了满足以上要求总结了接下来需要关注的几个方向。在环境管理方面需要我们抽象出一套统一的环境模型去适应不同的部署;另外还要有一个灵活便捷的组件管理服务统一管理组件元数据的依赖、配置等信息;最后还需要拥有功能抽象的能力,比如对常见的日志、监控、告警等功能可以通过抽象统一对上层业务屏蔽环境差异性。
环境管理与组件服务 环境管理
可以将整个环境按照功能划分成三个逻辑区域,分别是控制面、系统面和数据面,需要注意的是这三块区域只是逻辑区域的划分,并不是物理环境上的隔离。比如在一些场景下控制面可以与系统面进行合并,甚至在一些小型场景下,三个面也可以合并在一个物理集群内。
组件服务
通过对环境的区域划分对组件进行层级的划分,主要可以分成系统级、集群级、租户级和项目级。系统级负责大部分的业务管控逻辑;集群级则主要完成日志数据/监控数据 Agent 和内部自研的调度器及 Operator 等的采集工作;租户级主要用于支撑特定大用户独占的组件;最下层的项目级就是用户的作业实例、中间件实例及其他第三方工具等。通过这里的划分就把整个部署划分为了网格形式,使每个组件只需要关注自己所在的网格,很好的屏蔽了组件与环境信息的耦合。
组件服务:Helm 定制化改进
K8s 对单个资源的支持十分友好,对特定领域的操作也十分丰富。但是简单的服务也需要多个资源的配合,比如 Deployment 承载业务逻辑就需要 ConfigMap 去保存它的配置,然后又为了方便地对外暴露服务需要通过 Service 统一访问入口,但是这里的资源协调在 K8s 中并没有提供很好的工具。在开源的解决方案中很多开源组件基本上都提供了迁移 K8s 的 Helm Chart,但为了更好地融入开源的生态体系,我们也基于 Helm 构建了自己的组件服务。
由于开源 Helm 命令行工具并不适用于云原生场景下组件间的 API 调用,所以我们对开源 Helm 进行了深度服务化定制,在常见的部署、卸载、升级、回滚等需求中通过 API 的方式进行对外暴露,并增加可视化界面,同时还支持了一些深度的仿真部署,让用户可以快速的进行部署、验证、调试等工作,也对层级配置做了精细的划分使组件在部署时可以进行合并覆盖,另外在组件部署时配置了对资源的动态修改,通过以上措施使上层的业务组件可以更加关注在自己的业务领域。
磁盘管理
原生的 K8s 对无状态的负载支持是十分友好的,但对有状态的负载支持就有点差强人意,主要体现在本地磁盘的使用上,我们分析总结了以下痛点:
统一调度
为此我们开发了一套统一的 CSI(容器存储接口)来用于管理,不仅能够统一采集集群的所有磁盘信息,也可以进行统一管理。在此基础上我们将整个磁盘的使用场景分成了三类,分别是共享容量卷、共享磁盘卷和独占磁盘卷。
共享容量卷即容量是共享的,这类场景对 IO 不敏感,也不需要很强的空间容量的限制,但对于灵活性要求更高,比如典型的大数据作业的临时数据存储、日志等;
共享磁盘卷对 IO 也不是很敏感,但对隔离性、持久化有一定的需求,需要在出现故障时能够找回,但是找不回的情况也不会产生灾难性的后果,其中最典型的场景就是缓存;
独占磁盘卷需要高度的 IO 隔离特性,典型的场景如消息中间件 Kafka、HDFS 等。
磁盘管理概览
在磁盘管理中将其分成两大块区域,第一块区域是 K8s 维护的,比如常用的 EmptyDir,这个部分推荐用来存储配置数据或者少量的临时性数据。
剩下的区域就是上文提到的通过 CSI 进行统一管理,主要细化成了三块区域,分别对应的前面提到的三个存储卷,共享容量卷基于简单的本地路径的方式进行支持;对于共享磁盘卷会先会把所有的磁盘组装组合成一个 Volume Group,当业务组件申请共享磁盘卷时可以创建一个逻辑卷使用,从而达到隔离的效果。
独占磁盘卷就是拥有整块磁盘,然后通过统一的 CSI 抽象成一系列的 Storage Class,上层的业务组件可以根据自己的需求申请对应的存储卷。如果是公有云云盘或者有中心化存储的场景下,仍然推荐通过这套 CSI 给业务提供各类的存储卷,以达到容量管控的同时也可以通过这个 CSI 将磁盘信息与组件解耦。
统一的日志监控告警 日志
日志也是产生可移植性困难较大的一个因素,为此我们也做了统一的日志采集的链路管理,以达到业务隔离、高效采集、公平分配、安全可靠。
对于日志采集目前支持两种方式,一种是侵入式采集,即提供各种 Collector,主要支持 Java 、Python 两种方式,由于这种方式具有侵入性,大部分组件习惯使用基于文件的采集,因此我们也通过 Filebeat 支持文件方式的采集。采集后汇聚到本集群的日志代理上进行流量管控,后续再汇聚到统一的中心化存储中用统一的 API 支撑日志搜索场景。也对定制化引擎提供了针对性的 API使用户可以根据具体场景使用对应的 API。
第二种是 Filebeat 采集,在容器场景下是一个基于文件的采集,和基于物理机的采集不同,主要区别在容器视角,日志存储路径与实际的物理存储路径也是不一样的。为解决这个问题首先通过定制的日志规则 CRD 声明自己的采集规则,然后再通过组件部署服务,随着整个组件的创建、更新,Filebeat 的 Discovery 机制可以动态地发现日志规则的 CRD 创建、变更或删除,再通过 Filebeat 热加载的机制生成、加载成自己的日志采集规则。
在 Filebeat 的部署形态中如果能够感知到集群的节点信息并拥有对应的权限,那么就可以部署成 DeamonSet 的方式,使整体资源占比更低,集群是共用一套 Filebeat 进行数据采集。由于在公共云的容器服务场景下是感知不到具体的节点信息的,也没有部署 DeamonSet 的权限,所以这里支持通过 Sidecar 的方式注入到具体的一个个 Pod 中负责日志采集,通过这种方式我们就统一了在容器里基于文件的日志采集。
日志数据链路
在云原生的场景下,日志采集远远不只是统一采集链路,而是要用尽可能低的资源消耗支撑日志的高效采集需求。因为云原生场景天然地面向多租户,那么租户与租户间,组件与组件间的流量差异会很大,不能因为单个租户不正常的流量对整个日志采集造成扰动。所以我们在每个集群内部的日志代理中,会针对租户做流量管控,当发现大流量异常的时采取限流或者熔断措施。同时也要保障多租户场景下的公平分配、日志采集的故障转移、云原生场景下 Pod 重建/主动升级等,这几个部分都是后续将主要投入的大方向。
告警
告警体系整体是基于开源的夜莺改造而成的,在开源夜莺的概览中包括存储指标数据的 Prometheus、存储告警业务数据的数据库及核心组件: WebApi 和 Server。 WebApi 用于承担用户的交互,比如规则的增删改查及执行指标查询等。Server 负责加载规则、生成告警事件、发送告警通知等。在开源夜莺中,Server 还承担着 Prometheus 的 PushGateway 职责,字节的产品有自己的用户体系和监控系统,所以对告警方面的定制主要集中在 WebApi 和 Server 上。
流程概览
用户首先通过 WebAPI 生成自己的告警规则,并持久化到数据库中, Server 再加载规则到自己的内存中,通过一致性哈希环决定处理哪些规则并转换成指标查询判断是否有告警事件产生。当有告警事件产生时会调用对应的控制模块发送告警通知,将告警事件回填到的数据库中,主要优化体现在以下方面:
动态消息模板
通知模块
在通知模块中通过 Server 生成告警事件,再由前面的消息模板渲染得到一个真实的告警消息,然后将这个告警消息发送给通知模块,由通知模块结合通知方式及对象生成通知记录并放到队列中。为了更好地适应各种环境,这里的队列可以是真实的消息队列,也可以是通过数据库模拟的消息队列。最后由若干个 Worker 并发消费信息调用不同的发送插件发送消息;Worker 之外还有一些定时的线程轮询/巡检整体发送的状态对发送失败的消息进行重试,当重试次数达到一定量的时候生成运维上的告警。
开源夜莺系统还有一个比较大的特性是动态阈值,在整体的使用中就是事件发生、触发告警、然后人工反馈到训练分析形成循环监督学习的过程,并不断调整动态阈值的生成规则。
监控
在整体的监控架构概览中可以看到上层就是前面所说的数据面,数据面中的每个集群都会有一个Prometheus用来采集、汇聚本物理集群所有组件的监控数据,这里的 Prometheus 本质是一个 Agent,不会承担任何数据存储、查询等职责,最后由 Prometheus 把采集到的监控数据 Remote Write 到系统面的监控系统中。
监控系统用来保存所有的监控数据。为了方便对数据存储进行水平伸缩也做了一层抽象,背后的真正实现可以是公有云上现有的云监控服务、也可以是 S3 的对象存储服务,还可以是我们自研的大数据存储服务,在部分私有云的场景下,也可以对接用户自己自定义的存储服务。在本层的统一存储中通过加一个 Query 服务承担所有监控数据的查询服务,并形成了可视化的大盘展示及前端交互。
对于查询服务也做了一些针对性的优化。比如监控数据只有新增、没有更新,频繁对某个时间段的监控数据进行反复检索的场景引入了水平拆分的能力,通过将时间跨度大的查询拆分成多个小跨度的子查询并发执行,然后聚合回忆查询速度。另外由于监控数据本身不可变,我们引入了缓存,可以对部分查询出的数据做 一个缓存加上查询场景方便预测。通过以上两个优化相互促进提高了整体的查询效率。
经过整体优化后的监控系统核心优势是可以支持各种环境下的一键指标采集;在性能优化上支持了数据的预聚合、降采样等能力,丰富了整体的功能体系;并且对接了大数据存储,某种程度上可以具备存算分离的特性使得整体系统的水平伸缩能力得到了很大的增强;最后也把监控和其它的运维工具,比如日志、告警、链路追踪等功能做了深度整合,优化了产品的整体使用体验。
火山引擎云原生计算产品全景图