本文发布时间已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

Kubernetes 1.22:服务器端应用移至 GA

服务器端应用 (SSA) 已在 Kubernetes v1.22 版本中提升为 GA(正式可用)。GA 里程碑意味着您可以依赖该功能及其 API,而无需担心未来出现不向后兼容的更改。GA 功能受 Kubernetes 弃用策略保护。

什么是服务器端应用?

服务器端应用帮助用户和控制器通过声明式配置管理其资源。服务器端应用将 “kubectl apply” 实现的客户端应用功能替换为服务器端实现,允许 kubectl 之外的工具/客户端使用。服务器端应用是一种新的合并算法,以及对字段所有权的跟踪,它在 Kubernetes api-server 上运行。服务器端应用支持冲突检测等新功能,因此系统知道何时两个参与者正在尝试编辑同一字段。有关详细信息,请参阅服务器端应用文档Beta 2 版本公告

自 Beta 版本以来的新功能?

Beta 2 版本以来,已添加了对子资源的支持,并且 client-go 和 Kubebuilder 都为服务器端应用添加了全面的支持。这完成了使控制器开发切实可行的服务器端应用功能。

对子资源的支持

服务器端应用现在完全支持子资源,例如 statusscale。这对于控制器尤其重要,控制器通常负责写入子资源。

client-go 中的服务器端应用支持

以前,只能使用将 PatchType 设置为 ApplyPatchTypePatch 函数从 client-go 类型客户端调用服务器端应用。现在,客户端中包含 Apply 函数,从而允许以更直接和类型安全的方式调用服务器端应用。每个 Apply 函数都将一个“应用配置”类型作为参数,该类型是应用请求的结构化表示。例如

import (
         ...
         v1ac "k8s.io/client-go/applyconfigurations/autoscaling/v1"
)

hpaApplyConfig := v1ac.HorizontalPodAutoscaler(autoscalerName, ns).
         WithSpec(v1ac.HorizontalPodAutoscalerSpec().
                  WithMinReplicas(0)
         )

return hpav1client.Apply(ctx, hpaApplyConfig, metav1.ApplyOptions{FieldManager: "mycontroller", Force: true})

请注意,在此示例中,HorizontalPodAutoscaler 是从 “applyconfigurations” 包导入的。每个“应用配置”类型都表示与相应的 go 结构相同的 Kubernetes 对象类型,但是其中所有字段都是指针,使其成为可选的,从而可以准确地表示应用请求。例如,当以上示例中的应用配置被编组为 YAML 时,它将生成

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
    name: myHPA
    namespace: myNamespace
spec:
    minReplicas: 0

要了解为什么需要这样做,上述 YAML 不能由 v1.HorizontalPodAutoscaler go 结构生成。例如

hpa := v1.HorizontalPodAutoscaler{
         TypeMeta: metav1.TypeMeta{
                  APIVersion: "autoscaling/v1",
                  Kind:       "HorizontalPodAutoscaler",
         },
         ObjectMeta: ObjectMeta{
                  Namespace: ns,
                  Name:      autoscalerName,
         },
         Spec: v1.HorizontalPodAutoscalerSpec{
                  MinReplicas: pointer.Int32Ptr(0),
         },
}

上面的代码尝试声明与前面示例中显示的相同的应用配置,但是当编组为 YAML 时,将生成

kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v1
metadata
  name: myHPA
  namespace: myNamespace
  creationTimestamp: null
spec:
  scaleTargetRef:
    kind: ""
    name: ""
  minReplicas: 0
  maxReplicas: 0

其中,除其他外,包含设置为 0spec.maxReplicas。这几乎肯定不是调用者的意图(预期的应用配置没有提及 maxReplicas 字段),并且可能会对生产系统产生严重后果:它指示自动缩放器向下缩放到零个 Pod。此问题源于这样一个事实,即 go 结构包含如果未明确设置则为零值的必填字段。go 结构按预期用于创建和更新操作,但与 apply 从根本上不兼容,这就是为什么我们引入生成的“应用配置”类型的原因。

“应用配置”还具有便捷的 With<FieldName> 函数,可更轻松地构建应用请求。这使开发人员可以设置字段,而无需处理“应用配置”类型中的所有字段都是指针的事实,并且使用 go 设置不方便。例如,MinReplicas: &0 不是合法的 go 代码,因此,如果没有 With 函数,开发人员将通过使用库来解决此问题,例如 MinReplicas: pointer.Int32Ptr(0),但是像 corev1.Protocol 这样的字符串枚举仍然是一个问题,因为通用库无法支持它们。除了方便之外,With 函数还可以使开发人员与底层表示隔离,这使得更改底层表示以支持将来的其他功能更加安全。

在控制器中使用服务器端应用

无论您如何实现控制器,都可以使用对服务器端应用的新支持。但是,新的 client-go 支持使在控制器中使用服务器端应用更加容易。

在编写新控制器以使用服务器端应用时,一个好的方法是让控制器每次协调该对象时都重新创建该对象的应用配置。这可确保控制器完全协调它负责的所有字段。控制器通常应通过在 ApplyOptions 中设置 Force: true 无条件设置它们拥有的所有字段。控制器还必须提供一个 FieldManager 名称,该名称对于从中调用 apply 的协调循环是唯一的。

当升级现有控制器以使用服务器端应用时,相同的方法通常效果很好 - 将控制器迁移为在每次协调任何对象时重新创建应用配置。不幸的是,控制器可能具有多个代码路径,这些代码路径根据各种条件更新对象的不同部分。像这样将控制器迁移到服务器端应用可能存在风险,因为如果控制器忘记在以前的应用请求中包含的任何应用配置中包含任何字段,则可能会意外删除该字段。为了简化这种类型的迁移,client-go apply 支持提供了一种方法,可以使用“提取/修改/应用”工作流替换执行“读取/就地修改/更新”(或修补)工作流的任何控制器协调代码。这是新工作流程的示例

fieldMgr := "my-field-manager"
deploymentClient := clientset.AppsV1().Deployments("default")

// read, could also be read from a shared informer
deployment, err := deploymentClient.Get(ctx, "example-deployment", metav1.GetOptions{})
if err != nil {
  // handle error
}

// extract
deploymentApplyConfig, err := appsv1ac.ExtractDeployment(deployment, fieldMgr)
if err != nil {
  // handle error
}

// modify-in-place
deploymentApplyConfig.Spec.Template.Spec.WithContainers(corev1ac.Container().
	WithName("modify-slice").
	WithImage("nginx:1.14.2"),
)

// apply
applied, err := deploymentClient.Apply(ctx, deploymentApplyConfig, metav1.ApplyOptions{FieldManager: fieldMgr})

对于使用自定义资源定义(CRD)的开发人员,Kubebuilder apply 支持将提供相同的功能。可用时,文档将包含在 Kubebuilder 书中。

服务器端应用和自定义资源定义

强烈建议所有自定义资源定义 (CRD) 都具有架构。服务器端应用会将没有架构的 CRD 视为非结构化数据。键被视为结构中的字段,列表被假定为原子。

指定架构的 CRD 可以在架构中指定其他注释。请参阅文档以获取可用注释的完整列表。

自 Beta 版本以来的新注释

默认值: 对于应用者未明确表示出兴趣的字段,应设置默认值。这可以防止应用者无意中拥有可能导致与其他应用者冲突的默认字段。如果未指定,则默认值为 nil 或对应类型的 nil 等效值。

  • 用法:有关更多详细信息,请参阅 CRD 默认值 文档。
  • Golang: +default=<value>
  • OpenAPI 扩展:default: <value>

映射和结构的原子性

映射: 默认情况下,映射是细粒度的。不同的管理器可以管理每个映射条目。它们也可以配置为原子,以便单个管理器拥有整个映射。

  • 用法:有关更详细的概述,请参阅合并策略
  • Golang: +mapType=granular/atomic
  • OpenAPI 扩展:x-kubernetes-map-type: granular/atomic

结构: 默认情况下,结构是细粒度的,并且单独的应用者可以拥有每个字段。对于某些类型的结构,可能需要原子性。这在小型类似坐标的结构中最常见,例如字段/对象/命名空间选择器、对象引用、RGB 值、端点(协议/端口对)等。

  • 用法:有关更详细的概述,请参阅合并策略
  • Golang: +structType=granular/atomic
  • OpenAPI 扩展:x-kubernetes-map-type:atomic/granular

下一步是什么?

在服务器端应用之后,API 表达式工作组的下一个重点是改进已发布的 Kubernetes API 架构的表达性和大小。要查看我们正在处理的全部项目列表,请加入我们的工作组并参阅工作项目文档。

如何参与?

应用工作组是 wg-api-expression。它在 Slack 上可用:#wg-api-expression,通过 邮件列表,我们还在 Zoom 上每隔一个星期二的太平洋时间上午 9:30 开会。

我们还要借此机会感谢所有参与使 GA 推广成为可能的贡献者的辛勤工作

  • Andrea Nodari
  • Antoine Pelisse
  • Daniel Smith
  • Jeffrey Ying
  • Jenny Buckley
  • Joe Betz
  • Julian Modesto
  • Kevin Delgado
  • Kevin Wiesmüller
  • Maria Ntalla