本文已超过一年。旧文章可能包含过时内容。请检查页面信息自发布以来是否已发生变化。
Server Side Apply 非常棒,你应该使用它
服务器端应用 (SSA) 现已在 几个版本中正式可用,我发现在很多交流中,我都推荐不同情况下的个人或团队使用它。因此,我想写下一些原因。
SSA 显而易见 (以及不那么显而易见) 的好处
从各种方式切换到服务器端应用后,您获得的一系列改进/优点!
- 对比客户端应用 (即普通的
kubectl apply
)- 当您意外地与其他参与者争夺某个字段的值时,系统会给您冲突提示!
- 当与
--dry-run
结合使用时,不会意外地运行客户端干运行而不是服务器端干运行。
- 对比手动创建补丁
- SSA 补丁格式写起来非常自然,没有奇怪的语法。它只是一个普通对象,但您可以 (也应该) 省略您不关心的任何字段。
- 旧的补丁格式 (“战略性合并补丁”) 是临时性的,并且仍然有一些 Bug;JSON-patch 和 JSON merge-patch 无法处理 Kubernetes API 中常见的一些情况,即基于 “name” 或其他标识字段需要递归合并的项目列表。
- 现在还有很棒的 Go 语言库支持,用于以编程方式构建 Apply 调用!
- 您可以使用 SSA 通过将字段设置为
null
来明确删除您不“拥有”的字段,这使其成为所有旧补丁格式的功能完整替代品。
- 对比调用外部命令 kubectl
- 您可以使用任何语言的 apply API 调用,而无需调用外部命令 kubectl!
- 如上所述,Go 语言库现在有专门的机制使其变得容易。
- 对比 GET-修改-PUT
- (这一个更复杂,如果您从未写过控制器,可以跳过它!)
- 要正确使用 GET-修改-PUT,您必须在您 GET 和 PUT 之间对象被其他人以任何方式修改的情况下处理并重试写入失败。发生这种情况时,这是一个“乐观并发失败”。
- SSA 将此任务卸载到服务器 – 您只需在发生冲突时重试,并且您可能遇到的冲突都是有意义的,就像您实际上试图从系统中的另一个参与者手中夺取某个字段一样。
- 换句话说,如果 10 个参与者同时进行 GET-修改-PUT 循环,9 个将遇到乐观并发失败并不得不重试,然后是 8 个,依此类推,在最坏情况下总共最多 50 次 GET-PUT 尝试 (对于同时进行更改的 N 个参与者来说,这是 .5N^2 次 GET 和 PUT 调用)。如果参与者改用 SSA,并且更改实际上不冲突特定的字段,那么所有更改都可以以任何顺序进行。此外,SSA 更改通常根本无需进行 GET 调用。对于 N 个参与者来说,这只有 N 次 apply 请求,这是一个巨大的改进!
我如何使用 SSA?
用户
使用 kubectl apply --server-side
!很快我们 (SIG API Machinery) 希望将其设为默认值并完全移除“客户端”应用!
控制器作者
这里有两个主要类别,但对于两者来说,在使用 SSA 时,您可能应该强制冲突。这是因为当系统中的其他实体对某个特定字段与您的控制器有不同期望时,您的控制器可能不知道如何处理。(不过,请参阅CI/CD 部分!)
使用 GET-修改-PUT 序列或 PATCH 的控制器
这种控制器 GET 一个对象 (可能来自一个 watch),修改它,然后 PUT 回去以写入其更改。有时它会构建一个自定义 PATCH,但语义是相同的。大多数现有控制器 (特别是内置控制器) 都这样工作。
如果您的控制器是完美的,那太好了!您无需更改它。但如果您想更改它,您可以利用新的客户端库的提取工作流程 – 即 get 现有对象,提取您现有的期望,进行修改,然后重新apply。对于许多曾经计算最小可能 API 更改的控制器来说,这将是对现有实现的次要更新。
这个工作流程避免了意外地尝试拥有对象中每个字段的失败模式,如果您只是 GET 对象、进行更改然后 apply,就会发生这种情况。(请注意,服务器会注意到您这样做了并拒绝您的更改!)
重构型控制器
在 SSA 之前,这种控制器实际上是不可能的。这里的想法是 (无论何时发生变化等) 从头开始重构对象中控制器期望的字段,然后将更改 apply 到服务器,让服务器处理结果。我现在建议新的控制器从这种方式开始——说明您想要对象看起来是什么样子比说明您希望它如何改变要简单。
客户端库默认支持这种操作方法。
唯一的缺点是您最终可能会向 API 服务器发送不必要的 apply 请求,即使对象实际上已经匹配了控制器的期望。如果偶尔发生一次,这并不重要,但对于吞吐量极高的控制器来说,它可能会导致集群的性能问题——特别是 API 服务器。无操作写入不会写入存储 (etcd),也不会广播给任何观察者,所以这不是什么大问题。如果您仍然担心这个问题,今天您可以使用上一节中解释的方法,或者您现在仍然可以这样做,并等待一个额外的客户端机制来抑制零更改的应用。
为了克服这个缺点,为什么不 GET 对象,然后只在对象需要时才发送您的 apply 呢?令人惊讶的是,它并没有多大帮助——对于 API 服务器来说,一个无操作的 apply 的工作量不会比额外的 GET 多多少;而一个改变对象的 apply 比同样带有前置 GET 的 apply 更便宜。更糟糕的是,由于这是一个分布式系统,在您的 GET 和 apply 之间可能会发生变化,从而使您的计算失效。相反,您可以在从缓存中检索到的对象上使用此优化——这样它才能真正减少系统的负载 (代价是在需要更改时会产生延迟,并且缓存会稍有滞后)。
CI/CD 系统
持续集成 (CI) 和/或持续部署 (CD) 系统是一种特殊类型的控制器,它执行诸如从源代码控制 (例如 Git 仓库) 读取清单之类的操作,并自动将它们推送到集群中。CI/CD 过程可能会先从模板生成清单,然后运行一些测试,最后部署更改。通常,用户是将更改推送到源代码控制的实体,尽管情况并非总是如此。
有些这样的系统会持续与集群协调一致,其他系统可能只在更改被推送到源代码控制系统时才操作。以下注意事项对两者都很重要,但对于持续协调一致的类型来说更重要。
CI/CD 系统字面上是控制器,但对于 apply 来说,它们更像用户,并且与其他控制器不同,它们需要注意冲突。原因分析
- 抽象地说,CI/CD 系统可以更改任何内容,这意味着它们可能与任何其他控制器冲突。建议控制器强制冲突是假设控制器更改的东西有限,并且您可以合理地确定它们不会与其他控制器争夺这些东西;这显然不适用于 CI/CD 控制器。
- 具体示例:假设 CI/CD 系统希望某个 Deployment 的
.spec.replicas
是 3,因为这是签入源代码的值;然而,还有一个 HorizontalPodAutoscaler (HPA) 针对同一个 Deployment。HPA 计算目标规模并决定应该有 10 个副本。谁应该获胜?我刚才说过,大多数控制器——包括 HPA——应该忽略冲突。HPA 不知道自己是否被错误地启用,并且 HPA 没有方便的方式通知用户错误。 - CI/CD 系统发生冲突的另一个常见原因可能是当它试图覆盖由系统管理员 / SRE / 值班开发人员放置的热修复 (手动创建的补丁) 时。您几乎肯定不想自动覆盖它。
- 当然,有时 SRE 会进行意外更改,或开发人员进行未经授权的更改——这些您确实想要注意到并覆盖;然而,CI/CD 系统无法区分这最后两种情况。
希望这能说服您,CI/CD 系统需要错误路径——一种将这些冲突错误反向传播给人工的方式;实际上,它们应该已经有了这个,当然,持续集成系统需要一种方式来报告测试失败。但也许我还可以谈谈人工如何处理错误
拒绝热修复:CI/CD 系统的 (人工) 管理员观察到错误,并手动强制应用相关的清单。然后 CI/CD 系统将能够成功应用清单,并成为共同所有者。
可选:然后管理员应用一个空白清单 (只包含对象类型 / Namespace / 名称),以放弃他们成为管理者拥有的任何字段。如果省略此步骤,管理员可能会最终拥有字段并导致意外的未来冲突。
注意:为什么是管理员?我假设通常将更改推送到 CI/CD 系统和/或其源代码控制系统的开发者可能没有权限直接推送到集群。
接受热修复:相关更改的作者看到冲突,并编辑其更改以接受生产环境中运行的值。
先接受再拒绝:就像接受选项一样,但在该清单应用后,并且 CI/CD 队列再次拥有所有内容 (因此没有冲突),重新应用原始清单。
我还可以想象 CI/CD 系统允许您以某种方式将清单标记为“强制冲突”——如果有这方面的需求,我们可以考虑制定一个更标准化的方法来实现这一点。一个严格的版本,它允许您精确声明您打算强制哪些冲突,将需要 API 服务器的支持;作为替代,您可以创建一个只包含该字段子集的第二个清单。
未来工作:我们可以想象一个特别先进的 CI/CD 系统,它可以解析
metadata.managedFields
数据,查看与谁或什么在哪些字段上发生冲突,并决定是否忽略冲突。实际上,任何冲突错误中也提供了此信息,尽管格式可能不容易被机器解析。我们 (SIG API Machinery) 大多数时候并未期望人们会采取这种方法——因此我们非常想知道人们是否确实想要/需要这种方法所暗示的功能,例如,在 apply 时请求覆盖某些冲突但非其他冲突的能力。如果这听起来像是您想为自己的控制器采取的方法,请来和 SIG API Machinery 交流!
开心地 apply 吧!