“洋葱”系统是腾讯自研的主机入侵检测系统(HIDS),能够实时采集服务器上的各种行为并进行实时关联分析和落地存储,承载公司所有的服务器、虚拟机和容器的入侵防护、漏洞检测。洋葱作为公司的老牌安全系统,从2007年开始预研,经过几代洋葱人的不断努力,洋葱的功能不断完善、架构几经迭代,满足了公司的服务器安全需求。现在进入云计算时代,洋葱也要跟随业务拥抱云原生和DevOps。本文介绍洋葱后台系统在上云过程中的方案及实践,希望对大家的系统上云有一点帮助,涉及到的一些敏感信息将被隐去。
随着公司业务的发展,特别是云原生技术架构的演进,公司的主机资产(服务器、虚拟机、容器)在不断增加,洋葱客户端采集的数据量也在快速上涨(从2019到2020年,数据量增长了200%以上)。在未来的某个时间点,洋葱后台架构将无法适应快速增长的业务需求,可能出现各种问题(如无法快速扩缩容、版本发布流程冗长、需要人工介入等),老系统的维护将举步维艰。因此,对洋葱后台系统进行改造以适应当前不断变化的公司业务形态势在必行。
我们梳理了当前后台存在的一些问题,结合云原生技术发展的趋势,尝试去构建一套新的后台框架,全面拥抱云时代,引入DevOps提高研发效能,尽可能在较长时间内能满足业务需求。
洋葱系统分为客户端和后台两部分。客户端非本文重点,这里仅做简单说明,其主要由洋葱agent及安全插件组成,覆盖公司所有自营业务主机,负责采集主机上的操作系统数据,并通过一个与后端接入服务建立加密通信通道的网络连接将数据上传至洋葱后台进行分析。
洋葱后台则主要由接入层、实时流式审计分析、实时审计数据流压缩、工单系统、agent配置管理、路由服务、心跳服务、任务服务和文件服务等组成,负责对主机侧采集到的数据进行归档存储、提取特征及关联分析,判断主机上是否存在安全漏洞和异常行为以及对所有客户端agent进行管控。
随着公司业务服务的快速发展,洋葱客户端的数据量也是快速增长,原有后台系统架构的一些问题,也逐渐突出暴露出来,经过梳理,总结出了当前面临的一些主要矛盾和突出问题:
(1)agent通过TCP协议连接后台,数据和控制指令共用一个通道,在数据增量较大,后端负载较高的情况下,容易触发后台的负载均衡,导致agent频繁切换接入,进而影响后台对agent的控制指令触达率;
(2)当agent采集的数据量暴涨,后台容量不足时,传输的重传机制可能会引起后台超时雪崩,而服务的扩容需要运维人工参与,流程冗长;
(3)部分后台服务模块之间耦合性较高,平行扩缩容比较耗时,甚至无法平行扩容;
(4)后台开发、自测、系统测试、发布部署等流程依靠规范限制,人为因素占比较高,而且其中手工部分操作过多,效率不高,简单说就是没有实现DevOps。
对于前面章节梳理的问题,归纳起来,主要就是下面几点:性能、稳定性、研发效能。因此,我们这次改造的目标是:通过这次架构升级,提升以上几方面能力。为此,我们梳理了阻碍相关能力提升的因素:
(1) 性能提升:某些模块存在性能瓶颈、模块有状态扩容困难、模块依赖;
(2) 稳定性提升:数据和控制指令通道混用、数据波峰/波谷;
(3) 研发效能:原始作坊模式,手工操作部分过多。
摆在面前上云改造中有两种选择:
(1)直接将现有业务程序存放于容器中。这种方式优势是能快速上云,缺点是很多代码原有架构设计并未充分考虑云原生场景,仅仅是将服务用容器化封装,系统中存在的问题依旧没有得到很好的解决;
(2)将原有服务按微服务的方式进行拆分及重新设计,对之前存在瓶颈的模块尽可能做成无状态服务,以便于平行扩容。这种方式优势是上云后软件框架更易维护,如果微服务划分合理,对于开发、问题定位及后续扩展都大有裨益。缺点是工作量大,改造风险高。
经过评估,我们选择了第二种方式,主要考量是虽然这个方案前期改造大,但后期维护成本小,既能提升研发效能又能节约人力,而且业务上云是大趋势,从长远看,这个投入还是很值得的。
基于上面的分析,最终洋葱后台架构上云改造的思路:通过对服务进行无状态化改造、微服务拆分,将拆分后的服务,经由容器进行封装,通过K8S进行编排部署来实现业务上云,以提升后台整体性能。并且,利用K8S的服务编排能力,合理的配置每一个Pod的资源使用,以及实现服务扩缩容的监控触发以及自动执行,告别过去依靠运维人工参与服务扩缩容的落后的运维方式。
架构上将数据面/控制面分离、引入分布式pulsar消息队列、以及外部存储和缓存系统等提升后台稳定性,通过逻辑及配置分离提升后台安全性,通过引入DevOps流程(也感谢哈日特领导的开发安全团队,现在是DevSecOps了),提升研发效能。其中控制面包括agent配置管理、心跳、任务、文件等服务,数据面包括实时流式审计分析、实时审计数据流压缩、分布式pulsar消息队列等服务。最终确定的改造方案如下图所示:
首先,为了提升agent TCP连接及云端接入在数据量暴涨下的稳定性,我们将接入进行了拆分:agent通过两条TCP通道经由云负载均衡(Cloud Load Balancer,CLB)分别连接接入控制面及接入数据面。
关于这里进行控制面、数据面的拆分的考虑,主要是控制面涉及到后台对客户端的管控能力是整个系统强稳定的重要前提,老系统架构中,由于数据和控制指令,都是一个TCP通道进行传输,数据量的波动会较大概率的影响到agent接入的稳定性,使得后台对agent端的管控不稳定,比如版本发布、异常回滚等场景能力受限。
为了提升agent接入后台的质量,这次改造方案将接入层划分为控制面和数据面两个平面,分别封装到不同的docker中,监听不同的端口,可以在不同的机器上运行,支持平行扩容。
控制面主要处理如心跳上报、agent配置、文件上传/下载配置、路由等。控制面服务采用容器封装,服务间通过RPC调用,并且进行无状态改造,K8S支持Pod平行扩容,降低了控制数据无法正常传递到agent的问题,提升系统整体稳定性。
对于数据面,主要提供插件数据上报通道,以及实时流式审计分析,由于数据量较大,考虑到旧有传输架构预期无法支撑当前的数据增长速度,我们引入了分布式消息队列,作为数据面数据传递的媒介,起到下面的作用:
(1)起到“隔离带”的作用,隔离模块流量,避免整个后台雪崩;
(2)起到“缓冲带”的作用,协调模块处理速度,并能对临时的流量进行削峰处理。
通过此隔离带,进一步提升系统整体的数据传输稳定性及处理性能。
由于历史原因,洋葱后台的部分模块并未充分考虑过延展到云原生场景,其中比较突出的就是模块的有状态化,导致模块平行扩容存在一定难度,从而影响了服务的横向扩展及负载均衡。
云原生应用开发的几个基本特征是:服务解耦合、无状态、面向微服务、容器化封装、自动化管理。洋葱后台改造拥抱云原生,所以其改造设计也是按照云原生的原则进行改造的:
(1) 服务配置外部化管理,消除本地配置隐患和运维难度;
(2) 服务无状态改造,引入Redis等外部快速缓存,进行状态数据全局化;
(3) 引入RPC,做服务调用接口化;
(4) 服务容器化封装,提升资源利用率;
(5) K8S容器服务编排,实现容器化服务的自动扩缩容,解决容量问题。
上面的改造原则是针对所有模块改造的一个通用原则,每个模块设计时,需要考虑自身的场景进行改造。
这里可以举一个例子:比如我们文件上传下载的文件服务的无状态化改造。
老的文件服务架构如下:
老的文件服务架构,存在下面的问题:
(1) 将文件上传和下载集中到一个服务进程中,这样的设计使得这个服务相对臃肿,上传和下载这两个独立的功能也可能相互影响;
(2) 需要将待下发的文件通过文件同步或运维的方式同步到现网所有的文件服务器上去才可以实现下载。这样子带来的问题是,如果文件服务需要扩容,也需要完全同步磁盘上的文件,使得扩容相对复杂和耗时;
(3) 文件上传功能,是有状态服务,同一个agent上传同个文件,需要路由到同一台文件服务上,如果该文件服务异常或者agent切换接入,都可能使得上传链路异常,从而导致失败。
针对老的文件服务架构存在的问题,文件下载和上传服务上云改造方案架构如下:
如上图所示,上传和下载服务,做了拆分和容器化的封装,文件分片的上传和下载请求均做了RPC服务接口改造,由控制面接入服务调用,通过名字服务,随机转由任意一个downlaod/upload容器进行处理,实现了无状态的改造。主要原理是:
(1) 引入外部存储:腾讯云COS服务和CFS网络磁盘服务
在文件分片下载请求到达任意一个download容器时,服务会检查CFS是否存在待下载的文件,如果不在,会向COS发起文件拉取请求,将文件从COS服务拉取到CFS中(所有download服务将都可以从CFS中获取到该文件),然后再将文件读取到服务内存中,然后响应分片请求。
(2) 引入外部缓存服务:腾讯云Redis服务
当文件分片上传请求到达任意一个upload容器时,服务会检查Redis是否由该文件上传的状态信息,如果存在,则读取状态信息并向Reids中继续写入对应分片数据,然后继续发起下一个上传分片的请求;如果不存在,则新增写入文件上传状态信息,如此,文件的上传请求,无需固定发至某一个上传服务,实现了无状态。
(3) 将服务通过容器封装,并引入名字服务做服务发现,通过K8S进行容器服务编排,在服务负载超过设定阈值,将自动扩容新容器用于处理新请求
(4)将上传和下载服务进行拆分,并采用RPC的方式提供细粒度接口,提升服务可扩展性、可维护性以及高可用能力
为了提高研发效能,我们将整个研发-测试-发布,通过CICD的流水线方式串联起来:
需求通知到开发人员及测试人员,开发人员及测试人员对齐需求后,测试人员及开发人员可以并行开发(测试人员开发测试用例,开发人员开发相关代码),开发人员开发完成后,提交git,触发CI流程,CI流程通过go test完成自测并输出报告,其后走单元测试、代码规范检测、代码安全检测、代码覆盖率等,最后生成镜像与测试人员的测试用例一同跑通后进行集成测试,并将测试通过的镜像推送入制品库。发布至我们的待发布环境进行验证,如验证无问题,审核通过,可以出发CD部署,发布到正式环境。 这里需要说明的是这里的开发、测试、运维只是角色的划分,实际上是由研发人员独立闭环整个环节,实现了DevOps。
下面是一个CI流水线的实践样版:
流水线包括以下几个功能:
代码质量红线设置在单元测试和代码检查之后:
构建触发
构建触发监听master和dev分支,分支有以下提交事件:
代码分支dev作为开发分支,开发同学在dev分支做开发工作,当dev分支有commit事件时,需要触发流水线单元测试和代码检查。
当开发同学开发完成后,需要触发merge操作,当master分支有merge事件发生时,执行对应的操作。
Merge Request:执行完整的流水线,设置代码红线(单元测试、代码检查、接口测试、集成测试),将结果通知对应的代码评审人,并且提醒代码评审人进行代码评审。
Merge Accept:执行完整的流水线,设置代码红线(单元测试、代码检查、接口测试、集成测试),将结果通知群。
这里的性能提升,以“XXXXXX”这个运营日常功能为例进行说明。上云改造后的后台,在支撑了原来老系统10+倍的任务量的同时,成功率从原来的大约90%(有时候有波动成功率可能仅50%)提升到了稳定的99%+。
控制面服务灰度发布,验证功能完整之后,一个小时之内完成agent全量切换到新后台,期间数据上涨三倍多,触发了K8S Pod自动动态扩容,无需人工介入处理。
原有功能从开发完成到发布耗时由原有的2~3天缩短至1小时内。在保证研发代码质量的前提下,极大的提升了研发效率,同时研发同学可实现完整闭环,已具备模块从编译、构建、测试、发布整套流程的自动化,完全实现DevOps。
云原生技术正在演进,我们也将持续拥抱新技术,不仅是使用,还有适配和为云原生提供安全保障,敬请期待后续。
最后,致敬一代代的洋葱人,特别鸣谢:apple、阿波罗、宝哥、牛哥、keven、熊、职业欠钱、托马斯、驴、pink、登登、beck、kb、达达、snaker、momo、junjin……(请恕挂一漏万)