数数科技推出的云原生弹性集群方案——TE 云原生方案,通过架构的演进实现资源的按需伸缩,增强架构的扩展性,在提高查询效率的同时,平衡服务器成本。
云原生解决方案的业务价值
TE 系统云原生解决方案在业务上实现了资源隔离、多业务并行互不影响,以及基于更多业务和个性化需求的可拓展性。整个运维和治理过程也大大简化,方便后续的维护和管理。
在存储方面,支持跟云厂商的对象存储如 AWS S3 打通,在完全不影响上层数据查询的情况下,将历史数据存放在云存储中。结合实际落地案例数据可知,方案可实现游戏厂商月度服务器硬件成本 40% 以上的缩减。
在计算方面,方案可以实现对云计算资源的管理,将云厂商的容器服务如 AWS EKS 集群加入整体计算资源池中,根据隔离需求动态安装对应的弹性和隔离策略。这种方式可以提供隔离的计算环境,满足不同请求类型的查询,并且根据计算复杂情况动态伸缩,既满足资源的按需分配,又能降本增效。服务的查询响应速度也提升 50% 左右。
那么,为游戏厂商们带来「降本增效」新思路的云原生解决方案的背后,到底是怎样一套架构?
设计思路
整体方案的设计是围绕着 Trino 查询引擎资源瓶颈展开的,同时还需要兼顾到服务器成本,让 Trino 的架构具备资源弹性能力,在业务高峰时动态弹性扩资源,在资源负载低时释放资源,做到动态按需、按量使用资源。
现有的部署方式是同一个节点可能会部署多个程序。同节点和跨节点的程序与程序之间又相互有依赖关系,这对于架构的扩展非常难。想要实现查询引擎的弹性能力,在架构上就需要做一些调整。这里借鉴了一些微服务架构的设计方法:单一职责原则,服务化设计,程序独立运维部署。
将 Trino 从原有的架构体系中分离出来,独立部署、维护和扩展。原有的架构通过配置切换,即可使用新的查询引擎能力。新老查询引擎可以并存,按照架构实际使用效果逐步将老的查询引擎缩减至完全下线,使 Trino 的计算资源具备弹性能力。目前有两种方式可以使架构具备弹性能力,弹性节点和弹性容器。
弹性节点
从节点维度来控制和管理计算资源,仍旧采用虚拟机部署的方式,利用云厂商的弹性能力实现按需弹性资源。
弹性容器
采用容器化部署,利用 Kubernetes HPA 的弹性伸缩能力来满足需求。
弹性节点的优势在于架构简单,部署和维护方便。可以和现有的架构风格很好的兼容和融合(即均采用虚拟的部署方式)。不足之处在于弹性的维度比较单一,只支持 CPU 或内存的指标进行弹性伸缩,且弹性效率不足,大概需要 3min30s 左右才能弹性好资源。可以作为临时过渡方案,缓解 Trino 查询高峰资源瓶颈问题。
弹性容器云服务的优势在于可以无缝对接自动化 CI/CD 工具,可以以更细粒度、更丰富的方式对资源进行调度和控制。k8s 的 HPA 也能够很方便地支持多种类型的监控数据作为弹性指标,而且弹性速度也很快,可以较好地满足计算资源弹性需求。但这在技术实现上确实存在一些挑战,Trino 查询引擎云原生化并没有成熟的解决方案。Trino 设计之初是部署在稳定的虚拟机中,对云原生的兼容和支持存在一些不足,可以作为中长期方案存在。
由于数数更多的是 ToB 业务,服务器权限归属于用户。我们通常只能够通过有限的权限和访问路径帮助排查问题和更新、维护业务系统的版本内容。部署架构的简洁性也是其中一个重要参考维度。因此,弹性能力的设计大体方向是尽可能利用云厂商的 Serverless 容器服务作为基础设施,然后根据实际业务需求进行调整和设计。
云原生的架构通常不是孤立存在的。如果 Trino 从原有的架构体系中分离出来,使用云原生的方式,就必然需要把服务治理的内容给补全(服务网关、服务编排、监控、日志和配置等),查询引擎云原生化才能真正有效运行起来。
实现落地
总体方案包括 CI/CD、容器编排、资源调度、服务治理和可观测性,以下为核心价值模块的展开介绍:
弹性容器资源
预期能通过自定义的监控数据作为弹性伸缩指标,在超过阈值时弹性资源副本,低于阈值时缩减资源数量。始终能确保资源是按需调度和分配的。
Kubernetes 的 HPA 支持 CPU 和内存维度的监控数据作为弹性指标,但是对自定义弹性指标支持不太好。而我们的实际场景是需要根据一些特定的业务监控数据(例如 : 消息队列积压数、请求处理等待时间等)来触发扩缩。
我们利用 Kubernetes API Aggregator 的特性,支持将第三方的服务注册到 Kubernetes API。这样就能实现直接通过 Kubernetes API 访问到外部服务。以外部服务作为数据源,就具备了更好的灵活性和扩展性。在具体落地时,采用 prometheus 作为监控数据源,prometheus-adapter 作为扩展服务注册到 Kubernetes API。整体逻辑架构图如下:
到此已经基本满足容器的弹性伸缩需求。但是在实践的过程中,我们发现 Trino 的大查询任务已经下发给了弹性之前的资源,新弹性的资源只能提供给后续的查询。这就出现了 Trino 冷启动的问题,对此我们使用了 HPA 和 CronHPA 相结合的方式来应对不同的场景。负载缓慢增长的场景使用 HPA 根据业务指标进行控制。而在瞬时高负载场景下使用 CronHPA,根据业务繁忙与空闲规律,提前自动调度资源。在业务高峰之前扩资源,在业务空闲时释放资源。CronHPA 极大地缩短了应用冷启动时间。
服务接入
Kubernetes 抽象了一层网络供 pod 相互访问,外部无法访问。外部想要访问 Kubernetes 通常会采用NodePort、LoadBalancer 和 Ingress 等方式。因为 k8s Serverless 云服务的特殊性,底层 Kubernetes 工作节点是封装屏蔽的,所以选择通过云服务 LoadBalancer 将所有的请求转发到 ingress-nginx-controller,再通过 ingress 配置具体的路由转发规则。
镜象仓库
在镜像仓库的设计也是经历三次改进,有很多问题也无法完全提前预判。基本上是在实际的问题和场景中一步一步迭代演进的。镜像仓库一直围绕解决的问题是保证国内和国外的客户获取镜像的过程稳定可靠。在设计时也希望能够确保镜像同步和分发的一致性和可统一管理性。
阶段一: 能提供基本的镜像分发能力
存在问题:
a) 海外获取镜像非常慢。
阶段二: 国内和海外提供镜像分发能力,稳定运行了很长一段时间
也暴露了两个问题:
a) 国内访问 DockerHub 服务网络延时和不稳定,经常造成弹性容器无法顺利启动。
b) DockerHub 自身对镜像获取频次和速率有限制,超过阈值就会服务不响应。
阶段三: 国内和海外提供稳定的镜像分发能力
a) DNS 就近解析,国内解析到国内的镜像仓库,海外解析海外的镜像仓库。
b) 镜像优先推送到国内仓库,然后由国内镜像仓库同步到海外和 DockerHub。
c) DockerHub 作为备份依然保留,在异常时迅速切换使用。
镜像加速
在触发弹性资源后,Pod 要花费很长时间才能处于 Running 状态。排查了每个耗时环节后发现,绝大部分的时间花在了 Trino 镜像获取上(大概耗时了2~7min左右)。Trino 镜像大概 1.8G 左右,在网络带宽不足时可能会消耗更多的时间。从外部来看就会发现弹性应用一直处于等待状态,用户可能需要等待很久,新弹性的应用才会处于 ready 状态,查询体验才会提升。
不同云厂商的容器服务能力有很多差异:
-
-
-
阿里云: 容器服务支持 ImageCache CRD 方式的镜像缓存。底层封装了镜像快照,Pod 在启动时会将快照内容还原到 Pod 启动所在的卷。这样就不需要从远端镜像仓库获取镜像了。
-
腾讯云: 同阿里云。
-
AWS: 使用 EKS 弹性节点方式。弹性节点比弹性容器要慢非常多,大概 2~3min 才能处于可用状态。在和 AWS 技术专家共同探讨的过程中,决定采用 Bottlerocket 的 AMI,制作快照(提前将镜像缓存到快照)。然后使用 Karpenter 管理节点的弹性伸缩和节点加载的快照。弹性节点到 Trino Pod 处于 running 状态的总耗时从 3~7min 左右优化到 1min 以内。
-
华为云: 暂时不支持镜像缓存,我们利用 CronHPA 提前调度 Pod 来应对弹性效率问题。
-
谷歌云: 使用轻量化系统 Container-Optimized OS,然后利用谷歌云 GKE 的映像流式传输能力来获取 grc.io 镜像。这样可以大大缩短弹性时间,弹性速度也优化到了 1min 以内。
-
-
节点组
如果对不同业务场景和资源使用方式,采用同一种管理方式,可能会造成资源调度的灵活性不足,扩展难和资源浪费等问题。我们在设计时抽象一层节点组来管理不同类型的资源。节点组维度配置资费模式(包年包月/按量计费/Spot 竞价实例)、资费规格、芯片架构和区域,整合了云平台中多种实例类型,根据应用的特性调度不同类型的资源。所有节点组都会被统一管理,根据业务场景、资源规模进行调整优化,在应用性能和服务器成本之间达成一个平衡。
(1) 多种实例规格型号混用
云厂商提供的资源规格非常丰富,但是要合理搭配和使用才能花更小的成本产生更大的价值。得益于 Kubernetes 强大的兼容性和适配性,节点组可以让各种架构、厂商、规格、型号和版本的资源并存,不同的业务应用调度分配适合的资源类型。
比如有一些常规应用希望使用 x86 的服务器,而另外一些查询型应用希望使用 ARM 服务器,并希望在同一套架构混合使用。通常来说,ARM 的服务器要比 x86 便宜约 10%,而另外一些对安全和性能要求严格的应用,可以独立分配适量的资源。
(2) 资费模式混合
同一套架构为不同类型和使用场景的资源配置差异化的资费模式。例如,常驻留资源使用包年包月,弹性资源使用按量计费模式。程序的不同特性也可以选择是否采用 Spot 竞价模式。
(3) 多区域调度资源
同一个区域可能会出现特定型号资源存量不足,导致无法申请到资源。Trino 云原生方案结合云服务功能打通各区域网络,将资源统一调度和使用整合到一个 Kubernetes 集群中。这样可以大幅提高资源申请成功率,确保在业务高峰时有充足的资源可供使用。
成本优化的思考
云厂商的资源使用资费模式有很多种类型(包年包月 / 按量计费 / Spot 竞价实例 / 预留实例券 / 节省计划),根据业务实际需求和场景综合使用可以节省比较多成本。
长驻留资源比较适合包年包月,弹性资源部分因为是只在高负载时按需使用,尽可能地使用按量计费模式。但是如果在按量计费模式下,每天时间超过 8h,则需要重新计算成本。相同资源规格和使用时长计算的资费成本,按量计费比包年包月要贵一些。如果业务运行实例是无状态且偶发的,中断对业务的影响可以忽略,则可以使用 Spot 竞价实例模式。竞价计费模式通常比正常的资费要便宜很多,最低可达 1 折。如果能利用好这种计费模式,节省的服务器成本将相当可观。
利用节点组概念,来区分不同的资费类型。将不同的应用调度到不同的节点组。产生的资费也是按照不同节点组的实际使用情况来计算的。多种资源模式混合使用,将最大化地优化成本结构。
后续演进的方向
目前的方案基本满足了大数据查询引擎资源弹性的诉求,但我们对于架构的优化还有以下规划:
弹性资源稳定性
Kubernetes 通过 HPA 来控制资源的弹性伸缩,如果弹性策略配置不合理,很容易造成 Pod 的资源频繁的申请和释放。这会造成一定数量的中间态 Pod Terminating,导致整体架构的不稳定性。后续我们也将提升在资源控制上的精确度,让资源调度稳定性不断提升,确保资源的生命周期管理更有序、有效、合理。
实例规格比价调度
云厂商对同一配置需求有很多规格可供选择,如:在 AWS 上符合 16C 64G 的规格就有 10 多种,每种规格资费价格都在动态变化。资源的调度是按照云厂商调度算法来分配的,可能会调度到价格最高的规格。而如果能按照价格高低的优先级调度资源,将会进一步优化成本。关于云资源比价调度,我们也会进一步去探索。
历史数据预测调度
未来,我们希望能通过监控数据智能地识别出资源使用的规律,以此作为 Pod 资源调度的依据。在高峰来临前精确地调度好资源,在低谷时预留好最低 Pod 数量。让客户查询时体验更加顺畅,也能进一步提高低谷时的资源利用率。
在架构的调研演进的过程中,肯定会遇到各种挑战和难题,保持心态的开放,加强与云厂商技术人员间的沟通,不断拓展技术的边界,就能够不断深化解决方案的成熟度和完备性。
目前,该方案已经帮助 Habby、九九互动、青瓷、点点互动等游戏厂商加速了降本增效之路。未来,我们还将不断地探索、优化 TE 系统的技术架构,为更多游戏厂商提供更专业、更高效、更便捷的数据服务。
本文内容来源:陈云
数数科技资深云原生研发工程师