口述/作者:
姜江,新东方信息管理部平台架构负责人
陈博暄,新东方信息管理部高级运维工程师
编辑:
Rancher Labs
cdn.xitu.io/2019/7/24/16c21ac9da0c6313?w=1394&h=933&f=jpeg&s=118992″>
新东方信息管理部平台架构负责人 姜江
新东方信息管理部高级运维工程师 陈博暄
2017年,新东方开始了利用容器化手段将中间件业务服务化的探索,基于Rancher 1.6使用ES;2019年,新东方再次开始了扩大了中间件的业务服务化,基于Kubernetes使用Kafka、ES和Redis。利用容器化手段将中间件服务化,有效提升了运维团队的工作效率,极大地缩短了软件开发流程。本文将分享新东方在中间件服务化上的尝试。
从幼儿园、小学、中学、大学和出国留学,新东方几乎涉及了每一个教育领域。我们的教育产品线非常长,也非常复杂。那么,这么长的教育线,我们是用怎样的IT能力进行支持的呢?——新东方云。
我们目前有16个云数据中心,包括自建、租用IDC,我们还通过云联网直接连接了阿里云和腾讯云,最终形成了一套横跨多个云提供商的混合云架构。新东方的云体系很特别,你可以在里面看到一些相对传统的部分,比如SQL Server、windows和柜面服务程序。但也有比较新潮的东西,比如TiDB、容器、微服务等等。除此之外,还能找到视频双师、互动直播等偏互联网应用的部分。一个企业的IT架构和一个企业的业务阶段是密切相关的。新东方和万千从传统业务走向互联网+的企业一样,正处于数字化转型的关键阶段。
接下来我们来谈一谈新东方的容器化之路。新东方做容器也很多年了。2016年,新东方尝试了基于Docker Swarm的一些商业方案方案,不是很理想。2017年正是容器编排架构风云变换的一年,我们选择了Rancher的Cattle引擎开始了容器化的自主建设,同时也在观望业界动向。到了2018年,新东方的容器建设再次进化,最终全面转向了K8S。
那么在新东方是怎么看待K8S的呢?我们认为K8S是介于PaaS和IaaS层之间的中间层,对下层的IaaS层和上层的PaaS层都制定了接口和规范。但是并不关心功能实现。而我们要做一套完整的容器云,只有K8S是远远不够的,还需要引入其他开源组件进行补充。
从上图可以发现,我们利用各种开源组补充K8S生态,组合成目前的新东方的容器云平台。
我们的运行时组件基于Docker,Host主机操作系统选择的是Ubuntu,K8S网络组件用的是Canal,同时采用Mellanox网卡加速技术。Rancher 2.0作为我们的k8s管理平台,提供多租户管理、可视化、权限对接AD域等重要功能,帮助我们避免了大量的后台集成整合工作,为我们节约了人力的同时提供了一套稳定的图形化管理平台。
下面介绍一下我们的K8S实践, 从上图可以看到,我们完全基于原生社区版本的K8S。通过kubeadm工具和nginx stream负载均衡部署成一个三节点HA架构。
集群关键组件运行在host网络模式。这样可以减少网络上的资源消耗,获得更好地性能,比如Ingress组件,通过Flannel构建overlay容器网络,运行上层应用。
使用容器肯定需要涉及到镜像管理。新东方是早期Harbor的用户,我们从1.2版本就开始用Harbor,后端存储对接ceph对象存储。目前,我们在尝试镜像分发的功能,使用的是阿里云开源的Dragonfly。它可以将南北向的下载流量转换为东西向,使镜像在node之间复制成为可能。当集群规模非常大的时候,减缓拉取镜像对Harbor服务器造成的压力负载。
我们的K8S集群是完全跑在物理机上的,当集群规模大了之后,物理机增多,我们必须要引用物理机的管理软件减少我们的运维成本。
在这里,我们使用的是Ubuntu的Maas,它是个裸机管理平台,可以将没有操作系统的物理机,按照模板装成指定的标准物理机。我们再结合Ansible playbook初始化物理节点,将它变成我们想要的物理机节点,加入到集群里边。
从上图中可以看到,通过加载TiDB的role把标准物理机变成TiDB的节点,通过K8S的role把标准物理机变成K8S节点,我们会在每一种role里将Osquery和Filebeat推到物理机上,它们可以收集物理机的机器信息,推送到CMDB进行资产管理。
我们的CI/CD是基于业务来区分的,有一部分我们直接对接新东方自己的Jenkins,另一部分业务直接对接Rancher Pipeline功能来实现CI/CD。
集群监控方面,我们现在用的是开源社区Prometheus的Operator。一开始我们用的是原生的Prometheus,但是原生的Prometheus在配置告警发现和告警规则上特别麻烦。
引用了Operator以后,Operator帮我们简化了配置的过程,比较好用,推荐大家使用。
值得一提的是,Rancher 2.2版本之后的集群监控都是基于Prometheus Operator,大家感兴趣的话,可以回去下一个新版本的Rancher体验一下。
我们的日志是针对两个级别来设置的。业务日志通过sidecar的方式运行filebeat,把数据收集到kafka集群里面,再由logstash消费到ES,可以减轻ES的压力负载。
另一方面是集群层面,集群层面的日志通过Rancher 2.2提供日志收集功能,用fluentd收集到ES集群当中。
我们一共有五套集群,一个是跑线上业务的,生产和测试两套;一个是Platform1集群,跑中间件类应用,比如ES、Redis、Kafka,也是分生产和测试两套;还有一个是测试集群,它是测试功能的,K8S集群升级迭代、测试新的组件、测试新的功能都是在这个集群上完成的。
可能细心的朋友发现我们的集群是1.14.1版本的,版本非常新,为什么?因为Kubernetes 1.14有一个非常重要的功能,叫local PV,目前已经GA,我们非常看中这个功能,因此将集群一路升级到1.14。
目前,业务应用主要是两个方面:
-
掌上泡泡APP以及新东方APP后端服务都跑在容器云架构上。
-
中间件的服务化,比如Kafka、Redis、ES集群级别的中间件服务都跑在我们的容器云架构上。
为什么要将中间件服务化?
那么,我们为什么要将中间件服务化呢?
在我们看来,中间件比如ES、队列、Redis缓存等都有几个共同的特点,就像图中的怪兽一样,体型很大。
我举个例子做个比较: 比如我启动一个业务的虚机,4C8G比较常见,起10个就是40C 80G。作为对比, 40C80G是否能启动一个elasticsearch的节点呢? 40C 80G启动一个es节点是很紧张的。实际生产中, 一个高吞吐的ES节点一般需要100G以上的内存。从这个例子我们就可以体会到中间件类负载的单体资源消费非常的大。
另外,中间件在项目中应用非常广,随便一个应用,肯定会使用Redis、MQ等组件。随便一个组件单独部署就要占用占多台虚机。各个项目又都希望自己能有个小灶,希望自己能独占一个环境。而小灶对资源的耗费就更多,加上中间件不可避免的各种版本、各种配置不一样,我们需要雇佣非常多的人来维护中间件,这就是一个很大的问题。
当然,如果整个公司一共也就有十来个项目的话,完全使用虚机也可以。但是新东方现在有三四百个项目,中间件消耗了相当大的资源。如果全部用虚机资源的话,成本还是很高的。
那我们怎么解决这个问题呢?我们祭出三箭:容器化、自动化、服务化。
容器化最好理解,刚才提到了杂七杂八的各种配置,通通用容器来统一,你必须按我的标准来。直接将容器部署到物理机,以获得更好的性能和弹性。
容器化的下一步是自动化,自动化更精确地说其实是代码化。就是将基础设施代码化,以代码上线迭代的方式管理基础设施。我们使用Helm和Ansible来实现代码化和自动化。
前面两步都做好之后,我们就可以进入到第三步。如果我们拿自己的管理规范和最佳实践去约束大家,可能作用并不大。最简单的办法就是服务输出,让大家来用我们的服务。
逐渐地将小灶合并成大锅饭,削峰填谷,也避免了资源的浪费。每个公司多少多有一些超级VIP的项目. 这种业务就变成了大锅饭里面单独的小灶。同样是大锅饭的机制,但我单独为你提供资源隔离、权限隔离。
在服务化之前,我们对运维人员更多的理解是项目的劳务输出。每天都很忙,但也看不到做太多成果。服务化之后,我们将劳务输出转变为对服务平台的建设,赋能一线人员,让二线人员做更有意义的事。
我们的实践:ELK/ES
接下来,我们来逐一讲解新东方各个中间件编排的方式。
Elastic公司有个产品叫做ECE,是业内第一个容器化管理ES的平台,ECE基于K8S的1.7(也可能是1.8)实现。通过容器的方式、用物理机为用户提供各个版本的ES实例。但是它也存一些局限性:只能管ES,其他的中间件管不了。
这对我们启发很大,我们就想能不能用Rancher+Docker的方式,模仿制作一个我们自己服务平台。于是诞生了我们的第一版平台,来使用Rancher 1.6管理ELK。
上图就是我们的ELK集群,它是横跨Rancher 1.6和k8s和混合体,目前处于向k8s迁移的中间状态。
ELK的编排方式我们有两个版本:UAT环境的编排和生产编排。在UAT环境我们采用rook(ceph)的方案, ES节点用Statefulset方式启动。这种方案的好处是万一哪个节点挂了,存储计算完全分离,你想漂移到哪里就可以漂移到哪里。
我们的生产环境会比较不一样,我们将每个ES节点都做成一个deployment,我们不让它漂移,用Taint和label将deployment限定在某一主机上。POD的存储不再使用RBD,直接写入本地磁盘hostpath, 网络使用host网络获取最好的性能。
如果挂了怎么办呢?挂了就等待就地复活,机器挂了就地重启,换盘或者换硬件。如果还不能复活怎么办呢?我们有个机器的管理,机器干掉,直接从池里面重新拉一台新机器出来,上线,利用ES的复制功能把数据复制过去。
大家可能会很奇怪,你们怎么搞两套方案,而且生产编排还那么丑?
我们认为简约架构才是最美架构,中间环节的组件越少,故障点也越少,也就越可靠。本地磁盘的性能要优于RBD,本地网络要优于K8s网络栈。最重要的是:我们编排的这些中间件应用,实际上全部都是分布式的(或者内置HA架构),他们都有内置的副本机制,我们完全不用考虑在K8S这一层做保护机制。
我们也实验对比过这两种方案,如果节点挂掉,本地重启的时间要远低于漂移的时间,RBD有时候还会出现漂移不过去的问题。物理节完全挂掉的几率还是很小的。所以我们最终在线上环境选择了稍微保守一点的方案。
我们的实践:Redis
我们现在的Redis主要是哨兵的方案,同样采用deployment限定到特定节点的方式编排。我们的Redis不做任何持久化,纯作为cache使用。这就会带来一个问题:假如master挂了,那么K8S就会马上重启,这个重启时间是一定小于哨兵发现它挂了的时间。它起来之后还是master,是个空空的master,随后剩下的slave中的数据也会全部丢掉,这是不可接受的。
我们先前也做了很多调研,参考携程的做法, 在容器启动的时候用Supervisord来启动Redis,即使POD中的Redis挂掉也不会马上重启POD,从而给哨兵充分的时间进行主从切换,然后再通过人工干预的方式恢复集群。
在Redis的优化上,我们为每个Redis实例绑定CPU。我们知道 Redis进程会受到CPU上下文切换或者网卡软中断的影响。因此我们在Redis实例所在node上做了一些限制,并打上了taint。我们把操作系统所需要的进程全部要绑到前N个CPU上,空出来后面的CPU用来跑Redis。在启动Redis 的时候会将进程和CPU一一对应,获得更佳的性能。
我们的实践:Kafka
众所周知,Kafka是一种高吞吐量的分布式发布订阅消息,对比其他中间件,具有吞吐量高、数据可持久化、分布式架构等特点。
那么,新东方是怎样使用Kafka的?对Kafka集群有什么特殊的要求呢?
我们按照业务应用场景来分组,会划分为三类:第一类是使用Kafka作为交易类系统的消息队列;第二类是使用Kafka作为业务日志的中间件;第三类是Kafka作为交易类系统的消息队列。
如果想满足这三类应用场景,我们的Kafka就必须满足安全要求。比如不能明文传输交易数据,所以一定要进行安全加密。
下面,我们来讲解一下Kafka原生的安全加密,我们是怎么做的?又是如何选择的?
除了金融行业以外,其他行业使用Kafka一般不会使用它们的安全协议。在不使用安全协议情况下,Kafka集群的性能非常好,但是它明显不符合新东方对Kafka集群的要求,所以我们开启了数据加密。
我们使用Kafka原生支持,通过SSL对Kafka进行信道加密,使明文传输变成密文传输,通过SASL进行用户验证,通过ACL控制它的用户权限。
我们简单看一下两种SASL用户认证的区别。SASL_PLAIN是将用户名密码以明文的方式写在jaas文件里面,然后将jaas文件以启动参数的形式加载到Kafka进程里面,这样Kafka的client端访问服务器的时候会带着jaas文件去认证,就启动了用户认证。
SASL_GASSAPI是基于Kerberos KDC网络安全协议,熟悉AD域的朋友肯定了解kerberos,AD域也用到了Kerberos网络安全协议,客户端直接请求KDC服务器和KDC服务器交互,实现用户认证。
两种方法各有利弊,最终新东方选择的是第一个SASL_PLAIN的方式,原因很简单,这样我们可以避免单独维护KDC服务,减少运维部署成本。但是这种方法有一个问题,因为Kafka用户名和密码都是通过这个进程加载进去的,你想改文件比如添加用户、修改用户密码,那你就必须重启Kafka集群。
重启Kafka集群势必对业务造成影响,这是不能接受的。因此我们采用变通的方法, 按照权限分组,总共在jaas文件里面预先设置了150个用户,管理员为项目分配不同的用户.这样就避免了增加项目重启集群的尴尬。
如上图, 我们在Kafka集群上我们开放了两个端口,一个是带用户认证并且带SSL加密的端口,另一个是没有SSL加密,只启用了用户认证的SASL_PLAIN端口。连接Kafka的客户端根据自己的需求选择端口进行访问。
说完架构,我们来说说kafka的编排。我们kafka和zk集群都通过host网络部署,数据卷通过hostpath方式落到本地物理机,以获取更好地性能。
Kafka和zk都是单个deployment部署,固定在节点上,即使出现问题我们也让他在原机器上重新启动,不让容器随意迁移。
监控方面采用exporter+Prometheus方案,运行在overlay的容器网络。
我们的实践:服务化平台
我们在做这个服务化平台时想法很简单:不要重复发明轮子,尽量利用已有技术栈,组合helm、ansible、k8s。
以kafka为例, ansible 会根据环境生成helm chart , 像ssl证书,预埋用户配置等是由ansible按照用户输入进行生成,生成的结果插入到helm chart中,随后helm根据chart创建对应实例。
以下是我们平台1.0 Demo的一些截图。
这是集群管理,部署到不同的集群上会有不同集群的入口维护它的状态。
上面展示的是申请服务的步骤。整个步骤非常简单,选中集群和想要的版本就可以了。
在这个管理界面,你可以看到你的IP、访问入口、你的实例使用的端口(端口是平台自动分配的)。如果是SSL连接,你还可以得到你的证书,可以在页面上直接下载。我们后期还会将集群的日志都接入到平台中。
我们的后台还是挺复杂的。后台使用ansible 的AWX平台。这里可以看到创建一个集群其实需要很多的输入项,只不过这些输入项我们在前台界面中就直接帮用户生成出来了。
这是部署出来的完整的Kafka集群,有Zookeeper,有Kafka,有监控用的exporter等。我们为每个集群都配置了一个kafka Manager,这是一套图形化的管理控制台,你可以直接在manager中管理kafka。
监控报警是必不可少的,我们基于Prometheus Operator做了一些预置的报警规则。比如topic是否有延迟。当集群生成后,Operator会自动发现你的端点,也就是我们刚看的exporter,operator发现端点后就开始自动加入报警,完全不需要人工接入。
我们为每个项目生成了可视化面板,需要监控的时候可以直接登录Grafana查看。
上图是简单的压测结果。512K的Message,SSL+ACL的配置五分区三副本,约为100万消息每秒, 配置是五个16C 140G内存的容器,SSD硬盘。我们发现随着Message体积变大,性能也会随之下降。
服务化平台路线展望
刚才讲了我们今年的一些工作,那么我们明年想做什么呢?
2020财年开始,新东方计划将Redis、ES这些服务也全部服务化,并将这些暴露出来的API最终整合到云门户里,给集团内的用户或者是第三方系统调用。
另外一个不得不提的就是Operator,上周Elastic又发布了一个新项目叫ECK,ECK就是ES的官方Operator。
通过Operator,你只需要简单地输入CRD,Operator就会自动生成你需要的集群。
我们认为基于Helm的方式,虽然能极大地简化Yaml的工作,但它还不是终点,我们相信这个终点是Operator。
编者按:
本文是根据新东方姜江和陈博暄在Rancher于2019年6月20日在北京举办的第三届企业容器创新大会(Enterprise Container Innovation Conference,简称ECIC)上的演讲整理而成。本届ECIC规模宏大,全天共设置了17场主题演讲,吸引了近千名容器技术爱好者参加,超过10000名观众在线观看。您可在Rancher公众号中阅读更多大会演讲实录的文章。