本文已超过一年。较早的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已失效。

Kubernetes 1.22:Server Side Apply 迁移到正式发布 (GA)

Server-side Apply (SSA) 已在 Kubernetes v1.22 版本中升级为 GA。GA 里程碑意味着你可以依赖该特性及其 API,无需担心未来不向后兼容的变更。GA 特性受 Kubernetes 弃用策略的保护。

什么是 Server-side Apply?

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

自 Beta 版本以来有哪些新内容?

Beta 2 版本发布以来,新增了子资源支持,并且 client-go 和 Kubebuilder 都增加了对 Server-side Apply 的全面支持。这完善了使控制器开发切实可行的 Server-side Apply 功能。

子资源支持

Server-side Apply 现在完全支持 statusscale 等子资源。这对于控制器尤其重要,因为控制器通常负责写入子资源。

client-go 中的 Server-side Apply 支持

之前,Server-side Apply 只能通过 client-go 类型化客户端使用 Patch 函数调用,并将 PatchType 设置为 ApplyPatchType。现在,客户端中包含了 Apply 函数,以提供一种更直接、类型安全的方式来调用 Server-side Apply。每个 Apply 函数都接受一个“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" 包中导入的。每个“Apply 配置”类型都代表与相应的 Go struct 相同的 Kubernetes 对象类型,但所有字段都是指针,使其成为可选,从而允许准确地表示 Apply 请求。例如,当上面示例中的 Apply 配置被编码为 YAML 时,它生成:

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

为了理解为什么需要这样做,上述 YAML 不能由 v1.HorizontalPodAutoscaler Go struct 生成。例如:

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

上述代码试图声明与前面示例中所示相同的 Apply 配置,但当编码为 YAML 时,生成:

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

其中,spec.maxReplicas 被设置为 0。这几乎肯定不是调用者的意图(预期的 Apply 配置没有提及 maxReplicas 字段),并且可能对生产系统产生严重后果:它会指示自动扩缩器将 Pod 数量缩减到零。这里的问题源于 Go struct 中包含必需字段,如果未显式设置,这些字段将为零值。Go struct 对于创建和更新操作按预期工作,但与 Apply 操作根本不兼容,这就是我们引入生成的“Apply 配置”类型的原因。

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

在控制器中使用 Server-side Apply

无论你如何实现控制器,都可以使用 Server-side Apply 的新支持。但是,新的 client-go 支持使得在控制器中更容易使用 Server-side Apply。

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

在升级现有控制器以使用 Server-side Apply 时,相同的方法通常也适用——将控制器迁移为在每次协调任何对象时重新创建 Apply 配置。不幸的是,控制器可能具有多个代码路径,这些路径根据各种条件更新对象的不同部分。将这样的控制器迁移到 Server-side Apply 可能会有风险,因为如果控制器在 Apply 配置中忘记包含先前 Apply 请求中包含的任何字段,则可能会意外删除该字段。为了简化此类迁移,client-go Apply 支持提供了一种方法,可以用“提取/就地修改/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})

对于使用 Custom Resource Definitions (CRDs) 的开发者,Kubebuilder 的 Apply 支持将提供相同的功能。文档将在 Kubebuilder 手册可用时包含进去。

Server-side Apply 与 CustomResourceDefinitions

强烈建议所有Custom Resource Definitions (CRDs) 都包含一个 schema。没有 schema 的 CRD 被 Server-side Apply 视为非结构化数据。键被视为结构体中的字段,列表被假定为原子操作。

指定 schema 的 CRD 能够在 schema 中指定额外的注解 (annotations)。请参考文档以获取可用注解的完整列表。

自 Beta 版本以来的新注解

Defaulting(默认值): 应用者未明确表达感兴趣的字段值应使用默认值。这可以防止应用者意外拥有一个可能与其他应用者冲突的具有默认值的字段。如果未指定,默认值为 nil 或相应类型的 nil 等价物。

  • 用法:更多详情请参阅CRD Defaulting 文档。
  • Golang:+default=<value>
  • OpenAPI 扩展:default: <value>

Map 和 Struct 的原子性

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

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

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

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

下一步是什么?

在 Server-side Apply 之后,API Expression 工作组的下一个重点是改进已发布的 Kubernetes API schema 的表达能力和大小。要查看我们正在处理的完整项目列表,请加入我们的工作组并参阅工作项目文档。

如何参与?

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

我们借此机会感谢所有为促成此次升级到 GA 做出辛勤工作的贡献者们:

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