早在我加入 Urb-it 这家公司前,他们就决定使用 Kubernetes 作为云原生战略的基石。这一选择背后有两方面考虑:一是使用 Kubernetes 应对快速扩展的预期,二是利用它的容器编排功能为应用程序提供更动态、弹性和高效的环境。同时,Kubernetes 与我们的微服务架构非常契合。
早期决策
公司很早就决定选择 Kubernetes,这一选择受到质疑是合理的,因为初创公司(或任何公司)就要(对这一工具)产生深层依赖,同时需要学习大量相关的技术知识。除此之外,我们当时遇到过 Kubernetes 才能解决的问题吗?
也许有人会说,我们可以在最初使用一个相当大的单体,直到扩展及其他问题越发凸显,才转向 Kubernetes(或其他工具)。并且,Kubernetes 当时仍处于早期开发阶段。不过,我们下次再深入讨论这个问题吧。
8 年生产经验
我们在生产环境中运行 Kubernetes 已超过 8 年(每个环境都有单独的集群),在此期间做过的决策有好有坏。有些错误仅是因为“运气不好”,而有些错误则是因为我们部分(甚至可以说完全)没能理解底层技术。Kubernetes 功能强大,但也具有复杂性。
在没有任何大规模运行 Kubernetes 经验的情况下,我们只能迎难而上。
从 AWS 上的自托管迁移到 Azure 上的托管(AKS)
最初几年,我们在 AWS 上运行一个自管理集群。如果我没记错的话,我们最初没有选择使用 Azure Kubernetes Service(AKS)、Google Kubernetes Engine (GKE)、Amazon Elastic Kubernetes Service(EKS),是因为当时他们尚未提供官方的托管解决方案。正是在Amazon Web Services (AWS)自托管平台上,我们遭遇了 Urb-it 历史上第一次也是最可怕的一次集群崩溃,稍后再详述。
由于我们是小规模团队,所以完全掌握所需的所有新功能极具挑战性。同时,管理自托管集群需要持续关注及维护,这增加了我们的工作量。
当托管解决方案普遍可用时,我们花了些时间对比评估 AKS、GKE 和 EKS 。在我们看来,以上所有解决方案都比自己管理要好上数倍,而且我们能轻易预见迁移带来的快速投资回报。
当时,我们的平台 50% 是 .Net , 50% 是 Python,并且已经在使用 Azure Service Bus、Azure SQL Server 及其他 Azure 服务。因此,将集群迁移到 Azure 的好处不言而喻,我们不仅能更方便地集成使用这些服务,还可以利用 Azure 主干网络基础设施,节省因离开/进入外部网络和 VNET 相关的成本(在 AWS 和 Azure 混合设置的情况下,就无法避免这些成本)。此外,我们的许多工程师都熟悉 Azure 及其生态系统,学习成本较低。
意外之喜是,在 AKS 上进行初始设置时,我们无需为控制平面节点(主节点)付费,节省了节点费用。
2018 年冬天,我们进行了迁移,尽管这些年在 AKS 上也遇到过问题,但我们并未后悔过这次迁移。
集群崩溃 #1
使用 AWS 进行自托管期间,我们经历了一次大规模的集群崩溃,导致大部分系统和产品出现故障。根 CA 证书、etcd 证书和 API 服务器证书全部过期,导致集群停止工作、无法管理。当时,kube-aws 提供的解决支持非常有限。虽然我们请来一位专家指导,但最终还是不得不从零开始重建整个集群。
我们以为每个 git 仓库中都有所有制和 Helm 图表,但出乎意料的是,并非所有服务都是如此。最重要的是,库中没有存储创建集群的任何配置。我们与时间赛跑,重新建立集群并将我们所有服务及产品填充进去。其中有些服务需要重新制作 Helm 图表,以创建缺失的配置。有时会出现Dev1 问 Dev2 的情况:“你还还记得这个服务应该有多少 CPU 或RAM,或者有哪些网络和端口访问权限吗?”更不用说记得什么密钥,它们已随风而逝。
我们花了好几天才重新启动集群并将其运行起来。退一步说,这不是我们最骄傲的时刻。
得益于积极主动的沟通工作,我们使用了保持透明度、诚实的处理态度和客户关系维系等对策,没有失去任何业务或客户。
集群崩溃 #2
你可能会猜:第二次崩溃原因不可能是证书造成的,你们一定会从第一次崩溃中吸取教训,对吗?没错,但也不完全对。
不幸的是,在重新创建崩溃 #1 的集群时,我们使用的 kube-aws 的特定版本出现了问题。在创建新群集时,它没有将 etcd 证书的有效期设置为我们提供的有效期,而使用了一年的默认期限。因此,在第一次群集崩溃整整一年后,证书过期了,我们又经历了一次集群崩溃。不过这一次恢复就容易多了,我们不必重建一切,但这依旧是一个地狱般的周末。
附注 1:其他公司也和我们一样受到了这个漏洞的影响,但这对我们的客户并没有什么帮助……
附注 2:我们原计划在一年后更新所有证书,为了给自己留有余地,我们将过期时间设为两年(如果我没记错的话)。因此,虽然我们计划更新证书,但 bug 比我们早了一步。
自 2018 年以来,我们再也没有遇到过集群崩溃的情况……希望我没有乌鸦嘴。
经验教训
您需要任用对 Kubernetes 的基础设施和运营方面感兴趣并愿意参与其中的工程师。
就我们而言,我们需要几名工程师在日常工作之外研究 Kubernetes ,在必要时作为“首选专家”解决问题。如你所想,Kubernetes 特定任务的工作量各不相同,有时连着好几周差不多无事可做,有时几周需要保持高度注意力,比如集群升级期间。
将工作轮流分配给整个团队是不可能的,这项技术太过复杂,无法隔一周在不同工作之间“反复横跳”。当然,每个人都需要知道如何使用它(包括部署、调试等),但要在更具挑战性的方面表现出色,专门的学习时间必不可少。此外,拥有一个有远见、有集群发展战略的领导者也很重要。
经历过两次证书过期导致的集群崩溃,因此(我们意识到)精通 Kubernetes 内部证书及其过期日期的详细信息至关重要。
一旦落后,使用这两项工具成本就会增加,也出现问题。我们总是等待几个月才会升级到最新版本,以了解其他使用者会面临什么新版本问题。但即使保持更新版本,我们也会因为 Kubernetes 和 Helm 多样的新版本(Kubernetes API 从 alfa 到 beta,beta 到 1.0 等)而面临许多耗时的配置文件和图表重写工作。我知道Simon和Martin喜欢 Ingress 的所有变化。
说到 Helm 图表,我们已经厌倦了每次版本变更都要更新全数超过七十个图表,因此我们采用了一种更通用的“一图统一所有”的方法。使用集中式 Helm 图表有利有弊,但最终这种方法更符合我们的需求。
我再三强调:确保在需要时有办法重新创建集群。是的,你可以在用户界面中点击来创建新的群集,但这种方法在大规模或时间紧急的情况下是行不通的。
有不同的方法来处理这个问题,从简单的 shell 脚本到更高级的方法,如使用 Terraform(或类似方法)。Crossplane 还可用于管理基础设施即代码(IaC)等。对我们来说,由于团队带宽有限,我们选择了存储和使用 shell 脚本。无论选择哪种方法,都要确保不时测试流程,以确保在需要时可以重新创建集群。
制定密钥备份和存储策略。如果你的集群消失了,所有密钥都将丢失。相信我,我们亲身经历过这种情况,当你拥有多个不同的微服务和外部依赖关系时,需要花费大量时间才能重新恢复正常。
一开始,在迁移到 AKS 之后,我们试图不让集群与供应商绑定,这意味着我们将继续使用其他服务进行容器注册表、身份验证、密钥库等。我们的想法是,有朝一日我们可以轻松地迁移到另一个托管解决方案。虽然与供应商无关是个好主意,但对我们来说,机会成本很高。过了一段时间,我们决定全力开发与 AKS 相关的 Azure 产品,如容器注册表、安全扫描、身份验证等。对我们来说,这改善了开发人员的体验,简化了安全性(使用 Azure Entra Id 进行集中式访问管理)等,从而加快了产品上市速度并降低了成本(产生了效益)。
是的,我们全面使用了 Azure 产品,但我们的指导原则是尽可能减少自定义资源定义,而是使用内置的 Kubernetes 资源。不过,我们也有一些例外,比如 Traefik,因为 Ingress API 并不能满足我们的所有需求。
见下文。
见下文。
即使用了自动缩放器,我们有时也会缩放得太慢。通过使用流量数据和常识(我们是一家物流公司,节假日会出现高峰),我们在高峰来临前一天手动扩大了集群(ReplicaSet),然后在高峰来临后一天缩容(慢慢缩小以应对可能出现的第二波高峰)。
我们在stage集群中保留了 Drone 构建系统,这样做有好有坏。因为在同一集群中,易于扩展和使用;但如果同时构建太多,会消耗几乎所有资源,导致 Kubernetes 急于启动新节点。最好的解决方案可能是将其作为纯 SaaS 解决方案,而不必担心产品本身的托管和维护工作。
选择节点类型与具体情况密切相关,但根据节点类型,AKS 会保留约 10-30% 的可用内存(用于 AKS 内部服务)。因此,我们发现使用较少但较大的节点类型是有益的。此外,由于我们要在许多服务上运行 .Net,因此需要选择具有高效、大容量 IO 的节点类型。(.Net经常向磁盘写入JIT和日志,如果这需要网络访问,速度就会变慢。我们还确保节点磁盘/缓存的大小至少与配置的节点磁盘总大小相同,以再次避免网络跳转)。
你可能会认为这种方法有点违背云的灵活性,但对我们来说,将关键实例保留一两年可以节省大量成本。在许多情况下,与“即用即付”方法相比,我们可以节省 50%-60%的成本,相当可观。
对于想要比纯粹的 kubectl 更高一层抽象的用户来说,https://k9scli.io/是一个很好的工具。
可观测性
确保长期跟踪内存、CPU 等的使用情况,以便观察集群的运行情况,确定新功能是提高还是降低了集群性能。这样能更容易地为不同的 pod 找到并设置“正确的”限制(找到正确的平衡点非常重要,因为如果内存耗尽,pod 就会被杀死)。
完善告警系统需要一个过程,但最终我们将所有告警定向到 Slack 频道。当集群未按预期运行或出现意外问题时,我们能便捷地收到信息。
将所有日志整合到一个地方,同时采用强大的跟踪 ID 策略(例如 OpenTelemetry 或类似软件),对于任何微服务架构都至关重要。我们花了两三年时间才做到这一点。如果我们早点实施,就能节省大量时间。
安全性
Kubernetes 中的安全问题是一个庞大的话题,我强烈建议对其进行深入研究,以了解所有细微差别(例如,请参阅 NSA、CISA 发布的《Kubernetes 加固指南》)。以下是我们的一些经验要点,但请注意,这些内容并不完备。
简而言之,Kubernetes 默认限制并不过分。因此,我们投入了大量时间来收紧访问权限,对 pod 和容器实施最小权限原则。此外,由于存在特定漏洞,无权限攻击者有可能将其权限升级为 root 权限,从而规避 Linux 命名空间限制,在某些情况下,甚至可以逃离容器,获得主机节点上的 root 访问权限。这绝非好事。
您应该设置只读根文件系统、禁用服务帐户令牌自动挂载、禁用权限升级、放弃所有不必要的功能等等。在我们的具体设置中,我们使用 Azure Policy 和 Gatekeeper 来确保不会部署不安全的容器。
在 AKS 中的 Kubernetes 设置中,我们利用基于角色的访问控制(RBAC)的强大功能来进一步加强安全性和访问管理。
有很多优质工具可以扫描和验证容器以及 Kubernetes 的其他部分。我们使用 Azure Defender 和 Azure Defender for Containers 来满足一些需求。
注:与其陷入“分析瘫痪”,即试图找到一个完美的、拥有各种花哨功能的工具,不如先选择工具,然后直接开始学习。
多年实践的长期设置
与其他许多人一样,我们使用 Helm 来管理和简化 Kubernetes 上部署和打包应用程序。由于我们很久以前就开始使用 Helm,而且最初是 .Net/Go/Java/Python/PHP 混合使用,所以重写 Helm 图表的次数多得我都记不清了。
我们开始使用 Loggly 和 FluentD 进行集中式日志记录,但几年后,我们转而使用 Elastic 和 Kibana(ELK 堆栈)。对我们来说,使用 Elastic 和 Kibana 更方便,因为它们的使用范围更广,而且在我们的设置中,它们的价格也更便宜。
我们最初使用的是 Quay,这是一款不错的产品。但随着向 Azure 迁移,使用 Azure 容器注册表变得很自然,因为它是集成的,因此对我们来说是更“原生”的解决方案。(随后,我们还将容器置于 Azure Security Advisor 下)。
从一开始,我们就使用 Drone 构建容器。刚开始时,支持容器和 Docker 的 CI 系统并不多,也不提供代码配置。多年来,Drone 为我们提供了良好的服务。Harness 收购 Drone 后,它变得有点混乱,但在我们屈服并迁移到高级版本后,我们拥有了所需的所有功能。
改变游戏规则
过去几年,Kubernetes 改变了我们的游戏规则。它释放出的功能让我们能够更高效地扩展(应对流量波动)、优化基础设施成本、改善开发人员体验、更轻松地测试新创意,从而大大缩短了新产品和服务的上市时间/盈利时间。
我们开始使用 Kubernetes 有点太早了,在我们尚未真正遇到它能解决的问题之前。但从长远来看,尤其是最近几年,它已被证明能为我们带来巨大价值。
结语
回顾八年来的经历,我们有很多故事可以分享,其中很多已经淡出记忆。我希望我们的组建过程、所犯的错误以及一路走来所汲取的经验教训对您有所帮助。
感谢阅读。
特别感谢Matin、Simon和Niklas对本文提出的宝贵意见。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721