本文已发布一年以上。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不正确。
使用 Kluctl 和服务器端应用 (Server Side Apply) 共存
这篇博文的灵感来自之前一篇关于 高级服务端应用的 Kubernetes 博文。该博文的作者列出了应用程序和控制器在切换到服务端应用(以下简称 SSA)时的多个好处。特别是关于 CI/CD 系统的章节,促使我做出回应,并写下我的想法和经验。
这些想法和经验是我在过去 2 年里开发 Kluctl 的结果。我将 Kluctl 描述为“将由多个较小部分(Helm/Kustomize/…)组成的大型 Kubernetes 部署以可管理和统一的方式组合在一起的缺失的粘合剂。”
为了基本了解 Kluctl,我建议访问 kluctl.io 网站,并阅读文档和教程,例如 微服务演示教程。作为替代方案,您可以观看 Rawkode Academy YouTube 频道上的 Kluctl 入门实践,其中展示了实践演示会。
对于 podtato-head 演示项目,还有一个 Kluctl 交付场景。
和平共处
Kluctl 遵循的主要理念之一是 “和平共处”,这意味着它将尽最大努力与集群外部或内部运行的任何其他工具或控制器协同工作。除非您明确告知,否则 Kluctl 不会覆盖它失去所有权的任何字段。
如果没有 SSA 的使用,实现这一点是不可能的(或者至少难度要大几个数量级)。服务端应用允许 Kluctl 检测到何时失去对某个字段的所有权,例如当另一个控制器或操作员将该字段更新为另一个值时。然后,Kluctl 可以根据这些决定,逐个字段地决定在重试之前是否需要强制应用。
SSA 之前的日子
Kluctl 的第一个版本是基于 shelling out 到 kubectl
,因此隐式地依赖于客户端应用。那时,SSA 仍处于 alpha 阶段,并且存在很多 bug。老实说,那时我甚至不知道有这回事。
客户端应用的工作方式有一些严重的缺点。最明显的一个缺点(如果时间足够长,您肯定会自己遇到)是它依赖于添加到对象的注释 (kubectl.kubernetes.io/last-applied-configuration
),带来了巨大的注释值的限制和问题。一个很好的例子是 CRD 太大,以至于它们不再适合注释的值。
另一个缺点可以通过查看名称(客户端应用)来发现。 客户端意味着每个客户端都必须提供自己的应用逻辑,而当时该逻辑仅在 kubectl
中正确实现,这使得在控制器内部很难复制。
这会将 kubectl
作为依赖项(以可执行文件或 Go 包的形式)添加到所有想要利用应用逻辑的控制器中。
但是,即使设法从控制器内部运行客户端应用,最终得到的解决方案也无法控制其内部工作方式。例如,在外部发生更改的情况下,无法单独决定覆盖哪些字段以及放弃哪些字段。
发现 SSA 应用
我一直对上述解决方案不满意,然后不知何故偶然发现了 服务端应用,当时它仍处于 beta 阶段。通过 kubectl apply --server-side
对其进行实验立即发现,SSA 的真正力量无法通过 shelling out 到 kubectl
来轻松利用。
SSA 在 kubectl
中的实现方式不允许对冲突解决进行足够的控制,因为它只能在“不强制应用任何内容并报错”和“强制应用所有内容而不留情面!”之间切换。
但是,API 文档明确指出,SSA 能够通过简单地选择要包含哪些字段和要从提供的对象中省略哪些字段来在字段级别控制冲突解决。
远离 kubectl
这意味着 Kluctl 首先必须远离 shelling out 到 kubectl
。只有在完成之后,我才能正确地使用其强大的冲突解决功能来实现 SSA。
为了实现这一点,我首先通过 Kubernetes 客户端库实现了对目标集群的访问。这也带来了加速 Kluctl 的良好副作用。它还通过确保正在运行的 Kluctl 命令不会因在运行时外部修改 kubeconfig 而被搞乱,从而提高了 Kluctl 的安全性和可用性。
实现 SSA
在切换到 Kubernetes 客户端库之后,利用 SSA 感觉很容易。Kluctl 现在必须将每个清单作为 PATCH
请求的一部分发送到 API 服务器,这表示 Kluctl 想要执行 SSA 操作。然后,API 服务器以 OK 响应(HTTP 状态代码 200)或冲突响应(HTTP 状态 409)进行响应。
如果出现冲突响应,该响应的正文将包含有关冲突的机器可读详细信息。然后,Kluctl 可以使用这些详细信息来找出哪些字段存在冲突以及哪些参与者(字段管理器)已获得冲突字段的所有权。
然后,对于每个字段,Kluctl 将决定是否应忽略冲突,或者是否应强制应用。如果任何字段需要强制应用,Kluctl 将在忽略的字段被省略且 API 调用设置了 force
标志的情况下,重试应用操作。
如果冲突被忽略,Kluctl 将向用户发出警告,以便用户可以做出适当的反应(或者永远忽略它...)。
基本上就是这样。这就是利用 SSA 所需的全部。非常感谢并赞赏 Kubernetes 开发人员使之成为可能!
冲突解决
Kluctl 有一些简单的规则来确定是否应忽略冲突或强制应用。
它首先根据一个常用工具的已知字段管理器字符串列表检查该字段的执行者(字段管理器),这些工具通常用于执行手动修改。例如,这些工具包括 kubectl
和 k9s
。使用这些工具执行的任何修改都被视为“临时”的,将被 Kluctl 覆盖。
如果您将 Kluctl 与 kubectl
一起使用,并且不希望 kubectl
的更改被覆盖(例如,在脚本中使用),则可以在 kubectl
的命令行中指定 --field-manager=<管理器名称>
,Kluctl 将不会应用其特殊的启发式方法。
如果 Kluctl 不知道字段管理器,它将检查是否请求对该字段强制应用。可以通过不同的方式请求强制应用。
- 通过将
--force-apply
传递给 Kluctl。这将导致所有字段在发生冲突时被强制应用。 - 通过向相关对象添加
kluctl.io/force-apply=true
注解。这将导致该对象的所有字段在发生冲突时被强制应用。 - 通过向相关对象添加
kluctl.io/force-apply-field=my.json.path
注解。这将导致仅与 JSON 路径匹配的字段在发生冲突时被强制应用。
每当已知其他执行者错误地声明字段(例如,ECK 运算符对 nodeSets 字段执行此操作)时,都需要将字段标记为强制应用,您可以确保 Kluctl 始终将这些字段覆盖为原始值或新值。
将来,Kluctl 将允许对冲突解决进行更多控制。例如,CLI 将允许在字段级别控制强制应用。
DevOps 与控制器
那么,Kluctl 中的 SSA 如何实现“共存共荣”?
它允许经典的管道(例如 Github Actions 或 Gitlab CI)、控制器(例如 HPA 控制器或 GitOps 风格的控制器),甚至管理员从本地机器运行部署共存。
无论您在基础设施自动化之旅中处于什么位置,Kluctl 都能为您提供帮助。从在您的 PC 上使用脚本运行部署,一直到使用代码定义管道的完全自动化 CI/CD,Kluctl 旨在补充适合您的工作流程。
即使在完全自动化所有内容之后,您也可以在需要时使用您的管理员权限进行干预,并运行 kubectl
命令来修改字段并阻止 Kluctl 覆盖它。您只需要切换到一个不会被 Kluctl 覆盖的字段管理器(例如“admin-override”)。
一些要点
服务器端应用是一项很棒的功能,对于 Kubernetes 中控制器和工具的未来至关重要。所涉及的控制器数量只会越来越多,并且必须有适当的协同工作模式。
我认为与 CI/CD 相关的控制器和工具应利用 SSA 来执行适当的冲突解决。我还认为,其他控制器(例如 Flux 和 ArgoCD)也将受益于在字段级别进行相同类型的冲突解决控制。
为 CI/CD 相关工具控制冲突解决而共同制定一套标准化的注解可能也是一个好主意。
另一方面,非 CI/CD 相关控制器应确保它们在修改对象时不会引起不必要的冲突。根据 服务器端应用文档,强烈建议控制器始终执行强制应用。在遵循此建议时,控制器应真正确保应用于对象中仅包含与控制器相关的字段。否则,将保证会发生不必要的冲突。
在许多情况下,控制器仅用于修改它们所管理对象的状态子资源。在这种情况下,控制器应仅修补状态子资源,而不应触及实际对象。如果遵循此规则,则不可能发生冲突。
如果您是此类控制器的开发人员,并且不确定您的控制器是否遵守上述规则,只需尝试检索由您的控制器管理的对象并查看 managedFields
(您需要将 --show-managed-fields -oyaml
传递给 kubectl get
)以查看是否意外声明了某些字段。