前言-苦恼的春节假期
庞大复杂的智能外送系统
又到春节假期了,小张却不能安稳的度假,他作为智能外送系统的运维工程师,需要对一年一度最高流量的春节,进行系统扩缩容。
智能外送系统是即时配送业务承接平台,经过十多年的发展和沉淀,如今具有强大的实时订单处理和高效的资源调度能力,确保了在高峰期的大规模订单流量下依然能够稳健运行。
另一方面,庞大复杂的系统也给小张带来了苦恼。
• 业务量大:春节期间日均订单量上百万,午餐期间下单量每分钟近上万笔订单,核心查询接口每分钟数万次,同时在线骑手数万人。
• 业务复杂:拥有数十个业务服务,涵盖订单、骑手、餐厅、元数据等等模块;十多种调度算法服务,包括派单算法、爆单算法等十余种算法工程;还包括上百个定时任务,各类离线脚本等,种类繁多。
• 技术栈复杂:系统汇聚了PHP、Go、C++、Python等多种技术栈,想要掌握其门槛不低;尤其是系统特有的中间件,更让人难以维护。
• 系统扩容复杂:系统拥有上千台服务器,却采用“古典”的虚机部署方式,每当想到扩缩容时,都令人头皮发麻。
• 无法应对突发流量:由于是虚机部署,当流量激增达到系统瓶颈时,只能采取限流手段,断臂求生。
• 机器资源浪费:由于外送业务的特性,系统流量流量集中在午餐期和晚餐期,其他时段尤其夜间,流量几乎没有,而机器资源却静静躺在那里,白白浪费。
“扩扩缩缩”的一天
为了最大化的利用机器资源,以及对应餐期的尖峰流量,在春节期间,小张需要每天早上午餐期到来前进行扩容,晚餐期结束后进行缩容,用人力换机器资源的利用率。
• 7点,准备扩容机器列表。
• 8点,启动物理机,运行服务检测脚本。
• 9点,更新注册中心与路由配置。
• 10点,完成服务验证,引入流量。
• 22点,回退注册中心、路由配置变更。
• 23点,下线所有服务,确认系统正常后,恢复流量,关闭物理机。
虽然流程小张已然熟稔,但仍需精准无误、分秒不差的执行。
破局之道就在其中
虽然小张的身影忙碌在“扩容-缩容”的循环中,但是他并没有迷失方向。在一次公司技术分享中,小张了解到公司架构团队提供了一套针对开发、部署、监控、告警统一的标准流程,提供了整套的解决方案。遗憾的是,目前这套解决方案里只支持java和go技术栈。
小张所在的团队,经过努力完成了大部分核心业务的翻新,从php、c++翻新为java和go技术栈。但是还有部分业务服务,由于时间和精力并没有翻新,如何解决剩余部分是系统能否蜕变的关键。
随后,运维组提供了CMDB1.6发布标准,能够针对其他语言技术栈,提供了自定义部署的方式。
这也给小张团队指明了方向,于是大伙撸起袖子开始干起来,期望把复杂的外送系统所有服务,全部做成可伸缩的架构。
终于等到你-弹性伸缩架构
制作业务服务镜像——把“大象”装进笼子
第一步,编写Dockerfile是容器化。
对PHP、Python应用而言,将代码压缩至某一目录并添加启动脚本,是镜像构建的关键。而C++服务则需要先编译,将编译后的文件添加到镜像。
YAML |
这里遇到另一个问题:像PHP服务依赖第三方开源库,如何打进镜像?
制作三方开源库基础镜像——把“食物”也丢进笼子
对于PHP服务,需要集成例如PHP-FPM和Nginx,并封装动态库如mysqli和redis.so等,需要提前制作静态镜像包。借助Kubernetes Init容器特性,在主容器启动前,事先装载这些依赖。
Init容器是一种特殊容器,在Pod内的应用容器启动之前运行,Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。 |
YAML |
在编写Kubernetes部署文件时,通过init容器的方式加载基础镜像,然后创建共享目录/app/dependencies,将基础镜像的文件COPY到共享目录,主容器即可通过共享目录的方式加载到第三方开源库文件。
到这一步,服务可以正常启动了,但是服务如何被发现呢?
自动加载配置文件——“大象”的使用手册
关于如何加载配置文件,小张团队讨论了几种方案,首先想到的Kubernetes自带的ConfigMap技术。
ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时, Pod 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。 |
但是,由于业务服务配置文件数量非常多(上百个配置文件),而ConfigMap对多文件支持并不友好,只能放弃。最后采取了“配置中心”的方案。
所谓配置中心,就是专门用于存放配置的机器,提供配置文件下载的能力。
• 在Jenkins脚本,将服务所需配置文件从git拉取并打包,发送到配置中心所在机器。
• 配置中心机器开放url提供下载。
• 容器在启动时,从配置中心下载配置文件。
下载配置脚本示例
Shell |
构建注册发现中心——让“大象”被发现
在容器启动后,自动完成IP注册是关键。通过Kubernetes的生命周期管理,可以使用postStart和preStop事件来完成容器的自动注册与销毁。
postStart:这个回调在容器被创建之后立即被执行。 preStop:在容器因 API 请求或者管理事件而被终止之前,此回调会被调用。 |
YAML |
先通过status.podIP获取当前容器启动后自动分配的IP,并定义成环境变量。在postStart和preStop事件中,通过发送http请求,携带容器IP、端口信息到自定义的代理服务agent-service,在代理服务中即可按需求完成注册与销毁。
这里还有一些问题没有解决:
Q:在postStart事件中,如果容器启动后执行注册请求失败了,怎么办?
A:为了保证容器启动后一定能够注册成功,必须对请求结果进行判断:当注册请求响应失败时,销毁容器,让容器重启。
YAML |
Q:如何保证容器的状态和注册发现中心保持一致?
A:借助euraka心跳检查机制,让容器定期向代理服务发送心跳请求,记录心跳时间;当超过阈值没有收到心跳请求时,认为容器下线,从注册中心删除该容器信息。
Q:如何兼容系统原来的注册发现模式。
A:这里先介绍下系统在虚机部署时的注册发现模式。
1. 上游请求如何命中服务
关键是NG路由层,提前配置好业务服务的IP地址,进而能够正常的转发。
2. 业务服务之间如何互相调用
每个业务服务所属虚机,同时部署了一个NA的服务,NA服务会定期请求SFNS服务,SFNS服务再去读取ETCD,获取所有业务服务的IP信息。
这里的另一个关键就是,运维小张同学,需要提前把虚机IP信息配置到NG或ETCD中,才能保证上述的服务发现。
很明显,这种方式高度依赖运维人员手工配置,当虚机资源需要扩容或缩容时,其耗时可想而知。
更加遗留的是,NA服务已经和业务服务已经高度耦合,尤其是已经融合到C++程序的底层框架,短时间无法避免。
那么我们看看容器部署方式如何兼容这种方式,同时避免人工的干预。
首先,采用架构标准推荐的janus策略层替换NG,janus策略层能够通过janus管控平台,自动获取ETCD中的业务IP信息,避免人工的配置;
其次,保留“业务服务->NA服务->SFNS服务->ETCD”这条链路,但是业务服务IP不需要人为通过web端录ETCD。
这里就得依赖前面提到的代理服务agent-service。
在容器启动后或销毁前,已经请求了代理服务agent-service,在agent-service中,将容器IP信息写入ETCD,并且key的格式保持和SFNS写入的格式一致即可。
容器日志采集到ELK——收集“大象”的轨迹
除了容器服务本身的业务,还有一个至关重要的模块就是日志采集。容器服务产生的日志文件,如何收集到ES中?
有标准的采集方案:边车容器
边车容器是与主应用容器在同一个 Pod 中运行的辅助容器。 这些容器通过提供额外的服务或功能(如日志记录、监控、安全性或数据同步)来增强或扩展主应用容器的功能, 而无需直接修改主应用代码。 |
其配置内容如下:
YAML |
配合ConfigMap配置Filebeat,确保日志文件路径正确,并将数据传输至ELK环节:
YAML |
环境资源相关配置——给“大象”一个舒适的环境
• 环境变量
为了兼容虚机部署时的环境变量,在Kubernetes的配置文档中也可以定义:
Shell |
• 资源限制
通过设置CPU和内存限制,防止单个容器占用过多资源,影响系统整体性能。例如采集日志的容器filebeat,只需要配置少量的资源即可。
当你为 Pod 中的 Container 指定了资源 request(请求) 时, kube-scheduler 就利用该信息决定将 Pod 调度到哪个节点上。当你为 Container 指定了资源 limit(限制) 时,kubelet 就可以确保运行的容器不会使用超出所设限制的资源。 cpu: 0.01:表示占用0.01核 memory: 0.05Gi:表示占用0.05G内存 |
Shell |
• 健康检查
配置健康检查,确保应用容器运行正常,自动重启失败的容器。
tcpSocket:使用 TCP 套接字的方法来检查容器的健康状况。 port: 探活的端口 initialDelaySeconds:指定容器启动后多少秒开始执行 liveness probe periodSeconds:定义执行 liveness probe 的时间间隔(以秒为单位) failureThreshold:定义连续多少次检查失败后认为容器已经失效,并需要重启 |
YAML |
后记-弹性伸缩让春节更轻松
经过团队的努力改造,智能外送系统的多个语言技术栈服务,已经实现弹性伸缩的架构,它带来的好处包括:
• 资源优化:动态调整资源分配,提升利用率,降低成本。尤其是外送业务,有明显的流量峰谷期,弹性伸缩的优势明显。
• 应对突发流量:自动扩展容量满足高峰需求,避免系统过载。
• 自动化管理:自动化部署,减少人工干预和运维压力。
• 快速响应需求:缩短部署和响应时间,加快业务创新。
• 灵活性增强:支持多种应用和技术栈,方便更新和扩展。
而对运维小张来说,在架构改造前,小张扩容需要做的事:
而现在,小张只需要一行命令即可自由扩缩,终于可以安心享受春节假期。