<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>pengpeng (pengpeng)</title>
    <link>https://ruby-china.org/pengpeng</link>
    <description/>
    <language>en-us</language>
    <item>
      <title> Rails On K8S 使用两周年的经验与教训</title>
      <description>&lt;p&gt;我所在的公司主要产品是一款 SaaS 化的护理管理系统，当前业务系统后台技术栈为 Rails + MySQL，前端技术栈为 jQuery + Bootstrap。产研团队近三年保持在 10 人左右，产品与技术的人员比例维持在 1:3 左右。&lt;/p&gt;
&lt;h2 id="为什么选择 Kubernetes"&gt;为什么选择 Kubernetes&lt;/h2&gt;
&lt;p&gt;从 2021 年之前，基本上也是采用裸机部署，从最初的 1 台机器，扩展到 3 台机器（机器规格：4C16G），但是在业务高峰时，服务器负载过高，导致服务不可用，这时候就需要手动扩容，需要多久才能让业务恢复正常，全凭运维同学手速。这种情况下，我们开始考虑如何实现快速弹性扩缩容，最开始考虑的使用阿里云的弹性伸缩（Elastic Scaling Service），但是并不好用，就放弃了，后来开始考虑使用 Kubernetes 来实现弹性扩容。&lt;/p&gt;

&lt;p&gt;虽然我认为我准备的很充分了，也搭建了线上真实 K8S 环境测试，但是还是遇到了很多坑，这里就简单记录一下。对我们团队来说，收益远大于投入。&lt;/p&gt;
&lt;h2 id="一次只做一件事"&gt;一次只做一件事&lt;/h2&gt;
&lt;p&gt;由于当时的服务器和数据库资源都在阿里云青岛机房，当时阿里云青岛时不支持 VPC 的，不支持 VPC 就无法建立 K8S 集群，所有我就选择了同时迁移数据库和迁移服务到 K8S 集群，然后就出了大问题，最终折腾了 1 周，才把数据库迁移过来，并把业务运行稳定下来，还是领导替我顶住了压力，如果在大公司，我可能就被辞退了。&lt;/p&gt;
&lt;h2 id="静态资源 CDN"&gt;静态资源 CDN&lt;/h2&gt;
&lt;p&gt;K8S 部署之后，访问量突增，排查下来是静态资源没有走 CDN 的缘故，之前单机部署的时候，Nginx 代理一下就好了，上到 K8S 之后，需要做一些处理，简单说就是把静态资源发布之前推到 CDN，然后再发布到 K8S，这样就可以保证静态资源走 CDN，这样就可以保证静态资源的访问速度。&lt;/p&gt;
&lt;h2 id="弹性伸缩"&gt;弹性伸缩&lt;/h2&gt;
&lt;p&gt;用了一段时间 K8S 后，发现弹性伸缩并不理想，业务高峰依然会出现 504 之类的错误，大部分问题是数据库查询性能不佳，导致数据库压力过大，后来就开始优化业务查询性能，优化后，弹性伸缩还是不理想，结合自己的业务特性，调整伸缩阈值，同时配合定时扩缩容，基本上做到了降本增效。&lt;/p&gt;
&lt;h2 id="抢占式实例"&gt;抢占式实例&lt;/h2&gt;
&lt;p&gt;在没有使用 K8S 之前，数据库用的是阿里云 RDS，服务器用的是阿里云 ECS，基本上会采取包年包月的购买方式，一般一次买 3-5 年，会有 50%-~70% 的折扣。但是调整到阿里云之后，最开始我们也打算继续这种策略，但是并不能节省成本，最终，我们保留了几台基本服务器，其他的都是使用抢占式实例，这样相对来说可以节省成本。&lt;/p&gt;
&lt;h2 id="无限扩容"&gt;无限扩容&lt;/h2&gt;
&lt;p&gt;K8S 并不能无限扩容，通常来说，最多的制约因素是数据库连接数，由于在 K8S 上，扩缩容比较方便，很容易在不影响真实业务的情况下，测试系统的极限性能。&lt;/p&gt;
&lt;h2 id="新手 5 分钟完整部署"&gt;新手 5 分钟完整部署&lt;/h2&gt;
&lt;p&gt;我们的核心业务系统还是一个 Rails 大单体架构（20 万 行代码），但是对于刚加入一周的新手同学也能平稳地完成一次部署。&lt;/p&gt;

&lt;p&gt;我们的部署流程简单粗暴：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;提交代码、PR 审核通过，合并到预发布分支，再合并到主分支。&lt;/li&gt;
&lt;li&gt;如果需要更新静态资源或者执行数据库迁移，执行对应的 rake 任务。&lt;/li&gt;
&lt;li&gt;打包镜像（核心：把代码打包到镜像内）、推送镜像到私有仓库。&lt;/li&gt;
&lt;li&gt;更新对应的 Deployment(先 线上测试环境、后正式环境)，K8S 会自动拉取镜像并重启 Pod。
核心命令
&lt;code&gt;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 #更新正式环境(滚动更新)
&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="镜像管理"&gt;镜像管理&lt;/h2&gt;
&lt;p&gt;虽然 Docker 提供了多层打包技术，但是我没用，我选择手动分层。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;第一层是基础镜像，包含了 Ruby、Node 等基础环境，这个镜像是不会变的，只有在基础镜像有更新的时候才会更新。&lt;/li&gt;
&lt;li&gt;第二层是中间镜像，中间镜像是在基础镜像的基础上，执行 bundle install，这个镜像也是不会变的，只有 Gemfile 文件有变动时才需要更新&lt;/li&gt;
&lt;li&gt;第三层是最终镜像，最终镜像把代码打包到镜像内，这个镜像是会变的，每次代码有变动时都需要更新。但是由于代码通常只有几十 MB，如果 JS 没变动也不需要编译动作，所以这个镜像打包很快。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;基于手动分层的镜像管理，我们可以很方便快速地发布新版本，通常来说，Gemfile 文件变动的频率比较低，所以中间镜像也不会变动太频繁。&lt;/p&gt;
&lt;h2 id="200 倍算力的扩容"&gt;200 倍算力的扩容&lt;/h2&gt;
&lt;p&gt;由于业务特性，每到月、年、季度初都需要生成大量的报表，前面也提到过，K8S 并不能无限扩容，核心限制主要是数据库，为了应对大量报表计算服务（计算服务也是基于 Rails 开发的，计算引擎使用的是 ClickHouse），月末我们会提前手动扩容数据库（MySQL 从库和 MongoDB）一般是数据库 CPU 提升到原来的 32 倍，例如原配置是 1C2G，会扩容到 32C128G。计算服务扩容采用的定时扩容。&lt;/p&gt;

&lt;p&gt;原来需要 3~5 天才能完成的报表计算任务，基本上就可以在 3-5 小时内完成。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;题外话，数据库有往存储与计算分离的发展趋势，如果计算节点可以自动弹性扩容，这里的报表服务后续也能做到自动扩缩容。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="容器化与 Rails 版本有关系吗"&gt;容器化与 Rails 版本有关系吗&lt;/h2&gt;
&lt;p&gt;没关系，比如我这里有好几个 Rails 3 的项目，也有 Rails 4、5 的项目。都部署在 K8S 集群中，运行良好。&lt;/p&gt;

&lt;p&gt;如果能升级 Rails 版本，尽量升级，如果升级成本太高，那就维持现状吧，后续可以考虑服务分拆，把他分解掉。&lt;/p&gt;
&lt;h2 id="如何提升团队运维能力"&gt;如何提升团队运维能力&lt;/h2&gt;
&lt;p&gt;我这里没有专职的运维工程师，每个人都能做一些运维相关的工作，我相对来说了解的多一些，但主要工作还是业务开发。&lt;/p&gt;

&lt;p&gt;容器化是趋势，需要技术线领导（CTO）的支持，也需要推动容器化的技术人员提前储备技能，比如在没有上 K8S 之前，我就拿一些小项目用 Docker Compose 练手，这样在上 K8S 的时候，就不会陌生了。&lt;/p&gt;

&lt;p&gt;我也让小伙伴们本地尽量基于 Docker 搭建开发环境，提前熟悉容器化相关的技术。&lt;/p&gt;
&lt;h2 id="节省成本吗？"&gt;节省成本吗？&lt;/h2&gt;
&lt;p&gt;服务器的成本我认为并没有节省，在没使用 K8S 之前，服务器的平均使用效率在 40% 左右，使用了 K8S 后，平均使用效率在 20% 左右，但是由于使用的是抢占式实例，所以成本并没有增加太多。
我使用的是阿里云的 ACK 服务，也就是 K8S 的集群是托管于阿里云的，这样就不需要自己搭建 K8S 集群了，节省了很多时间，但是成本也相应的增加了。&lt;/p&gt;

&lt;p&gt;人力成本是节省的大头，因为不需要专职的运维工程师了，也不需要救火了，因为 K8S 会自动扩缩容，通过调节 Deployment 配置，也能很多做到蓝绿发布等，之前有 20% 的时间用于救火，现在不到 5% &lt;/p&gt;

&lt;p&gt;还是就是建立线上测试环境也比较方便，一条命令即可。&lt;/p&gt;

&lt;p&gt;总体来说，成本是节省了，但是不是很多。&lt;/p&gt;
&lt;h2 id="安全性提升"&gt;安全性提升&lt;/h2&gt;
&lt;p&gt;我们的报表服务，之前是通过公网调用接口地址，现在都是在集群内部调用，不用 K8S 也能实现内部调用，只是 K8S 的方式更加方便，现在只要不是最终用户调用的服务，我都不会做成公开服务，安全性也有所提升。&lt;/p&gt;
&lt;h2 id="容器化会导致性能下降吗"&gt;容器化会导致性能下降吗&lt;/h2&gt;
&lt;p&gt;容器化不同于虚拟机，虚拟机是完全隔离的，容器是共享内核的，所以性能上会有所下降，但是这个下降不会很大，我做过粗略的测试，性能下降在 5% 左右，我认为这个下降幅度是可以接受的。&lt;/p&gt;
&lt;h2 id="有状态服务的容器化"&gt;有状态服务的容器化&lt;/h2&gt;
&lt;p&gt;首先，尽可能转成无状态服务，其次状态尽可能存在用户端、数据库、或者共享存储上。能不用有状态服务就不用有状态服务。
比如 Session，如果之前是存储在服务器磁盘上的话，可以考虑存储在浏览器端，或者存储在 Redis 上等，这样对应的服务就可以无状态化了。
还有一些业务，比如要生成图片，之前可能是生成后存储在服务器上，现在可以考虑存储在 NAS 上，或者存储在阿里云 OSS 上，数据库中只存文件的 URL 地址。&lt;/p&gt;
&lt;h2 id="哪些服务不适合容器化"&gt;哪些服务不适合容器化&lt;/h2&gt;
&lt;p&gt;数据库服务不适合容器化。虽然相同配置的数据库，使用云数据库比自己搭建的数据库要便宜，但是云数据库无需考虑太多运维问题，
无需专职的数据库运维工程师，多付的钱其实就是运维成本。&lt;/p&gt;
&lt;h2 id="什么时候考虑使用 K8S"&gt;什么时候考虑使用 K8S&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;天时、地利、人和&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;日 PV（去除静态资源访问）在 300 万以上；&lt;/li&gt;
&lt;li&gt;业务高峰期扩容不及时，经常需要救火，客户经常抱怨，内部人员也经常抱怨；&lt;/li&gt;
&lt;li&gt;有专职的运维工程师，或者有人学习过 K8S；&lt;/li&gt;
&lt;li&gt;业务在持续上升期；&lt;/li&gt;
&lt;li&gt;领导全力支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;没必要一上来就上 K8S，先把业务做好，再考虑容器化，不要因为容器化而容器化，容器化只是为了解决问题。&lt;/p&gt;

&lt;p&gt;容器化是趋势，K8S 是容器化集群管理的事实标准，如果你的业务适合容器化，那么 K8S 是你的选择。&lt;/p&gt;

&lt;p&gt;原文地址：&lt;a href="http://note.upgradecoder.com/2023/04.html" rel="nofollow" target="_blank"&gt;http://note.upgradecoder.com/2023/04.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>pengpeng</author>
      <pubDate>Tue, 14 Feb 2023 22:57:35 +0800</pubDate>
      <link>https://ruby-china.org/topics/42880</link>
      <guid>https://ruby-china.org/topics/42880</guid>
    </item>
  </channel>
</rss>
