一、当前遇到的环境问题
初期,根据实际研发需要,LigaAI 主要应用了以下四套环境:
- Dev 环境是开发环境,专供前 / 后端开发人员进行功能开发自测、联调等;
- Sit 环境为测试环境,供测试成员进行迭代功能验收;
- Pre 环境为预发布环境,主要承担整体测试、回归测试等;
- 最后,Prod 环境为生产环境。
随着团队规模不断扩大、业务组划分走向清晰,以及微服务拆分愈发精细,环境资源开始逐步缩紧,资源紧张带来的冲突频繁制约着团队发展。
- 对迭代有风险的复杂需求需要剥离迭代,进行单独测试;
- 开发人员需要不同的 Dev 环境进行联调;
- 迭代小组的迭代进度各异,需要分批提测;
- 紧急 Hotfix 急需测试,但环境已被占用;
- 需要进行系统压测,却缺乏一套压测环境;
为缓解环境资源紧张问题,LigaAI 对原有的 Dev 环境和 Sit 环境做了如下扩展。
如此虽一定程度上满足了不断增加的环境需求,但不可避免地导致了其他问题。因此,针对以下环境需求痛点,LigaAI 着手搭建了一套高弹性、可伸缩的测试环境。
二、测试环境的改造目标
开始正式改造前,LigaAI 结合研发流程和环境需求,对目标测试环境提出了几点要求:
- 多环境:针对不同的测试场景,可以提供不同的测试环境。
- 互相区隔:数据、配置信息和应用访问在不同环境之间要相互隔离。
- 高效稳定:环境要保持稳定且高效地运转,不能影响正常迭代进度。
- 低成本:产生尽可能较低的额外成本,任何时期都要将资源用在刀刃上。
- 可复用:针对特殊需求可快速扩展新环境;简化测试环境维护,让团队聚焦研发与测试。
第一步:测试环境的标准化改造
Step 1:搭建底层基础设施
围绕弹性、扩展和部署等关键词,LigaAI 最终选用 Amazon EKS 作为环境伸缩的底层基础设施。
Amazon EKS 即 Amazon Elastic Kubernetes Service,是一项完全托管的服务。无需安装、操作或维护集群也可轻松运行 Kubernetes,而其内置的控制面板则为 LigaAI 的集群管理提供了便利。
Step 2:规范中间件资源
为方便应用接入,我们梳理了系统中需要改造或命名规范化的基础资源和中间件,包括域名命名、数据库地址、消息队列、Redis 和 Elasticsearch 等。
命名规范化可以以环境名称为统一标识,采用「资源名称_环境名称」等形式完成。
Step 3:应用改造
下一步,依据梳理好的基础资源和中间件规范,重新整理并统一测试环境配置信息,规范化应用配置和持久化数据。
应用改造目标是,只需注入环境名称,即可替换所有与环境相关的配置信息,如回调地址、消息队列、数据库信息等;而持久化的数据不应保存与环境信息相关的数据。
该阶段可能涉及到一部分代码改造,需要开发团队参与配合。
Step 4:制作环境模板
想要自动生成环境,首先必须有一套含有持久化数据、配置文件和应用包的环境模板;其中,应用包数据包括后端镜像和前端资源包。
在本次改造中,LigaAI 指定 Pre 环境作为基准环境模板来源。
Step 5:一键搭建环境
为最大程度提高产研团队效率,环境搭建必须方便快捷,且满足可复用要求。
将环境搭建流程的各个环节脚本化,并将这些脚本集成到流水线任务当中,实现环境搭建的一键生成效果;其中,将部分流程并行处理,也会加快环境生成速度。
Step 6:环境登记责任人
除一键生成环境外,制定标准化的环境管理流程也很重要。明确环境负责人,将环境生成、重置与释放流程集成到运维平台,让申请信息可视化;
同时,通过信息通知集成 IM,支持环境生成 / 释放信息、环境到期提醒等通知的及时触达。
四、标准化改造成果:环境生成自动化
至此,测试环境的标准化改造告一段落,而 LigaAI 也成功打通了环境模板生成流程和环境申请流程的数据传递通道。
左边:环境模板生成流程
发布生产后,触发模板构建任务;
通过提取 Pre 环境的数据,找出当前的镜像信息,打上 Tag 并标记为模板镜像;
再将环境模板所需要的数据库、配置文件和前端资源等数据上传到 Amazon S3 进行保存。
右边:环境申请流程
环境申请人先在运营平台登记申请,填写使用时间和使用范围;
提交申请后,触发任务:从 Amazon S3 拉取配置数据,初始化中间件资源,从镜像仓库拉取模板镜像部署应用,一键生成环境;
环境到期后会自动释放,如需继续使用,提交延长登记信息即可。
环境生成的标准化改造通过流水线任务自动搭建、环境自动释放和申请人登记,解决了环境需求的三大痛点。想要进一步解决环境资源紧缺或浪费的问题,需要继续完成测试环境的弹性伸缩改造。
第二步:测试环境的弹性伸缩改造
容器资源主要分为 Pod 级别和 Node 级别。每个 Pod 包含一个或多个应用容器,并以整体的形式在 Node 之上运行。
与资源分类对应,资源伸缩也有两种模式:
- Pod 级别的资源伸缩为 HPA,是 Kubernetes 的内置服务,主要用于副本数量的调整;
- Node 级别的资源伸缩为 Cluster Autoscaler,非 Kubernetes 内置服务,需要手动部署 Deployment 安装;
想要让服务器资源满足变化频繁的研发环境需求,则需要对 Node 级别进行弹性伸缩改造。
Amazon EC2 Auto Scaling groups(以下简称自动伸缩组)提供了弹性扩展服务器实例的功能,支持设置服务器的初始生成数量、最小数量以及最大数量限制。
当扩展策略生效时,自动伸缩组会在指定的最小和最大容量值之间调整组的所需容量,并根据需要启动或终止实例。
痛点 4:服务器资源不足时,如何扩容?
环境申请成功后,Pod 数量增加,但因节点资源饱和 / 不足,此时新的 Pod 会处于 Pending 状态。
Cluster Autoscaler 定期(默认间隔 10 秒)检测节点资源的使用情况。一旦监测到 Pending 状态的 Pod,便会触发节点扩容,调用 Cloud Provider,创建新的 Node 以满足增加的环境需求。
痛点 5:服务器资源过剩时,如何缩容?
测试完成后,环境资源释放,Pod 被删除。此时,Node 资源呈闲置状态。
当检测到一个 Node 已超过 10 分钟没有执行任何扩展操作,或资源利用率均低于 50% 时,Cluster Autoscaler 会控制自动伸缩组进行回收节点,自动将其删除;原来的 Pod 也会自动调度到其他 Node 上。
以上,Cluster Autoscaler 通过 Cloud Provider 插件,操作自动伸缩组完成服务器资源的扩展与回收,达成测试环境的弹性伸缩改造。
六、如何实现低成本,高效运行环境?
1. 中间件资源复用
云环境下,对数据库、Redis、Elasticsearch、配置中心和注册中心等中间件资源进行复用,可大幅节省使用成本和资源,降低环境扩展难度。
比如,在一个数据库实例上,以环境名称为索引标识,创建不同环境的数据库,以节省资源。
2. 合理分配资源
此外,还可以通过以下几个方面,控制云服务器上的成本支出。
第一,合理分配 CPU 和内存的 Pod 资源预留与资源限制。 资源预留(Requests)表示 Pod 需要占用的最小资源,即节点上必须有大于 Request 值的资源,才能调度这个节点;而资源限制(Limits)则表示 Pod 可使用资源的最大值,当容器占用资源超过 Limit 值时,就会被终止或重启。
CPU 是弹性资源,而大部分 IO 密集型应用在测试时对 CPU 的使用率和消耗都比较低,因此建议将 CPU 的 Requests 设置小一些,将 Limits 调大一点。
Java 应用分配内存则可以通过分析 GC 日志、应用监控信息、JVM 监控信息等,根据 GC 频率和 GC 耗时的表现,找到一个可接受的内存设置平衡点,用较小的内存满足当前的服务需要。
第二,关注节点的配置比例和大小选取。CPU 和内存的节点配置比例需要根据实际业务系统的需求确定。比例设置原则是避免 CPU 或内存成为明显的短板,常见的应用比例是 1:4 或者 1:8。
节点的选择上,一方面要避免选择配置低的小节点。因为节点上需要部署 Kubernetes 的基础组件,这会消耗固定的资源,且节点越多,整体开销和成本越大;此外,过小的节点还可能出现资源碎片的问题,所以建议适当挑选大一些的节点。
另一方面,切忌盲目追求大节点。大节点允许的 Pod 数量较多,一旦节点出现问题,影响范围也会更大。同时,大节点的自动伸缩成本更高,容易出现「杀鸡用牛刀」的情况。
第三,奉行勤俭节约原则。 一次性运行的任务或执行频率较低的周期任务,可以通过 K8S Job 和 CronJob 执行。另外,资源节约意识也表现在环境不用时及时回收、没有特别情况不随便申请环境、避免非常规环境空置等习惯之中。
3. 控制云上的成本
LigaAI 的测试环境改造采用了多项亚马逊云科技服务,因此我们也通过减少云服务商的成本费用,进一步实现低成本的高效运行。
首先,挑选合适的区域很重要。就亚马逊云服务而言,不同区域的费用标准也会有所差异。可以多多对比中国区和外国区,或宁夏区域和北京区域的相关费用标准,选择最合适的区域服务。
其次,还可以关注云服务器的不同定价方案。以 Amazon EC2 云服务为例,我们主要考虑按需实例、预留实例和竞价实例三种定价方案。
- 按需实例 / On-Demand Instances:按小时或秒(最少 60 秒)收费。
- 预留实例 / Reserved Instances:类似按年付费;相比按需实例,最高可提供 72% 折扣。
- 竞价实例 / Spot Instances:在服务器需求不高时,可出售空闲服务器资源;相比按需实例,最多可享 90% 折扣。
采用「预留实例 + 竞价实例」的组合方式,也能进一步实现经济效益最大化。
对于固定的环境,提前计算好需要的服务器数量,申请预留实例,并将其设置为自动伸缩组中的最小实例数量;再将额外的扩展环境设置为最大数量,以此实现弹性扩容。
对稳定性要求不高的环境或应用(如开发环境),可以通过 Spot 自动伸缩组进行环境扩容。
01 配置节点亲和性和污点
设置节点亲和性或污点,让对的 Pod 被调度到正确的节点上。节点亲和性使得 Pod 被吸引到一类具有特定标签的节点上,而节点污点(Taint)则相反,它使得 Pod 避免被调度到这些节点上。
通过将需要稳定运行的 Pod 固定在按需实例或预留实例的节点组上,将可容忍中断的 Pod 限制在 Spot 节点组上,也可满足成本控制的需要。
为了方便环境回收时的节点释放,按环境名称设置 Pod 亲和性,将同一环境的 Pod 尽量放置到相同的节点,减少节点中的 Pod 转移,提高节点回收率。
02 如何降低 Spot 节点中断的影响?
第一,选择多个可用区和多种实例类型。 注意,同一个节点组的实例类型的 CPU 核心数量跟内存大小必须一致,这样会有更充足的资源。
第二,设置 Pod 优先级。 对非关键 Pod 设置低优先级,可以在节点回收时,率先将重要的 Pod 转移到其它空闲资源中去;
第三,还可以在节点关闭前,先将 Pod 调度转移。具体实现路径如下:
首先,开启自动伸缩组的「容量再平衡设置」。开启后,如果 Spot 节点有中断风险,自动伸缩组就会提前发出容量再平衡通知,并重新分配一个新的 Spot 节点。
旧节点关闭前两分钟,自动伸缩组会发出终止实例的通知(需手动开启相应的通知配置)。
而在 Kubernetes 中,我们需要部署 AWS Node Termination Handler 服务,并通过它来接收事件,驱逐旧节点里的 Pod;Pod 驱逐完成后,再关闭旧的节点。
以上步骤可以基本保证 Spot 节点的相对稳定性。
4. 账单费用监控告警
最后,做好费用的监控预警也很重要。以下服务可用于监控费用明细,提供资源优化支撑,也可以对账单费用突增、预留实例覆盖率进行告警通知。
- Amazon Cost Explorer
- Amazon Budgets
- AWS Tweaker