type Deployment struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec DeploymentSpec `json:"spec,omitempty"`

	Status DeploymentStatus `json:"status,omitempty"`
}

一、TypeMeta

type TypeMeta struct {
	Kind string `json:"kind,omitempty"`
	APIVersion string `json:"apiVersion,omitempty"`
}

由于 metav1.TypeMeta json:",inline" 后面指定了 inline,所以我们在 yaml 中看到的 Kind 和 APIVersion 是展开在外面的,比如

apiVersion: apps/v1
kind: Deployment
metadata:
  name: lmflow
  namespace: lmflow
spec:
  replicas: 1
...

二、ObjectMeta

type ObjectMeta struct {
	Name string `json:"name,omitempty"`
	GenerateName string `json:"generateName,omitempty"`
	Namespace string `json:"namespace,omitempty"`
	SelfLink string `json:"selfLink,omitempty"`
	UID types.UID `json:"uid,omitempty"`
	ResourceVersion string `json:"resourceVersion,omitempty"`
	Generation int64 `json:"generation,omitempty"`
	CreationTimestamp Time `json:"creationTimestamp,omitempty"`
	DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"`
	DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
	Labels map[string]string `json:"labels,omitempty"`
	Annotations map[string]string `json:"annotations,omitempty"`
	OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid"`
	Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge"`
	ClusterName string `json:"clusterName,omitempty"`
	ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty"`
}

2.1 ResourceVersion

k8s 中对每一个资源都设置了一个版本号,一旦资源发生了变更,版本号就会增大,如果有 controller watch 了该资源类型,就可以获取变化的内容。

2.2 Generation

Generation 用于记录 Deployment 的版本,当 Deployment 发生变更时,通过比较 Generation 和 ReplicasAvailable,我们可以知道 Deployment 是否已经完成更新。只有当 Generation 增加,且新的 ReplicasAvailable 达到期望值后,我们才能确认更新完成。

2.3 DeletionGracePeriodSeconds

删除等待时间,k8s 在删除 Deployment 时,并不会立即从 etcd 中删除,而是设置 DeletionTimestamp,并等待 DeletionGracePeriodSeconds 时间,默认是 30s,这个时间内,可以完成一些对应的操作:

  • Deployment 的清理逻辑
  • 恢复或备份数据
  • 确保 workload 工作的连续性
  • 其他预删除逻辑

2.4 Labels

deployment 的 labels 是多个键值对的组合,对于 deployment 没有特别的含义。主要有以下作用

  • 记录 deployment 的信息

recall704.github.io/user: recall
recall704.github.io/tenant: test
我们可以在 label 中,记录这个 deployment 所属的用户、租户等相关信息,这主要方便在业务上扩展使用。

  • 在 deployment 上扩展额外功能时记录信息

假设我们要在 deployment 的基础上,创建我们的自定义资源,则我们可以利用 labels 来存放对应的一些数据
也可以相当于 deployment 的 LabelSelector 匹配 pod 的 label

2.5 Annotations

和 labels 类似,Annotations 主要用来存放一些非标信息,比如

  • description: 应用的描述
  • version、author 等信息
  • 实现扩展功能

Prometheus 的 ServiceMonitor 通过 Annotation 获取被监控 Service 的信息
ExternalDNS 通过 Annotation 实现 DNS 记录的自动创建

2.6 OwnerReferences

在 k8s 中,OwnerReferences 一般用于定义父子关系,比如 Deployment 创建 ReplicaSet, ReplicaSet 创建 Pod
Pod 的 OwnerReferences 会记录 ReplicaSet 的信息,ReplicaSet 的 OwnerReferences 中会记录 Deployment 的信息

OwnerReferences 有以下作用:

  • 实现资源对象的垃圾回收

在删除 Deployment 时,k8s 的垃圾回收会根据 OwnerReferences 来层层回收对应的资源

  • 方便资源对象的管理
  • 实现级联删除/更新

2.7 Finalizers

在 k8s 中,Finalizers 是资源的 preDelete hook,即在删除资源前需要执行的一系列操作

它主要有以下作用:

  • 实现资源的预删除钩子,在删除前执行必要的逻辑

比如我们实现了一个 Namespaced 的自定义资源,在删除某个 Namespace 时,也需要删除该 Namespace 下对应的自定义资源

  • 防止资源被误删除

比如 pvc, pv 等存储资源,在删除前,可以检查该资源是否被其它 workload 使用,如果还在使用中,则拦截该删除操作

  • 保证资源删除的顺序性

Finalizers 是一个数组,可以定义多个 Finalizer 来按顺序执行响应的处理

如果要实现一个 Finalizer,可以编写对应的 Kubernetes Admission Controller

func mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
    // 检查 DeletionTimestamp
    if ar.Request.Operation == admissionv1.Delete && ar.Request.Object.DeletionTimestamp != nil {
        // 执行 Finalizer 逻辑
        ......
    }
}

2.8 ClusterName

在有多集群时,可以指定 deployment 所属的 cluster,它有以下作用

  • 多集群中避免资源名称冲突
  • 方便跨集群对资源进行管理操作
  • 实现跨集群的资源间级联管理
  • 隔离不同的环境

有了以上的 TypeMeta 和 ObjectMeta 信息之后,我们可以给出下面一个常见的 deployment 配置示例

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
	recall704.github.io/description: "An extensible, convenient, and efficient toolbox for finetuning large machine learning models, designed to be user-friendly, speedy and reliable, and accessible to the entire community."
  labels:
    recall704.github.io/user: recall
    recall704.github.io/tenant: test
  name: lmflow
  namespace: lmflow
spec:
# ...

三、DeploymentSpec

type DeploymentSpec struct {
	Replicas *int32 `json:"replicas,omitempty"`

	Selector *metav1.LabelSelector `json:"selector"`

	Template v1.PodTemplateSpec `json:"template"`

	Strategy DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys"`

	MinReadySeconds int32 `json:"minReadySeconds,omitempty"`

	RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"`

	Paused bool `json:"paused,omitempty"`

	ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty"`
}

3.1 Replicas

期望的 Pod 数量,Pod 的数量一般和业务有一定的关系

  • 使用块存储的 Pod

块存储一般只允许挂载到一个 Node 上,这个时候 Pod 数量一般设置为 1,而且 Strategy.Type 还需要设置为 Recreate

  • 无状态服务的 Pod

如果是无状态服务的 Pod,那么 Pod 的数量一般和业务承载的流量有关,如果业务流量突然增大,则可以按需增加 Pod 的数量

3.2 Selector

Deployment 的 spec.selector 用于定义该 Deployment 要管理的 Pod 标签选择器。Deployment 会持续监听与这个选择器匹配的 Pod,并确保它们达到 spec.replicas 字段指定的期望副本数。

  • 选择 Deployment 要管理和监听的 Pod
  • 作为 Deployment 扩缩容与升级的判断依据
  • Pod 标签变更后的回收与更新

3.3 Strategy

Deployment 的升级策略,分为两种

  • Recreate

对于像挂载了块存储的 deployment,需要卸载对应的块存储,删除 Pod,才能重新启动新的 Pod,所以升级策略只能是 Recreate

  • RollingUpdate

滚动升级,对于无状态服务,则可以使用滚动升级,即先升级部分 Pod,升级成功之后,再升级剩下的 Pod
为了控制滚动升级的 Pod 数量,RollingUpdate 策略有两个参数需要设置
MaxUnavailable:最大不可用 Pod 数量,可以是具体的数值,也可以是百分比,计算时向下取整
MaxSurge: 升级过程中 Pod 的最大数量,可以是具体的数值,也可以是百分比,计算时向上取整
升级过程中: AvailablePodsNumber >= spec.replicas - spec.strategy.maxUnavailable
AllPodsNumber <= spec.replicas + spec.strategy.maxSurge
这里我们可以举三个例子来说明升级的过程

  1. replicas = 3,maxUnavailable = 0, maxSurge = 1

先升级,再删除旧 Pod,最多有 4 个 Pod,最少有 3 个 Pod
升级过程中 Pod 数量是: 4 个 -> 3 个,4 个 -> 3 个,4 个 -> 3 个

  1. replicas = 3,maxUnavailable = 1, maxSurge = 0

先删除旧 Pod,再升级,最多有 3 个 Pod,最少有 2 个 Pod
升级过程中 Pod 数量是: 2 个 -> 3 个,2 个 -> 3 个,2 个 -> 3 个

  1. replicas = 3,maxUnavailable = 1, maxSurge = 1
  1. 升级开始时,有 3 个旧 Pod 运行
  2. 根据 maxSurge=1,可以创建 1 个新的 Pod,此时有 3 个旧 Pod 和 1 个新 Pod,总数 4 个
  3. 根据 maxUnavailable=1,可以删除 1 个旧 Pod,此时有 2 个旧 Pod 和 1 个新 Pod,总数 3 个
  4. 重复步骤 2 和 3,创建 1 个新的 Pod,然后删除 1 个旧 Pod
  5. 直到删除完所有的旧 Pod,此时有 3 个新 Pod
  6. 删除 maxSurge 中多创建的 1 个 Pod,最终达到 replicas=3 的期望状态

为了保证服务升级过程中的可用性,强烈建议给 Pod 配置就绪检查 readinessProbe ,如果集群中的 Pod 数量非常多,可能还需要配置 PreStop ,同时业务调用方可能还需要设置失败重试功能。

3.4 MinReadySeconds

最小 ready 等待时间,默认为 0。有些 pod 启动之后可能会 crash,但不会立即 crash,可能运行一段时间后才 crash,如果设置了 MinReadySeconds,则可以等待 pod 运行一定时间之后,才任务其状态是 Ready。
与 readinessProbe 不同的时,MinReadySeconds 只会等待特定的时间,不参与逻辑判断。

3.5 RevisionHistoryLimit

rs 保留的数量,如果数量过少,则当业务需要回滚时之前的版本已经丢失;如果数量过多,则会存放大量的无用数据。个人推荐设置为 10 以内的值。

3.6 Paused

如果 Paused 为 true,则 deployment 不会对现有的 pod 做任何变更。
它有以下作用:

  • 暂停:临时停止 Deployment 的运行,现有的 pod 无影响
  • 一次性更新:一次更新 Deployment 的 template,避免多次 incremental 更新带来的问题
  • 调试 Deployment

在 Deployment 正常运行后,我们设置 spec.paused=true 可以暂停其管理的资源,以便我们对其进行调试、测试与修改。这避免了调试过程中由于Deployment的操作导致的干扰。

3.7 ProgressDeadlineSeconds

spec.progressDeadlineSeconds 字段用于指定 Deployment 在多长时间内如果没有任何进展则视为失败。它的值必须为整数,默认为 600(10 分钟)。

progressDeadlineSeconds 主要有以下作用:

  • 防止卡住的 Deployment

当 Deployment 由于某些原因卡住无法进展时,progressDeadlineSeconds 可以确保在指定的时间后 Deployment 被视为失败。这可以避免 Deployment 永久处于不正常状态。

  • 及时发现问题

如果 Deployment 运行中遇到问题无法正常进展,progressDeadlineSeconds 可以快速发现该问题,而不必等待资源被耗尽。这有助于我们及时定位并修复问题。

  • 防止资源消耗过多

progressDeadlineSeconds 可以确保即使 Deployment 运行失败,也不会过度消耗资源。它为 Deployment的失败状态设置了一个最大时长,超过该期限 Deployment 将被视为失败并停止操作。

3.8 Template

PodTemplateSpec 中指定了 pod 的具体参数,由于内容过多,单开一篇文章来描述。