新东方信息管理部平台架构负责人 姜江
新东方信息管理部高级运维工程师 陈博暄
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。
评论