当前位置: 首页 > 科技观察

《六天》爱上了Kubernetes_0

时间:2023-03-18 12:12:13 科技观察

过去相当长一段时间,我认为自己是一个强烈的Kubernetes怀疑论者。无论是项目还是创业,裸机永远是我的首选,包括运行这个博客的堆栈(https://freeman.vc/notes/architecting-a-blog)。该堆栈是一个持续集成(CI)工具链,在主机上配置了Nginx。它可以处理数量惊人的并发负载,每月10美元的托管费用,它可以很好地与企业级博客平台配合使用,而后者的托管成本要高出两个数量级。我相信一家过早将其架构变得过于复杂的公司不仅会给工程师带来问题,还会给用户带来不稳定。所以我认为带有简单服务器的单个代码库(Monorepo)可能就足够了。此时只需运行一个基本的Docker实例即可最大限度地减少依赖地狱并确保远程配置可在本地开发机器上重现。如果需要增加流量,可以租用更强大的服务器;或者部署一个简单的负载均衡系统来路由到多个后端设备。虽然这正是我在做的事情,但最近用于负载平衡系统背后的几个副项目的单个物理服务器不能满足我的要求——它大部分时间都是空闲的,但它需要几十个带有GPU的虚拟机。这迫使我求助于更复杂的服务器管理解决方案。在使用Kubernetes构建了几个月之后,我必须承认:我越来越喜欢它了。第1天:奋斗我已经在裸机上拥有一个功能完备的后端应用程序。我真的必须用不同的产品重新构建我的堆栈吗?尤其是像Kubernetes这样的超级解决方案?我正在尝试使用实例模板和托管组来设置集群,这相当于谷歌的亚马逊ECS。从控制台启动基本集群相对简单,但我需要一些自定义逻辑来处理SHAS更新时的启动和停止。我将其切换为编写为Python库的GitHubActions管道。在仔细研究了GCP文档后,我想出了一个可行的解决方案,但同时也发现了一些缺点。①大部分必要的部署代码都可以用标准的谷歌客户端库函数实现,这对于参数类型检查和面向对象非常有用。但是,pypi库中有一些命令不受支持。为此,我通过代理连接到GCP公开的RESTWeb服务。为什么会出现这种不匹配?我不知道。在开发过程中处理两种相似但不同的API格式真的很令人困惑。②部署需要在启动时提供Docker镜像,实例模板支持这些镜像。但他们不通过API支持它们,而只能在Web控制台或CLI中支持。通过REST调用实现容器服务需要检查gcloud网络流量并复制包含在实例创建负载中的yaml文件。它带有一条注释警告不要复制yaml文件,因为更改不是语义版本化的并且可能随时更改。③开始觉得自己是在重新实现服务端的核心逻辑。以前一定有人解决过这个问题数百次,考虑到范围相对简单,我可以忽略它,但它不知何故让我烦恼。这里的主要障碍是价格。我需要部署其中两个具有不同容器的集群,这些容器需要由内部负载平衡器进行前置。这允许主后端服务器与静态API端点通信并分配负载。这两个负载平衡系统是对路由后端流量的现有负载平衡系统的补充。这意味着存储成本也开始成倍增加,因为我需要在默认硬盘驱动器配置上有一些缓冲空间。即使在零数据处理的情况下,测试该基础设施的费用也会飙升至每天28美元,这对于一家有现金消耗的公司来说微不足道,但对我来说还不够。第2天:Kubernetes,你好吗?显然,Google的裸机方法看起来前景不佳。它需要太多的手动绑定,以至于成本猛增,所以我开始寻找其他托管产品。如果我在这个单独的数据管理集群中重构计算密集型部分,它可能会将数据处理与机器学习分开。但是,我查看的所有产品都只提供预留GPU。如果我们选择硬件配置,它们会生成虚拟机或专用服务器。如果我们有请求需要处理,动态扩容是没有的。虽然每美元的芯片比谷歌的好,但它与专业计算的结构相同。对于此部署,空闲时间将多于活动时间,因此这是行不通的。我不情愿地深入Kubernetes堆栈,从文档开始,然后用扩展逻辑的核心元素构建了一个POC集群。我什至没有专注于部署实际的应用程序。我只是优先考虑基础设施配置,这是让我亲身体验Kubernetes设计的好方法。第3天:晚餐费用GCP和AWS都对Kubernetes控制平面收取每月70美元左右的费用。控制平面与每个托管的Kubernetes集群捆绑在一起,因此它甚至在使用计算资源之前就设定了业务成本的底线。GCP增加这项费用时引起了轩然大波,因为它成倍增加了用户使用单独集群的成本。虽然Azure是唯一仍然拥有免费控制面板的主要提供商,但我发现其托管产品的功能少于其他两个。因此,我不希望提供商的控制面板永远免费。虽然GCP仍然在单个区域提供免费仪表板,但它主要用于Beta测试。此外,这种单区域支持与使用裸机的专用服务器相同,虽然用户可以在托管设备的任何区域进行托管,但如果该区域出现问题(或者如果底层服务器),你运气不好。如果要在裸机上做多区域负载均衡,那么硬件成本会翻倍。但即使有开销,设置托管集群仍然比使用特定于云的产品自己构建一个集群便宜得多。GCP上的内部负载平衡每月花费18美元,推出4项服务来创建内部DNS网格将超过控制面板的成本。而我使用Google产品的简单组合从头开始制作的原型成本接近18美元。我目前正在做的是,我开始在这个免费的区域集群中托管我的所有项目。到目前为止,它完全可靠,没有停机时间。如果没有机器学习相关的计算资源,平均成本约为每天6美元。其中大部分(85%)用于3个CPU、11.25GB内存集群的计算、内存和存储资源。其余费用用于存储和Docker映像托管。第4天:理解术语我发现Kubernetes的一个有用的心智模型是将容器编排视为对不同原语的操作。一些原语包括:Pod是Docker容器或容器化的应用程序逻辑;服务控制同一pod的重复副本,因此如果pod在托管期间崩溃,则可以确保冗余;入站控制器定义外部连接(例如常规Web请求)如何流入集群,到达正确的服务;节点是物理硬件,是运行1+个pod的实际服务器;集群是一组具有相似特征的一个或多个节点,指示哪些pod放置在哪里等信息。还有操作:获取(get)一个原语下可用的实例列表;描述(describe)这些元素的定义,并查看当前状态;查看活动或失败对象的日志(日志);一切都围绕着这些逻辑对象,这些实体中的大多数都具有与其关联的相同CLI行为:$kubectlgetpodNAMEREADYSTATUSRESTARTSAGEbackend-deployment-6d985b8854-45wfr1/1Running018hbackend-deployment-6d985b8854-g7cph1/1运行018hbackend-deployment-6d985b8854-mqtdc1/1运行018hfrontend-deployment-5576fb487-7xj5r1/1运行027hfrontend-deployment-5576fb487-8dkvx1/1运行027hfrontend-deployment-5576q/6f128s027h$kubectlgetserviceNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGEbackendClusterIP10.171.351.380/TCP34hfrontendClusterIP10.171.334.2880/TCP34hkubernetesClusterIP10.171.310.1443/TCP4d23h因此,每个命令将根据逻辑对象显示不同的元数据如果检查pod,它将显示正在运行的Docker容器的版本和健康状况;deploy动作会显示当前镜像的冗余度和集群的健康状况;所谓的服务会提供DNS地址供其他服务访问内部负载均衡器系统。这种逻辑对象方法的美妙之处在于它使API发现变得易于访问。我们可以通过记住操作(get、describe)和对象(pods、services)来避免组合爆炸。Kubernetes提供的原语对服务器的行为方式有自己的看法。例如,pod和服务有不同的概念,因为服务提供了某种跨多个pod的内置负载平衡。我们可以使用单独的pod轻松编写此逻辑,并探测本地KubernetesAPI以获取同一组中pod的IP地址。但是因为服务被广泛使用,Kubernetes将它们重构为一个单独的对象类型。通常,抽象是服务器功能上最常见的垫片,可以:运行进程、接受入站连接、运行守护进程。这使我们能够在新工具中轻松完成我们已经在做的事情。虽然术语有点难以学习,但核心概念却并非如此。第5天:认识朋友我最喜欢Kubernetes的一点是它抽象了跨云托管服务器的概念。无论我们使用GCP、AWS还是Azure进行托管,我们都会有想要运行容器(pods)的原始计算(节点)和偶尔的一次性脚本(作业)。特别是对于托管Kubernetes配置,云提供商负责编写逻辑Kubernetes对象和物理硬件之间的转换层。如果我们想要启动一个新设备,我们只需要将helm配置推送到集群中的新节点,云提供商会处理剩下的事情。虽然这不会使云迁移完全顺畅,但它确实允许云托管被视为一种更加民主化的实用服务,避免通过编写自定义云集成代码而被锁定在一个供应商。这一切都归结为KubernetesAPI。根据我的经验,无论我们的系统有多大,这个接口层都处于完美的抽象级别。我们不必担心启动磁盘或机器本身的底层管理实用程序。API遵循明确的弃用时间表,这将使我们能够可靠地将业务逻辑集成到更大的管道中,例如在CI中推送部署更改、通过计划任务启动节点等。一切都已编程。甚至kubectl(本机集群管理实用程序)也是集群内托管API的抽象层。这意味着我们可以对任何可以手动完成的事情进行编程,只要可以编程,我们就可以将其自动化。我之前管理的大约95%的服务器是自动化的。有一个主要的bash脚本完成大部分环境设置,但尽管如此,我仍然需要做一些手动工作,如更新Nginx文件系统配置、配置iptables等。由于每个Linux发行版都略有不同,这个脚本将需要在定期升级底层操作系统时进行更改。API使我们的运行手册完全可编程。工程师/SRE可以随叫随到并遵循程序指南,而不是编写文档来对已知问题进行分类或对集群运行命令。一个常见的需求是检查一个git分支是否已经成功部署,如果我们想用sha标记一个Docker镜像,简单的方法是检查sha是否相同。这通常需要一些远程ssh/Docker处理,或者在API端点之一中公开托管的sha。可以自动化吗?当然。很简单的?不是很简单。相反,KubernetesAPI使得编写这样的逻辑并将它们捆绑到可安装的软件包中变得非常容易。这使得随叫随到的常见类别操作可被发现,并引导用户自行完成命令。这是我的实用程序的示例:$on-callUsage:on-call[OPTIONS]COMMAND[ARGS]...Options:--helpShowthismessageandexit.Commands:check-healthCheckservicehealthbootstrap-databaseRuninitialdatabasesetup...$on-callcheck-health--appfrontend1.检查正常运行时间(`yes`确认):>yesTheapplicationhasbeenstablefor465minutes.2.检查sha匹配(`yes`确认):>yesLatestgithubsha:86597faLatestdeployedsha:8d3f42eMismatchingshas...第6天:Laborspecialization在架构上,我仍然认为单体架构是要走的路,但这是另一个话题。即使是微服务爱好者也会承认,有时我们需要将服务绑定到底层计算,这在GPU上的硬件加速机器学习分布中尤其如此。我们通常每个硬件设备只有一个GPU集群,因此约束存在于节点级别,而不是特定于pod。结合污点和容差,很容易将pod分配给独立的硬件实例。默认情况下,pod将放置在任何有合适内存和CPU空间可用的地方。我将污染视为油和水,因为它们与默认的pod分配不兼容。而容差更像是油和油,它们可以让pod在那些有污点的节点上启动。以正确的方式配置污点和容差可以将1个pod锁定到1个节点。这让我们完全回到了在裸机上托管服务的范例。在极端情况下,它允许我们将Kubernetes用作连接服务的负载平衡层和管理API的抽象层。我们可以更轻松地保留单体应用并委派其他任务。结论我仍然看好裸机和强大的单体架构,但在我对Kubernetes的初步了解之后,我很想说当我们需要多台服务器协同工作时应该使用Kubernetes。我们总是要管理Kubernetes,因为它不可避免地会带来可靠性问题。但是担心Kubernetes被矫枉过正似乎是不必要的,因为编写微服务架构的底层问题才是需要担心的关键。但如果我们将两者分开,也许我们可以两全其美。一种是优先考虑功能交付的简单代码库;另一种是灵活但可编程的硬件架构。Kubernetes比我预期的更容易融入后台,使我们能够专注于提供主要价值:我们的应用程序。标题:FallingforKubernetes作者:PierceFreeman,译者|布加迪