上云价值倒挂严重,1年云支出高达320万美元,这家公司彻底去云、放弃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。这样就能保住多年以来投入并建设的资产,“单纯”把我们的工具重新部署在新位置。
另一个挑战是,我们的大部分应用程序在几年之前就完成了容器化,为了继续兼容遗留资产,我们希望保持这种状态。
这主意听起来全是好处,但实际操作起来却昂贵且极为复杂。所以我们还是得回到规划中来。
mrsk 成为我们这波去云加去 K8s 行动的核心范式:
它能够创建一条路径,帮助我们简化对现有容器化应用的部署,同时极大加快整个操作过程。我们既能保留大量现有容器化技术,又能以相当熟悉的全新方式运行应用程序。
多年以来,我们一直在数据中心内用 capistrano 部署。
Basecamp 4 和其他应用,而 mrsk 指向的也是相同的命令式模型。不必新增控制平面、活动部件还能有所减少,这太棒了!
Tadalist 无疑是个完美的迁移样本。它在所有应用程序中的重要度最低,而且没有任何付费客户。事实上,Tadalist 就像是为运营系统报警的“金丝雀”,而这一次它将再次扮演这个角色。
当然,迁移不可能一蹴而就。在开始之前,我们还得做好其他准备。
近年来,我们一直关注云及相关技术,所以我们的本地基础设施配置流程已经有点落后。除了去云工作之外,我们还希望全面实现配置管理的现代化与精简,并升级至最新版 Chef。
由于 mrsk 将以本地服务器为目标,我们还需要建立新流程来快速、轻松配置虚拟机。
结合现有知识,我们决定运行基于 KVM 的虚拟机访客,使用 cloud-init 简化引导,并将这一切都编排进同一套架构。这些改进让访客虚拟机的完全引导时间从之前约 20 分钟缩短到了不足 1 分钟——现在我们还可以在 KVM 主机上通过一条 chef converge 同时定义和启动多个虚拟机。
# Example definition from our cookbook
node.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" },
}
end
include_recipe "::_create_guests"
# Excerpt from our ::_create_guests Chef recipe
node['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"
end
end
之所以获得如此巨大的速度提升,就是因为我们的系统变简单了,除了基本的用户管理、Filebeat 配置和 Docker 安装之外,再没什么其他多余的部分。
我们的日志记录管线是一套完全整合的 ELK 栈,能跟我们的云和本地栈实现互操作。
mrsk 的日志记录以纯 Docker 日志为基础,所以我们要做的就是对 Filebeat 进行重新路由,从 /var/lib/docker/containers/ 中获取这些日志并将其发送至 Logstash 安装。
多年以来,我们一直在用 CloudFront 和 Route53 来获取 CDN 和 DNS 功能。但现在我们决定去云,自然得找个替代方案 — 答案就是 Cloudflare,现在位于我们数据中心本地 F5 负载均衡器之前。
之前用的是 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。
因为之前有了 Tadalist 的迁移经验,所以 Writeboard 的整个迁移过程只用了不到两周时间。
考虑到 Writeboard 涉及计划作业的概念,所以我们的安全 / 基础设施 / 性能团队随后也加入进来,从应用方面进行扩展验证和最终变更。但除此之外,Writeboard 对于应用和基础设施的要求跟 Tadalist 基本一致。
由于 Writeboard 涉及付费客户而且数据库更大,所以 SLO 服务等级目标也有所变化。这里,我们为数据库预迁移选择了简单的三重复制过程。
在部署到位后,实际迁移步骤为:
启用维护窗口
将 RDS 设置为只读
将本地数据库设置为可写入
停止从 RDS 复制
切换 DNS
Writeboard 的整个切换过程耗时 16 分钟,没有停机也没有其他故障。
再次成功,那让我们继续前进!
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 的探索远未结束,请大家与我们共同见证!
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
你也「在看」吗? 👇
微信扫码关注该文公众号作者