我所在的公司主要产品是一款 SaaS 化的护理管理系统,当前业务系统后台技术栈为 Rails + MySQL,前端技术栈为 jQuery + Bootstrap。产研团队近三年保持在 10 人左右,产品与技术的人员比例维持在 1:3 左右。
从 2021 年之前,基本上也是采用裸机部署,从最初的 1 台机器,扩展到 3 台机器(机器规格:4C16G),但是在业务高峰时,服务器负载过高,导致服务不可用,这时候就需要手动扩容,需要多久才能让业务恢复正常,全凭运维同学手速。这种情况下,我们开始考虑如何实现快速弹性扩缩容,最开始考虑的使用阿里云的弹性伸缩(Elastic Scaling Service),但是并不好用,就放弃了,后来开始考虑使用 Kubernetes 来实现弹性扩容。
虽然我认为我准备的很充分了,也搭建了线上真实 K8S 环境测试,但是还是遇到了很多坑,这里就简单记录一下。对我们团队来说,收益远大于投入。
由于当时的服务器和数据库资源都在阿里云青岛机房,当时阿里云青岛时不支持 VPC 的,不支持 VPC 就无法建立 K8S 集群,所有我就选择了同时迁移数据库和迁移服务到 K8S 集群,然后就出了大问题,最终折腾了 1 周,才把数据库迁移过来,并把业务运行稳定下来,还是领导替我顶住了压力,如果在大公司,我可能就被辞退了。
K8S 部署之后,访问量突增,排查下来是静态资源没有走 CDN 的缘故,之前单机部署的时候,Nginx 代理一下就好了,上到 K8S 之后,需要做一些处理,简单说就是把静态资源发布之前推到 CDN,然后再发布到 K8S,这样就可以保证静态资源走 CDN,这样就可以保证静态资源的访问速度。
用了一段时间 K8S 后,发现弹性伸缩并不理想,业务高峰依然会出现 504 之类的错误,大部分问题是数据库查询性能不佳,导致数据库压力过大,后来就开始优化业务查询性能,优化后,弹性伸缩还是不理想,结合自己的业务特性,调整伸缩阈值,同时配合定时扩缩容,基本上做到了降本增效。
在没有使用 K8S 之前,数据库用的是阿里云 RDS,服务器用的是阿里云 ECS,基本上会采取包年包月的购买方式,一般一次买 3-5 年,会有 50%-~70% 的折扣。但是调整到阿里云之后,最开始我们也打算继续这种策略,但是并不能节省成本,最终,我们保留了几台基本服务器,其他的都是使用抢占式实例,这样相对来说可以节省成本。
K8S 并不能无限扩容,通常来说,最多的制约因素是数据库连接数,由于在 K8S 上,扩缩容比较方便,很容易在不影响真实业务的情况下,测试系统的极限性能。
我们的核心业务系统还是一个 Rails 大单体架构(20 万 行代码),但是对于刚加入一周的新手同学也能平稳地完成一次部署。
我们的部署流程简单粗暴:
bash
sh deploy/push.sh #打包镜像并推送到私有仓库,并更新 deploy/*/service/web.yaml 文件中的镜像版本
# mina assets 按需执行 这一步会自动等于到一台服务器上,执行资源编译并推送到 CDN
# mina migrate 按需执行
kubectl apply -f deploy/test/service/web.yaml #更新测试环境
kubectl apply -f deploy/pro/service/web.yaml #更新正式环境(滚动更新)
虽然 Docker 提供了多层打包技术,但是我没用,我选择手动分层。
基于手动分层的镜像管理,我们可以很方便快速地发布新版本,通常来说,Gemfile 文件变动的频率比较低,所以中间镜像也不会变动太频繁。
由于业务特性,每到月、年、季度初都需要生成大量的报表,前面也提到过,K8S 并不能无限扩容,核心限制主要是数据库,为了应对大量报表计算服务(计算服务也是基于 Rails 开发的,计算引擎使用的是 ClickHouse),月末我们会提前手动扩容数据库(MySQL 从库和 MongoDB)一般是数据库 CPU 提升到原来的 32 倍,例如原配置是 1C2G,会扩容到 32C128G。计算服务扩容采用的定时扩容。
原来需要 3~5 天才能完成的报表计算任务,基本上就可以在 3-5 小时内完成。
题外话,数据库有往存储与计算分离的发展趋势,如果计算节点可以自动弹性扩容,这里的报表服务后续也能做到自动扩缩容。
没关系,比如我这里有好几个 Rails 3 的项目,也有 Rails 4、5 的项目。都部署在 K8S 集群中,运行良好。
如果能升级 Rails 版本,尽量升级,如果升级成本太高,那就维持现状吧,后续可以考虑服务分拆,把他分解掉。
我这里没有专职的运维工程师,每个人都能做一些运维相关的工作,我相对来说了解的多一些,但主要工作还是业务开发。
容器化是趋势,需要技术线领导(CTO)的支持,也需要推动容器化的技术人员提前储备技能,比如在没有上 K8S 之前,我就拿一些小项目用 Docker Compose 练手,这样在上 K8S 的时候,就不会陌生了。
我也让小伙伴们本地尽量基于 Docker 搭建开发环境,提前熟悉容器化相关的技术。
服务器的成本我认为并没有节省,在没使用 K8S 之前,服务器的平均使用效率在 40% 左右,使用了 K8S 后,平均使用效率在 20% 左右,但是由于使用的是抢占式实例,所以成本并没有增加太多。 我使用的是阿里云的 ACK 服务,也就是 K8S 的集群是托管于阿里云的,这样就不需要自己搭建 K8S 集群了,节省了很多时间,但是成本也相应的增加了。
人力成本是节省的大头,因为不需要专职的运维工程师了,也不需要救火了,因为 K8S 会自动扩缩容,通过调节 Deployment 配置,也能很多做到蓝绿发布等,之前有 20% 的时间用于救火,现在不到 5%
还是就是建立线上测试环境也比较方便,一条命令即可。
总体来说,成本是节省了,但是不是很多。
我们的报表服务,之前是通过公网调用接口地址,现在都是在集群内部调用,不用 K8S 也能实现内部调用,只是 K8S 的方式更加方便,现在只要不是最终用户调用的服务,我都不会做成公开服务,安全性也有所提升。
容器化不同于虚拟机,虚拟机是完全隔离的,容器是共享内核的,所以性能上会有所下降,但是这个下降不会很大,我做过粗略的测试,性能下降在 5% 左右,我认为这个下降幅度是可以接受的。
首先,尽可能转成无状态服务,其次状态尽可能存在用户端、数据库、或者共享存储上。能不用有状态服务就不用有状态服务。 比如 Session,如果之前是存储在服务器磁盘上的话,可以考虑存储在浏览器端,或者存储在 Redis 上等,这样对应的服务就可以无状态化了。 还有一些业务,比如要生成图片,之前可能是生成后存储在服务器上,现在可以考虑存储在 NAS 上,或者存储在阿里云 OSS 上,数据库中只存文件的 URL 地址。
数据库服务不适合容器化。虽然相同配置的数据库,使用云数据库比自己搭建的数据库要便宜,但是云数据库无需考虑太多运维问题, 无需专职的数据库运维工程师,多付的钱其实就是运维成本。
天时、地利、人和
- 日 PV(去除静态资源访问)在 300 万以上;
- 业务高峰期扩容不及时,经常需要救火,客户经常抱怨,内部人员也经常抱怨;
- 有专职的运维工程师,或者有人学习过 K8S;
- 业务在持续上升期;
- 领导全力支持。
没必要一上来就上 K8S,先把业务做好,再考虑容器化,不要因为容器化而容器化,容器化只是为了解决问题。
容器化是趋势,K8S 是容器化集群管理的事实标准,如果你的业务适合容器化,那么 K8S 是你的选择。