作者 | 刘燕,核子可乐
我们成功了,找到了一条适合自己的迁移道路”。
1 37signals 的“去云”和“去 K8s”行动
根据 37signals 公布的一组数据显示,2022 年,37signals 在所有 AWS 云服务上总共花费了 3,201,564 美元,这相当于每月 266,797 美元。
这一巨额支出令 37signals 决定,通过“去云”的方式大幅削减费用。
2022 年 10 月,37signals 公司首席技术官兼 Ruby On Rails 之父 David Heinemeier Hansson 发文称,37Signals 要“下云”。
对 37signals 运营团队来说,2023 年里最重要的一项举措就是摆脱云依赖,着手将应用程序栈迁移回本地数据中心的自有硬件之上。37signals 还决定放弃目前业内主流的容器编排系统 K8s,也没有选择 Docker Swarm,转而寻求性价比更高的替代方案。“去云 + 去 K8s”,并不是一个简单的决定,这意味着要舍弃行业的主流方案和基准,走一条没人走过的道路。
但令 37signals 感到惊喜地是,这项行动在短时间内取得了惊人进展。
近日,37signals 在博客上讲述了这一决定背后的考量,实施历程和成效。以下是是博客原文,由 InfoQ 翻译。
为什么要折腾?
多年以来,我们的很多应用程序都运行在不同云服务商的平台之上。
最初是把应用程序从自有数据中心迁往 AWS ECS,毕竟听说这样能直接使用 Docker build 并节约最终成本。
我们对 Docker 没有任何意见,但 ECS 本身的灵活性确实不够。所以我们决定转向 Google Cloud 的 GKE 来尝试 Kubernetes,但由于网络控制平面宕机,我们决定马上放弃。但请别误会,我们的云之旅并没有就此终结,反而把遗留应用程序迁移到了 AWS Kubernetes(EKS),一部分应用程序至今仍然运行在那里。
但在这样的过程中,我们不可避免地要积累下一定的技术债务和复杂性。除了部署策略之外,我们还需要发明新的工具来管理这些堆栈,并创建贴合需求的 CI/CD 来支撑运营和编程。还有监控策略,这些都是需要费心的工作。
再就是怎样把信息安全和运营安全这两种完全不同的范式融合起来,比如对员工做安全培训。反正云服务用起来就那个样子,一会是某项服务出问题了,一会是整个区域崩溃了……
总之,运营这样一套体系会涉及到大量流程。我们发现实际支出往往超过我们从中获得的回报。这种价值倒挂不仅体现在经济上,更体现在运营上。
下面来看我们的最小应用程序 Tadalist 在 EKS 上的运行情况:
看起来就很乱,对吧?
而且这还只是粗略的基础设施结构,不涉及运行其上的其他辅助工具,例如:
  • 集群自动缩放器
  • 入口控制器
  • 存储驱动
  • 外部 DNS
  • 节点终止处理程序
  • VPN、对等互连、路由表、NAT 等复杂概念DNS 处理位置
这里还没提到资源身份与访问管理等其他需要维护的元素,更不用说由此衍生出的基础设施即代码了。
公平地讲,纵观整个上云经历,我们一直也没体会到“云让生活更简单”的承诺。
而 Tadalist 还只是个非常基础、非常独立的 Rails 应用核心。其他体量更大的应用程序会对后端服务有着更多依赖性,所以架构复杂程度可想而知。
我们去云化的第一步,是打算配合厂商支持在自有硬件上运行 Kubernetes。这样就能保住多年以来投入并建设的资产,“单纯”把我们的工具重新部署在新位置。
另一个挑战是,我们的大部分应用程序在几年之前就完成了容器化,为了继续兼容遗留资产,我们希望保持这种状态。
这主意听起来全是好处,但实际操作起来却昂贵且极为复杂。所以我们还是得回到规划中来。
迁移 Tadalist
mrsk 成为我们这波去云加去 K8s 行动的核心范式:
它能够创建一条路径,帮助我们简化对现有容器化应用的部署,同时极大加快整个操作过程。我们既能保留大量现有容器化技术,又能以相当熟悉的全新方式运行应用程序。
多年以来,我们一直在数据中心内用 capistrano 部署。
Basecamp 4 和其他应用,而 mrsk 指向的也是相同的命令式模型。不必新增控制平面、活动部件还能有所减少,这太棒了!
Tadalist 无疑是个完美的迁移样本。它在所有应用程序中的重要度最低,而且没有任何付费客户。事实上,Tadalist 就像是为运营系统报警的“金丝雀”,而这一次它将再次扮演这个角色。
当然,迁移不可能一蹴而就。在开始之前,我们还得做好其他准备。
配置虚拟机
近年来,我们一直关注云及相关技术,所以我们的本地基础设施配置流程已经有点落后。除了去云工作之外,我们还希望全面实现配置管理的现代化与精简,并升级至最新版 Chef。
由于 mrsk 将以本地服务器为目标,我们还需要建立新流程来快速、轻松配置虚拟机
结合现有知识,我们决定运行基于 KVM 的虚拟机访客,使用 cloud-init 简化引导,并将这一切都编排进同一套架构。这些改进让访客虚拟机的完全引导时间从之前约 20 分钟缩短到了不足 1 分钟——现在我们还可以在 KVM 主机上通过一条 chef converge 同时定义和启动多个虚拟机
# Example definition from our cookbooknode.default['guests'] = case node['hostname']when"kvm-host-123" {"test-guest-101": { ip_address:"XX.XX.XX.XX/XX", memory:"8192", cpu:"4", disk:"30G", os:"ubuntu22.04", os_name:"jammy", domain:"guest.internal-domain.com" }, }when"kvm-host-456" {"test-guest-08": { ip_address:"XX.XX.XX.XX/XX", memory:"4096", cpu:"2", disk:"20G", os:"ubuntu18.04", os_name:"bionic", domain:"guest.internal-domain.com" }, }endinclude_recipe "::_create_guests"
# Excerpt from our ::_create_guests Chef recipenode['guests'].each do|guest_name, guest_config| execute "Create and start a new guest with virt-install"do command "virt-install --name #{guest_name} --memory #{guest_config[:memory]} \ --vcpus #{guest_config[:cpu]} --network bridge=br0,model=virtio --graphics none \ --events on_reboot=restart --os-variant #{guest_config[:os]} \ --import --disk /u/kvm/guests-vol/#{guest_name}-OS.qcow2 \ --noautoconsole --cloud-init user-data=/u/kvm/guests-config/user-data-#{guest_name},network-config=/u/kvm/guests-config/network-config-#{guest_name} \ --autostart"endend
之所以获得如此巨大的速度提升,就是因为我们的系统变简单了,除了基本的用户管理、Filebeat 配置和 Docker 安装之外,再没什么其他多余的部分。
日志记录
我们的日志记录管线是一套完全整合的 ELK 栈,能跟我们的云和本地栈实现互操作。
mrsk 的日志记录以纯 Docker 日志为基础,所以我们要做的就是对 Filebeat 进行重新路由,从 /var/lib/docker/containers/ 中获取这些日志并将其发送至 Logstash 安装。
CDN
多年以来,我们一直在用 CloudFront 和 Route53 来获取 CDN 和 DNS 功能。但现在我们决定去云,自然得找个替代方案 — 答案就是 Cloudflare,现在位于我们数据中心本地 F5 负载均衡器之前。
CI/CD
之前用的是 Buildkite,现在我们转向了 GitHub Actions。
数据库复制和备份
RDS 确实不错,但我们之前也已经有几十年的本地 MySQL 数据库运行经验。对于由 mrsk 支持的应用部署,我们沿袭之前的实践并升级了技术栈以运行 Percona MySQL 8,还把基于 cron 的备份流程引入数据中心内的专用备份位置,所有这些都被封装在同一 cookbook 内以供相关数据库服务器使用。
在不到六周时间内,我们已经建立起了上述运营设施,mrsk 开始正常起效,Tadalist 也立足自有硬件成功对接了生产环境。
切换过程非常简单,一条转移小型数据库和转型 DNS 的调用就能实现。但期间确实出现了一定停机时间,这是因为 Tadalist 不支持零停机。
完成之后,我们来看 Tadalist 现在的样子:
这就是非常标准的 Rails 应用部署样式了,对吧?事实证明,包括 Tadalist 在内,我们的大多数应用也就只需要这些。
应用程序主机被配置为 F5 负载均衡器上的池,负责将流量路由至它该去的地方,同时为 Cloudflare 提供作为通信目标的公共端点。这里的一切就是最基础的 Ruby、Rails 还有 Docker,Docker 还被包含在 mrsk 当中。
现在我们的部署时间从几分钟缩短到了大约一分钟,有时候还更短。
好事成双 :迁移 Writeboard
继续选取下一个复杂度较低的迁移对象,Writeboard。
因为之前有了 Tadalist 的迁移经验,所以 Writeboard 的整个迁移过程只用了不到两周时间。
考虑到 Writeboard 涉及计划作业的概念,所以我们的安全 / 基础设施 / 性能团队随后也加入进来,从应用方面进行扩展验证和最终变更。但除此之外,Writeboard 对于应用和基础设施的要求跟 Tadalist 基本一致。
由于 Writeboard 涉及付费客户而且数据库更大,所以 SLO 服务等级目标也有所变化。这里,我们为数据库预迁移选择了简单的三重复制过程。
在部署到位后,实际迁移步骤为:
  • 启用维护窗口
  • 将 RDS 设置为只读
  • 将本地数据库设置为可写入
  • 停止从 RDS 复制
  • 切换 DNS
Writeboard 的整个切换过程耗时 16 分钟,没有停机也没有其他故障。
再次成功,那让我们继续前进!
乘胜追击:迁移 Backpack
Backpack 同样可以借助之前积累的迁移经验,只有一点需要注意:它运行着一个由 postfix 实现的有状态邮件管线,所以需要在磁盘上处理这些邮件。
在 EKS 上,我们可以在单独的 EC2 节点上运行 postfix,再通过由 EFS 支持的共享 PVC 将其挂载至作业 pod 当中。所以在 mrsk 部署架构中,我们也决定采用类似的方案,在邮件接收主机和作业容器之间使用共享 NFS。
这里就要聊聊 mrsk 的下一项重要功能了—— 将文件系统挂载至容器。这对 Docker 并不是什么大事,问题是如何 mrsk 中实现并做全面测试。
Backpack 运行的计划作业比 Writeboard 还要多,所以我们必须找到确保各作业正常运行、不会在给定的应用要求中相互干扰的办法。之前的解决方案,是将它们运行在同一 pod/ 主机之上,并共享一个临时的文件锁。但我们需要同时规划多个节点,这种方式起不了作用、也不够灵活。所以,我们在重写的逻辑中指定 Redis 作为后端,借此将其与作业主机隔离开来
运行容器化工作负载的应用和作业虚拟机,均由 mrsk 负责部署。我们还选择在虚拟机上运行非容器化的关键有状态工作负载,例如 MySQL 和 postfix。
Backpack 的迁移工作在 Writeboard 切换完成后立即开始,但为了安全起见并开展扩展验收测试,这次迁移前后花了约三个星期。尽管如此,我们仍然顺利按照之前的迁移步骤完成了零停机切换,现在的我们信心满满!
总结
在各应用之间整理出的共通方案,帮助我们取得了一个又一个胜利:
  • 基础设施复杂度大幅降低,活动部件减少
  • 部署策略的一致性更强
  • 将部署时间大大缩短
  • 清理了大量代码 —— 如下图所示
更重要的是,这次去云 / 去 K8s 行动还促使我们:
  • 用最新版本的 Chef 重写了整个配置管理系统
  • 结合实际需求建立起超快的虚拟机配置流程
  • 将 MySQL 从 5.7 升级至 8
我们还重新审视了自己的应用程序。它们真的需要跟云和 Kubernetes 牢牢绑定吗?难道没有更好、更简单、成本更低的替代选项吗
事实证明是有的,而且我们在达成目标的同时,也仍然保持着多年来一直追寻的容器化优势
未来展望
在放弃本地实现 K8s 的尝试之后,我们踏上了一段漫长的旅程,以自己的方式解决问题、满足需求、验证假设。
学习曲线确实陡峭,也调动了 37signals 的全体运营人力。但无论如何,我们成功了,找到了一条适合自己的迁移道路。
而且从目前来看,复杂性与依赖性的降低已经转化为实实在在的收益,体现在我们的云账单数字上。现在,我们正在积极筹备新一轮迁移计划。
我们要“带回家”的不只是应用程序, 还有搜索后端、监控等元素。
这场去云、去 K8s 的探索远未结束,请大家与我们共同见证!
2 “去 K8s”的大讨论
37signals 的博客文章发布后,在 Hacker News 上引起了激烈讨论,“去 K8s” 成为热议焦点,这里摘取了部分网友的观点:
因为觉得自托管 K8s 太贵、太复杂,所以 37signals 决定自主构建编排工具。对此,一位网友“crabbone ”评论道:
“先强调一点,千万别把 Kubernetes 理解成那种可以解决所有基础设施问题的交钥匙方案。其实 K8s 里一切有价值的东西都不是现成的,需要稍后添加并自主管理。容器运行时、存储部署信息的数据库、网络设置和管理、存储设置和管理等等,这一切都不是 Kubernetes 的本体。在实际使用 Kubernetes 之后,我们肯定会用其他东西替换掉它所默认的几乎每个组件。CoreDNS?支持不了大系统。存储分卷?谁会在本地文件系统中用分卷啊...... 一般都会用 Ceph 之类的方案代替。至于权限管理……目前可选的只有 Kyverno,而且效果实在不怎么样。所以 K8s 在实际部署中终将沦为一大堆不同组件的集合体,让人看了就头皮发麻。但我们又无法摆脱,因为所需要的功能也被缠杂在这团乱麻之上”。
K8s 的复杂性得到了很多人的认同。一位网友对 37signals 遇到的问题表示深有同感。“我大规模使用过 K8s,确实不简单。云服务商也无法提供开箱即用的选项。要在 AWS 中构建一套可持续且‘生产就绪’的 K8s 版本,就不可避免地做大量的调整、插件扩展和测试工作。我也觉得 K8s 还当不起‘部署 / 编排技术的顶峰’这个名号。我期待后续情况能有所好转,毕竟我被它折磨得不行,相信很多朋友都跟我有同感。总之,K8s 就是一款工具,有时候还很难用”。
但也有人认为,问题的核心不在于 K8s 有多难。K8s 的学习曲线确实很陡,但其他很多技术也差不多。问题是要想理解 K8s 的全部复杂性、维护方式和服务运营方法,很多企业根本就拿不出这样的时间和精力。K8s 提供的好处对于大多数尚处于生命周期之初的项目也没什么意义,甚至永远用不上。从财务角度看,这代表着风险高、回报却很低。所以如果能找到一种既能降低投资和维护成本,又适合短期与长期需求的解决方案,那么对于大多数业务体量不是特别大的公司来说,这应该才是最理想的答案。
“我也觉得 K8s 的操作没有那么难。概念其实都摆在那里,它本质上只是提供一套框架。而且很多 K8s 运营商还提供托管选项,真的没必要一棒子打死”,网友“llama052”说道。
对于成本和回报的问题,一位网友从他的个人经历出发谈道,“从成本和管理角度来看,公有云的特性决定其适合承载的负载规模是有最佳值的。一旦超过这个值,IAM 管理就会变得非常痛苦。而且往往也在同一时间,公有云的成本也开始爆发和失控,迫使我们处理这些问题。所以这应该就是开展健全性检查的最佳时机。其实 K8s 有的问题,其他方案也都有。最重要的是保证技术栈的维护投入物有所值,合理的回报率才是判断标准
“恕我直言,一切问题的根源在于生态”,另一位网友认为,“无论是 K8s 还是 k3s,它们在实验环境下倒是表现不错。可一旦涉及云生产环境,那就必须得考虑负载均衡和入口控制器、存储、网络、IAM 和角色、安全组、集中日志记录、注册表管理、漏洞扫描、CI/CD 和 GitOps 等等。这样搞下来,K8s 根本就没有复杂性优势,而且没有任何一家云服务商真能降低 K8s 的使用门槛。再有,如果某些关键部分出了故障,那惹出的就是大麻烦,调试起来也很困难,我们相当于是在一碗汤面里找出特定一根面条(日志记录)”。
不过,也有很多网友发表了更为中立的意见:
一位名叫“0x500x79”的网友表示,“我承认,你们说的有道理:EKS 的初始部署需要配合大量插件,K8s 一出故障就是大问题,本地磁盘对某些工作负载类型的支持一塌糊涂等等……但你说它是不是好工具、值不值得用,我觉得仍然是值得的。只要度过了前面的艰难适应期,后面还是有不少优点。当然,我也觉得它绝对不是基础设施 / 容器编排的终极解决方案。很多朋友似乎认为 K8s 是那种刚开始简单,后面越用越复杂的工具。我觉得并不是这样。另外,我们公司拥有自己的一套基础设施部署栈,效果比 K8s 好上千倍。我觉得这才是 K8s 接下来的发展方向,把自己这些‘棱角’都藏在引擎盖下面。目前 K8s 的抽象水平有问题,有点太过贪多了”。
“K8s 一直在被误解”,jpgvm 表示,“人们总是关注它的复杂性、过度工程之类的问题,但这些东西在宏观层面上并不重要。K8s 的核心,在于以一致的 API 和部署目标对职责做正确划分。至于这种方式能带来多大的价值,那要由你实际运行的东西、有多少相关方的参与来决定。如果运行的东西不多、参与其中的相关方也不多,那 K8s 就没什么价值。但如果这两项中任何一项很多,那价值就非常大。总之,K8s 的意义在于组织价值,其他技术优点都是次要的”。
如果你对“去 K8s”有什么看法,欢迎在评论区留言~
参考链接
https://dev.37signals.com/bringing-our-apps-back-home/
https://news.ycombinator.com/item?id=35263285
今日好文推荐
活动推荐
🔥 口碑好课|Java 性能调优实战
对于 Java 性能优化,不仅要理解系统架构、应用代码,还需要关注 JVM 层甚至操作系统底层。有时候,深入理解 Java 底层源码就能达到事半功倍的效果。
我很认可金山软件西山居技术经理刘超的观点,他根据自己的实战经验,把 Java 性能调优分成 5 个层级:Java 编程、多线程、JVM 性能检测、设计模式、数据库性能,每个层级下都覆盖了最常见的优化问题
刘超目前是金山软件西山居(国内最早的游戏开发工作室)技术经理,主导游戏支付系统的研发工作。在这个专栏里,他将从实战出发,精选高频性能问题,透过 Java 底层源码,提炼出优化思路和它背后的实现原理,最后形成一套“学完就能用的调优方法论”
现在有个特惠活动,原价 ¥129,新用户低至 5 折,老用户 7 折抢,仅限前 100 名用户。推荐给有需要的同学,也欢迎同学们转发图片进行分享。
扫码或点击阅读原文即可免费试读👇👇
继续阅读
阅读原文