如何设计一个大规模远程命令执行系统

作者简介

运小宇    百度高级研发工程师

负责基础运维组件相关开发工作,致力于运维基础设施的建设,夙兴夜寐,只为提高设备操作的便利性,一桥飞架南北,天堑变通途。

干货概览

书接前文,在上一篇文章中我们介绍了大规模命令执行的意义以及所面对的问题和困难,简单介绍了百度集群控制系统(Cluster Control System,以下简称CCS系统)通过构建 两级数据模型 四级调度模型 三级代理执行 的方式解决了这些问题,在本篇文章中,我们将续接前文,继续对CCS系统的 设计实现 进行详细剖析。

两级数据模型

设计考量

回顾前文,在面临的需求中我们提到,需要在大规模的服务器上执行命令并且能够灵活控制。为了满足这样的需求,建立数据模型时,只有执行信息是不够的,还要有 控制信息 ,如路由、并发度、暂停点等,两者组合在一起,构成了CCS系统中的数据模型。

控制信息

控制信息包括命令传递所需的“路由”信息和调度过程的控制信息,如下:

  • 目标机器 :命令执行的目标服务器列表,可以是IP,也可以是Hostname。

  • 并发程度 :分组并发执行时每组的机器数量,用于控制分组执行,避免系统升级时所有服务器同时升级造成业务中断。

  • 暂停节点 :指定 执行到第几台服务器时暂停执行,方便先操作几台服务器并确认没问题后再继续执行,若有问题也可将问题控制在小范围内。

执行信息

执行信息是指命令到达目标机器后开始执行时所必需的信息,如下:

  • 认证信息 :标示执行者是谁的信息,用来确认执行者的合法性,如不合法则拒绝执行。

  • 鉴权信息 :标示执行者所持有的权限,如果权限不够也要拒绝执行。

  • 命令信息 :真正要在目标机执行的命令,也是数据模型这个包装盒中最有价值的信息。

除去控制信息和执行信息这两个关键信息外,还有一些 辅助信息 如任务类型、任务创建/结束时间、任务超时时间等也是数据模型实际应用中的必要信息,但并非关键,不再详述。

四级调度模型

设计考量

在调度模型设计上,考虑到服务器的地域分布特点、任务调度与业务的强相关性、单机环境的复杂性以及传输过程的稳定性要求,我们将传输模型分为四层,自顶向下分别是统一接入层、分级调度层、机房汇聚层、代理执行层,分别负责全局业务接入、按业务等级的分级调度、机房内服务器的任务管理、单机层面的任务执行。同时为保障可用性,每一层均要保证 足够的冗余度 (通过无状态集群/多机热备实现)与 数据容灾性 (通过在每层设置缓存与持久层实现)。 

各层介绍

  • 统一接入层 :统一接入层的目的之一是为用户提供 一致的接入体验 ,要达到这个目的有很多种方案,目前在CCS系统中,是通过VIP的方式实现。统一接入层的目的之二是通过Quota与Block对用户 流量进行控制 ,在实现用户分账的同时,避免突发流量将CCS系统后端冲垮。 

  • 分级调度层 :分级调度层是任务调度的核心层,起到 承上启下 的作用,在通信上各节点与机房执行层建立逻辑上的Full-Mesh连接,如图1所示,使每一个节点都有能力将命令送达百度内部的每一台服务器。这里还要注意分级调度层中的分级概念,分级通过区分不同业务的重要程度,独占或混用此层的某个调度节点,而通过各节点之间互相隔离,可做到重要业务间 互不干扰 。通过设立分级调度层,在保证全网调度能力的同时,可以有效区分不同等级的业务,使业务间相互隔离,互不干扰。

图1  分级调度层与机房汇聚层的Full-Mesh连接

  • 机房汇聚层 :在网络基础设施中,有网络汇聚层的概念,起到本地流量汇聚的作用。机房汇聚层与之类似,负责本机房所有机器的状态看护、命令下发、结果收集。机房汇聚层通过 心跳消息 (由各服务器上的执行代理定时上报)与下层进行通信,心跳消息有两方面的作用,一是保活,二是命令批量下发与结果回收,业务信息借助周期性的心跳消息搭车传递,既减少了消息量,又避免了复杂的实现逻辑。通过建立机房汇聚层,将一个机房内的机器统一管理,避免了内部大量的心跳消息传播到其他机房,在保障通信可靠性的同时还可减少公网带宽占用。

图2  机房汇聚层通过心跳与执行代理层通信

  • 执行代理层 :这里是命令传输的终点,也是命令执行的起点,命令信息通过部署在服务器上的 执行客户端 (以下简称CCS-Agent)与机房汇聚之间的一次次心跳被拉取到目标机器,在通信层面上来说,命令信息到这里就结束了,但执行代理的功能不止于此,相关内容在下一节中详细介绍。

三级代理执行

设计考量

在单机执行方案的设计上,最重要的问题就是执行端的 稳定性 ,万分之一发生率的问题,部署在几十万台服务器上后也会对业务造成严重影响。为了实现这样的超稳态,我们设计了如图3所示的单机执行架构,将最重要的命令执行逻辑从CCS-Agent中分离出来,组成独立的通用执行层,在CCS-Agent中只保留认证、鉴权、备份、命令组装这些非执行逻辑。同时为了保证通用执行层各用户进程间的异常隔离,我们又将执行层分为 执行代理进程 执行端进程 用户进程 三部分,每一个执行端进程与一个用户进程对应,负责用户进程的启停控制与结果收集。 

图3  执行代理层  

详细介绍

  • CCS-Agent :在执行层面,如前所述,主要负责用户认证/用户鉴权/数据备份/命令组装等命令执行前的准备工作,在前文中有读者对认证与鉴权提出疑问,这两个功能也是CCS-Agent最主要的功能,这里着重讲一下。CCS系统有着严格的认证/鉴权机制,毕竟在线上服务器执行命令是一项高危操作。在CCS系统中,用户的权限控制不是基于Linux系统的账号系统,而是基于 身份与角色 ,如zhangsan想要在机器A上执行一条命令,当命令下发到目标机器时,CCS-Agent就会首先对zhangsan这个用户进行身份认证,确认身份的合法性,如身份合法则还要对此身份所属的角色(如RD、QA、OP等,RD与QA具有线上系统的查看权限,OP具有变更权限)进行验证,只有身份合法,角色适当,才可以执行相应的命令。

  • 通用执行层 :如前所述,通用执行层分为执行代理进程、执行端进程和用户进程,除了可以隔离用户进程异常,带来稳定性的提升外,还有一个好处—— 无损升级 。当执行代理需要升级时,可以将执行代理进程直接停止,由于此时已经执行的任务由执行端进程控制,结果可以正常回传,不受影响,已经下发到执行代理还没来得及执行的任务,在执行代理重新启动后CCS-Agent会重新下发,也不会受影响。当执行端需要升级时则更简单,可以直接升级执行端,升级完成以后,旧的执行端会继续执行到结束,新的任务则会启动新的执行端。 

异常处理

在系统运行过程中,免不了会发生各种问题,此处我们将设计与实践中遇到的一些问题以及相应解决方案拿出来与大家一起探讨。  

容量不足

自CCS系统上线以来,由容量问题引起的系统异常是最多的,当前的CCS系统特性有很多都是以前的经验与教训的总结,最突出的就是统一接入层的加入和分级调度理念的引入。

统一接入层除了给用户提供一致的体验外,更重要的一层意义是 统一分级Quota与Block ,通过设置分级流量配额和阈值,可以给不同用户不同的访问配额,在必要时还可以暂停某个用户的任务下发,避免CCS系统后端因突发流量被拖垮。

如果说统一接入层的加入消除了突发任务的影响,那么分级调度理念的引入则 保障了重要任务不受影响 。未引入分级调度之前,不同重要等级的任务混合在一起调度,曾经发生过因某一任务数据结构异常引起整个调度节点挂死的惨案。在这之后,通过对调度节点分级,对任务分级并将任务从统一接入层分级导流的方式,解决了重要任务得不到保障的问题。

网络抖动

网络抖动对CCS系统最直接的影响就是 丢消息 。在任务调度过程中,单纯的Client端拉取执行进度消息与Server端主动推送执行进度消息都有各自的弊端,前者可靠性高但时延不好控制,后者时延低但稳定性不足。为了保障CCS系统的高可用性,在通信模型的层间采用推拉结合的方式,Client端拉取信息时通过降低拉取频率降低性能消耗,此时的时延上升由Server端主动推送来弥补。

单机执行异常

在统一执行代理没有诞生的史前时代,命令单机执行过程中我们遇到过数不清的异常,典型的 三方软件Bug型 (如Python Bug导致的程序hung住), 资源耗尽型 (操作系统PID耗尽导致的新命令无法执行), 操作系统Bug型 (如内核Bug导致的服务器突然重启), 不可抗力型 (如电力故障导致的批量服务器断电等情况)等。

为了保证异常不丢任务、异常快速恢复,我们在设计中主要遵循了以下两点:

  • 备份优先 :执行端收到任务的第一步不是考虑如何执行,而是考虑如何备份,即使后续执行端出现异常,也可以用备份信息重新执行。

  • 单线程优先 :单线程优先主要是为了使执行端的逻辑尽量简化,避免复杂的多线程操作,毕竟越简单越容易保证稳定,这也是执行端使用epoll执行代理进程+多执行端进程模式的主要原因。

遵循以上原则设计和实现的统一执行层,在百度内部的所有服务器上部署,在长时间的运行中体现出了极高的稳定性。 

通过构建CCS系统,我们解决了命令在大量服务器上规模执行的问题,目前已在百度内部广泛使用。但回顾从设计到上线运行至今的用户反馈及故障处理,还有很多不完美的地方,如命令传输的时效性现在只达到了秒级,目前我们正在尝试优化,多机热备方案是否必要,我们也在着手分析,希望一段时间以后,我们可以拿出更优的方案与大家分享。

相关文章

今天我们来聊一个很基础的话题:如何执行一条命令

↓↓↓ 点击”阅读原文” 【了解更多精彩内容】