深入Kubernetes,你会很快发现“期望状态”的概念。不过,这与你的梦想工作、完美的云原生工具套件、或者你想要如何设计Kubernete集群无关。
这个概念很简单:期望状态表示你希望应用程序和集群如何运行,Kubernetes“控制器”创建一个反馈循环来实现这一点。尽管如此,云原生生态系统还是创造了文化和技术障碍,使期望状态比以往任何时候都更难以实现。障碍包括:
——只有一些人对Kubernetes有足够的了解,才能在概念上或在清单级别上定义理想状态。
——期望状态会随着市场和客户的变化而变化。
——期望状态取决于使用的云原生工具。一些公司使用裸YAML,其他公司为其部署生成Helm Charts或使用Kustosize来区分环境。
——它还取决于应用程序源代码、打包方式以及应用程序存储的数据。
——随着组织的发展,你希望拥有对期望状态的每一次更改的历史记录,就像在版本控制中所做的那样——如何以及为什么对期望状态进行这些更改的。
所有这些都让我们怀疑,期望状态最开始有多让人满意。
Kubernetes中的“期望状态”是什么?
关于控制回路有一个常见的类比,有助于区分当前状态和期望状态。当你将恒温器设置到特定温度时,你就建立了你期望的状态。在HVAC设备的帮助下,恒温器的工作是将房间从当前状态(当前温度)调节到期望状态(设定温度)。恒温器反复循环检查更改循环,以保持状态一致。
在Kubernetes中,期望状态是基础设施或应用程序在运行后应该如何运行。控制器是HVAC设备,所有的神秘机械都能让你从0状态变为期望状态。设置的温度是配置。
在云原生世界中,所有这些都是通过声明性方法实现的。你可以定义集群的每一部分应该如何操作,并让Kubernetes担心脏活累活,而不是通过列出所谓的命令式配置步骤来配置基础设施。这也很适合基础设施即代码(IaC),其中管理和配置基础设施是一个自动化而非手动的过程。
Kubernetes控制器连续运行并跟踪相关对象(pod、服务等),查找YAML配置中提供的spec字段(期望状态)和存储当前状态的status字段之间的差异。如果有变化,它会自动采取行动,调用API服务器进行所需的更改以弥补差距。
这种循环系统是Kubernetes如何处理大量移动部件和不断变化的变量的变化。例如,实际应用程序状态是应用程序镜像、Kubernetes的期望状态和应用程序状态的混合,这些状态存储在通常位于集群本身外部的数据库中。
这种复杂性意味着集群通常不可能达到期望状态,这使得它更像是一个白日梦,一个始终在移动的目标。
那么,期望状态取决于四件事:
——代码及其运行的操作系统(作为镜像存储在容器注册表中)。
——将镜像部署到Kubernetes所需的配置。
——CD进程的输出,该进程使用配置部署镜像,并在需要时对其进行修改。
——存储在应用程序中的数据(通常在数据库中)。
容器注册表和清单
Kubernetes控制器总是试图将期望状态与实际状态同步,但应用程序状态也取决于其他事情。正在处理的一个问题是容器注册表,它负责存储为容器和pod(整个基础设施)提供基础的所有镜像。镜像将保存正在部署的代码版本。
随着应用程序的发展或底层操作系统的变化,应用程序将必须更新。这些更改通过CI/CD管道移动到不同的环境中,从而生成新的镜像。在容器中,镜像通过设计“分层”,在另一层之上构建若干层,以提供最终的容器用途。例如,你可以有一个基本层,即操作系统,而应用程序运行在第二层上,或者可以有更多的层用于更复杂的应用程序。
由于Kubernetes(和现代软件开发风格)鼓励持续的应用程序部署,你可能会及时更改这两个层:
——为应用程序所依赖的库/服务的新版本更新基础OS层。
——更新应用程序层,以获取更新的依赖关系或应用程序本身中的新功能/错误修复。
有时,这些变化也是由更大的架构变化驱动的。例如,你需要改变数据库,因为客户数据太大了,而且已经不能正常运行,因此你需要改变期望状态,这实际上可能意味着要对你的每一层镜像进行额外的调整。或者,如果要处理多区域部署,镜像需要是可配置的,以便在全球基础设施中均匀部署pod。
为了管理所有这些镜像级别的更改,应该使用特定的标签来安装操作系统和应用程序级别的验证版本,而不是简单地使用最新版本并希望获得最佳效果。然后,更新后,控制器不再自动更新镜像版本,因此需要在准备就绪时触发更新。
所有这些编辑、调整和可能性对容器注册表都有现实意义,因为它试图提供将集群恢复到期望状态所需的镜像。
CD进程:希望部署
Kubernetes控制器确保应用程序状态是您定义的状态,但需要从配置的本地定义移动到集群中定义的期望状态,确保中间没有中断。
从我们的角度来看,Argo CD和Flux是云原生持续交付(CD)的领导者。它们都遵循GitOps模式,使用git存储库作为定义基础设施/应用程序期望状态的唯一真实来源,并作为应用程序部署在集群内。
CD工具监视git存储库中的清单变化(无论是基于YAML的清单、Helm图表还是Kustomize应用程序),并自动将其与集群同步。为了避免问题,执行不同类型的自动测试。如果测试发现问题,则要求开发人员更改配置并重试。即使这在大多数情况下都有效,最好还是让一些人仔细检查更改,以确保不会引入测试中未涉及的问题。
在自动化测试和开发人员请求对等方批准或拒绝其更改的pull/merge之间,这些进程旨在通过允许频繁发布来改善最终用户体验。它有时看起来很麻烦,但通过减少部署所需的时间,你可以通过控制谁将代码投入生产来交付更高质量的代码并提高安全性。
CD管道可以用于基于pull或push的部署。在基于push的部署中,你使用某种操作来触发CD进程,例如GitHub Action或Webhook。在基于pull的部署中,代理识别何时进行了更改,并自动触发流程。在任何情况下,build都可能包含许多步骤,包括验证。
这里提出的一些高级工具支持两种风格的GitOps。一些用于IaC的高级工具通过提供一些幂等性(可以多次执行它们,结果不会改变)来缓解强制策略,但需要编写所需的步骤。两者都有利弊,但最后,重要的是以最佳的方式定义期望状态。
GitOps时代的协调与漂移
当你使用GitOps和IaC时,CD触发器通常是实际状态和期望状态之间的差异。但是,如果实际状态的变化超出了你的直接干预,或者控制器无法达到期望状态,会发生什么?Kubernetes支持声明性配置,但它使用kubectl响应命令性命令,这些命令改变状态,不依赖YAML清单。这就是我们所说的漂移,它可能会因为许多原因而发生,比如应用程序配置、运行时环境、自动缩放或镜像的一个或多个层的更改。
你甚至可能需要在特定的pod中快速进行手动配置更改,例如为其提供更高的内存请求,以防止其崩溃并影响最终用户体验。你现在有了一个带外变化,它与之前定义的期望状态不一致,但也在实现弹性和可靠性的目标。
每次进行手动更改时,CD管道都会尝试还原它,因此你需要禁用CD或修改期望的配置状态以使其永久。如果你为生产问题找到了完美的短期解决方案,那就没什么意思了,但CD管道通过进入一个不工作的定义状态来不断“修复”它。
此外,期望状态还取决于策略。如何确保现行策略到位?使用的是经过验证的镜像(而不是“最新”版本),还是特定的批准版本?有一些最佳实践和策略可以帮助提高部署的安全性和可维护性,你可以自动应用它们。
Open Policy Agent和Kyverno是最常用的为云原生基础设施/应用程序应用策略的项目。每当策略发生变化时(例如,有人定义了成本管理所需的标签),期望状态也会发生变化,这通常意味着额外的配置变化。
哪一个赢了?期望状态的白日梦,或者最终用户想要的,一个实际运行的应用程序?现在,你或你的CI/CD管道需要开始采取具体行动,以减少协调过程中的漂移。
如何控制从容器到生产的期望状态?
当你看到期望状态、容器复杂性和协调/漂移的现实时,“你构建它,你部署它”的梦想似乎就只是一个梦想。对于开发人员来说,在生产中维护自己的应用程序还有太多的工作要做。
无论你的组织在DevOps、平台工程或GitOps上投入了多大的资金,现实情况是,永远无法确保实现所配置的期望状态。你需要适应这种情况,及时更新期望状态的配置,并随时将其与集群中部署的实际状态进行比较。
许多组织正在通过内部开发平台解决围绕期望状态的问题,这些平台将Kubernetes配置的复杂性抽象为一个精心策划的服务、工具和服务目录,但这些平台带来了新的挑战和好处。当出现问题时,平台工程师会浪费时间钻研自动生成的配置以寻求答案,并且在满足开发人员的要求(许多库和应用程序的最新版本)和通过遵循其他策略确保其正常、安全地工作之间始终存在平衡。
基于最佳实践变化的速度,我们远远没有看到云原生社区在任何一个策略上达成一致。有些项目旨在简化流程:如果开发人员使用新的X部署语言,将概念抽象为更高级的语言,那么他们不需要了解Kubernetes的细节。这个想法很好,并且在过去已经在基础设施(Puppet、Ansible、Salt和其他)方面付诸实践。但复杂的问题通常不是在抽象层中唯一解决的。
最后,你需要一个工具,将使用Kubernetes配置的任何人,从开发人员到DevOps,再到平台工程师,带到同一个高效的平台上,他们可以通过以下方式更好地了解期望状态:
——了解正在使用的镜像。
——可视化创建的每个资源及其依赖关系。
——根据架构和策略验证配置。
——协调跨分支和环境的配置版本与视觉差异。
——与其他团队合作,防止漂移。
Monkle Cloud和Monkle Desktop就是一套用于探索和分析Kubernetes配置以及优化GitOps工作流的免费工具,帮助实现期望状态。