这是本节的多页打印视图。
点击此处打印 .
返回本页常规视图 .
工作负载
理解 Kubernetes 中可部署的最小计算对象 Pod 以及辅助 Pod 运行的上层抽象。
工作负载是在 Kubernetes 上运行的应用程序。
在 Kubernetes 中,无论你的负载是由单个组件还是由多个一同工作的组件构成,
你都可以在一组 Pod 中运行它。
在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组
容器 的集合。
Kubernetes Pod 遵循预定义的生命周期 。
例如,当在你的集群中运行了某个 Pod,但是 Pod 所在的
节点 出现致命错误时,
所有该节点上的 Pod 的状态都会变成失败。Kubernetes 将这类失败视为最终状态:
即使该节点后来恢复正常运行,你也需要创建新的 Pod 以恢复应用。
不过,为了减轻用户的使用负担,通常不需要用户直接管理每个 Pod
。
而是使用负载资源 来替用户管理一组 Pod。
这些负载资源通过配置 控制器
来确保正确类型的、处于运行状态的 Pod 个数是正确的,与用户所指定的状态相一致。
Kubernetes 提供若干种内置的工作负载资源:
DaemonSet
定义提供节点本地支撑设施的 Pod。这些 Pod 可能对于你的集群的运维是
非常重要的,例如作为网络链接的辅助工具或者作为网络
插件
的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某 DaemonSet
的规约匹配,则控制平面会为该 DaemonSet 调度一个 Pod 到该新节点上运行。
Job 和
CronJob 。
定义一些一直运行到结束并停止的任务。
你可以使用 Job
来定义只需要执行一次并且执行后即视为完成的任务。你可以使用
CronJob
来根据某个排期表来多次运行同一个 Job。
在庞大的 Kubernetes 生态系统中,你还可以找到一些提供额外操作的第三方工作负载相关的资源。
通过使用定制资源定义(CRD) ,
你可以添加第三方工作负载资源,以完成原本不是 Kubernetes 核心功能的工作。
例如,如果你希望运行一组 Pod,但要求所有 Pod 都可用时才执行操作
(比如针对某种高吞吐量的分布式任务),你可以基于定制资源实现一个能够满足这一需求的扩展,
并将其安装到集群中运行。
接下来
除了阅读了解每类资源外,你还可以了解与这些资源相关的任务:
要了解 Kubernetes 将代码与配置分离的实现机制,可参阅配置 节。
关于 Kubernetes 如何为应用管理 Pod,还有两个支撑概念能够提供相关背景信息:
一旦你的应用处于运行状态,你就可能想要以
Service
的形式使之可在互联网上访问;或者对于 Web 应用而言,使用
Ingress 资源将其暴露到互联网上。
1 - Pod
Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod (就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个)
容器 ;
这些容器共享存储、网络、以及怎样运行这些容器的声明。
Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。
Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器,
这些容器相对紧密地耦合在一起。
在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的
Init 容器 。
你也可以在集群支持临时性容器 的情况下,
为调试的目的注入临时性容器。
什么是 Pod?
说明:
为了运行 Pod,你需要提前在每个节点安装好容器运行时 。
Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离方面,
即用来隔离容器 的技术。
在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。
Pod 类似于共享名字空间并共享文件系统卷的一组容器。
使用 Pod
下面是一个 Pod 示例,它由一个运行镜像 nginx:1.14.2
的容器组成。
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
要创建上面显示的 Pod,请运行以下命令:
kubectl apply -f https://k8s.io/examples/pods/simple-pod.yaml
Pod 通常不是直接创建的,而是使用工作负载资源创建的。
有关如何将 Pod 用于工作负载资源的更多信息,请参阅使用 Pod 。
用于管理 Pod 的工作负载资源
通常你不需要直接创建 Pod,甚至单实例 Pod。相反,你会使用诸如
Deployment 或
Job 这类工作负载资源来创建 Pod。
如果 Pod 需要跟踪状态,可以考虑
StatefulSet 资源。
Kubernetes 集群中的 Pod 主要有两种用法:
运行单个容器的 Pod 。"每个 Pod 一个容器" 模型是最常见的 Kubernetes 用例;
在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。
运行多个协同工作的容器的 Pod 。
Pod 可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。
这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众,
而另一个单独的 “边车”(sidecar)容器则刷新或更新这些文件。
Pod 将这些容器和存储资源打包为一个可管理的实体。
说明:
将多个并置、同管的容器组织到一个 Pod 中是一种相对高级的使用场景。
只有在一些场景中,容器之间紧密关联时你才应该使用这种模式。
每个 Pod 都旨在运行给定应用程序的单个实例。如果希望横向扩展应用程序
(例如,运行多个实例以提供更多的资源),则应该使用多个 Pod,每个实例使用一个 Pod。
在 Kubernetes 中,这通常被称为副本(Replication) 。
通常使用一种工作负载资源及其控制器 来创建和管理一组 Pod 副本。
参见 Pod 和控制器 以了解 Kubernetes
如何使用工作负载资源及其控制器以实现应用的扩缩和自动修复。
Pod 怎样管理多个容器
Pod 被设计成支持形成内聚服务单元的多个协作过程(形式为容器)。
Pod 中的容器被自动安排到集群中的同一物理机或虚拟机上,并可以一起进行调度。
容器之间可以共享资源和依赖、彼此通信、协调何时以及何种方式终止自身。
例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的
"边车 (sidercar)" 容器负责从远端更新这些文件,如下图所示:
有些 Pod 具有 Init 容器 和
应用容器 。
Init 容器会在启动应用容器之前运行并完成。
特性状态: Kubernetes v1.28 [alpha]
启用 SidecarContainers
特性门控 允许你为
Init 容器指定 restartPolicy: Always
。设置重启策略为 Always
会确保 Init 容器在 Pod 的整个生命周期内保持运行。
更多细节参阅边车容器和重启策略
Pod 天生地为其成员容器提供了两种共享资源:网络 和存储 。
使用 Pod
你很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。
这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。
当 Pod 由你或者间接地由控制器
创建时,它被调度在集群中的节点 上运行。
Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被驱逐 或者节点失效为止。
说明:
重启 Pod 中的容器不应与重启 Pod 混淆。
Pod 不是进程,而是容器运行的环境。
在被删除之前,Pod 会一直存在。
Pod 的名称必须是一个合法的
DNS 子域 值,
但这可能对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签 规则。
Pod 操作系统
特性状态: Kubernetes v1.25 [stable]
你应该将 .spec.os.name
字段设置为 windows
或 linux
以表示你希望 Pod 运行在哪个操作系统之上。
这两个是 Kubernetes 目前支持的操作系统。将来,这个列表可能会被扩充。
在 Kubernetes v1.29 中,为此字段设置的值对 Pod
的调度 没有影响。
设置 .spec.os.name
有助于确定性地标识 Pod 的操作系统并用于验证。
如果你指定的 Pod 操作系统与运行 kubelet 所在节点的操作系统不同,
那么 kubelet 将会拒绝运行该 Pod。
Pod 安全标准 也使用这个字段来避免强制执行与该操作系统无关的策略。
Pod 和控制器
你可以使用工作负载资源来创建和管理多个 Pod。
资源的控制器能够处理副本的管理、上线,并在 Pod 失效时提供自愈能力。
例如,如果一个节点失败,控制器注意到该节点上的 Pod 已经停止工作,
就可以创建替换性的 Pod。调度器会将替身 Pod 调度到一个健康的节点执行。
下面是一些管理一个或者多个 Pod 的工作负载资源的示例:
Pod 模板
工作负载 资源的控制器通常使用
**Pod 模板(Pod Template)**来替你创建 Pod 并管理它们。
Pod 模板是包含在工作负载对象中的规范,用来创建 Pod。这类负载资源包括
Deployment 、
Job 和
DaemonSet 等。
工作负载的控制器会使用负载对象中的 PodTemplate
来生成实际的 Pod。
PodTemplate
是你用来运行应用时指定的负载资源的目标状态的一部分。
下面的示例是一个简单的 Job 的清单,其中的 template
指示启动一个容器。
该 Pod 中的容器会打印一条消息之后暂停。
apiVersion : batch/v1
kind : Job
metadata :
name : hello
spec :
template :
# 这里是 Pod 模板
spec :
containers :
- name : hello
image : busybox:1.28
command : ['sh' , '-c' , 'echo "Hello, Kubernetes!" && sleep 3600' ]
restartPolicy : OnFailure
# 以上为 Pod 模板
修改 Pod 模板或者切换到新的 Pod 模板都不会对已经存在的 Pod 直接起作用。
如果改变工作负载资源的 Pod 模板,工作负载资源需要使用更新后的模板来创建 Pod,
并使用新创建的 Pod 替换旧的 Pod。
例如,StatefulSet 控制器针对每个 StatefulSet 对象确保运行中的 Pod 与当前的 Pod
模板匹配。如果编辑 StatefulSet 以更改其 Pod 模板,
StatefulSet 将开始基于更新后的模板创建新的 Pod。
每个工作负载资源都实现了自己的规则,用来处理对 Pod 模板的更新。
如果你想了解更多关于 StatefulSet 的具体信息,
请阅读 StatefulSet 基础教程中的更新策略 。
在节点上,kubelet 并不直接监测或管理与
Pod 模板相关的细节或模板的更新,这些细节都被抽象出来。
这种抽象和关注点分离简化了整个系统的语义,
并且使得用户可以在不改变现有代码的前提下就能扩展集群的行为。
Pod 更新与替换
正如前面章节所述,当某工作负载的 Pod 模板被改变时,
控制器会基于更新的模板创建新的 Pod 对象而不是对现有 Pod 执行更新或者修补操作。
Kubernetes 并不禁止你直接管理 Pod。对运行中的 Pod 的某些字段执行就地更新操作还是可能的。不过,类似
patch
和
replace
这类更新操作有一些限制:
Pod 的绝大多数元数据都是不可变的。例如,你不可以改变其 namespace
、name
、
uid
或者 creationTimestamp
字段;generation
字段是比较特别的,
如果更新该字段,只能增加字段取值而不能减少。
如果 metadata.deletionTimestamp
已经被设置,则不可以向 metadata.finalizers
列表中添加新的条目。
Pod 更新不可以改变除 spec.containers[*].image
、spec.initContainers[*].image
、
spec.activeDeadlineSeconds
或 spec.tolerations
之外的字段。
对于 spec.tolerations
,你只被允许添加新的条目到其中。
在更新 spec.activeDeadlineSeconds
字段时,以下两种更新操作是被允许的:
如果该字段尚未设置,可以将其设置为一个正数;
如果该字段已经设置为一个正数,可以将其设置为一个更小的、非负的整数。
资源共享和通信
Pod 使它的成员容器间能够进行数据共享和通信。
Pod 中的存储
一个 Pod 可以设置一组共享的存储卷 。
Pod 中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。
卷还允许 Pod 中的持久数据保留下来,即使其中的容器需要重新启动。
有关 Kubernetes 如何在 Pod 中实现共享存储并将其提供给 Pod 的更多信息,
请参考存储 。
Pod 联网
每个 Pod 都在每个地址族中获得一个唯一的 IP 地址。
Pod 中的每个容器共享网络名字空间,包括 IP 地址和网络端口。
Pod 内 的容器可以使用 localhost
互相通信。
当 Pod 中的容器与 Pod 之外 的实体通信时,它们必须协调如何使用共享的网络资源(例如端口)。
在同一个 Pod 内,所有容器共享一个 IP 地址和端口空间,并且可以通过 localhost
发现对方。
他们也能通过如 SystemV 信号量或 POSIX 共享内存这类标准的进程间通信方式互相通信。
不同 Pod 中的容器的 IP 地址互不相同,如果没有特殊配置,就无法通过 OS 级 IPC 进行通信。
如果某容器希望与运行于其他 Pod 中的容器通信,可以通过 IP 联网的方式实现。
Pod 中的容器所看到的系统主机名与为 Pod 配置的 name
属性值相同。
网络 部分提供了更多有关此内容的信息。
容器的特权模式
说明:
你的容器运行时 必须支持特权容器的概念才能使用这一配置。
Pod 中的所有容器都可以在特权模式下运行,以使用原本无法访问的操作系统管理权能。
此模式同时适用于 Windows 和 Linux。
Linux 特权容器
在 Linux 中,Pod 中的所有容器都可以使用容器规约中的
安全性上下文 中的
privileged
(Linux)参数启用特权模式。
这对于想要使用操作系统管理权能(Capabilities,如操纵网络堆栈和访问硬件设备)的容器很有用。
Windows 特权容器
特性状态: Kubernetes v1.26 [stable]
在 Windows 中,你可以使用 Pod 规约中安全上下文的 windowsOptions.hostProcess
参数来创建
Windows HostProcess Pod 。
这些 Pod 中的所有容器都必须以 Windows HostProcess 容器方式运行。
HostProcess Pod 可以直接运行在主机上,它也能像 Linux 特权容器一样,用于执行管理任务。
静态 Pod
**静态 Pod(Static Pod)**直接由特定节点上的 kubelet
守护进程管理,
不需要 API 服务器 看到它们。
尽管大多数 Pod 都是通过控制面(例如,Deployment )
来管理的,对于静态 Pod 而言,kubelet
直接监控每个 Pod,并在其失效时重启之。
静态 Pod 通常绑定到某个节点上的 kubelet 。
其主要用途是运行自托管的控制面。
在自托管场景中,使用 kubelet
来管理各个独立的控制面组件 。
kubelet
自动尝试为每个静态 Pod 在 Kubernetes API
服务器上创建一个镜像 Pod 。
这意味着在节点上运行的 Pod 在 API 服务器上是可见的,但不可以通过 API 服务器来控制。
有关更多信息,请参阅创建静态 Pod 的指南。
容器探针
Probe 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 可以执行三种动作:
ExecAction
(借助容器运行时执行)
TCPSocketAction
(由 kubelet 直接检测)
HTTPGetAction
(由 kubelet 直接检测)
你可以参阅 Pod 的生命周期文档中的探针 部分。
接下来
要了解为什么 Kubernetes 会在其他资源
(如 StatefulSet
或 Deployment )
封装通用的 Pod API,相关的背景信息可以在前人的研究中找到。具体包括:
1.1 - Pod 的生命周期
本页面讲述 Pod 的生命周期。
Pod 遵循预定义的生命周期,起始于 Pending
阶段 ,
如果至少其中有一个主要容器正常启动,则进入 Running
,之后取决于 Pod
中是否有容器以失败状态结束而进入 Succeeded
或者 Failed
阶段。
在 Pod 运行期间,kubelet
能够重启容器以处理一些失效场景。
在 Pod 内部,Kubernetes 跟踪不同容器的状态 并确定使
Pod 重新变得健康所需要采取的动作。
在 Kubernetes API 中,Pod 包含规约部分和实际状态部分。
Pod 对象的状态包含了一组 Pod 状况(Conditions) 。
如果应用需要的话,你也可以向其中注入自定义的就绪态信息 。
Pod 在其生命周期中只会被调度 一次。
一旦 Pod 被调度(分派)到某个节点,Pod 会一直在该节点运行,直到 Pod
停止或者被终止 。
Pod 生命期
和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。
Pod 会被创建、赋予一个唯一的
ID(UID ),
并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。
如果一个节点 死掉了,调度到该节点的
Pod 也被计划在给定超时期限结束后删除 。
Pod 自身不具有自愈能力。如果 Pod
被调度到某节点 而该节点之后失效,
Pod 会被删除;类似地,Pod 无法在因节点资源耗尽或者节点维护而被驱逐期间继续存活。
Kubernetes 使用一种高级抽象来管理这些相对而言可随时丢弃的 Pod 实例,
称作控制器 。
任何给定的 Pod (由 UID 定义)从不会被“重新调度(rescheduled)”到不同的节点;
相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。
如果需要,新 Pod 的名字可以不变,但是其 UID 会不同。
如果某物声称其生命期与某 Pod 相同,例如存储卷 ,
这就意味着该对象在此 Pod (UID 亦相同)存在期间也一直存在。
如果 Pod 因为任何原因被删除,甚至某完全相同的替代 Pod 被创建时,
这个相关的对象(例如这里的卷)也会被删除并重建。
Pod 结构图例
一个包含多个容器的 Pod 中包含一个用来拉取文件的程序和一个 Web 服务器,
均使用持久卷作为容器间共享的存储。
Pod 阶段
Pod 的 status
字段是一个
PodStatus
对象,其中包含一个 phase
字段。
Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述。
该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。
Pod 阶段的数量和含义是严格定义的。
除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase
值。
下面是 phase
可能的值:
取值
描述
Pending
(悬决)
Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。
Running
(运行中)
Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
Succeeded
(成功)
Pod 中的所有容器都已成功终止,并且不会再重启。
Failed
(失败)
Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。
Unknown
(未知)
因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。
说明:
当一个 Pod 被删除时,执行一些 kubectl 命令会展示这个 Pod 的状态为 Terminating
(终止)。
这个 Terminating
状态并不是 Pod 阶段之一。
Pod 被赋予一个可以体面终止的期限,默认为 30 秒。
你可以使用 --force
参数来强制终止 Pod 。
从 Kubernetes 1.27 开始,除了静态 Pod
和没有 Finalizer 的强制终止 Pod
之外,kubelet
会将已删除的 Pod 转换到终止阶段
(Failed
或 Succeeded
具体取决于 Pod 容器的退出状态),然后再从 API 服务器中删除。
如果某节点死掉或者与集群中其他节点失联,Kubernetes
会实施一种策略,将失去的节点上运行的所有 Pod 的 phase
设置为 Failed
。
容器状态
Kubernetes 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的阶段 一样。
你可以使用容器生命周期回调
来在容器生命周期中的特定时间点触发事件。
一旦调度器 将 Pod
分派给某个节点,kubelet
就通过容器运行时 开始为
Pod 创建容器。容器的状态有三种:Waiting
(等待)、Running
(运行中)和
Terminated
(已终止)。
要检查 Pod 中容器的状态,你可以使用 kubectl describe pod <pod 名称>
。
其输出中包含 Pod 中每个容器的状态。
每种状态都有特定的含义:
Waiting
(等待)
如果容器并不处在 Running
或 Terminated
状态之一,它就处在 Waiting
状态。
处于 Waiting
状态的容器仍在运行它完成启动所需要的操作:例如,
从某个容器镜像仓库拉取容器镜像,或者向容器应用 Secret
数据等等。
当你使用 kubectl
来查询包含 Waiting
状态的容器的 Pod 时,你也会看到一个
Reason 字段,其中给出了容器处于等待状态的原因。
Running
(运行中)
Running
状态表明容器正在执行状态并且没有问题发生。
如果配置了 postStart
回调,那么该回调已经执行且已完成。
如果你使用 kubectl
来查询包含 Running
状态的容器的 Pod 时,
你也会看到关于容器进入 Running
状态的信息。
Terminated
(已终止)
处于 Terminated
状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。
如果你使用 kubectl
来查询包含 Terminated
状态的容器的 Pod 时,
你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。
如果容器配置了 preStop
回调,则该回调会在容器进入 Terminated
状态之前执行。
容器重启策略
Pod 的 spec
中包含一个 restartPolicy
字段,其可能取值包括
Always、OnFailure 和 Never。默认值是 Always。
restartPolicy
应用于 Pod
中的应用容器 和常规的
Init 容器 。
Sidecar 容器 忽略
Pod 级别的 restartPolicy
字段:在 Kubernetes 中,Sidecar 被定义为
initContainers
内的一个条目,其容器级别的 restartPolicy
被设置为 Always
。
对于因错误而退出的 Init 容器,如果 Pod 级别 restartPolicy
为 OnFailure
或 Always
,
则 kubelet 会重新启动 Init 容器。
当 kubelet 根据配置的重启策略处理容器重启时,仅适用于同一 Pod
内替换容器并在同一节点上运行的重启。当 Pod 中的容器退出时,kubelet
会按指数回退方式计算重启的延迟(10s、20s、40s、...),其最长延迟为 5 分钟。
一旦某容器执行了 10 分钟并且没有出现问题,kubelet
对该容器的重启回退计时器执行重置操作。
Sidecar 容器和 Pod 生命周期 中解释了
init containers
在指定 restartpolicy
字段时的行为。
Pod 状况
Pod 有一个 PodStatus 对象,其中包含一个
PodConditions
数组。Pod 可能通过也可能未通过其中的一些状况测试。
Kubelet 管理以下 PodCondition:
PodScheduled
:Pod 已经被调度到某节点;
PodReadyToStartContainers
:Pod 沙箱被成功创建并且配置了网络(Beta 特性,默认 启用);
ContainersReady
:Pod 中所有容器都已就绪;
Initialized
:所有的 Init 容器 都已成功完成;
Ready
:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
字段名称
描述
type
Pod 状况的名称
status
表明该状况是否适用,可能的取值有 "True
"、"False
" 或 "Unknown
"
lastProbeTime
上次探测 Pod 状况时的时间戳
lastTransitionTime
Pod 上次从一种状态转换到另一种状态时的时间戳
reason
机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因
message
人类可读的消息,给出上次状态转换的详细信息
Pod 就绪态
特性状态: Kubernetes v1.29 [beta]
你的应用可以向 PodStatus 中注入额外的反馈或者信号:Pod Readiness(Pod 就绪态) 。
要使用这一特性,可以设置 Pod 规约中的 readinessGates
列表,为 kubelet
提供一组额外的状况供其评估 Pod 就绪态时使用。
就绪态门控基于 Pod 的 status.conditions
字段的当前值来做决定。
如果 Kubernetes 无法在 status.conditions
字段中找到某状况,
则该状况的状态值默认为 "False
"。
这里是一个例子:
kind : Pod
...
spec :
readinessGates :
- conditionType : "www.example.com/feature-1"
status :
conditions :
- type : Ready # 内置的 Pod 状况
status : "False"
lastProbeTime : null
lastTransitionTime : 2018-01-01T00:00:00Z
- type : "www.example.com/feature-1" # 额外的 Pod 状况
status : "False"
lastProbeTime : null
lastTransitionTime : 2018-01-01T00:00:00Z
containerStatuses :
- containerID : docker://abcd...
ready : true
...
你所添加的 Pod 状况名称必须满足 Kubernetes
标签键名格式 。
Pod 就绪态的状态
命令 kubectl patch
不支持修改对象的状态。
如果需要设置 Pod 的 status.conditions
,应用或者
Operators
需要使用 PATCH
操作。你可以使用
Kubernetes 客户端库 之一来编写代码,
针对 Pod 就绪态设置定制的 Pod 状况。
对于使用定制状况的 Pod 而言,只有当下面的陈述都适用时,该 Pod 才会被评估为就绪:
Pod 中所有容器都已就绪;
readinessGates
中的所有状况都为 True
值。
当 Pod 的容器都已就绪,但至少一个定制状况没有取值或者取值为 False
,
kubelet
将 Pod 的状况 设置为 ContainersReady
。
Pod 网络就绪
特性状态: Kubernetes v1.25 [alpha]
说明:
在其早期开发过程中,这种状况被命名为 PodHasNetwork
。
在 Pod 被调度到某节点后,它需要被 kubelet 接受并且挂载所需的存储卷。
一旦这些阶段完成,Kubelet 将与容器运行时(使用容器运行时接口(Container Runtime Interface;CRI) )
一起为 Pod 生成运行时沙箱并配置网络。如果启用了 PodReadyToStartContainersCondition
特性门控
(Kubernetes 1.29 版本中默认启用),
PodReadyToStartContainers
状况会被添加到 Pod 的 status.conditions
字段中。
当 kubelet 检测到 Pod 不具备配置了网络的运行时沙箱时,PodReadyToStartContainers
状况将被设置为 False
。以下场景中将会发生这种状况:
在 Pod 生命周期的早期阶段,kubelet 还没有开始使用容器运行时为 Pod 设置沙箱时。
在 Pod 生命周期的末期阶段,Pod 的沙箱由于以下原因被销毁时:
节点重启时 Pod 没有被驱逐
对于使用虚拟机进行隔离的容器运行时,Pod 沙箱虚拟机重启时,需要创建一个新的沙箱和全新的容器网络配置。
在运行时插件成功完成 Pod 的沙箱创建和网络配置后,
kubelet 会将 PodReadyToStartContainers
状况设置为 True
。
当 PodReadyToStartContainers
状况设置为 True
后,
Kubelet 可以开始拉取容器镜像和创建容器。
对于带有 Init 容器的 Pod,kubelet 会在 Init 容器成功完成后将 Initialized
状况设置为 True
(这发生在运行时成功创建沙箱和配置网络之后),
对于没有 Init 容器的 Pod,kubelet 会在创建沙箱和网络配置开始之前将
Initialized
状况设置为 True
。
Pod 调度就绪态
特性状态: Kubernetes v1.26 [alpha]
有关详细信息,请参阅 Pod 调度就绪态 。
容器探针
probe 是由 kubelet 对容器执行的定期诊断。
要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。
检查机制
使用探针来检查容器有四种不同的方法。
每个探针都必须准确定义为这四种机制中的一种:
exec
在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
grpc
使用 gRPC 执行一个远程过程调用。
目标应该实现
gRPC 健康检查 。
如果响应的状态是 "SERVING",则认为诊断成功。
httpGet
对容器的 IP 地址上指定端口和路径执行 HTTP GET
请求。如果响应的状态码大于等于 200
且小于 400,则诊断被认为是成功的。
tcpSocket
对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。
如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。
注意: 和其他机制不同,exec
探针的实现涉及每次执行时创建/复制多个进程。
因此,在集群中具有较高 pod 密度、较低的 initialDelaySeconds
和 periodSeconds
时长的时候,
配置任何使用 exec 机制的探针可能会增加节点的 CPU 负载。
这种场景下,请考虑使用其他探针机制以避免额外的开销。
探测结果
每次探测都将获得以下三种结果之一:
Success
(成功)
容器通过了诊断。
Failure
(失败)
容器未通过诊断。
Unknown
(未知)
诊断失败,因此不会采取任何行动。
探测类型
针对运行中的容器,kubelet
可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器,
并且容器将根据其重启策略 决定未来。如果容器不提供存活探针,
则默认状态为 Success
。
readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败,
端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。
初始延迟之前的就绪态的状态值默认为 Failure
。
如果容器不提供就绪态探针,则默认状态为 Success
。
startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被
禁用,直到此探针成功为止。如果启动探测失败,kubelet
将杀死容器,
而容器依其重启策略 进行重启。
如果容器没有提供启动探测,则默认状态为 Success
。
如欲了解如何设置存活态、就绪态和启动探针的进一步细节,
可以参阅配置存活态、就绪态和启动探针 。
何时该使用存活态探针?
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针;
kubelet
将根据 Pod 的 restartPolicy
自动执行修复操作。
如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针,
并指定 restartPolicy
为 "Always
" 或 "OnFailure
"。
何时该使用就绪态探针?
如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。
在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着
Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。
如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针,
检查某个特定于就绪态的因此不同于存活态探测的端点。
如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。
当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。
这可以帮助你避免将流量导向只能返回错误信息的 Pod。
如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移,
你可以使用启动探针 。
然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。
说明:
请注意,如果你只是想在 Pod 被删除时能够排空请求,则不一定需要使用就绪态探针;
在删除 Pod 时,Pod 会自动将自身置于未就绪状态,无论就绪态探针是否存在。
等待 Pod 中的容器停止期间,Pod 会一直处于未就绪状态。
何时该使用启动探针?
对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。
你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定,
对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds
总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。
periodSeconds
的默认值是 10 秒。你应该将其 failureThreshold
设置得足够高,
以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。
这一设置有助于减少死锁状况的发生。
Pod 的终止
由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时允许其体面地终止是很重要的。
一般不应武断地使用 KILL
信号终止它们,导致这些进程没有机会完成清理操作。
设计的目标是令你能够请求删除进程,并且知道进程何时被终止,同时也能够确保删除操作终将完成。
当你请求删除某个 Pod 时,集群会记录并跟踪 Pod 的体面终止周期,
而不是直接强制地杀死 Pod。在存在强制关闭设施的前提下,
kubelet 会尝试体面地终止
Pod。
通常 Pod 体面终止的过程为:kubelet 先发送一个带有体面超时限期的 TERM(又名 SIGTERM)
信号到每个容器中的主进程,将请求发送到容器运行时来尝试停止 Pod 中的容器。
停止容器的这些请求由容器运行时以异步方式处理。
这些请求的处理顺序无法被保证。许多容器运行时遵循容器镜像内定义的 STOPSIGNAL
值,
如果不同,则发送容器镜像中配置的 STOPSIGNAL,而不是 TERM 信号。
一旦超出了体面终止限期,容器运行时会向所有剩余进程发送 KILL 信号,之后
Pod 就会被从 API 服务器 上移除。
如果 kubelet
或者容器运行时的管理服务在等待进程终止期间被重启,
集群会从头开始重试,赋予 Pod 完整的体面终止限期。
下面是一个例子:
你使用 kubectl
工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期是默认值(30 秒)。
API 服务器中的 Pod 对象被更新,记录涵盖体面终止限期在内 Pod
的最终死期,超出所计算时间点则认为 Pod 已死(dead)。
如果你使用 kubectl describe
来查验你正在删除的 Pod,该 Pod 会显示为
"Terminating" (正在终止)。
在 Pod 运行所在的节点上:kubelet
一旦看到 Pod
被标记为正在终止(已经设置了体面终止限期),kubelet
即开始本地的 Pod 关闭过程。
如果 Pod 中的容器之一定义了 preStop
回调 ,
kubelet
开始在容器内运行该回调逻辑。如果超出体面终止限期时,
preStop
回调逻辑仍在运行,kubelet
会请求给予该 Pod 的宽限期一次性增加 2 秒钟。
如果 preStop
回调在体面期结束后仍在运行,kubelet 将请求短暂的、一次性的体面期延长 2 秒。
说明:
如果 `preStop` 回调所需要的时间长于默认的体面终止限期,你必须修改
`terminationGracePeriodSeconds` 属性值来使其正常工作。
kubelet
接下来触发容器运行时发送 TERM 信号给每个容器中的进程 1。
说明:
Pod 中的容器会在不同时刻收到 TERM 信号,接收顺序也是不确定的。
如果关闭的顺序很重要,可以考虑使用 `preStop` 回调逻辑来协调。
在 kubelet
启动 Pod 的体面关闭逻辑的同时,控制平面会评估是否将关闭的
Pod 从对应的 EndpointSlice(和端点)对象中移除,过滤条件是 Pod
被对应的服务 以某
选择算符 选定。
ReplicaSet
和其他工作负载资源不再将关闭进程中的 Pod 视为合法的、能够提供服务的副本。
关闭动作很慢的 Pod 不应继续处理常规服务请求,而应开始终止并完成对打开的连接的处理。
一些应用程序不仅需要完成对打开的连接的处理,还需要更进一步的体面终止逻辑 -
比如:排空和完成会话。
任何正在终止的 Pod 所对应的端点都不会立即从 EndpointSlice
中被删除,EndpointSlice API(以及传统的 Endpoints API)会公开一个状态来指示其处于
终止状态 。
正在终止的端点始终将其 ready
状态设置为 false
(为了向后兼容 1.26 之前的版本),
因此负载均衡器不会将其用于常规流量。
如果需要排空正被终止的 Pod 上的流量,可以将 serving
状况作为实际的就绪状态。你可以在教程
探索 Pod 及其端点的终止行为
中找到有关如何实现连接排空的更多详细信息。
说明:
如果你的集群中没有启用 EndpointSliceTerminatingCondition 特性门控
(该门控从 Kubernetes 1.22 开始默认开启,在 1.26 中锁定为默认),
那么一旦 Pod 的终止宽限期开始,Kubernetes 控制平面就会从所有的相关 EndpointSlices 中移除 Pod。
上述行为是在 EndpointSliceTerminatingCondition 特性门控被启用时描述的。
说明:
从 Kubernetes 1.29 开始,如果你的 Pod 包含一个或多个 Sidecar
容器(重启策略为 Always
的 Init 容器),kubelet 将延迟向这些
Sidecar 容器发送 TERM 信号,直到最后一个主容器完全终止。
Sidecar 容器将以 Pod 规约中定义的相反顺序终止。
这可确保 Sidecar 容器继续为 Pod 中的其他容器提供服务,直到不再需要它们为止。
请注意,主容器的缓慢终止也会延迟边车容器的终止。
如果宽限期在终止过程完成之前到期,Pod 可能会进入紧急终止状态。
在这种情况下,Pod 中的所有剩余容器将在短暂的宽限期内同时终止。
同样,如果 Pod 的 preStop 回调超过了终止宽限期,则可能会发生紧急终止。
一般来说,如果你在没有 Sidecar 容器的情况下使用 preStop 回调来控制终止顺序,
那么现在可以删除它们从而允许 kubelet 自动管理 Sidecar 终止。
超出终止宽限期限时,kubelet
会触发强制关闭过程。容器运行时会向 Pod
中所有容器内仍在运行的进程发送 SIGKILL
信号。
kubelet
也会清理隐藏的 pause
容器,如果容器运行时使用了这种容器的话。
kubelet
将 Pod 转换到终止阶段(Failed
或 Succeeded
具体取决于其容器的结束状态)。
这一步从 1.27 版本开始得到保证。
kubelet
触发强制从 API 服务器上删除 Pod 对象的逻辑,并将体面终止限期设置为 0
(这意味着马上删除)。
API 服务器删除 Pod 的 API 对象,从任何客户端都无法再看到该对象。
强制终止 Pod
注意:
对于某些工作负载及其 Pod 而言,强制删除很可能会带来某种破坏。
默认情况下,所有的删除操作都会附有 30 秒钟的宽限期限。
kubectl delete
命令支持 --grace-period=<seconds>
选项,允许你重载默认值,
设定自己希望的期限值。
将宽限期限强制设置为 0
意味着立即从 API 服务器删除 Pod。
如果 Pod 仍然运行于某节点上,强制删除操作会触发 kubelet
立即执行清理操作。
说明:
你必须在设置 --grace-period=0
的同时额外设置 --force
参数才能发起强制删除请求。
执行强制删除操作时,API 服务器不再等待来自 kubelet
的、关于 Pod
已经在原来运行的节点上终止执行的确认消息。
API 服务器直接删除 Pod 对象,这样新的与之同名的 Pod 即可以被创建。
在节点侧,被设置为立即终止的 Pod 仍然会在被强行杀死之前获得一点点的宽限时间。
注意:
马上删除时不等待确认正在运行的资源已被终止。这些资源可能会无限期地继续在集群上运行。
如果你需要强制删除 StatefulSet 的 Pod,
请参阅从 StatefulSet 中删除 Pod 的任务文档。
Pod 的垃圾收集
对于已失败的 Pod 而言,对应的 API 对象仍然会保留在集群的 API 服务器上,
直到用户或者控制器 进程显式地将其删除。
Pod 的垃圾收集器(PodGC)是控制平面的控制器,它会在 Pod 个数超出所配置的阈值
(根据 kube-controller-manager
的 terminated-pod-gc-threshold
设置)时删除已终止的
Pod(阶段值为 Succeeded
或 Failed
)。
这一行为会避免随着时间演进不断创建和终止 Pod 而引起的资源泄露问题。
此外,PodGC 会清理满足以下任一条件的所有 Pod:
孤儿 Pod - 绑定到不再存在的节点,
计划外终止的 Pod
终止过程中的 Pod,当启用 NodeOutOfServiceVolumeDetach
特性门控时,
绑定到有 node.kubernetes.io/out-of-service
污点的未就绪节点。
若启用 PodDisruptionConditions
特性门控,在清理 Pod 的同时,
如果它们处于非终止状态阶段,PodGC 也会将它们标记为失败。
此外,PodGC 在清理孤儿 Pod 时会添加 Pod 干扰状况。参阅
Pod 干扰状况 了解更多详情。
接下来
1.2 - Init 容器
本页提供了 Init 容器的概览。Init 容器是一种特殊容器,在 Pod
内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。
你可以在 Pod 的规约中与用来描述应用容器的 containers
数组平行的位置指定
Init 容器。
理解 Init 容器
每个 Pod 中可以包含多个容器,
应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
它们总是运行到完成。
每个都必须在下一个启动之前成功完成。
如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。
然而,如果 Pod 对应的 restartPolicy
值为 "Never",并且 Pod 的 Init 容器失败,
则 Kubernetes 会将整个 Pod 状态设置为失败。
为 Pod 设置 Init 容器需要在
Pod 规约 中添加 initContainers
字段,
该字段以 Container
类型对象数组的形式组织,和应用的 containers
数组同级相邻。
参阅 API 参考的容器 章节了解详情。
Init 容器的状态在 status.initContainerStatuses
字段中以容器状态数组的格式返回
(类似 status.containerStatuses
字段)。
与普通容器的不同之处
Init 容器支持应用容器的全部字段和特性,包括资源限制、
数据卷 和安全设置。
然而,Init 容器对资源请求和限制的处理稍有不同,
在下面容器内的资源共享 节有说明。
同时 Init 容器不支持 lifecycle
、livenessProbe
、readinessProbe
和 startupProbe
,
因为它们必须在 Pod 就绪之前运行完成。
如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。
每个 Init 容器必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成时,
Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。
使用 Init 容器
因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
与同一 Pod 中的多个应用容器相比,Init 容器能以不同的文件系统视图运行。因此,Init
容器可以被赋予访问应用容器不能访问的 Secret 的权限。
由于 Init 容器必须在应用容器启动之前运行完成,因此 Init
容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。
一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
Init 容器可以安全地运行实用程序或自定义代码,而在其他方式下运行这些实用程序或自定义代码可能会降低应用容器镜像的安全性。
通过将不必要的工具分开,你可以限制应用容器镜像的被攻击范围。
示例
下面是一些如何使用 Init 容器的想法:
使用 Init 容器的情况
下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice
启动,
第二个等待 mydb
启动。 一旦这两个 Init 容器都启动完成,Pod 将启动 spec
节中的应用容器。
apiVersion : v1
kind : Pod
metadata :
name : myapp-pod
labels :
app.kubernetes.io/name : MyApp
spec :
containers :
- name : myapp-container
image : busybox:1.28
command : ['sh' , '-c' , 'echo The app is running! && sleep 3600' ]
initContainers :
- name : init-myservice
image : busybox:1.28
command : ['sh' , '-c' , "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done" ]
- name : init-mydb
image : busybox:1.28
command : ['sh' , '-c' , "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done" ]
你通过运行下面的命令启动 Pod:
kubectl apply -f myapp.yaml
输出类似于:
pod/myapp-pod created
使用下面的命令检查其状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m
或者查看更多详细信息:
kubectl describe -f myapp.yaml
输出类似于:
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container init-myservice
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container init-myservice
如需查看 Pod 内 Init 容器的日志,请执行:
kubectl logs myapp-pod -c init-myservice # 查看第一个 Init 容器
kubectl logs myapp-pod -c init-mydb # 查看第二个 Init 容器
在这一刻,Init 容器将会等待至发现名称为 mydb
和 myservice
的服务 。
如下为创建这些 Service 的配置文件:
---
apiVersion : v1
kind : Service
metadata :
name : myservice
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9376
---
apiVersion : v1
kind : Service
metadata :
name : mydb
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9377
创建 mydb
和 myservice
服务的命令:
kubectl apply -f services.yaml
输出类似于:
service/myservice created
service/mydb created
这样你将能看到这些 Init 容器执行完毕,随后 my-app
的 Pod 进入 Running
状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
这个简单例子应该能为你创建自己的 Init 容器提供一些启发。
接下来 节提供了更详细例子的链接。
具体行为
在 Pod 启动过程中,每个 Init 容器会在网络和数据卷初始化之后按顺序启动。
kubelet 运行依据 Init 容器在 Pod 规约中的出现顺序依次运行之。
每个 Init 容器成功退出后才会启动下一个 Init 容器。
如果某容器因为容器运行时的原因无法启动,或以错误状态退出,kubelet 会根据
Pod 的 restartPolicy
策略进行重试。
然而,如果 Pod 的 restartPolicy
设置为 "Always",Init 容器失败时会使用
restartPolicy
的 "OnFailure" 策略。
在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready
状态。
Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending
状态,
但会将状况 Initializing
设置为 false。
如果 Pod 重启 ,所有 Init 容器必须重新执行。
对 Init 容器规约的修改仅限于容器的 image
字段。
更改 Init 容器的 image
字段,等同于重启该 Pod。
因为 Init 容器可能会被重启、重试或者重新执行,所以 Init 容器的代码应该是幂等的。
特别地,基于 emptyDirs
写文件的代码,应该对输出文件可能已经存在做好准备。
Init 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe
,
因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。
Kubernetes 会在校验时强制执行此检查。
在 Pod 上使用 activeDeadlineSeconds
和在容器上使用 livenessProbe
可以避免
Init 容器一直重复失败。
activeDeadlineSeconds
时间包含了 Init 容器启动的时间。
但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds
,
因为 activeDeadlineSeconds
在 Init 容器结束后仍有效果。
如果你设置了 activeDeadlineSeconds
,已经在正常运行的 Pod 会被杀死。
在 Pod 中的每个应用容器和 Init 容器的名称必须唯一;
与任何其它容器共享同一个名称,会在校验时抛出错误。
边车容器 API
特性状态: Kubernetes v1.28 [alpha]
Kubernetes 自 1.28 版本起引入了一个名为 SidecarContainers
的 Alpha 特性门控,
允许你为 Init 容器指定独立于 Pod 和其他 Init 容器的 restartPolicy
。
你还可以添加容器探针 来控制
Init 容器的生命周期。
如果 Init 容器被创建时 restartPolicy
设置为 Always
,则 Init 容器将启动并在整个 Pod
的生命期内保持运行,这对于运行与主应用容器分离的支持服务非常有用。
如果为该 Init 容器指定了 readinessProbe
,则其结果将用于确定 Pod 的 ready
状态。
由于这些容器以 Init 容器的形式定义,所以它们具有与其他 Init 容器相同的按序执行和顺序保证优势,
从而允许使用这些容器与其他 Init 容器混合在一起构造复杂的 Pod 初始化流程。
与常规的 Init 容器相比,只要 kubelet 将边车风格的 Init 容器的 started
容器状态设置为 true,
边车风格的 Init 容器会继续运行,下一个 Init 容器可以开始启动。
到达该状态的前提是,要么需要容器中有进程正在运行且未定义启动探针,要么其 startupProbe
的结果是成功的。
此特性可用于以更稳健的方式实现边车容器模式,这是因为如果某个边车容器失败,kubelet 总会重新启动它。
以下是一个具有两个容器的 Deployment 示例,其中一个是边车:
apiVersion : apps/v1
kind : Deployment
metadata :
name : myapp
labels :
app : myapp
spec :
replicas : 1
selector :
matchLabels :
app : myapp
template :
metadata :
labels :
app : myapp
spec :
containers :
- name : myapp
image : alpine:latest
command : ['sh' , '-c' , 'echo "logging" > /opt/logs.txt' ]
volumeMounts :
- name : data
mountPath : /opt
initContainers :
- name : logshipper
image : alpine:latest
restartPolicy : Always
command : ['sh' , '-c' , 'tail /opt/logs.txt' ]
volumeMounts :
- name : data
mountPath : /opt
volumes :
- name : data
emptyDir : {}
此特性也可用于运行带有边车的 Job,因为在主容器完成后,边车容器不会阻止 Job 完成。
以下是一个具有两个容器的 Job 示例,其中一个是边车:
apiVersion : batch/v1
kind : Job
metadata :
name : myjob
spec :
template :
spec :
containers :
- name : myjob
image : alpine:latest
command : ['sh' , '-c' , 'echo "logging" > /opt/logs.txt' ]
volumeMounts :
- name : data
mountPath : /opt
initContainers :
- name : logshipper
image : alpine:latest
restartPolicy : Always
command : ['sh' , '-c' , 'tail /opt/logs.txt' ]
volumeMounts :
- name : data
mountPath : /opt
restartPolicy : Never
volumes :
- name : data
emptyDir : {}
容器内的资源共享
在给定的 Init 容器执行顺序下,资源使用适用于如下规则:
所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值,作为
Pod 有效初始 request/limit 。
如果任何资源没有指定资源限制,这被视为最高限制。
Pod 对资源的 有效 limit/request 是如下两者中的较大者:
所有应用容器对某个资源的 limit/request 之和
对某个资源的有效初始 limit/request
基于有效 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源,
这些资源在 Pod 生命周期过程中并没有被使用。
Pod 的 有效 QoS 层 ,与 Init 容器和应用容器的一样。
配额和限制适用于有效 Pod 的请求和限制值。
Pod 级别的 cgroups 是基于有效 Pod 的请求和限制值,和调度器相同。
Pod 重启的原因
Pod 重启会导致 Init 容器重新执行,主要有如下几个原因:
当 Init 容器的镜像发生改变或者 Init 容器的完成记录因为垃圾收集等原因被丢失时,
Pod 不会被重启。这一行为适用于 Kubernetes v1.20 及更新版本。
如果你在使用较早版本的 Kubernetes,可查阅你所使用的版本对应的文档。
接下来
1.3 - 干扰(Disruptions)
本指南针对的是希望构建高可用性应用的应用所有者,他们有必要了解可能发生在 Pod 上的干扰类型。
文档同样适用于想要执行自动化集群操作(例如升级和自动扩展集群)的集群管理员。
自愿干扰和非自愿干扰
Pod 不会消失,除非有人(用户或控制器)将其销毁,或者出现了不可避免的硬件或软件系统错误。
我们把这些不可避免的情况称为应用的非自愿干扰(Involuntary Disruptions) 。例如:
节点下层物理机的硬件故障
集群管理员错误地删除虚拟机(实例)
云提供商或虚拟机管理程序中的故障导致的虚拟机消失
内核错误
节点由于集群网络隔离从集群中消失
由于节点资源不足 导致 pod 被驱逐。
除了资源不足的情况,大多数用户应该都熟悉这些情况;它们不是特定于 Kubernetes 的。
我们称其他情况为自愿干扰(Voluntary Disruptions) 。
包括由应用所有者发起的操作和由集群管理员发起的操作。
典型的应用所有者的操作包括:
删除 Deployment 或其他管理 Pod 的控制器
更新了 Deployment 的 Pod 模板导致 Pod 重启
直接删除 Pod(例如,因为误操作)
集群管理员操作包括:
这些操作可能由集群管理员直接执行,也可能由集群管理员所使用的自动化工具执行,或者由集群托管提供商自动执行。
咨询集群管理员或联系云提供商,或者查询发布文档,以确定是否为集群启用了任何资源干扰源。
如果没有启用,可以不用创建 Pod Disruption Budgets(Pod 干扰预算)
注意:
并非所有的自愿干扰都会受到 Pod 干扰预算的限制。
例如,删除 Deployment 或 Pod 的删除操作就会跳过 Pod 干扰预算检查。
处理干扰
以下是减轻非自愿干扰的一些方法:
确保 Pod 在请求中给出所需资源 。
如果需要更高的可用性,请复制应用。
(了解有关运行多副本的无状态
和有状态 应用的信息。)
为了在运行复制应用时获得更高的可用性,请跨机架(使用
反亲和性 )
或跨区域(如果使用多区域集群 )扩展应用。
自愿干扰的频率各不相同。在一个基本的 Kubernetes 集群中,没有自愿干扰(只有用户触发的干扰)。
然而,集群管理员或托管提供商可能运行一些可能导致自愿干扰的额外服务。例如,节点软
更新可能导致自愿干扰。另外,集群(节点)自动缩放的某些
实现可能导致碎片整理和紧缩节点的自愿干扰。集群
管理员或托管提供商应该已经记录了各级别的自愿干扰(如果有的话)。
有些配置选项,例如在 pod spec 中
使用 PriorityClasses
也会产生自愿(和非自愿)的干扰。
干扰预算
特性状态: Kubernetes v1.21 [stable]
即使你会经常引入自愿性干扰,Kubernetes 提供的功能也能够支持你运行高度可用的应用。
作为一个应用的所有者,你可以为每个应用创建一个 PodDisruptionBudget
(PDB)。
PDB 将限制在同一时间因自愿干扰导致的多副本应用中发生宕机的 Pod 数量。
例如,基于票选机制的应用希望确保运行中的副本数永远不会低于票选所需的数量。
Web 前端可能希望确保提供负载的副本数量永远不会低于总数的某个百分比。
集群管理员和托管提供商应该使用遵循 PodDisruptionBudgets 的接口
(通过调用Eviction API ),
而不是直接删除 Pod 或 Deployment。
例如,kubectl drain
命令可以用来标记某个节点即将停止服务。
运行 kubectl drain
命令时,工具会尝试驱逐你所停服的节点上的所有 Pod。
kubectl
代表你所提交的驱逐请求可能会暂时被拒绝,
所以该工具会周期性地重试所有失败的请求,
直到目标节点上的所有的 Pod 都被终止,或者达到配置的超时时间。
PDB 指定应用可以容忍的副本数量(相当于应该有多少副本)。
例如,具有 .spec.replicas: 5
的 Deployment 在任何时间都应该有 5 个 Pod。
如果 PDB 允许其在某一时刻有 4 个副本,那么驱逐 API 将允许同一时刻仅有一个(而不是两个)Pod 自愿干扰。
使用标签选择器来指定构成应用的一组 Pod,这与应用的控制器(Deployment、StatefulSet 等)
选择 Pod 的逻辑一样。
Pod 的“预期”数量由管理这些 Pod 的工作负载资源的 .spec.replicas
参数计算出来的。
控制平面通过检查 Pod 的
.metadata.ownerReferences
来发现关联的工作负载资源。
PDB 无法防止非自愿干扰 ;
但它们确实计入预算。
由于应用的滚动升级而被删除或不可用的 Pod 确实会计入干扰预算,
但是工作负载资源(如 Deployment 和 StatefulSet)
在进行滚动升级时不受 PDB 的限制。
应用更新期间的故障处理方式是在对应的工作负载资源的 spec
中配置的。
建议在你的 PodDisruptionBudget 中将
不健康 Pod 驱逐策略
设置为 AlwaysAllow
以支持在节点腾空期间驱逐行为不当的应用程序。
默认行为是等待应用程序 Pod 变得
健康 ,然后才能继续执行腾空。
当使用驱逐 API 驱逐 Pod 时,Pod 会被体面地
终止 ,期间会
参考 PodSpec
中的 terminationGracePeriodSeconds
配置值。
PodDisruptionBudget 例子
假设集群有 3 个节点,node-1
到 node-3
。集群上运行了一些应用。
其中一个应用有 3 个副本,分别是 pod-a
,pod-b
和 pod-c
。
另外,还有一个不带 PDB 的无关 pod pod-x
也同样显示出来。
最初,所有的 Pod 分布如下:
node-1
node-2
node-3
pod-a available
pod-b available
pod-c available
pod-x available
3 个 Pod 都是 deployment 的一部分,并且共同拥有同一个 PDB,要求 3 个 Pod 中至少有 2 个 Pod 始终处于可用状态。
例如,假设集群管理员想要重启系统,升级内核版本来修复内核中的缺陷。
集群管理员首先使用 kubectl drain
命令尝试腾空 node-1
节点。
命令尝试驱逐 pod-a
和 pod-x
。操作立即就成功了。
两个 Pod 同时进入 terminating
状态。这时的集群处于下面的状态:
node-1 draining
node-2
node-3
pod-a terminating
pod-b available
pod-c available
pod-x terminating
Deployment 控制器观察到其中一个 Pod 正在终止,因此它创建了一个替代 Pod pod-d
。
由于 node-1
被封锁(cordon),pod-d
落在另一个节点上。
同样其他控制器也创建了 pod-y
作为 pod-x
的替代品。
(注意:对于 StatefulSet 来说,pod-a
(也称为 pod-0
)需要在替换 Pod 创建之前完全终止,
替代它的也称为 pod-0
,但是具有不同的 UID。除此之外,此示例也适用于 StatefulSet。)
当前集群的状态如下:
node-1 draining
node-2
node-3
pod-a terminating
pod-b available
pod-c available
pod-x terminating
pod-d starting
pod-y
在某一时刻,Pod 被终止,集群如下所示:
node-1 drained
node-2
node-3
pod-b available
pod-c available
pod-d starting
pod-y
此时,如果一个急躁的集群管理员试图排空(drain)node-2
或 node-3
,drain 命令将被阻塞,
因为对于 Deployment 来说只有 2 个可用的 Pod,并且它的 PDB 至少需要 2 个。
经过一段时间,pod-d
变得可用。
集群状态如下所示:
node-1 drained
node-2
node-3
pod-b available
pod-c available
pod-d available
pod-y
现在,集群管理员试图排空(drain)node-2
。
drain 命令将尝试按照某种顺序驱逐两个 Pod,假设先是 pod-b
,然后是 pod-d
。
命令成功驱逐 pod-b
,但是当它尝试驱逐 pod-d
时将被拒绝,因为对于
Deployment 来说只剩一个可用的 Pod 了。
Deployment 创建 pod-b
的替代 Pod pod-e
。
因为集群中没有足够的资源来调度 pod-e
,drain 命令再次阻塞。集群最终将是下面这种状态:
node-1 drained
node-2
node-3
no node
pod-b terminating
pod-c available
pod-e pending
pod-d available
pod-y
此时,集群管理员需要增加一个节点到集群中以继续升级操作。
可以看到 Kubernetes 如何改变干扰发生的速率,根据:
应用需要多少个副本
优雅关闭应用实例需要多长时间
启动应用新实例需要多长时间
控制器的类型
集群的资源能力
Pod 干扰状况
特性状态: Kubernetes v1.26 [beta]
说明:
要使用此行为,你必须在集群中启用 PodDisruptionConditions
特性门控 。
启用后,会给 Pod 添加一个 DisruptionTarget
状况 ,
用来表明该 Pod 因为发生干扰 而被删除。
状况中的 reason
字段进一步给出 Pod 终止的原因,如下:
PreemptionByScheduler
Pod 将被调度器抢占 ,
目的是接受优先级更高的新 Pod。
要了解更多的相关信息,请参阅 Pod 优先级和抢占 。
DeletionByTaintManager
由于 Pod 不能容忍 NoExecute
污点,Pod 将被
Taint Manager(kube-controller-manager
中节点生命周期控制器的一部分)删除;
请参阅基于污点 的驱逐。
EvictionByEvictionAPI
Pod 已被标记为通过 Kubernetes API 驱逐 。
DeletionByPodGC
绑定到一个不再存在的 Node 上的 Pod 将被
Pod 垃圾收集 删除。
TerminationByKubelet
Pod
由于节点压力驱逐 或节点体面关闭 而被
kubelet 终止。
说明:
Pod 的干扰可能会被中断。控制平面可能会重新尝试继续干扰同一个 Pod,但这没办法保证。
因此,DisruptionTarget
状况可能会被添加到 Pod 上,
但该 Pod 实际上可能不会被删除。
在这种情况下,一段时间后,Pod 干扰状况将被清除。
当 PodDisruptionConditions
特性门控被启用时,在清理 Pod 的同时,如果这些 Pod 处于非终止阶段,
则 Pod 垃圾回收器 (PodGC) 也会将这些 Pod 标记为失效
(另见 Pod 垃圾回收 )。
使用 Job(或 CronJob)时,你可能希望将这些 Pod 干扰状况作为 Job
Pod 失效策略 的一部分。
分离集群所有者和应用所有者角色
通常,将集群管理者和应用所有者视为彼此了解有限的独立角色是很有用的。这种责任分离在下面这些场景下是有意义的:
当有许多应用团队共用一个 Kubernetes 集群,并且有自然的专业角色
当第三方工具或服务用于集群自动化管理
Pod 干扰预算通过在角色之间提供接口来支持这种分离。
如果你的组织中没有这样的责任分离,则可能不需要使用 Pod 干扰预算。
如果你是集群管理员,并且需要对集群中的所有节点执行干扰操作,例如节点或系统软件升级,则可以使用以下选项
接受升级期间的停机时间。
故障转移到另一个完整的副本集群。
没有停机时间,但是对于重复的节点和人工协调成本可能是昂贵的。
编写可容忍干扰的应用和使用 PDB。
不停机。
最小的资源重复。
允许更多的集群管理自动化。
编写可容忍干扰的应用是棘手的,但对于支持容忍自愿干扰所做的工作,和支持自动扩缩和容忍非
自愿干扰所做工作相比,有大量的重叠
接下来
1.4 - 临时容器
特性状态: Kubernetes v1.25 [stable]
本页面概述了临时容器:一种特殊的容器,该容器在现有
Pod
中临时运行,以便完成用户发起的操作,例如故障排查。
你会使用临时容器来检查服务,而不是用它来构建应用程序。
了解临时容器
Pod 是 Kubernetes 应用程序的基本构建块。
由于 Pod 是一次性且可替换的,因此一旦 Pod 创建,就无法将容器加入到 Pod 中。
取而代之的是,通常使用 Deployment
以受控的方式来删除并替换 Pod。
有时有必要检查现有 Pod 的状态。例如,对于难以复现的故障进行排查。
在这些场景中,可以在现有 Pod 中运行临时容器来检查其状态并运行任意命令。
什么是临时容器?
临时容器与其他容器的不同之处在于,它们缺少对资源或执行的保证,并且永远不会自动重启,
因此不适用于构建应用程序。
临时容器使用与常规容器相同的 ContainerSpec
节来描述,但许多字段是不兼容和不允许的。
临时容器没有端口配置,因此像 ports
、livenessProbe
、readinessProbe
这样的字段是不允许的。
Pod 资源分配是不可变的,因此 resources
配置是不允许的。
有关允许字段的完整列表,请参见
EphemeralContainer 参考文档 。
临时容器是使用 API 中的一种特殊的 ephemeralcontainers
处理器进行创建的,
而不是直接添加到 pod.spec
段,因此无法使用 kubectl edit
来添加一个临时容器。
与常规容器一样,将临时容器添加到 Pod 后,将不能更改或删除临时容器。
临时容器的用途
当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec
无用时,
临时容器对于交互式故障排查很有用。
尤其是,Distroless 镜像
允许用户部署最小的容器镜像,从而减少攻击面并减少故障和漏洞的暴露。
由于 distroless 镜像不包含 Shell 或任何的调试工具,因此很难单独使用
kubectl exec
命令进行故障排查。
使用临时容器时,
启用进程名字空间共享 很有帮助,
可以查看其他容器中的进程。
接下来
1.5 - Pod QoS 类
本页介绍 Kubernetes 中的 服务质量(Quality of Service,QoS) 类,
阐述 Kubernetes 如何根据为 Pod 中的容器指定的资源约束为每个 Pod 设置 QoS 类。
Kubernetes 依赖这种分类来决定当 Node 上没有足够可用资源时要驱逐哪些 Pod。
QoS 类
Kubernetes 对你运行的 Pod 进行分类,并将每个 Pod 分配到特定的 QoS 类 中。
Kubernetes 使用这种分类来影响不同 Pod 被处理的方式。Kubernetes 基于 Pod
中容器 的资源请求 进行分类,
同时确定这些请求如何与资源限制相关。
这称为服务质量 (QoS) 类。
Kubernetes 基于每个 Pod 中容器的资源请求和限制为 Pod 设置 QoS 类。Kubernetes 使用 QoS
类来决定从遇到节点压力 的
Node 中驱逐哪些 Pod。可选的 QoS 类有 Guaranteed
、Burstable
和 BestEffort
。
当一个 Node 耗尽资源时,Kubernetes 将首先驱逐在该 Node 上运行的 BestEffort
Pod,
然后是 Burstable
Pod,最后是 Guaranteed
Pod。当这种驱逐是由于资源压力时,
只有超出资源请求的 Pod 才是被驱逐的候选对象。
Guaranteed
Guaranteed
Pod 具有最严格的资源限制,并且最不可能面临驱逐。
在这些 Pod 超过其自身的限制或者没有可以从 Node 抢占的低优先级 Pod 之前,
这些 Pod 保证不会被杀死。这些 Pod 不可以获得超出其指定 limit 的资源。这些 Pod 也可以使用
static
CPU 管理策略来使用独占的 CPU。
判据
Pod 被赋予 Guaranteed
QoS 类的几个判据:
Pod 中的每个容器必须有内存 limit 和内存 request。
对于 Pod 中的每个容器,内存 limit 必须等于内存 request。
Pod 中的每个容器必须有 CPU limit 和 CPU request。
对于 Pod 中的每个容器,CPU limit 必须等于 CPU request。
Burstable
Burstable
Pod 有一些基于 request 的资源下限保证,但不需要特定的 limit。
如果未指定 limit,则默认为其 limit 等于 Node 容量,这允许 Pod 在资源可用时灵活地增加其资源。
在由于 Node 资源压力导致 Pod 被驱逐的情况下,只有在所有 BestEffort
Pod 被驱逐后
这些 Pod 才会被驱逐。因为 Burstable
Pod 可以包括没有资源 limit 或资源 request 的容器,
所以 Burstable
Pod 可以尝试使用任意数量的节点资源。
判据
Pod 被赋予 Burstable
QoS 类的几个判据:
Pod 不满足针对 QoS 类 Guaranteed
的判据。
Pod 中至少一个容器有内存或 CPU 的 request 或 limit。
BestEffort
BestEffort
QoS 类中的 Pod 可以使用未专门分配给其他 QoS 类中的 Pod 的节点资源。
例如若你有一个节点有 16 核 CPU 可供 kubelet 使用,并且你将 4 核 CPU 分配给一个 Guaranteed
Pod,
那么 BestEffort
QoS 类中的 Pod 可以尝试任意使用剩余的 12 核 CPU。
如果节点遇到资源压力,kubelet 将优先驱逐 BestEffort
Pod。
判据
如果 Pod 不满足 Guaranteed
或 Burstable
的判据,则它的 QoS 类为 BestEffort
。
换言之,只有当 Pod 中的所有容器没有内存 limit 或内存 request,也没有 CPU limit 或
CPU request 时,Pod 才是 BestEffort
。Pod 中的容器可以请求(除 CPU 或内存之外的)
其他资源并且仍然被归类为 BestEffort
。
使用 cgroup v2 的内存 QoS
特性状态: Kubernetes v1.22 [alpha]
内存 QoS 使用 cgroup v2 的内存控制器来保证 Kubernetes 中的内存资源。
Pod 中容器的内存请求和限制用于设置由内存控制器所提供的特定接口 memory.min
和 memory.high
。
当 memory.min
被设置为内存请求时,内存资源被保留并且永远不会被内核回收;
这就是内存 QoS 确保 Kubernetes Pod 的内存可用性的方式。而如果容器中设置了内存限制,
这意味着系统需要限制容器内存的使用;内存 QoS 使用 memory.high
来限制接近其内存限制的工作负载,
确保系统不会因瞬时内存分配而不堪重负。
内存 QoS 依赖于 QoS 类来确定应用哪些设置;它们的机制不同,但都提供对服务质量的控制。
某些行为独立于 QoS 类
某些行为独立于 Kubernetes 分配的 QoS 类。例如:
所有超过资源 limit 的容器都将被 kubelet 杀死并重启,而不会影响该 Pod 中的其他容器。
如果一个容器超出了自身的资源 request,且该容器运行的节点面临资源压力,则该容器所在的 Pod
就会成为被驱逐 的候选对象。
如果出现这种情况,Pod 中的所有容器都将被终止。Kubernetes 通常会在不同的节点上创建一个替代的 Pod。
Pod 的资源 request 等于其成员容器的资源 request 之和,Pod 的资源 limit 等于其成员容器的资源 limit 之和。
kube-scheduler 在选择要抢占 的
Pod 时不考虑 QoS 类。当集群没有足够的资源来运行你所定义的所有 Pod 时,就会发生抢占。
接下来
1.6 - 用户命名空间
特性状态: Kubernetes v1.25 [alpha]
本页解释了在 Kubernetes Pod 中如何使用用户命名空间。
用户命名空间将容器内运行的用户与主机中的用户隔离开来。
在容器中以 root 身份运行的进程可以在主机中以不同的(非 root)用户身份运行;
换句话说,该进程在用户命名空间内的操作具有完全的权限,
但在命名空间外的操作是无特权的。
你可以使用这个功能来减少被破坏的容器对主机或同一节点中的其他 Pod 的破坏。
有几个安全漏洞 被评为 高 或 重要 ,
当用户命名空间处于激活状态时,这些漏洞是无法被利用的。
预计用户命名空间也会减轻一些未来的漏洞。
准备开始
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
这是一个只对 Linux 有效的功能特性,且需要 Linux 支持在所用文件系统上挂载 idmap。
这意味着:
在节点上,你用于 /var/lib/kubelet/pods/
的文件系统,或你为此配置的自定义目录,
需要支持 idmap 挂载。
Pod 卷中使用的所有文件系统都必须支持 idmap 挂载。
在实践中,这意味着你最低需要 Linux 6.3,因为 tmpfs 在该版本中开始支持 idmap 挂载。
这通常是需要的,因为有几个 Kubernetes 功能特性使用 tmpfs
(默认情况下挂载的服务账号令牌使用 tmpfs、Secret 使用 tmpfs 等等)。
Linux 6.3 中支持 idmap 挂载的一些比较流行的文件系统是:btrfs、ext4、xfs、fat、
tmpfs、overlayfs。
此外,需要在容器运行时 提供支持,
才能在 Kubernetes Pod 中使用这一功能:
CRI-O:1.25(及更高)版本支持配置容器的用户命名空间。
containerd v1.7 与 Kubernetes v1.27 至 v1.29
版本中的用户命名空间不兼容。
Kubernetes v1.25 和 v1.26 使用了早期的实现,在用户命名空间方面与 containerd v1.7 兼容。
如果你使用的 Kubernetes 版本不是 1.29,请查看该版本 Kubernetes
的文档以获取更准确的信息。
如果有比 v1.7 更新的 containerd 版本可供使用,请检查 containerd 文档以获取兼容性信息。
你可以在 GitHub 上的 [issue][CRI-dockerd-issue] 中查看 cri-dockerd
中用户命名空间支持的状态。
介绍
用户命名空间是一个 Linux 功能,允许将容器中的用户映射到主机中的不同用户。
此外,在某用户命名空间中授予 Pod 的权能只在该命名空间中有效,在该命名空间之外无效。
一个 Pod 可以通过将 pod.spec.hostUsers
字段设置为 false
来选择使用用户命名空间。
kubelet 将挑选 Pod 所映射的主机 UID/GID,
并以此保证同一节点上没有两个 Pod 使用相同的方式进行映射。
pod.spec
中的 runAsUser
、runAsGroup
、fsGroup
等字段总是指的是容器内的用户。
启用该功能时,有效的 UID/GID 在 0-65535 范围内。这以限制适用于文件和进程(runAsUser
、runAsGroup
等)。
使用这个范围之外的 UID/GID 的文件将被视为属于溢出 ID,
通常是 65534(配置在 /proc/sys/kernel/overflowuid和/proc/sys/kernel/overflowgid
)。
然而,即使以 65534 用户/组的身份运行,也不可能修改这些文件。
大多数需要以 root 身份运行但不访问其他主机命名空间或资源的应用程序,
在用户命名空间被启用时,应该可以继续正常运行,不需要做任何改变。
了解 Pod 的用户命名空间
一些容器运行时的默认配置(如 Docker Engine、containerd、CRI-O)使用 Linux 命名空间进行隔离。
其他技术也存在,也可以与这些运行时(例如,Kata Containers 使用虚拟机而不是 Linux 命名空间)结合使用。
本页适用于使用 Linux 命名空间进行隔离的容器运行时。
在创建 Pod 时,默认情况下会使用几个新的命名空间进行隔离:
一个网络命名空间来隔离容器网络,一个 PID 命名空间来隔离进程视图等等。
如果使用了一个用户命名空间,这将把容器中的用户与节点中的用户隔离开来。
这意味着容器可以以 root 身份运行,并将该身份映射到主机上的一个非 root 用户。
在容器内,进程会认为它是以 root 身份运行的(因此像 apt
、yum
等工具可以正常工作),
而实际上该进程在主机上没有权限。
你可以验证这一点,例如,如果你从主机上执行 ps aux
来检查容器进程是以哪个用户运行的。
ps
显示的用户与你在容器内执行 id
命令时看到的用户是不一样的。
这种抽象限制了可能发生的情况,例如,容器设法逃逸到主机上时的后果。
鉴于容器是作为主机上的一个非特权用户运行的,它能对主机做的事情是有限的。
此外,由于每个 Pod 上的用户将被映射到主机中不同的非重叠用户,
他们对其他 Pod 可以执行的操作也是有限的。
授予一个 Pod 的权能也被限制在 Pod 的用户命名空间内,
并且在这一命名空间之外大多无效,有些甚至完全无效。这里有两个例子:
CAP_SYS_MODULE
若被授予一个使用用户命名空间的 Pod 则没有任何效果,这个 Pod 不能加载内核模块。
CAP_SYS_ADMIN
只限于 Pod 所在的用户命名空间,在该命名空间之外无效。
在不使用用户命名空间的情况下,以 root 账号运行的容器,在容器逃逸时,在节点上有 root 权限。
而且如果某些权能被授予了某容器,这些权能在宿主机上也是有效的。
当我们使用用户命名空间时,这些都不再成立。
如果你想知道关于使用用户命名空间时的更多变化细节,请参见 man 7 user_namespaces
。
设置一个节点以支持用户命名空间
建议主机的文件和主机的进程使用 0-65535 范围内的 UID/GID。
kubelet 会把高于这个范围的 UID/GID 分配给 Pod。
因此,为了保证尽可能多的隔离,主机的文件和主机的进程所使用的 UID/GID 应该在 0-65535 范围内。
请注意,这个建议对减轻 CVE-2021-25741 等 CVE 的影响很重要;
在这些 CVE 中,Pod 有可能读取主机中的任意文件。
如果 Pod 和主机的 UID/GID 不重叠,Pod 能够做的事情就会受到限制:
Pod 的 UID/GID 不会与主机的文件所有者/组相匹配。
限制
当 Pod 使用用户命名空间时,不允许 Pod 使用其他主机命名空间。
特别是,如果你设置了 hostUsers: false
,那么你就不可以设置如下属性:
hostNetwork: true
hostIPC: true
hostPID: true
接下来
1.7 - Downward API
有两种方法可以将 Pod 和容器字段暴露给运行中的容器:环境变量和由特殊卷类型承载的文件。 这两种暴露 Pod 和容器字段的方法统称为 Downward API。
对于容器来说,在不与 Kubernetes 过度耦合的情况下,拥有关于自身的信息有时是很有用的。
Downward API 允许容器在不使用 Kubernetes 客户端或 API 服务器的情况下获得自己或集群的信息。
例如,现有应用程序假设某特定的周知的环境变量是存在的,其中包含唯一标识符。
一种方法是对应用程序进行封装,但这很繁琐且容易出错,并且违背了低耦合的目标。
更好的选择是使用 Pod 名称作为标识符,并将 Pod 名称注入到周知的环境变量中。
在 Kubernetes 中,有两种方法可以将 Pod 和容器字段暴露给运行中的容器:
这两种暴露 Pod 和容器字段的方式统称为 Downward API 。
可用字段
只有部分 Kubernetes API 字段可以通过 Downward API 使用。本节列出了你可以使用的字段。
你可以使用 fieldRef
传递来自可用的 Pod 级字段的信息。在 API 层面,一个 Pod 的
spec
总是定义了至少一个 Container 。
你可以使用 resourceFieldRef
传递来自可用的 Container 级字段的信息。
可通过 fieldRef
获得的信息
对于某些 Pod 级别的字段,你可以将它们作为环境变量或使用 downwardAPI
卷提供给容器。
通过这两种机制可用的字段有:
metadata.name
Pod 的名称
metadata.namespace
Pod 的命名空间
metadata.uid
Pod 的唯一 ID
metadata.annotations['<KEY>']
Pod 的注解 <KEY>
的值(例如:metadata.annotations['myannotation']
)
metadata.labels['<KEY>']
Pod 的标签 <KEY>
的值(例如:metadata.labels['mylabel']
)
以下信息可以通过环境变量获得,但不能作为 downwardAPI
卷 fieldRef
获得:
spec.serviceAccountName
Pod 的服务账号 名称
spec.nodeName
Pod 运行时所处的节点 名称
status.hostIP
Pod 所在节点的主 IP 地址
status.hostIPs
这组 IP 地址是 status.hostIP
的双协议栈版本,第一个 IP 始终与 status.hostIP
相同。
该字段在启用了 PodHostIPs
特性门控 后可用。
status.podIP
Pod 的主 IP 地址(通常是其 IPv4 地址)
status.podIPs
这组 IP 地址是 status.podIP
的双协议栈版本, 第一个 IP 始终与 status.podIP
相同。
以下信息可以通过 downwardAPI
卷 fieldRef
获得,但不能作为环境变量 获得:
metadata.labels
Pod 的所有标签,格式为 标签键名="转义后的标签值"
,每行一个标签
metadata.annotations
Pod 的全部注解,格式为 注解键名="转义后的注解值"
,每行一个注解
可通过 resourceFieldRef
获得的信息
resource: limits.cpu
容器的 CPU 限制值
resource: requests.cpu
容器的 CPU 请求值
resource: limits.memory
容器的内存限制值
resource: requests.memory
容器的内存请求值
resource: limits.hugepages-*
容器的巨页限制值
resource: requests.hugepages-*
容器的巨页请求值
resource: limits.ephemeral-storage
容器的临时存储的限制值
resource: requests.ephemeral-storage
容器的临时存储的请求值
如果没有为容器指定 CPU 和内存限制时尝试使用 Downward API 暴露该信息,那么 kubelet 默认会根据
节点可分配资源
计算并暴露 CPU 和内存的最大可分配值。
接下来
你可以阅读有关 downwardAPI
卷 的内容。
你可以尝试使用 Downward API 暴露容器或 Pod 级别的信息:
2 - 工作负载资源
Kubernetes 提供了几个内置的 API
来声明式管理工作负载 及其组件。
最终,你的应用以容器的形式在 Pods 中运行;
但是,直接管理单个 Pod 的工作量将会非常繁琐。例如,如果一个 Pod 失败了,你可能希望运行一个新的
Pod 来替换它。Kubernetes 可以为你完成这些操作。
你可以使用 Kubernetes API 创建工作负载对象 ,
这些对象所表达的是比 Pod 更高级别的抽象概念,Kubernetes
控制平面 根据你定义的工作负载对象规约自动管理 Pod 对象。
用于管理工作负载的内置 API 包括:
Deployment
(也间接包括 ReplicaSet )
是在集群上运行应用的最常见方式。Deployment 适合在集群上管理无状态应用工作负载,
其中 Deployment 中的任何 Pod 都是可互换的,可以在需要时进行替换。
(Deployment 替代原来的 ReplicationController API)。
StatefulSet
允许你管理一个或多个运行相同应用代码、但具有不同身份标识的 Pod。
StatefulSet 与 Deployment 不同。Deployment 中的 Pod 预期是可互换的。
StatefulSet 最常见的用途是能够建立其 Pod 与其持久化存储之间的关联。
例如,你可以运行一个将每个 Pod 关联到 PersistentVolume
的 StatefulSet。如果该 StatefulSet 中的一个 Pod 失败了,Kubernetes 将创建一个新的 Pod,
并连接到相同的 PersistentVolume。
DaemonSet
定义了在特定节点 上提供本地设施的 Pod,
例如允许该节点上的容器访问存储系统的驱动。当必须在合适的节点上运行某种驱动或其他节点级别的服务时,
你可以使用 DaemonSet。DaemonSet 中的每个 Pod 执行类似于经典 Unix / POSIX
服务器上的系统守护进程的角色。DaemonSet 可能对集群的操作至关重要,
例如作为插件让该节点访问集群网络 ,
也可能帮助你管理节点,或者提供增强正在运行的容器平台所需的、不太重要的设施。
你可以在集群的每个节点上运行 DaemonSets(及其 Pod),或者仅在某个子集上运行
(例如,只在安装了 GPU 的节点上安装 GPU 加速驱动)。
你可以使用 Job 和/或
CronJob 定义一次性任务和定时任务。
Job 表示一次性任务,而每个 CronJob 可以根据排期表重复执行。
本节中的其他主题:
2.1 - Deployments
Deployment 用于管理运行一个应用负载的一组 Pod,通常适用于不保持状态的负载。
一个 Deployment 为 Pod
和 ReplicaSet
提供声明式的更新能力。
你负责描述 Deployment 中的目标状态 ,而 Deployment 控制器(Controller)
以受控速率更改实际状态,
使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment,
并通过新的 Deployment 收养其资源。
说明:
不要管理 Deployment 所拥有的 ReplicaSet。
如果存在下面未覆盖的使用场景,请考虑在 Kubernetes 仓库中提出 Issue。
用例
以下是 Deployments 的典型用例:
创建 Deployment 以将 ReplicaSet 上线 。ReplicaSet 在后台创建 Pod。
检查 ReplicaSet 的上线状态,查看其是否成功。
通过更新 Deployment 的 PodTemplateSpec,声明 Pod 的新状态 。
新的 ReplicaSet 会被创建,Deployment 以受控速率将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet。
每个新的 ReplicaSet 都会更新 Deployment 的修订版本。
下面是一个 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx
Pod:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
labels :
app : nginx
spec :
replicas : 3
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
在该例中:
创建名为 nginx-deployment
(由 .metadata.name
字段标明)的 Deployment。
该名称将成为后续创建 ReplicaSet 和 Pod 的命名基础。
参阅编写 Deployment 规约 获取更多详细信息。
该 Deployment 创建一个 ReplicaSet,它创建三个(由 .spec.replicas
字段标明)Pod 副本。
.spec.selector
字段定义所创建的 ReplicaSet 如何查找要管理的 Pod。
在这里,你选择在 Pod 模板中定义的标签(app: nginx
)。
不过,更复杂的选择规则是也可能的,只要 Pod 模板本身满足所给规则即可。
说明:
.spec.selector.matchLabels
字段是 {key,value}
键值对映射。
在 matchLabels
映射中的每个 {key,value}
映射等效于 matchExpressions
中的一个元素,
即其 key
字段是 “key”,operator
为 “In”,values
数组仅包含 “value”。
在 matchLabels
和 matchExpressions
中给出的所有条件都必须满足才能匹配。
template
字段包含以下子字段:
Pod 被使用 .metadata.labels
字段打上 app: nginx
标签。
Pod 模板规约(即 .template.spec
字段)指示 Pod 运行一个 nginx
容器,
该容器运行版本为 1.14.2 的 nginx
Docker Hub 镜像。
创建一个容器并使用 .spec.template.spec.containers[0].name
字段将其命名为 nginx
。
开始之前,请确保的 Kubernetes 集群已启动并运行。
按照以下步骤创建上述 Deployment :
通过运行以下命令创建 Deployment :
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
运行 kubectl get deployments
检查 Deployment 是否已创建。
如果仍在创建 Deployment,则输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 0/3 0 0 1s
在检查集群中的 Deployment 时,所显示的字段有:
NAME
列出了名字空间中 Deployment 的名称。
READY
显示应用程序的可用的“副本”数。显示的模式是“就绪个数/期望个数”。
UP-TO-DATE
显示为了达到期望状态已经更新的副本数。
AVAILABLE
显示应用可供用户使用的副本数。
AGE
显示应用程序运行的时间。
请注意期望副本数是根据 .spec.replicas
字段设置 3。
要查看 Deployment 上线状态,运行 kubectl rollout status deployment/nginx-deployment
。
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out
几秒钟后再次运行 kubectl get deployments
。输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 18s
注意 Deployment 已创建全部三个副本,并且所有副本都是最新的(它们包含最新的 Pod 模板)
并且可用。
要查看 Deployment 创建的 ReplicaSet(rs
),运行 kubectl get rs
。
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-75675f5897 3 3 3 18s
ReplicaSet 输出中包含以下字段:
NAME
列出名字空间中 ReplicaSet 的名称;
DESIRED
显示应用的期望副本个数,即在创建 Deployment 时所定义的值。
此为期望状态;
CURRENT
显示当前运行状态中的副本个数;
READY
显示应用中有多少副本可以为用户提供服务;
AGE
显示应用已经运行的时间长度。
注意 ReplicaSet 的名称格式始终为 [Deployment 名称]-[哈希]
。
该名称将成为所创建的 Pod 的命名基础。
其中的哈希
字符串与 ReplicaSet 上的 pod-template-hash
标签一致。
要查看每个 Pod 自动生成的标签,运行 kubectl get pods --show-labels
。
输出类似于:
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897
nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897
所创建的 ReplicaSet 确保总是存在三个 nginx
Pod。
说明:
你必须在 Deployment 中指定适当的选择算符和 Pod 模板标签(在本例中为 app: nginx
)。
标签或者选择算符不要与其他控制器(包括其他 Deployment 和 StatefulSet)重叠。
Kubernetes 不会阻止你这样做,但是如果多个控制器具有重叠的选择算符,
它们可能会发生冲突执行难以预料的操作。
Pod-template-hash 标签
Deployment 控制器将 pod-template-hash
标签添加到 Deployment
所创建或收留的每个 ReplicaSet 。
此标签可确保 Deployment 的子 ReplicaSets 不重叠。
标签是通过对 ReplicaSet 的 PodTemplate
进行哈希处理。
所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet
可能拥有的任何现有 Pod 中。
更新 Deployment
说明:
仅当 Deployment Pod 模板(即 .spec.template
)发生改变时,例如模板的标签或容器镜像被更新,
才会触发 Deployment 上线。其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作。
按照以下步骤更新 Deployment:
先来更新 nginx Pod 以使用 nginx:1.16.1
镜像,而不是 nginx:1.14.2
镜像。
kubectl set image deployment.v1.apps/nginx-deployment nginx = nginx:1.16.1
或者使用下面的命令:
kubectl set image deployment/nginx-deployment nginx = nginx:1.16.1
在这里,deployment/nginx-deployment
表明 Deployment 的名称,nginx
表明需要进行更新的容器,
而 nginx:1.16.1
则表示镜像的新版本以及它的标签。
输出类似于:
deployment.apps/nginx-deployment image updated
或者,可以对 Deployment 执行 edit
操作并将 .spec.template.spec.containers[0].image
从
nginx:1.14.2
更改至 nginx:1.16.1
。
kubectl edit deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment edited
要查看上线状态,运行:
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
或者
deployment "nginx-deployment" successfully rolled out
获取关于已更新的 Deployment 的更多信息:
运行 kubectl get rs
以查看 Deployment 通过创建新的 ReplicaSet 并将其扩容到
3 个副本并将旧 ReplicaSet 缩容到 0 个副本完成了 Pod 的更新操作:
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1564180365 3 3 3 6s
nginx-deployment-2035384211 0 0 0 36s
现在运行 get pods
应仅显示新的 Pod:
输出类似于:
NAME READY STATUS RESTARTS AGE
nginx-deployment-1564180365-khku8 1/1 Running 0 14s
nginx-deployment-1564180365-nacti 1/1 Running 0 14s
nginx-deployment-1564180365-z9gth 1/1 Running 0 14s
下次要更新这些 Pod 时,只需再次更新 Deployment Pod 模板即可。
Deployment 可确保在更新时仅关闭一定数量的 Pod。默认情况下,它确保至少所需 Pod 的 75% 处于运行状态(最大不可用比例为 25%)。
Deployment 还确保仅所创建 Pod 数量只可能比期望 Pod 数高一点点。
默认情况下,它可确保启动的 Pod 个数比期望个数最多多出 125%(最大峰值 25%)。
例如,如果仔细查看上述 Deployment ,将看到它首先创建了一个新的 Pod,然后删除旧的 Pod,
并创建了新的 Pod。它不会杀死旧 Pod,直到有足够数量的新 Pod 已经出现。
在足够数量的旧 Pod 被杀死前并没有创建新 Pod。它确保至少 3 个 Pod 可用,
同时最多总共 4 个 Pod 可用。
当 Deployment 设置为 4 个副本时,Pod 的个数会介于 3 和 5 之间。
获取 Deployment 的更多信息
kubectl describe deployments
输出类似于:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Thu, 30 Nov 2017 10:56:25 +0000
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=2
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-1564180365 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set nginx-deployment-2035384211 to 3
Normal ScalingReplicaSet 24s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 1
Normal ScalingReplicaSet 22s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 2
Normal ScalingReplicaSet 22s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 2
Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 1
Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 3
Normal ScalingReplicaSet 14s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 0
可以看到,当第一次创建 Deployment 时,它创建了一个 ReplicaSet(nginx-deployment-2035384211
)
并将其直接扩容至 3 个副本。更新 Deployment 时,它创建了一个新的 ReplicaSet
(nginx-deployment-1564180365),并将其扩容为 1,等待其就绪;然后将旧 ReplicaSet 缩容到 2,
将新的 ReplicaSet 扩容到 2 以便至少有 3 个 Pod 可用且最多创建 4 个 Pod。
然后,它使用相同的滚动更新策略继续对新的 ReplicaSet 扩容并对旧的 ReplicaSet 缩容。
最后,你将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩容到 0。
说明:
Kubernetes 在计算 availableReplicas
数值时不考虑终止过程中的 Pod,
availableReplicas
的值一定介于 replicas - maxUnavailable
和 replicas + maxSurge
之间。
因此,你可能在上线期间看到 Pod 个数比预期的多,Deployment 所消耗的总的资源也大于
replicas + maxSurge
个 Pod 所用的资源,直到被终止的 Pod 所设置的
terminationGracePeriodSeconds
到期为止。
翻转(多 Deployment 动态更新)
Deployment 控制器每次注意到新的 Deployment 时,都会创建一个 ReplicaSet 以启动所需的 Pod。
如果更新了 Deployment,则控制标签匹配 .spec.selector
但模板不匹配 .spec.template
的 Pod 的现有 ReplicaSet 被缩容。
最终,新的 ReplicaSet 缩放为 .spec.replicas
个副本,
所有旧 ReplicaSets 缩放为 0 个副本。
当 Deployment 正在上线时被更新,Deployment 会针对更新创建一个新的 ReplicaSet
并开始对其扩容,之前正在被扩容的 ReplicaSet 会被翻转,添加到旧 ReplicaSets 列表
并开始缩容。
例如,假定你在创建一个 Deployment 以生成 nginx:1.14.2
的 5 个副本,但接下来
更新 Deployment 以创建 5 个 nginx:1.16.1
的副本,而此时只有 3 个 nginx:1.14.2
副本已创建。在这种情况下,Deployment 会立即开始杀死 3 个 nginx:1.14.2
Pod,
并开始创建 nginx:1.16.1
Pod。它不会等待 nginx:1.14.2
的 5
个副本都创建完成后才开始执行变更动作。
更改标签选择算符
通常不鼓励更新标签选择算符。建议你提前规划选择算符。
在任何情况下,如果需要更新标签选择算符,请格外小心,
并确保自己了解这背后可能发生的所有事情。
说明:
在 API 版本 apps/v1
中,Deployment 标签选择算符在创建后是不可变的。
添加选择算符时要求使用新标签更新 Deployment 规约中的 Pod 模板标签,否则将返回验证错误。
此更改是非重叠的,也就是说新的选择算符不会选择使用旧选择算符所创建的 ReplicaSet 和 Pod,
这会导致创建新的 ReplicaSet 时所有旧 ReplicaSet 都会被孤立。
选择算符的更新如果更改了某个算符的键名,这会导致与添加算符时相同的行为。
删除选择算符的操作会删除从 Deployment 选择算符中删除现有算符。
此操作不需要更改 Pod 模板标签。现有 ReplicaSet 不会被孤立,也不会因此创建新的 ReplicaSet,
但请注意已删除的标签仍然存在于现有的 Pod 和 ReplicaSet 中。
回滚 Deployment
有时,你可能想要回滚 Deployment;例如,当 Deployment 不稳定时(例如进入反复崩溃状态)。
默认情况下,Deployment 的所有上线记录都保留在系统中,以便可以随时回滚
(你可以通过修改修订历史记录限制来更改这一约束)。
说明:
Deployment 被触发上线时,系统就会创建 Deployment 的新的修订版本。
这意味着仅当 Deployment 的 Pod 模板(.spec.template
)发生更改时,才会创建新修订版本
-- 例如,模板的标签或容器镜像发生变化。
其他更新,如 Deployment 的扩缩容操作不会创建 Deployment 修订版本。
这是为了方便同时执行手动缩放或自动缩放。
换言之,当你回滚到较早的修订版本时,只有 Deployment 的 Pod 模板部分会被回滚。
按 Ctrl-C 停止上述上线状态观测。有关上线停滞的详细信息,参考这里 。
你可以看到旧的副本(算上来自 nginx-deployment-1564180365
和 nginx-deployment-2035384211
的副本)有 3 个,
新的副本(来自 nginx-deployment-3066724191
)有 1 个:
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1564180365 3 3 3 25s
nginx-deployment-2035384211 0 0 0 36s
nginx-deployment-3066724191 1 1 0 6s
查看所创建的 Pod,你会注意到新 ReplicaSet 所创建的 1 个 Pod 卡顿在镜像拉取循环中。
输出类似于:
NAME READY STATUS RESTARTS AGE
nginx-deployment-1564180365-70iae 1/1 Running 0 25s
nginx-deployment-1564180365-jbqqo 1/1 Running 0 25s
nginx-deployment-1564180365-hysrc 1/1 Running 0 25s
nginx-deployment-3066724191-08mng 0/1 ImagePullBackOff 0 6s
说明:
Deployment 控制器自动停止有问题的上线过程,并停止对新的 ReplicaSet 扩容。
这行为取决于所指定的 rollingUpdate 参数(具体为 maxUnavailable
)。
默认情况下,Kubernetes 将此值设置为 25%。
获取 Deployment 描述信息:
kubectl describe deployment
输出类似于:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Tue, 15 Mar 2016 14:48:04 -0700
Labels: app=nginx
Selector: app=nginx
Replicas: 3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.161
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
OldReplicaSets: nginx-deployment-1564180365 (3/3 replicas created)
NewReplicaSet: nginx-deployment-3066724191 (1/1 replicas created)
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-2035384211 to 3
22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 1
22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 2
22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 2
21s 21s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 1
21s 21s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 3
13s 13s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 0
13s 13s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-3066724191 to 1
要解决此问题,需要回滚到以前稳定的 Deployment 版本。
检查 Deployment 上线历史
按照如下步骤检查回滚历史:
首先,检查 Deployment 修订历史:
kubectl rollout history deployment/nginx-deployment
输出类似于:
deployments "nginx-deployment"
REVISION CHANGE-CAUSE
1 kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml
2 kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
3 kubectl set image deployment/nginx-deployment nginx=nginx:1.161
CHANGE-CAUSE
的内容是从 Deployment 的 kubernetes.io/change-cause
注解复制过来的。
复制动作发生在修订版本创建时。你可以通过以下方式设置 CHANGE-CAUSE
消息:
使用 kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.16.1"
为 Deployment 添加注解。
手动编辑资源的清单。
要查看修订历史的详细信息,运行:
kubectl rollout history deployment/nginx-deployment --revision= 2
输出类似于:
deployments "nginx-deployment" revision 2
Labels: app=nginx
pod-template-hash=1159050644
Annotations: kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
QoS Tier:
cpu: BestEffort
memory: BestEffort
Environment Variables: <none>
No volumes.
回滚到之前的修订版本
按照下面给出的步骤将 Deployment 从当前版本回滚到以前的版本(即版本 2)。
假定现在你已决定撤消当前上线并回滚到以前的修订版本:
kubectl rollout undo deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment rolled back
或者,你也可以通过使用 --to-revision
来回滚到特定修订版本:
kubectl rollout undo deployment/nginx-deployment --to-revision= 2
输出类似于:
deployment.apps/nginx-deployment rolled back
与回滚相关的指令的更详细信息,请参考
kubectl rollout
。
现在,Deployment 正在回滚到以前的稳定版本。正如你所看到的,Deployment
控制器生成了回滚到修订版本 2 的 DeploymentRollback
事件。
检查回滚是否成功以及 Deployment 是否正在运行,运行:
kubectl get deployment nginx-deployment
输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 30m
获取 Deployment 描述信息:
kubectl describe deployment nginx-deployment
输出类似于:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Sun, 02 Sep 2018 18:17:55 -0500
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=4
kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-c4747d96c (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 12m deployment-controller Scaled up replica set nginx-deployment-75675f5897 to 3
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 1
Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 2
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 2
Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 1
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 3
Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 0
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-595696685f to 1
Normal DeploymentRollback 15s deployment-controller Rolled back deployment "nginx-deployment" to revision 2
Normal ScalingReplicaSet 15s deployment-controller Scaled down replica set nginx-deployment-595696685f to 0
缩放 Deployment
你可以使用如下指令缩放 Deployment:
kubectl scale deployment/nginx-deployment --replicas= 10
输出类似于:
deployment.apps/nginx-deployment scaled
假设集群启用了Pod 的水平自动缩放 ,
你可以为 Deployment 设置自动缩放器,并基于现有 Pod 的 CPU 利用率选择要运行的
Pod 个数下限和上限。
kubectl autoscale deployment/nginx-deployment --min= 10 --max= 15 --cpu-percent= 80
输出类似于:
deployment.apps/nginx-deployment scaled
比例缩放
RollingUpdate 的 Deployment 支持同时运行应用程序的多个版本。
当自动缩放器缩放处于上线进程(仍在进行中或暂停)中的 RollingUpdate Deployment 时,
Deployment 控制器会平衡现有的活跃状态的 ReplicaSets(含 Pod 的 ReplicaSets)中的额外副本,
以降低风险。这称为 比例缩放(Proportional Scaling) 。
例如,你正在运行一个 10 个副本的 Deployment,其
maxSurge =3,maxUnavailable =2。
镜像更新使用 ReplicaSet nginx-deployment-1989198191
启动新的上线过程,
但由于上面提到的 maxUnavailable
要求,该进程被阻塞了。检查上线状态:
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1989198191 5 5 0 9s
nginx-deployment-618515232 8 8 8 1m
然后,出现了新的 Deployment 扩缩请求。自动缩放器将 Deployment 副本增加到 15。
Deployment 控制器需要决定在何处添加 5 个新副本。如果未使用比例缩放,所有 5 个副本
都将添加到新的 ReplicaSet 中。使用比例缩放时,可以将额外的副本分布到所有 ReplicaSet。
较大比例的副本会被添加到拥有最多副本的 ReplicaSet,而较低比例的副本会进入到
副本较少的 ReplicaSet。所有剩下的副本都会添加到副本最多的 ReplicaSet。
具有零副本的 ReplicaSets 不会被扩容。
在上面的示例中,3 个副本被添加到旧 ReplicaSet 中,2 个副本被添加到新 ReplicaSet。
假定新的副本都很健康,上线过程最终应将所有副本迁移到新的 ReplicaSet 中。
要确认这一点,请运行:
输出类似于:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 15 18 7 8 7m
上线状态确认了副本是如何被添加到每个 ReplicaSet 的。
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1989198191 7 7 0 7m
nginx-deployment-618515232 11 11 11 7m
暂停、恢复 Deployment 的上线过程
在你更新一个 Deployment 的时候,或者计划更新它的时候,
你可以在触发一个或多个更新之前暂停 Deployment 的上线过程。
当你准备应用这些变更时,你可以重新恢复 Deployment 上线过程。
这样做使得你能够在暂停和恢复执行之间应用多个修补程序,而不会触发不必要的上线操作。
你可以根据需要执行很多更新操作,例如,可以要使用的资源:
kubectl set resources deployment/nginx-deployment -c= nginx --limits= cpu = 200m,memory= 512Mi
输出类似于:
deployment.apps/nginx-deployment resource requirements updated
暂停 Deployment 上线之前的初始状态将继续发挥作用,但新的更新在 Deployment
上线被暂停期间不会产生任何效果。
说明:
你不可以回滚处于暂停状态的 Deployment,除非先恢复其执行状态。
Deployment 状态
Deployment 的生命周期中会有许多状态。上线新的 ReplicaSet 期间可能处于
Progressing(进行中) ,可能是
Complete(已完成) ,也可能是
Failed(失败) 以至于无法继续进行。
进行中的 Deployment
执行下面的任务期间,Kubernetes 标记 Deployment 为进行中 (Progressing)_:
Deployment 创建新的 ReplicaSet
Deployment 正在为其最新的 ReplicaSet 扩容
Deployment 正在为其旧有的 ReplicaSet(s) 缩容
新的 Pod 已经就绪或者可用(就绪至少持续了 MinReadySeconds 秒)。
当上线过程进入“Progressing”状态时,Deployment 控制器会向 Deployment 的
.status.conditions
中添加包含下面属性的状况条目:
type: Progressing
status: "True"
reason: NewReplicaSetCreated
| reason: FoundNewReplicaSet
| reason: ReplicaSetUpdated
你可以使用 kubectl rollout status
监视 Deployment 的进度。
完成的 Deployment
当 Deployment 具有以下特征时,Kubernetes 将其标记为完成(Complete) ;
与 Deployment 关联的所有副本都已更新到指定的最新版本,这意味着之前请求的所有更新都已完成。
与 Deployment 关联的所有副本都可用。
未运行 Deployment 的旧副本。
当上线过程进入“Complete”状态时,Deployment 控制器会向 Deployment 的
.status.conditions
中添加包含下面属性的状况条目:
type: Progressing
status: "True"
reason: NewReplicaSetAvailable
这一 Progressing
状况的状态值会持续为 "True"
,直至新的上线动作被触发。
即使副本的可用状态发生变化(进而影响 Available
状况),Progressing
状况的值也不会变化。
你可以使用 kubectl rollout status
检查 Deployment 是否已完成。
如果上线成功完成,kubectl rollout status
返回退出代码 0。
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out
从 kubectl rollout
命令获得的返回状态为 0(成功):
0
失败的 Deployment
你的 Deployment 可能会在尝试部署其最新的 ReplicaSet 受挫,一直处于未完成状态。
造成此情况一些可能因素如下:
配额(Quota)不足
就绪探测(Readiness Probe)失败
镜像拉取错误
权限不足
限制范围(Limit Ranges)问题
应用程序运行时的配置错误
检测此状况的一种方法是在 Deployment 规约中指定截止时间参数:
(.spec.progressDeadlineSeconds
)。
.spec.progressDeadlineSeconds
给出的是一个秒数值,Deployment 控制器在(通过 Deployment 状态)
标示 Deployment 进展停滞之前,需要等待所给的时长。
以下 kubectl
命令设置规约中的 progressDeadlineSeconds
,从而告知控制器
在 10 分钟后报告 Deployment 的上线没有进展:
kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
输出类似于:
deployment.apps/nginx-deployment patched
超过截止时间后,Deployment 控制器将添加具有以下属性的 Deployment 状况到
Deployment 的 .status.conditions
中:
type: Progressing
status: "False"
reason: ProgressDeadlineExceeded
这一状况也可能会比较早地失败,因而其状态值被设置为 "False"
,
其原因为 ReplicaSetCreateError
。
一旦 Deployment 上线完成,就不再考虑其期限。
参考
Kubernetes API Conventions
获取更多状态状况相关的信息。
说明:
除了报告 Reason=ProgressDeadlineExceeded
状态之外,Kubernetes 对已停止的
Deployment 不执行任何操作。更高级别的编排器可以利用这一设计并相应地采取行动。
例如,将 Deployment 回滚到其以前的版本。
说明:
如果你暂停了某个 Deployment 上线,Kubernetes 不再根据指定的截止时间检查 Deployment 上线的进展。
你可以在上线过程中间安全地暂停 Deployment 再恢复其执行,这样做不会导致超出最后时限的问题。
Deployment 可能会出现瞬时性的错误,可能因为设置的超时时间过短,
也可能因为其他可认为是临时性的问题。例如,假定所遇到的问题是配额不足。
如果描述 Deployment,你将会注意到以下部分:
kubectl describe deployment nginx-deployment
输出类似于:
<...>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
ReplicaFailure True FailedCreate
<...>
如果运行 kubectl get deployment nginx-deployment -o yaml
,Deployment 状态输出
将类似于这样:
status:
availableReplicas: 2
conditions:
- lastTransitionTime: 2016-10-04T12:25:39Z
lastUpdateTime: 2016-10-04T12:25:39Z
message: Replica set "nginx-deployment-4262182780" is progressing.
reason: ReplicaSetUpdated
status: "True"
type: Progressing
- lastTransitionTime: 2016-10-04T12:25:42Z
lastUpdateTime: 2016-10-04T12:25:42Z
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: 2016-10-04T12:25:39Z
lastUpdateTime: 2016-10-04T12:25:39Z
message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
object-counts, requested: pods=1, used: pods=3, limited: pods=2'
reason: FailedCreate
status: "True"
type: ReplicaFailure
observedGeneration: 3
replicas: 2
unavailableReplicas: 2
最终,一旦超过 Deployment 进度限期,Kubernetes 将更新状态和进度状况的原因:
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded
ReplicaFailure True FailedCreate
可以通过缩容 Deployment 或者缩容其他运行状态的控制器,或者直接在命名空间中增加配额
来解决配额不足的问题。如果配额条件满足,Deployment 控制器完成了 Deployment 上线操作,
Deployment 状态会更新为成功状况(Status=True
和 Reason=NewReplicaSetAvailable
)。
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
type: Available
加上 status: True
意味着 Deployment 具有最低可用性。
最低可用性由 Deployment 策略中的参数指定。
type: Progressing
加上 status: True
表示 Deployment 处于上线过程中,并且正在运行,
或者已成功完成进度,最小所需新副本处于可用。
请参阅对应状况的 Reason 了解相关细节。
在我们的案例中 reason: NewReplicaSetAvailable
表示 Deployment 已完成。
你可以使用 kubectl rollout status
检查 Deployment 是否未能取得进展。
如果 Deployment 已超过进度限期,kubectl rollout status
返回非零退出代码。
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
kubectl rollout
命令的退出状态为 1(表明发生了错误):
1
对失败 Deployment 的操作
可应用于已完成的 Deployment 的所有操作也适用于失败的 Deployment。
你可以对其执行扩缩容、回滚到以前的修订版本等操作,或者在需要对 Deployment 的
Pod 模板应用多项调整时,将 Deployment 暂停。
清理策略
你可以在 Deployment 中设置 .spec.revisionHistoryLimit
字段以指定保留此
Deployment 的多少个旧有 ReplicaSet。其余的 ReplicaSet 将在后台被垃圾回收。
默认情况下,此值为 10。
说明:
显式将此字段设置为 0 将导致 Deployment 的所有历史记录被清空,因此 Deployment 将无法回滚。
金丝雀部署
如果要使用 Deployment 向用户子集或服务器子集上线版本,
则可以遵循资源管理 所描述的金丝雀模式,
创建多个 Deployment,每个版本一个。
编写 Deployment 规约
同其他 Kubernetes 配置一样, Deployment 需要 .apiVersion
,.kind
和 .metadata
字段。
有关配置文件的其他信息,请参考部署 Deployment 、
配置容器和使用 kubectl 管理资源 等相关文档。
当控制面为 Deployment 创建新的 Pod 时,Deployment 的 .metadata.name
是命名这些 Pod 的部分基础。
Deployment 的名称必须是一个合法的
DNS 子域 值,
但这会对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签 规则。
Deployment 还需要
.spec
部分 。
Pod 模板
.spec
中只有 .spec.template
和 .spec.selector
是必需的字段。
.spec.template
是一个 Pod 模板 。
它和 Pod 的语法规则完全相同。
只是这里它是嵌套的,因此不需要 apiVersion
或 kind
。
除了 Pod 的必填字段外,Deployment 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。
对于标签,请确保不要与其他控制器重叠。请参考选择算符 。
只有 .spec.template.spec.restartPolicy
等于 Always
才是被允许的,这也是在没有指定时的默认设置。
副本
.spec.replicas
是指定所需 Pod 的可选字段。它的默认值是1。
如果你对某个 Deployment 执行了手动扩缩操作(例如,通过
kubectl scale deployment deployment --replicas=X
),
之后基于清单对 Deployment 执行了更新操作(例如通过运行
kubectl apply -f deployment.yaml
),那么通过应用清单而完成的更新会覆盖之前手动扩缩所作的变更。
如果一个 HorizontalPodAutoscaler
(或者其他执行水平扩缩操作的类似 API)在管理 Deployment 的扩缩,
则不要设置 .spec.replicas
。
恰恰相反,应该允许 Kubernetes
控制面 来自动管理
.spec.replicas
字段。
选择算符
.spec.selector
是指定本 Deployment 的 Pod
标签选择算符 的必需字段。
.spec.selector
必须匹配 .spec.template.metadata.labels
,否则请求会被 API 拒绝。
在 API apps/v1
版本中,.spec.selector
和 .metadata.labels
如果没有设置的话,
不会被默认设置为 .spec.template.metadata.labels
,所以需要明确进行设置。
同时在 apps/v1
版本中,Deployment 创建后 .spec.selector
是不可变的。
当 Pod 的标签和选择算符匹配,但其模板和 .spec.template
不同时,或者此类 Pod
的总数超过 .spec.replicas
的设置时,Deployment 会终结之。
如果 Pod 总数未达到期望值,Deployment 会基于 .spec.template
创建新的 Pod。
说明:
你不应直接创建与此选择算符匹配的 Pod,也不应通过创建另一个 Deployment 或者类似于
ReplicaSet 或 ReplicationController 这类控制器来创建标签与此选择算符匹配的 Pod。
如果这样做,第一个 Deployment 会认为它创建了这些 Pod。
Kubernetes 不会阻止你这么做。
如果有多个控制器的选择算符发生重叠,则控制器之间会因冲突而无法正常工作。
策略
.spec.strategy
策略指定用于用新 Pod 替换旧 Pod 的策略。
.spec.strategy.type
可以是 “Recreate” 或 “RollingUpdate”。“RollingUpdate” 是默认值。
重新创建 Deployment
如果 .spec.strategy.type==Recreate
,在创建新 Pod 之前,所有现有的 Pod 会被杀死。
说明:
这只会确保为了升级而创建新 Pod 之前其他 Pod 都已终止。如果你升级一个 Deployment,
所有旧版本的 Pod 都会立即被终止。控制器等待这些 Pod 被成功移除之后,
才会创建新版本的 Pod。如果你手动删除一个 Pod,其生命周期是由 ReplicaSet 来控制的,
后者会立即创建一个替换 Pod(即使旧的 Pod 仍然处于 Terminating 状态)。
如果你需要一种“最多 n 个”的 Pod 个数保证,你需要考虑使用
StatefulSet 。
滚动更新 Deployment
Deployment 会在 .spec.strategy.type==RollingUpdate
时,采取
滚动更新的方式更新 Pod。你可以指定 maxUnavailable
和 maxSurge
来控制滚动更新
过程。
最大不可用
.spec.strategy.rollingUpdate.maxUnavailable
是一个可选字段,用来指定
更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是所需
Pod 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。
如果 .spec.strategy.rollingUpdate.maxSurge
为 0,则此值不能为 0。
默认值为 25%。
例如,当此值设置为 30% 时,滚动更新开始时会立即将旧 ReplicaSet 缩容到期望 Pod 个数的70%。
新 Pod 准备就绪后,可以继续缩容旧有的 ReplicaSet,然后对新的 ReplicaSet 扩容,
确保在更新期间可用的 Pod 总数在任何时候都至少为所需的 Pod 个数的 70%。
最大峰值
.spec.strategy.rollingUpdate.maxSurge
是一个可选字段,用来指定可以创建的超出期望
Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pod 的百分比(例如,10%)。
如果 MaxUnavailable
为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。
此字段的默认值为 25%。
例如,当此值为 30% 时,启动滚动更新后,会立即对新的 ReplicaSet 扩容,同时保证新旧 Pod
的总数不超过所需 Pod 总数的 130%。一旦旧 Pod 被杀死,新的 ReplicaSet 可以进一步扩容,
同时确保更新期间的任何时候运行中的 Pod 总数最多为所需 Pod 总数的 130%。
以下是一些使用 maxUnavailable
和 maxSurge
的滚动更新 Deployment 的示例:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
labels :
app : nginx
spec :
replicas : 3
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
strategy :
type : RollingUpdate
rollingUpdate :
maxUnavailable : 1
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
labels :
app : nginx
spec :
replicas : 3
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
strategy :
type : RollingUpdate
rollingUpdate :
maxSurge : 1
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
labels :
app : nginx
spec :
replicas : 3
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
strategy :
type : RollingUpdate
rollingUpdate :
maxSurge : 1
maxUnavailable : 1
进度期限秒数
.spec.progressDeadlineSeconds
是一个可选字段,用于指定系统在报告 Deployment
进展失败 之前等待 Deployment 取得进展的秒数。
这类报告会在资源状态中体现为 type: Progressing
、status: False
、
reason: ProgressDeadlineExceeded
。Deployment 控制器将在默认 600 毫秒内持续重试 Deployment。
将来,一旦实现了自动回滚,Deployment 控制器将在探测到这样的条件时立即回滚 Deployment。
如果指定,则此字段值需要大于 .spec.minReadySeconds
取值。
最短就绪时间
.spec.minReadySeconds
是一个可选字段,用于指定新创建的 Pod
在没有任意容器崩溃情况下的最小就绪时间,
只有超出这个时间 Pod 才被视为可用。默认值为 0(Pod 在准备就绪后立即将被视为可用)。
要了解何时 Pod 被视为就绪,
可参考容器探针 。
修订历史限制
Deployment 的修订历史记录存储在它所控制的 ReplicaSets 中。
.spec.revisionHistoryLimit
是一个可选字段,用来设定出于回滚目的所要保留的旧 ReplicaSet 数量。
这些旧 ReplicaSet 会消耗 etcd 中的资源,并占用 kubectl get rs
的输出。
每个 Deployment 修订版本的配置都存储在其 ReplicaSets 中;因此,一旦删除了旧的 ReplicaSet,
将失去回滚到 Deployment 的对应修订版本的能力。
默认情况下,系统保留 10 个旧 ReplicaSet,但其理想值取决于新 Deployment 的频率和稳定性。
更具体地说,将此字段设置为 0 意味着将清理所有具有 0 个副本的旧 ReplicaSet。
在这种情况下,无法撤消新的 Deployment 上线,因为它的修订历史被清除了。
paused(暂停的)
.spec.paused
是用于暂停和恢复 Deployment 的可选布尔字段。
暂停的 Deployment 和未暂停的 Deployment 的唯一区别是,Deployment 处于暂停状态时,
PodTemplateSpec 的任何修改都不会触发新的上线。
Deployment 在创建时是默认不会处于暂停状态。
接下来
2.2 - ReplicaSet
ReplicaSet 的作用是维持在任何给定时间运行的一组稳定的副本 Pod。 通常,你会定义一个 Deployment,并用这个 Deployment 自动管理 ReplicaSet。
ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。
因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。
ReplicaSet 的工作原理
ReplicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 Pod
的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod
以满足副本个数条件时要使用的 Pod 模板等等。
每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值,
进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。
ReplicaSet 通过 Pod 上的
metadata.ownerReferences
字段连接到附属 Pod,该字段给出当前对象的属主资源。
ReplicaSet 所获得的 Pod 都在其 ownerReferences 字段中包含了属主 ReplicaSet
的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态,
并据此计划其操作行为。
ReplicaSet 使用其选择算符来辨识要获得的 Pod 集合。如果某个 Pod 没有
OwnerReference 或者其 OwnerReference 不是一个控制器 ,
且其匹配到某 ReplicaSet 的选择算符,则该 Pod 立即被此 ReplicaSet 获得。
何时使用 ReplicaSet
ReplicaSet 确保任何时间都有指定数量的 Pod 副本在运行。
然而,Deployment 是一个更高级的概念,它管理 ReplicaSet,并向 Pod
提供声明式的更新以及许多其他有用的功能。
因此,我们建议使用 Deployment 而不是直接使用 ReplicaSet,
除非你需要自定义更新业务流程或根本不需要更新。
这实际上意味着,你可能永远不需要操作 ReplicaSet 对象:而是使用
Deployment,并在 spec 部分定义你的应用。
示例
apiVersion : apps/v1
kind : ReplicaSet
metadata :
name : frontend
labels :
app : guestbook
tier : frontend
spec :
# 按你的实际情况修改副本数
replicas : 3
selector :
matchLabels :
tier : frontend
template :
metadata :
labels :
tier : frontend
spec :
containers :
- name : php-redis
image : gcr.io/google_samples/gb-frontend:v3
将此清单保存到 frontend.yaml
中,并将其提交到 Kubernetes 集群,
就能创建 yaml 文件所定义的 ReplicaSet 及其管理的 Pod。
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你可以看到当前被部署的 ReplicaSet:
并看到你所创建的前端:
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 6s
你也可以查看 ReplicaSet 的状态:
kubectl describe rs/frontend
你会看到类似如下的输出:
Name: frontend
Namespace: default
Selector: tier=frontend
Labels: app=guestbook
tier=frontend
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"labels":{"app":"guestbook","tier":"frontend"},"name":"frontend",...
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: tier=frontend
Containers:
php-redis:
Image: gcr.io/google_samples/gb-frontend:v3
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 117s replicaset-controller Created pod: frontend-wtsmm
Normal SuccessfulCreate 116s replicaset-controller Created pod: frontend-b2zdv
Normal SuccessfulCreate 116s replicaset-controller Created pod: frontend-vcmts
最后可以查看启动了的 Pod 集合:
你会看到类似如下的 Pod 信息:
NAME READY STATUS RESTARTS AGE
frontend-b2zdv 1/1 Running 0 6m36s
frontend-vcmts 1/1 Running 0 6m36s
frontend-wtsmm 1/1 Running 0 6m36s
你也可以查看 Pod 的属主引用被设置为前端的 ReplicaSet。
要实现这点,可取回运行中的某个 Pod 的 YAML:
kubectl get pods frontend-b2zdv -o yaml
输出将类似这样,frontend ReplicaSet 的信息被设置在 metadata 的
ownerReferences
字段中:
apiVersion : v1
kind : Pod
metadata :
creationTimestamp : "2020-02-12T07:06:16Z"
generateName : frontend-
labels :
tier : frontend
name : frontend-b2zdv
namespace : default
ownerReferences :
- apiVersion : apps/v1
blockOwnerDeletion : true
controller : true
kind : ReplicaSet
name : frontend
uid : f391f6db-bb9b-4c09-ae74-6a1f77f3d5cf
...
非模板 Pod 的获得
尽管你完全可以直接创建裸的 Pod,强烈建议你确保这些裸的 Pod 并不包含可能与你的某个
ReplicaSet 的选择算符相匹配的标签。原因在于 ReplicaSet 并不仅限于拥有在其模板中设置的
Pod,它还可以像前面小节中所描述的那样获得其他 Pod。
以前面的 frontend ReplicaSet 为例,并在以下清单中指定这些 Pod:
apiVersion : v1
kind : Pod
metadata :
name : pod1
labels :
tier : frontend
spec :
containers :
- name : hello1
image : gcr.io/google-samples/hello-app:2.0
---
apiVersion : v1
kind : Pod
metadata :
name : pod2
labels :
tier : frontend
spec :
containers :
- name : hello2
image : gcr.io/google-samples/hello-app:1.0
由于这些 Pod 没有控制器(Controller,或其他对象)作为其属主引用,
并且其标签与 frontend ReplicaSet 的选择算符匹配,它们会立即被该 ReplicaSet 获取。
假定你在 frontend ReplicaSet 已经被部署之后创建 Pod,并且你已经在 ReplicaSet
中设置了其初始的 Pod 副本数以满足其副本计数需要:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
新的 Pod 会被该 ReplicaSet 获取,并立即被 ReplicaSet 终止,
因为它们的存在会使得 ReplicaSet 中 Pod 个数超出其期望值。
取回 Pod:
输出显示新的 Pod 或者已经被终止,或者处于终止过程中:
NAME READY STATUS RESTARTS AGE
frontend-b2zdv 1/1 Running 0 10m
frontend-vcmts 1/1 Running 0 10m
frontend-wtsmm 1/1 Running 0 10m
pod1 0/1 Terminating 0 1s
pod2 0/1 Terminating 0 1s
如果你先行创建 Pod:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
之后再创建 ReplicaSet:
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你会看到 ReplicaSet 已经获得了该 Pod,并仅根据其规约创建新的 Pod,
直到新的 Pod 和原来的 Pod 的总数达到其预期个数。
这时取回 Pod 列表:
将会生成下面的输出:
NAME READY STATUS RESTARTS AGE
frontend-hmmj2 1/1 Running 0 9s
pod1 1/1 Running 0 36s
pod2 1/1 Running 0 36s
采用这种方式,一个 ReplicaSet 中可以包含异质的 Pod 集合。
编写 ReplicaSet 的清单
与所有其他 Kubernetes API 对象一样,ReplicaSet 也需要 apiVersion
、kind
、和 metadata
字段。
对于 ReplicaSet 而言,其 kind
始终是 ReplicaSet。
当控制平面为 ReplicaSet 创建新的 Pod 时,ReplicaSet
的 .metadata.name
是命名这些 Pod 的部分基础。ReplicaSet 的名称必须是一个合法的
DNS 子域 值,
但这可能对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签 规则。
ReplicaSet 也需要
.spec
部分。
Pod 模板
.spec.template
是一个 Pod 模板 ,
要求设置标签。在 frontend.yaml
示例中,我们指定了标签 tier: frontend
。
注意不要将标签与其他控制器的选择算符重叠,否则那些控制器会尝试收养此 Pod。
对于模板的重启策略
字段,.spec.template.spec.restartPolicy
,唯一允许的取值是 Always
,这也是默认值.
Pod 选择算符
.spec.selector
字段是一个标签选择算符 。
如前文中所讨论的 ,这些是用来标识要被获取的 Pod
的标签。在签名的 frontend.yaml
示例中,选择算符为:
matchLabels :
tier : frontend
在 ReplicaSet 中,.spec.template.metadata.labels
的值必须与 spec.selector
值相匹配,否则该配置会被 API 拒绝。
说明:
对于设置了相同的 .spec.selector
,但
.spec.template.metadata.labels
和 .spec.template.spec
字段不同的两个
ReplicaSet 而言,每个 ReplicaSet 都会忽略被另一个 ReplicaSet 所创建的 Pod。
Replicas
你可以通过设置 .spec.replicas
来指定要同时运行的 Pod 个数。
ReplicaSet 创建、删除 Pod 以与此值匹配。
如果你没有指定 .spec.replicas
,那么默认值为 1。
使用 ReplicaSet
删除 ReplicaSet 和它的 Pod
要删除 ReplicaSet 和它的所有 Pod,使用
kubectl delete
命令。
默认情况下,垃圾收集器
自动删除所有依赖的 Pod。
当使用 REST API 或 client-go
库时,你必须在 -d
选项中将 propagationPolicy
设置为 Background
或 Foreground
。例如:
kubectl proxy --port= 8080
curl -X DELETE 'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
-H "Content-Type: application/json"
只删除 ReplicaSet
你可以只删除 ReplicaSet 而不影响它的各个 Pod,方法是使用
kubectl delete
命令并设置 --cascade=orphan
选项。
当使用 REST API 或 client-go
库时,你必须将 propagationPolicy
设置为 Orphan
。
例如:
kubectl proxy --port= 8080
curl -X DELETE 'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
-H "Content-Type: application/json"
一旦删除了原来的 ReplicaSet,就可以创建一个新的来替换它。
由于新旧 ReplicaSet 的 .spec.selector
是相同的,新的 ReplicaSet 将接管老的 Pod。
但是,它不会努力使现有的 Pod 与新的、不同的 Pod 模板匹配。
若想要以可控的方式更新 Pod 的规约,可以使用
Deployment
资源,因为 ReplicaSet 并不直接支持滚动更新。
将 Pod 从 ReplicaSet 中隔离
可以通过改变标签来从 ReplicaSet 中移除 Pod。
这种技术可以用来从服务中去除 Pod,以便进行排错、数据恢复等。
以这种方式移除的 Pod 将被自动替换(假设副本的数量没有改变)。
扩缩 ReplicaSet
通过更新 .spec.replicas
字段,ReplicaSet 可以被轻松地进行扩缩。ReplicaSet
控制器能确保匹配标签选择器的数量的 Pod 是可用的和可操作的。
在降低集合规模时,ReplicaSet 控制器通过对可用的所有 Pod 进行排序来优先选择要被删除的那些 Pod。
其一般性算法如下:
首先选择剔除悬决(Pending,且不可调度)的各个 Pod
如果设置了 controller.kubernetes.io/pod-deletion-cost
注解,则注解值较小的优先被裁减掉
所处节点上副本个数较多的 Pod 优先于所处节点上副本较少者
如果 Pod 的创建时间不同,最近创建的 Pod 优先于早前创建的 Pod 被裁减。
(当 LogarithmicScaleDown
这一特性门控
被启用时,创建时间是按整数幂级来分组的)。
如果以上比较结果都相同,则随机选择。
Pod 删除开销
特性状态: Kubernetes v1.22 [beta]
通过使用 controller.kubernetes.io/pod-deletion-cost
注解,用户可以对 ReplicaSet 缩容时要先删除哪些 Pod 设置偏好。
此注解要设置到 Pod 上,取值范围为 [-2147483648, 2147483647]。
所代表的是删除同一 ReplicaSet 中其他 Pod 相比较而言的开销。
删除开销较小的 Pod 比删除开销较高的 Pod 更容易被删除。
Pod 如果未设置此注解,则隐含的设置值为 0。负值也是可接受的。
如果注解值非法,API 服务器会拒绝对应的 Pod。
此功能特性处于 Beta 阶段,默认被启用。你可以通过为 kube-apiserver 和
kube-controller-manager 设置特性门控
PodDeletionCost
来禁用此功能。
说明:
此机制实施时仅是尽力而为,并不能对 Pod 的删除顺序作出任何保证;
用户应避免频繁更新注解值,例如根据某观测度量值来更新此注解值是应该避免的。
这样做会在 API 服务器上产生大量的 Pod 更新操作。
使用场景示例
同一应用的不同 Pod 可能其利用率是不同的。在对应用执行缩容操作时,
可能希望移除利用率较低的 Pod。为了避免频繁更新 Pod,应用应该在执行缩容操作之前更新一次
controller.kubernetes.io/pod-deletion-cost
注解值
(将注解值设置为一个与其 Pod 利用率对应的值)。
如果应用自身控制器缩容操作时(例如 Spark 部署的驱动 Pod),这种机制是可以起作用的。
ReplicaSet 作为水平的 Pod 自动扩缩器目标
ReplicaSet 也可以作为水平的 Pod 扩缩器 (HPA)
的目标。也就是说,ReplicaSet 可以被 HPA 自动扩缩。
以下是 HPA 以我们在前一个示例中创建的副本集为目标的示例。
apiVersion : autoscaling/v1
kind : HorizontalPodAutoscaler
metadata :
name : frontend-scaler
spec :
scaleTargetRef :
kind : ReplicaSet
name : frontend
minReplicas : 3
maxReplicas : 10
targetCPUUtilizationPercentage : 50
将这个列表保存到 hpa-rs.yaml
并提交到 Kubernetes 集群,就能创建它所定义的
HPA,进而就能根据复制的 Pod 的 CPU 利用率对目标 ReplicaSet 进行自动扩缩。
kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml
或者,可以使用 kubectl autoscale
命令完成相同的操作(而且它更简单!)
kubectl autoscale rs frontend --max= 10 --min= 3 --cpu-percent= 50
ReplicaSet 的替代方案
Deployment(推荐)
Deployment
是一个可以拥有
ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。
尽管 ReplicaSet 可以独立使用,目前它们的主要用途是提供给 Deployment 作为编排
Pod 创建、删除和更新的一种机制。当使用 Deployment 时,你不必关心如何管理它所创建的
ReplicaSet,Deployment 拥有并管理其 ReplicaSet。
因此,建议你在需要 ReplicaSet 时使用 Deployment。
裸 Pod
与用户直接创建 Pod 的情况不同,ReplicaSet 会替换那些由于某些原因被删除或被终止的
Pod,例如在节点故障或破坏性的节点维护(如内核升级)的情况下。
因为这个原因,我们建议你使用 ReplicaSet,即使应用程序只需要一个 Pod。
想像一下,ReplicaSet 类似于进程监视器,只不过它在多个节点上监视多个 Pod,
而不是在单个节点上监视单个进程。
ReplicaSet 将本地容器重启的任务委托给了节点上的某个代理(例如,Kubelet)去完成。
Job
使用Job
代替 ReplicaSet,
可以用于那些期望自行终止的 Pod。
DaemonSet
对于管理那些提供主机级别功能(如主机监控和主机日志)的容器,
就要用 DaemonSet
而不用 ReplicaSet。
这些 Pod 的寿命与主机寿命有关:这些 Pod 需要先于主机上的其他 Pod 运行,
并且在机器准备重新启动/关闭时安全地终止。
ReplicationController
ReplicaSet 是 ReplicationController
的后继者。二者目的相同且行为类似,只是 ReplicationController 不支持
标签用户指南
中讨论的基于集合的选择算符需求。
因此,相比于 ReplicationController,应优先考虑 ReplicaSet。
接下来
2.3 - StatefulSet
StatefulSet 运行一组 Pod,并为每个 Pod 保留一个稳定的标识。 这可用于管理需要持久化存储或稳定、唯一网络标识的应用。
StatefulSet 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理某 Pod 集合的部署和扩缩,
并为这些 Pod 提供持久存储和持久标识符。
和 Deployment 类似,
StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是,
StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的,
但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。
尽管 StatefulSet 中的单个 Pod 仍可能出现故障,
但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。
使用 StatefulSet
StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:
稳定的、唯一的网络标识符。
稳定的、持久的存储。
有序的、优雅的部署和扩缩。
有序的、自动的滚动更新。
在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。
如果应用程序不需要任何稳定的标识符或有序的部署、删除或扩缩,
则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如
Deployment 或者
ReplicaSet
可能更适用于你的无状态应用部署需要。
限制
给定 Pod 的存储必须由
PersistentVolume Provisioner
基于所请求的 storage class
来制备,或者由管理员预先制备。
删除或者扩缩 StatefulSet 并不会 删除它关联的存储卷。
这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
StatefulSet 当前需要无头服务 来负责 Pod
的网络标识。你需要负责创建此服务。
当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。
为了实现 StatefulSet 中的 Pod 可以有序且体面地终止,可以在删除之前将 StatefulSet
缩容到 0。
在默认 Pod 管理策略 (OrderedReady
) 时使用滚动更新 ,
可能进入需要人工干预 才能修复的损坏状态。
组件
下面的示例演示了 StatefulSet 的组件。
apiVersion : v1
kind : Service
metadata :
name : nginx
labels :
app : nginx
spec :
ports :
- port : 80
name : web
clusterIP : None
selector :
app : nginx
---
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : web
spec :
selector :
matchLabels :
app : nginx # 必须匹配 .spec.template.metadata.labels
serviceName : "nginx"
replicas : 3 # 默认值是 1
minReadySeconds : 10 # 默认值是 0
template :
metadata :
labels :
app : nginx # 必须匹配 .spec.selector.matchLabels
spec :
terminationGracePeriodSeconds : 10
containers :
- name : nginx
image : registry.k8s.io/nginx-slim:0.8
ports :
- containerPort : 80
name : web
volumeMounts :
- name : www
mountPath : /usr/share/nginx/html
volumeClaimTemplates :
- metadata :
name : www
spec :
accessModes : [ "ReadWriteOnce" ]
storageClassName : "my-storage-class"
resources :
requests :
storage : 1Gi
上述例子中:
名为 nginx
的 Headless Service 用来控制网络域名。
名为 web
的 StatefulSet 有一个 Spec,它表明将在独立的 3 个 Pod 副本中启动 nginx 容器。
volumeClaimTemplates
将通过 PersistentVolume 制备程序所准备的
PersistentVolumes 来提供稳定的存储。
StatefulSet 的命名需要遵循
DNS 标签 规范。
Pod 选择算符
你必须设置 StatefulSet 的 .spec.selector
字段,使之匹配其在
.spec.template.metadata.labels
中设置的标签。
未指定匹配的 Pod 选择算符将在创建 StatefulSet 期间导致验证错误。
卷申领模板
你可以设置 .spec.volumeClaimTemplates
,
它可以使用 PersistentVolume 制备程序所准备的
PersistentVolumes 来提供稳定的存储。
最短就绪秒数
特性状态: Kubernetes v1.25 [stable]
.spec.minReadySeconds
是一个可选字段。
它指定新创建的 Pod 应该在没有任何容器崩溃的情况下运行并准备就绪,才能被认为是可用的。
这用于在使用滚动更新 策略时检查滚动的进度。
该字段默认为 0(Pod 准备就绪后将被视为可用)。
要了解有关何时认为 Pod 准备就绪的更多信息,
请参阅容器探针 。
Pod 标识
StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。
该标识和 Pod 是绑定的,与该 Pod 调度到哪个节点上无关。
序号索引
对于具有 N 个副本 的 StatefulSet,该 StatefulSet 中的每个 Pod 将被分配一个整数序号,
该序号在此 StatefulSet 中是唯一的。默认情况下,这些 Pod 将被赋予从 0 到 N-1 的序号。
StatefulSet 的控制器也会添加一个包含此索引的 Pod 标签:apps.kubernetes.io/pod-index
。
起始序号
特性状态: Kubernetes v1.27 [beta]
.spec.ordinals
是一个可选的字段,允许你配置分配给每个 Pod 的整数序号。
该字段默认为 nil 值。你必须启用 StatefulSetStartOrdinal
特性门控 才能使用此字段。
一旦启用,你就可以配置以下选项:
.spec.ordinals.start
:如果 .spec.ordinals.start
字段被设置,则 Pod 将被分配从
.spec.ordinals.start
到 .spec.ordinals.start + .spec.replicas - 1
的序号。
稳定的网络 ID
StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。
组合主机名的格式为$(StatefulSet 名称)-$(序号)
。
上例将会创建三个名称分别为 web-0、web-1、web-2
的 Pod。
StatefulSet 可以使用无头服务 控制它的
Pod 的网络域。管理域的这个服务的格式为:
$(服务名称).$(名字空间).svc.cluster.local
,其中 cluster.local
是集群域。
一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:
$(pod 名称).$(所属服务的 DNS 域名)
,其中所属服务由 StatefulSet 的 serviceName
域来设定。
取决于集群域内部 DNS 的配置,有可能无法查询一个刚刚启动的 Pod 的 DNS 命名。
当集群内其他客户端在 Pod 创建完成前发出 Pod 主机名查询时,就会发生这种情况。
负缓存 (在 DNS 中较为常见) 意味着之前失败的查询结果会被记录和重用至少若干秒钟,
即使 Pod 已经正常运行了也是如此。
如果需要在 Pod 被创建之后及时发现它们,可使用以下选项:
直接查询 Kubernetes API(比如,利用 watch 机制)而不是依赖于 DNS 查询
缩短 Kubernetes DNS 驱动的缓存时长(通常这意味着修改 CoreDNS 的 ConfigMap,目前缓存时长为 30 秒)
正如限制 中所述,
你需要负责创建无头服务 以便为 Pod 提供网络标识。
下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例:
集群域名
服务(名字空间/名字)
StatefulSet(名字空间/名字)
StatefulSet 域名
Pod DNS
Pod 主机名
cluster.local
default/nginx
default/web
nginx.default.svc.cluster.local
web-{0..N-1}.nginx.default.svc.cluster.local
web-{0..N-1}
cluster.local
foo/nginx
foo/web
nginx.foo.svc.cluster.local
web-{0..N-1}.nginx.foo.svc.cluster.local
web-{0..N-1}
kube.local
foo/nginx
foo/web
nginx.foo.svc.kube.local
web-{0..N-1}.nginx.foo.svc.kube.local
web-{0..N-1}
说明:
集群域会被设置为 cluster.local
,除非有其他配置 。
稳定的存储
对于 StatefulSet 中定义的每个 VolumeClaimTemplate,每个 Pod 接收到一个 PersistentVolumeClaim。
在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class
制备的
1 GiB 的 PersistentVolume。如果没有指定 StorageClass,就会使用默认的 StorageClass。
当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts
会挂载与其
PersistentVolumeClaims 相关联的 PersistentVolume。
请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的
PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。
Pod 名称标签
当 StatefulSet 控制器 创建 Pod 时,
它会添加一个标签 statefulset.kubernetes.io/pod-name
,该标签值设置为 Pod 名称。
这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。
Pod 索引标签
特性状态: Kubernetes v1.28 [beta]
当 StatefulSet 控制器 创建一个 Pod 时,
新的 Pod 会被打上 apps.kubernetes.io/pod-index
标签。标签的取值为 Pod 的序号索引。
此标签使你能够将流量路由到特定索引值的 Pod、使用 Pod 索引标签来过滤日志或度量值等等。
注意要使用这一特性需要启用特性门控 PodIndexLabel
,而该门控默认是被启用的。
部署和扩缩保证
对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0..N-1
。
当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0
。
在将扩缩操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。
在一个 Pod 终止之前,所有的继任者必须完全关闭。
StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds
设置为 0。
这种做法是不安全的,要强烈阻止。
更多的解释请参考强制删除 StatefulSet Pod 。
在上面的 nginx 示例被创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。
在 web-0 进入 Running 和 Ready
状态前不会部署 web-1。在 web-1 进入 Running 和 Ready 状态前不会部署 web-2。
如果 web-1 已经处于 Running 和 Ready 状态,而 web-2 尚未部署,在此期间发生了
web-0 运行失败,那么 web-2 将不会被部署,要等到 web-0 部署完成并进入 Running 和
Ready 状态后,才会部署 web-2。
如果用户想将示例中的 StatefulSet 扩缩为 replicas=1
,首先被终止的是 web-2。
在 web-2 没有被完全停止和删除前,web-1 不会被终止。
当 web-2 已被终止和删除、web-1 尚未被终止,如果在此期间发生 web-0 运行失败,
那么就不会终止 web-1,必须等到 web-0 进入 Running 和 Ready 状态后才会终止 web-1。
Pod 管理策略
StatefulSet 允许你放宽其排序保证,
同时通过它的 .spec.podManagementPolicy
域保持其唯一性和身份保证。
OrderedReady Pod 管理
OrderedReady
Pod 管理是 StatefulSet 的默认设置。
它实现了上面 描述的功能。
并行 Pod 管理
Parallel
Pod 管理让 StatefulSet 控制器并行的启动或终止所有的 Pod,
启动或者终止其他 Pod 前,无需等待 Pod 进入 Running 和 Ready 或者完全停止状态。
这个选项只会影响扩缩操作的行为,更新则不会被影响。
更新策略
StatefulSet 的 .spec.updateStrategy
字段让你可以配置和禁用掉自动滚动更新 Pod
的容器、标签、资源请求或限制、以及注解。有两个允许的值:
OnDelete
当 StatefulSet 的 .spec.updateStrategy.type
设置为 OnDelete
时,
它的控制器将不会自动更新 StatefulSet 中的 Pod。
用户必须手动删除 Pod 以便让控制器创建新的 Pod,以此来对 StatefulSet 的
.spec.template
的变动作出反应。
RollingUpdate
RollingUpdate
更新策略对 StatefulSet 中的 Pod 执行自动的滚动更新。这是默认的更新策略。
滚动更新
当 StatefulSet 的 .spec.updateStrategy.type
被设置为 RollingUpdate
时,
StatefulSet 控制器会删除和重建 StatefulSet 中的每个 Pod。
它将按照与 Pod 终止相同的顺序(从最大序号到最小序号)进行,每次更新一个 Pod。
Kubernetes 控制平面会等到被更新的 Pod 进入 Running 和 Ready 状态,然后再更新其前身。
如果你设置了 .spec.minReadySeconds
(查看最短就绪秒数 ),
控制平面在 Pod 就绪后会额外等待一定的时间再执行下一步。
分区滚动更新
通过声明 .spec.updateStrategy.rollingUpdate.partition
的方式,RollingUpdate
更新策略可以实现分区。
如果声明了一个分区,当 StatefulSet 的 .spec.template
被更新时,
所有序号大于等于该分区序号的 Pod 都会被更新。
所有序号小于该分区序号的 Pod 都不会被更新,并且,即使它们被删除也会依据之前的版本进行重建。
如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition
大于它的
.spec.replicas
,则对它的 .spec.template
的更新将不会传递到它的 Pod。
在大多数情况下,你不需要使用分区,但如果你希望进行阶段更新、执行金丝雀或执行分阶段上线,则这些分区会非常有用。
最大不可用 Pod
特性状态: Kubernetes v1.24 [alpha]
你可以通过指定 .spec.updateStrategy.rollingUpdate.maxUnavailable
字段来控制更新期间不可用的 Pod 的最大数量。
该值可以是绝对值(例如,“5”)或者是期望 Pod 个数的百分比(例如,10%
)。
绝对值是根据百分比值四舍五入计算的。
该字段不能为 0。默认设置为 1。
该字段适用于 0
到 replicas - 1
范围内的所有 Pod。
如果在 0
到 replicas - 1
范围内存在不可用 Pod,这类 Pod 将被计入 maxUnavailable
值。
说明:
maxUnavailable
字段处于 Alpha 阶段,仅当 API 服务器启用了 MaxUnavailableStatefulSet
特性门控 时才起作用。
强制回滚
在默认 Pod 管理策略 (OrderedReady
) 下使用滚动更新 ,
可能进入需要人工干预才能修复的损坏状态。
如果更新后 Pod 模板配置进入无法运行或就绪的状态(例如,
由于错误的二进制文件或应用程序级配置错误),StatefulSet 将停止回滚并等待。
在这种状态下,仅将 Pod 模板还原为正确的配置是不够的。
由于已知问题 ,StatefulSet
将继续等待损坏状态的 Pod 准备就绪(永远不会发生),然后再尝试将其恢复为正常工作配置。
恢复模板后,还必须删除 StatefulSet 尝试使用错误的配置来运行的 Pod。这样,
StatefulSet 才会开始使用被还原的模板来重新创建 Pod。
PersistentVolumeClaim 保留
特性状态: Kubernetes v1.27 [beta]
在 StatefulSet 的生命周期中,可选字段
.spec.persistentVolumeClaimRetentionPolicy
控制是否删除以及如何删除 PVC。
使用该字段,你必须在 API 服务器和控制器管理器启用 StatefulSetAutoDeletePVC
特性门控 。
启用后,你可以为每个 StatefulSet 配置两个策略:
whenDeleted
配置删除 StatefulSet 时应用的卷保留行为。
whenScaled
配置当 StatefulSet 的副本数减少时应用的卷保留行为;例如,缩小集合时。
对于你可以配置的每个策略,你可以将值设置为 Delete
或 Retain
。
Delete
对于受策略影响的每个 Pod,基于 StatefulSet 的 volumeClaimTemplate
字段创建的 PVC 都会被删除。
使用 whenDeleted
策略,所有来自 volumeClaimTemplate
的 PVC 在其 Pod 被删除后都会被删除。
使用 whenScaled
策略,只有与被缩减的 Pod 副本对应的 PVC 在其 Pod 被删除后才会被删除。
Retain
(默认)
来自 volumeClaimTemplate
的 PVC 在 Pod 被删除时不受影响。这是此新功能之前的行为。
请记住,这些策略仅 适用于由于 StatefulSet 被删除或被缩小而被删除的 Pod。
例如,如果与 StatefulSet 关联的 Pod 由于节点故障而失败,
并且控制平面创建了替换 Pod,则 StatefulSet 保留现有的 PVC。
现有卷不受影响,集群会将其附加到新 Pod 即将启动的节点上。
策略的默认值为 Retain
,与此新功能之前的 StatefulSet 行为相匹配。
这是一个示例策略。
apiVersion : apps/v1
kind : StatefulSet
...
spec :
persistentVolumeClaimRetentionPolicy :
whenDeleted : Retain
whenScaled : Delete
...
StatefulSet 控制器 为其 PVC
添加了属主引用 ,
这些 PVC 在 Pod 终止后被垃圾回收器 删除。
这使 Pod 能够在删除 PVC 之前(以及在删除后备 PV 和卷之前,取决于保留策略)干净地卸载所有卷。
当你设置 whenDeleted
删除策略,对 StatefulSet 实例的属主引用放置在与该 StatefulSet 关联的所有 PVC 上。
whenScaled
策略必须仅在 Pod 缩减时删除 PVC,而不是在 Pod 因其他原因被删除时删除。
执行协调操作时,StatefulSet 控制器将其所需的副本数与集群上实际存在的 Pod 进行比较。
对于 StatefulSet 中的所有 Pod 而言,如果其 ID 大于副本数,则将被废弃并标记为需要删除。
如果 whenScaled
策略是 Delete
,则在删除 Pod 之前,
首先将已销毁的 Pod 设置为与 StatefulSet 模板对应的 PVC 的属主。
这会导致 PVC 仅在已废弃的 Pod 终止后被垃圾收集。
这意味着如果控制器崩溃并重新启动,在其属主引用更新到适合策略的 Pod 之前,不会删除任何 Pod。
如果在控制器关闭时强制删除了已废弃的 Pod,则属主引用可能已被设置,也可能未被设置,具体取决于控制器何时崩溃。
更新属主引用可能需要几个协调循环,因此一些已废弃的 Pod 可能已经被设置了属主引用,而其他可能没有。
出于这个原因,我们建议等待控制器恢复,控制器将在终止 Pod 之前验证属主引用。
如果这不可行,则操作员应验证 PVC 上的属主引用,以确保在强制删除 Pod 时删除预期的对象。
副本数
.spec.replicas
是一个可选字段,用于指定所需 Pod 的数量。它的默认值为 1。
如果你手动扩缩已部署的负载,例如通过 kubectl scale statefulset statefulset --replicas=X
,
然后根据清单更新 StatefulSet(例如:通过运行 kubectl apply -f statefulset.yaml
),
那么应用该清单的操作会覆盖你之前所做的手动扩缩。
如果 HorizontalPodAutoscaler
(或任何类似的水平扩缩 API)正在管理 StatefulSet 的扩缩,
请不要设置 .spec.replicas
。
相反,允许 Kubernetes 控制平面自动管理 .spec.replicas
字段。
接下来
2.4 - DaemonSet
DaemonSet 定义了提供节点本地设施的 Pod。这些设施可能对于集群的运行至关重要,例如网络辅助工具,或者作为 add-on 的一部分。
DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。
当有节点加入集群时, 也会为他们新增一个 Pod 。
当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
DaemonSet 的一些典型用法:
在每个节点上运行集群守护进程
在每个节点上运行日志收集守护进程
在每个节点上运行监控守护进程
一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。
一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志,
并且对不同硬件类型具有不同的内存、CPU 要求。
编写 DaemonSet Spec
创建 DaemonSet
你可以在 YAML 文件中描述 DaemonSet。
例如,下面的 daemonset.yaml 文件描述了一个运行 fluentd-elasticsearch Docker 镜像的 DaemonSet:
apiVersion : apps/v1
kind : DaemonSet
metadata :
name : fluentd-elasticsearch
namespace : kube-system
labels :
k8s-app : fluentd-logging
spec :
selector :
matchLabels :
name : fluentd-elasticsearch
template :
metadata :
labels :
name : fluentd-elasticsearch
spec :
tolerations :
# 这些容忍度设置是为了让该守护进程集在控制平面节点上运行
# 如果你不希望自己的控制平面节点运行 Pod,可以删除它们
- key : node-role.kubernetes.io/control-plane
operator : Exists
effect : NoSchedule
- key : node-role.kubernetes.io/master
operator : Exists
effect : NoSchedule
containers :
- name : fluentd-elasticsearch
image : quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources :
limits :
memory : 200Mi
requests :
cpu : 100m
memory : 200Mi
volumeMounts :
- name : varlog
mountPath : /var/log
# 可能需要设置较高的优先级类以确保 DaemonSet Pod 可以抢占正在运行的 Pod
# priorityClassName: important
terminationGracePeriodSeconds : 30
volumes :
- name : varlog
hostPath :
path : /var/log
基于 YAML 文件创建 DaemonSet:
kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml
必需字段
与所有其他 Kubernetes 配置一样,DaemonSet 也需要 apiVersion
、kind
和 metadata
字段。
有关使用这些配置文件的通用信息,
参见运行无状态应用 和使用 kubectl 管理对象 。
DaemonSet 对象的名称必须是一个合法的
DNS 子域名 。
DaemonSet 也需要 .spec
节区。
Pod 模板
.spec
中唯一必需的字段是 .spec.template
。
.spec.template
是一个 Pod 模板 。
除了它是嵌套的,因而不具有 apiVersion
或 kind
字段之外,它与
Pod 具有相同的 schema。
除了 Pod 必需字段外,在 DaemonSet 中的 Pod 模板必须指定合理的标签(查看 Pod 选择算符 )。
在 DaemonSet 中的 Pod 模板必须具有一个值为 Always
的
RestartPolicy
。
当该值未指定时,默认是 Always
。
Pod 选择算符
.spec.selector
字段表示 Pod 选择算符,它与
Job 的 .spec.selector
的作用是相同的。
你必须指定与 .spec.template
的标签匹配的 Pod 选择算符。
此外,一旦创建了 DaemonSet,它的 .spec.selector
就不能修改。
修改 Pod 选择算符可能导致 Pod 意外悬浮,并且这对用户来说是费解的。
spec.selector
是一个对象,如下两个字段组成:
matchLabels
- 与 ReplicationController
的 .spec.selector
的作用相同。
matchExpressions
- 允许构建更加复杂的选择器,可以通过指定 key、value
列表以及将 key 和 value 列表关联起来的 Operator。
当上述两个字段都指定时,结果会按逻辑与(AND)操作处理。
.spec.selector
必须与 .spec.template.metadata.labels
相匹配。
如果配置中这两个字段不匹配,则会被 API 拒绝。
在选定的节点上运行 Pod
如果指定了 .spec.template.spec.nodeSelector
,DaemonSet 控制器将在能够与
Node 选择算符 匹配的节点上创建 Pod。
类似这种情况,可以指定 .spec.template.spec.affinity
,之后 DaemonSet
控制器将在能够与节点亲和性 匹配的节点上创建 Pod。
如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。
Daemon Pods 是如何被调度的
DaemonSet 可用于确保所有符合条件的节点都运行该 Pod 的一个副本。
DaemonSet 控制器为每个符合条件的节点创建一个 Pod,并添加 Pod 的 spec.affinity.nodeAffinity
字段以匹配目标主机。Pod 被创建之后,默认的调度程序通常通过设置 .spec.nodeName
字段来接管 Pod 并将
Pod 绑定到目标主机。如果新的 Pod 无法放在节点上,则默认的调度程序可能会根据新 Pod
的优先级 抢占
(驱逐)某些现存的 Pod。
说明:
当 DaemonSet 中的 Pod 必须运行在每个节点上时,通常需要将 DaemonSet
的 .spec.template.spec.priorityClassName
设置为具有更高优先级的
PriorityClass ,
以确保可以完成驱逐。
用户通过设置 DaemonSet 的 .spec.template.spec.schedulerName
字段,可以为 DaemonSet
的 Pod 指定不同的调度程序。
当评估符合条件的节点时,原本在 .spec.template.spec.affinity.nodeAffinity
字段上指定的节点亲和性将由
DaemonSet 控制器进行考量,但在创建的 Pod 上会被替换为与符合条件的节点名称匹配的节点亲和性。
ScheduleDaemonSetPods
允许你使用默认调度器而不是 DaemonSet 控制器来调度这些 DaemonSet,
方法是将 NodeAffinity
条件而不是 .spec.nodeName
条件添加到这些 DaemonSet Pod。
默认调度器接下来将 Pod 绑定到目标主机。
如果 DaemonSet Pod 的节点亲和性配置已存在,则被替换
(原始的节点亲和性配置在选择目标主机之前被考虑)。
DaemonSet 控制器仅在创建或修改 DaemonSet Pod 时执行这些操作,
并且不会更改 DaemonSet 的 spec.template
。
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchFields :
- key : metadata.name
operator : In
values :
- target-host-name
污点和容忍度
DaemonSet 控制器会自动将一组容忍度添加到 DaemonSet Pod:
你也可以在 DaemonSet 的 Pod 模板中定义自己的容忍度并将其添加到 DaemonSet Pod。
因为 DaemonSet 控制器自动设置 node.kubernetes.io/unschedulable:NoSchedule
容忍度,
所以 Kubernetes 可以在标记为不可调度 的节点上运行 DaemonSet Pod。
如果你使用 DaemonSet 提供重要的节点级别功能,
例如集群联网 ,
Kubernetes 在节点就绪之前将 DaemonSet Pod 放到节点上会很有帮助。
例如,如果没有这种特殊的容忍度,因为网络插件未在节点上运行,所以你可能会在未标记为就绪的节点上陷入死锁状态,
同时因为该节点还未就绪,所以网络插件不会在该节点上运行。
与 Daemon Pod 通信
与 DaemonSet 中的 Pod 进行通信的几种可能模式如下:
推送(Push) :配置 DaemonSet 中的 Pod,将更新发送到另一个服务,例如统计数据库。
这些服务没有客户端。
NodeIP 和已知端口 :DaemonSet 中的 Pod 可以使用 hostPort
,从而可以通过节点 IP
访问到 Pod。客户端能通过某种方法获取节点 IP 列表,并且基于此也可以获取到相应的端口。
DNS :创建具有相同 Pod 选择算符的无头服务 ,
通过使用 endpoints
资源或从 DNS 中检索到多个 A 记录来发现 DaemonSet。
Service :创建具有相同 Pod 选择算符的服务,并使用该服务随机访问到某个节点上的守护进程(没有办法访问到特定节点)。
更新 DaemonSet
如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod,
同时删除不匹配的节点上的 Pod。
你可以修改 DaemonSet 创建的 Pod。不过并非 Pod 的所有字段都可更新。
下次当某节点(即使具有相同的名称)被创建时,DaemonSet 控制器还会使用最初的模板。
你可以删除一个 DaemonSet。如果使用 kubectl
并指定 --cascade=orphan
选项,
则 Pod 将被保留在节点上。接下来如果创建使用相同选择算符的新 DaemonSet,
新的 DaemonSet 会收养已有的 Pod。
如果有 Pod 需要被替换,DaemonSet 会根据其 updateStrategy
来替换。
你可以对 DaemonSet 执行滚动更新 操作。
DaemonSet 的替代方案
init 脚本
直接在节点上启动守护进程(例如使用 init
、upstartd
或 systemd
)的做法当然是可行的。
不过,基于 DaemonSet 来运行这些进程有如下一些好处:
像所运行的其他应用一样,DaemonSet 具备为守护进程提供监控和日志管理的能力。
为守护进程和应用所使用的配置语言和工具(如 Pod 模板、kubectl
)是相同的。
在资源受限的容器中运行守护进程能够增加守护进程和应用容器的隔离性。
然而,这一点也可以通过在容器中运行守护进程但却不在 Pod 中运行之来实现。
裸 Pod
直接创建 Pod并指定其运行在特定的节点上也是可以的。
然而,DaemonSet 能够替换由于任何原因(例如节点失败、例行节点维护、内核升级)
而被删除或终止的 Pod。
由于这个原因,你应该使用 DaemonSet 而不是单独创建 Pod。
静态 Pod
通过在一个指定的、受 kubelet
监视的目录下编写文件来创建 Pod 也是可行的。
这类 Pod 被称为静态 Pod 。
不像 DaemonSet,静态 Pod 不受 kubectl
和其它 Kubernetes API 客户端管理。
静态 Pod 不依赖于 API 服务器,这使得它们在启动引导新集群的情况下非常有用。
此外,静态 Pod 在将来可能会被废弃。
Deployment
DaemonSet 与 Deployment 非常类似,
它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(例如,Web 服务器、存储服务器)。
建议为无状态的服务使用 Deployment,比如前端服务。
对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要得多。
当需要 Pod 副本总是运行在全部或特定主机上,并且当该 DaemonSet 提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时,
应该使用 DaemonSet。
例如,网络插件 通常包含一个以 DaemonSet 运行的组件。
这个 DaemonSet 组件确保它所在的节点的集群网络正常工作。
接下来
2.5 - Job
Job 表示一次性任务,运行完成后就会停止。
Job 会创建一个或者多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。
随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。
当数量达到指定的成功个数阈值时,任务(即 Job)结束。
删除 Job 的操作会清除所创建的全部 Pod。
挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。
当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job
对象会启动一个新的 Pod。
你也可以使用 Job 以并行的方式运行多个 Pod。
如果你想按某种排期表(Schedule)运行 Job(单个任务或多个并行任务),请参阅
CronJob 。
运行示例 Job
下面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。
此计算大约需要 10 秒钟完成。
apiVersion : batch/v1
kind : Job
metadata :
name : pi
spec :
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
backoffLimit : 4
你可以使用下面的命令来运行此示例:
kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml
输出类似于:
job.batch/pi created
使用 kubectl
来检查 Job 的状态:
Name: pi
Namespace: default
Selector: batch.kubernetes.io/controller-uid= c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels: batch.kubernetes.io/controller-uid= c9948307-e56d-4b5d-8302-ae2d7b7da67c
batch.kubernetes.io/job-name= pi
...
Annotations: batch.kubernetes.io/job-tracking: ""
Parallelism: 1
Completions: 1
Start Time: Mon, 02 Dec 2019 15:20:11 +0200
Completed At: Mon, 02 Dec 2019 15:21:16 +0200
Duration: 65s
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: batch.kubernetes.io/controller-uid= c9948307-e56d-4b5d-8302-ae2d7b7da67c
batch.kubernetes.io/job-name= pi
Containers:
pi:
Image: perl:5.34.0
Port: <none>
Host Port: <none>
Command:
perl
-Mbignum= bpi
-wle
print bpi( 2000)
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 21s job-controller Created pod: pi-xf9p4
Normal Completed 18s job-controller Job completed
apiVersion: batch/v1
kind: Job
metadata:
annotations: batch.kubernetes.io/job-tracking: ""
...
creationTimestamp: "2022-11-10T17:53:53Z"
generation: 1
labels:
batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
batch.kubernetes.io/job-name: pi
name: pi
namespace: default
resourceVersion: "4751"
uid: 204fb678-040b-497f-9266-35ffa8716d14
spec:
backoffLimit: 4
completionMode: NonIndexed
completions: 1
parallelism: 1
selector:
matchLabels:
batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
suspend: false
template:
metadata:
creationTimestamp: null
labels:
batch.kubernetes.io/controller-uid: 863452e6-270d-420e-9b94-53a54146c223
batch.kubernetes.io/job-name: pi
spec:
containers:
- command:
- perl
- -Mbignum= bpi
- -wle
- print bpi( 2000)
image: perl:5.34.0
imagePullPolicy: IfNotPresent
name: pi
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Never
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
active: 1
ready: 0
startTime: "2022-11-10T17:53:57Z"
uncountedTerminatedPods: {}
要查看 Job 对应的已完成的 Pod,可以执行 kubectl get pods
。
要以机器可读的方式列举隶属于某 Job 的全部 Pod,你可以使用类似下面这条命令:
pods = $( kubectl get pods --selector= batch.kubernetes.io/job-name= pi --output= jsonpath = '{.items[*].metadata.name}' )
echo $pods
输出类似于:
pi-5rwd7
这里,选择算符与 Job 的选择算符相同。--output=jsonpath
选项给出了一个表达式,
用来从返回的列表中提取每个 Pod 的 name 字段。
查看其中一个 Pod 的标准输出:
另外一种查看 Job 日志的方法:
输出类似于:
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
编写 Job 规约
与 Kubernetes 中其他资源的配置类似,Job 也需要 apiVersion
、kind
和 metadata
字段。
当控制面为 Job 创建新的 Pod 时,Job 的 .metadata.name
是命名这些 Pod 的基础组成部分。
Job 的名字必须是合法的 DNS 子域名 值,
但这可能对 Pod 主机名产生意料之外的结果。为了获得最佳兼容性,此名字应遵循更严格的
DNS 标签 规则。
即使该名字被要求遵循 DNS 子域名规则,也不得超过 63 个字符。
Job 配置还需要一个 .spec
节 。
Job 标签
Job 标签将为 job-name
和 controller-uid
加上 batch.kubernetes.io/
前缀。
Pod 模板
Job 的 .spec
中只有 .spec.template
是必需的字段。
字段 .spec.template
的值是一个 Pod 模板 。
其定义规范与 Pod
完全相同,只是其中不再需要 apiVersion
或 kind
字段。
除了作为 Pod 所必需的字段之外,Job 中的 Pod 模板必须设置合适的标签
(参见 Pod 选择算符 )和合适的重启策略。
Job 中 Pod 的 RestartPolicy
只能设置为 Never
或 OnFailure
之一。
Pod 选择算符
字段 .spec.selector
是可选的。在绝大多数场合,你都不需要为其赋值。
参阅设置自己的 Pod 选择算符 。
Job 的并行执行
适合以 Job 形式来运行的任务主要有三种:
非并行 Job:
通常只启动一个 Pod,除非该 Pod 失败。
当 Pod 成功终止时,立即视 Job 为完成状态。
具有确定完成计数 的并行 Job:
.spec.completions
字段设置为非 0 的正数值。
Job 用来代表整个任务,当成功的 Pod 个数达到 .spec.completions
时,Job 被视为完成。
当使用 .spec.completionMode="Indexed"
时,每个 Pod 都会获得一个不同的
索引值,介于 0 和 .spec.completions-1
之间。
带工作队列 的并行 Job:
不设置 spec.completions
,默认值为 .spec.parallelism
。
多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目。
例如,任一 Pod 都可以从工作队列中取走最多 N 个工作条目。
每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成。
当 Job 中任何 Pod 成功终止,不再创建新 Pod。
一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成。
一旦任何 Pod 成功退出,任何其它 Pod 都不应再对此任务执行任何操作或生成任何输出。
所有 Pod 都应启动退出过程。
对于非并行 的 Job,你可以不设置 spec.completions
和 spec.parallelism
。
这两个属性都不设置时,均取默认值 1。
对于确定完成计数 类型的 Job,你应该设置 .spec.completions
为所需要的完成个数。
你可以设置 .spec.parallelism
,也可以不设置。其默认值为 1。
对于一个工作队列 Job,你不可以设置 .spec.completions
,但要将.spec.parallelism
设置为一个非负整数。
关于如何利用不同类型的 Job 的更多信息,请参见 Job 模式 一节。
控制并行性
并行性请求(.spec.parallelism
)可以设置为任何非负整数。
如果未设置,则默认为 1。
如果设置为 0,则 Job 相当于启动之后便被暂停,直到此值被增加。
实际并行性(在任意时刻运行状态的 Pod 个数)可能比并行性请求略大或略小,
原因如下:
对于确定完成计数 Job,实际上并行执行的 Pod 个数不会超出剩余的完成数。
如果 .spec.parallelism
值较高,会被忽略。
对于工作队列 Job,有任何 Job 成功结束之后,不会有新的 Pod 启动。
不过,剩下的 Pod 允许执行完毕。
如果 Job 控制器 没有来得及作出响应,或者
如果 Job 控制器因为任何原因(例如,缺少 ResourceQuota
或者没有权限)无法创建 Pod。
Pod 个数可能比请求的数目小。
Job 控制器可能会因为之前同一 Job 中 Pod 失效次数过多而压制新 Pod 的创建。
当 Pod 处于体面终止进程中,需要一定时间才能停止。
完成模式
特性状态: Kubernetes v1.24 [stable]
带有确定完成计数 的 Job,即 .spec.completions
不为 null 的 Job,
都可以在其 .spec.completionMode
中设置完成模式:
NonIndexed
(默认值):当成功完成的 Pod 个数达到 .spec.completions
所
设值时认为 Job 已经完成。换言之,每个 Job 完成事件都是独立无关且同质的。
要注意的是,当 .spec.completions
取值为 null 时,Job 被隐式处理为 NonIndexed
。
Indexed
:Job 的 Pod 会获得对应的完成索引,取值为 0 到 .spec.completions-1
。
该索引可以通过四种方式获取:
Pod 注解 batch.kubernetes.io/job-completion-index
。
Pod 标签 batch.kubernetes.io/job-completion-index
(适用于 v1.28 及更高版本)。
请注意,必须启用 PodIndexLabel
特性门控才能使用此标签,默认被启用。
作为 Pod 主机名的一部分,遵循模式 $(job-name)-$(index)
。
当你同时使用带索引的 Job(Indexed Job)与 服务(Service) ,
Job 中的 Pod 可以通过 DNS 使用确切的主机名互相寻址。
有关如何配置的更多信息,请参阅带 Pod 间通信的 Job 。
对于容器化的任务,在环境变量 JOB_COMPLETION_INDEX
中。
当每个索引都对应一个成功完成的 Pod 时,Job 被认为是已完成的。
关于如何使用这种模式的更多信息,可参阅
用带索引的 Job 执行基于静态任务分配的并行处理 。
说明:
带同一索引值启动的 Pod 可能不止一个(由于节点故障、kubelet
重启或 Pod 驱逐等各种原因),尽管这种情况很少发生。
在这种情况下,只有第一个成功完成的 Pod 才会被记入完成计数中并更新作业的状态。
其他为同一索引值运行或完成的 Pod 一旦被检测到,将被 Job 控制器删除。
处理 Pod 和容器失效
Pod 中的容器可能因为多种不同原因失效,例如因为其中的进程退出时返回值非零,
或者容器因为超出内存约束而被杀死等等。
如果发生这类事件,并且 .spec.template.spec.restartPolicy = "OnFailure"
,
Pod 则继续留在当前节点,但容器会被重新运行。
因此,你的程序需要能够处理在本地被重启的情况,或者要设置
.spec.template.spec.restartPolicy = "Never"
。
关于 restartPolicy
的更多信息,可参阅
Pod 生命周期 。
整个 Pod 也可能会失败,且原因各不相同。
例如,当 Pod 启动时,节点失效(被升级、被重启、被删除等)或者其中的容器失败而
.spec.template.spec.restartPolicy = "Never"
。
当 Pod 失败时,Job 控制器会启动一个新的 Pod。
这意味着,你的应用需要处理在一个新 Pod 中被重启的情况。
尤其是应用需要处理之前运行所产生的临时文件、锁、不完整的输出等问题。
默认情况下,每个 Pod 失效都被计入 .spec.backoffLimit
限制,
请参阅 Pod 回退失效策略 。
但你可以通过设置 Job 的 Pod 失效策略 自定义对 Pod 失效的处理方式。
此外,你可以通过设置 .spec.backoffLimitPerIndex
字段,
选择为 Indexed Job 的每个索引独立计算 Pod 失败次数
(细节参阅逐索引的回退限制 )。
注意,即使你将 .spec.parallelism
设置为 1,且将 .spec.completions
设置为
1,并且 .spec.template.spec.restartPolicy
设置为 "Never",同一程序仍然有可能被启动两次。
如果你确实将 .spec.parallelism
和 .spec.completions
都设置为比 1 大的值,
那就有可能同时出现多个 Pod 运行的情况。
为此,你的 Pod 也必须能够处理并发性问题。
当特性门控
PodDisruptionConditions
和 JobPodFailurePolicy
都被启用且 .spec.podFailurePolicy
字段被设置时,
Job 控制器不会将终止过程中的 Pod(已设置 .metadata.deletionTimestamp
字段的 Pod)视为失效 Pod,
直到该 Pod 完全终止(其 .status.phase
为 Failed
或 Succeeded
)。
但只要终止变得显而易见,Job 控制器就会创建一个替代的 Pod。一旦 Pod 终止,Job 控制器将把这个刚终止的
Pod 考虑在内,评估相关 Job 的 .backoffLimit
和 .podFailurePolicy
。
如果不满足任一要求,即使 Pod 稍后以 phase: "Succeeded"
终止,Job 控制器也会将此即将终止的 Pod 计为立即失效。
Pod 回退失效策略
在有些情形下,你可能希望 Job 在经历若干次重试之后直接进入失败状态,
因为这很可能意味着遇到了配置错误。
为了实现这点,可以将 .spec.backoffLimit
设置为视 Job 为失败之前的重试次数。
失效回退的限制值默认为 6。
与 Job 相关的失效的 Pod 会被 Job 控制器重建,回退重试时间将会按指数增长
(从 10 秒、20 秒到 40 秒)最多至 6 分钟。
计算重试次数有以下两种方法:
计算 .status.phase = "Failed"
的 Pod 数量。
当 Pod 的 restartPolicy = "OnFailure"
时,针对 .status.phase
等于 Pending
或
Running
的 Pod,计算其中所有容器的重试次数。
如果两种方式其中一个的值达到 .spec.backoffLimit
,则 Job 被判定为失败。
说明:
如果你的 Job 的 restartPolicy
被设置为 "OnFailure",就要注意运行该 Job 的 Pod
会在 Job 到达失效回退次数上限时自动被终止。
这会使得调试 Job 中可执行文件的工作变得非常棘手。
我们建议在调试 Job 时将 restartPolicy
设置为 "Never",
或者使用日志系统来确保失效 Job 的输出不会意外遗失。
逐索引的回退限制
特性状态: Kubernetes v1.28 [alpha]
说明:
只有在集群中启用了 JobBackoffLimitPerIndex
特性门控 ,
才能为 Indexed Job 配置逐索引的回退限制。
运行 Indexed Job 时,你可以选择对每个索引独立处理 Pod 失败的重试。
为此,可以设置 .spec.backoffLimitPerIndex
来指定每个索引的最大 Pod 失败次数。
当某个索引超过逐索引的回退限制后,Kubernetes 将视该索引为已失败,并将其添加到 .status.failedIndexes
字段中。
无论你是否设置了 backoffLimitPerIndex
字段,已成功执行的索引(具有成功执行的 Pod)将被记录在
.status.completedIndexes
字段中。
请注意,失败的索引不会中断其他索引的执行。一旦在指定了逐索引回退限制的 Job 中的所有索引完成,
如果其中至少有一个索引失败,Job 控制器会通过在状态中设置 Failed 状况将整个 Job 标记为失败。
即使其中一些(可能几乎全部)索引已被成功处理,该 Job 也会被标记为失败。
你还可以通过设置 .spec.maxFailedIndexes
字段来限制标记为失败的最大索引数。
当失败的索引数量超过 maxFailedIndexes
字段时,Job 控制器会对该 Job
的运行中的所有余下 Pod 触发终止操作。一旦所有 Pod 被终止,Job 控制器将通过设置 Job
状态中的 Failed 状况将整个 Job 标记为失败。
以下是定义 backoffLimitPerIndex
的 Job 示例清单:
apiVersion : batch/v1
kind : Job
metadata :
name : job-backoff-limit-per-index-example
spec :
completions : 10
parallelism : 3
completionMode : Indexed # 此特性所必需的字段
backoffLimitPerIndex : 1 # 每个索引最大失败次数
maxFailedIndexes : 5 # 终止 Job 执行之前失败索引的最大个数
template :
spec :
restartPolicy : Never # 此特性所必需的字段
containers :
- name : example
image : python
command : # 作业失败,因为至少有一个索引失败(此处所有偶数索引均失败),
# 但由于未超过 maxFailedIndexes,所以所有索引都会被执行
- python3
- -c
- |
import os, sys
print("Hello world")
if int(os.environ.get("JOB_COMPLETION_INDEX")) % 2 == 0:
sys.exit(1)
在上面的示例中,Job 控制器允许每个索引重新启动一次。
当失败的索引总数超过 5 个时,整个 Job 将被终止。
Job 完成后,该 Job 的状态如下所示:
kubectl get -o yaml job job-backoff-limit-per-index-example
status :
completedIndexes : 1 ,3 ,5 ,7 ,9
failedIndexes : 0 ,2 ,4 ,6 ,8
succeeded : 5 # 每 5 个成功的索引有 1 个成功的 Pod
failed : 10 # 每 5 个失败的索引有 2 个失败的 Pod(1 次重试)
conditions :
- message : Job has failed indexes
reason : FailedIndexes
status : "True"
type : Failed
此外,你可能想要结合使用逐索引回退与 Pod 失败策略 。
在使用逐索引回退时,有一个新的 FailIndex
操作可用,它让你避免就某个索引进行不必要的重试。
Pod 失效策略
特性状态: Kubernetes v1.26 [beta]
说明:
只有你在集群中启用了
JobPodFailurePolicy
特性门控 ,
你才能为某个 Job 配置 Pod 失效策略。
此外,建议启用 PodDisruptionConditions
特性门控以便在 Pod 失效策略中检测和处理 Pod 干扰状况
(参考:Pod 干扰状况 )。
这两个特性门控都是在 Kubernetes 1.29 中提供的。
Pod 失效策略使用 .spec.podFailurePolicy
字段来定义,
它能让你的集群根据容器的退出码和 Pod 状况来处理 Pod 失效事件。
在某些情况下,你可能希望更好地控制 Pod 失效的处理方式,
而不是仅限于 Pod 回退失效策略 所提供的控制能力,
后者是基于 Job 的 .spec.backoffLimit
实现的。以下是一些使用场景:
通过避免不必要的 Pod 重启来优化工作负载的运行成本,
你可以在某 Job 中一个 Pod 失效且其退出码表明存在软件错误时立即终止该 Job。
为了保证即使有干扰也能完成 Job,你可以忽略由干扰导致的 Pod 失效
(例如抢占 、
通过 API 发起的驱逐
或基于污点 的驱逐),
这样这些失效就不会被计入 .spec.backoffLimit
的重试限制中。
你可以在 .spec.podFailurePolicy
字段中配置 Pod 失效策略,以满足上述使用场景。
该策略可以根据容器退出码和 Pod 状况来处理 Pod 失效。
下面是一个定义了 podFailurePolicy
的 Job 的清单:
apiVersion : batch/v1
kind : Job
metadata :
name : job-pod-failure-policy-example
spec :
completions : 12
parallelism : 3
template :
spec :
restartPolicy : Never
containers :
- name : main
image : docker.io/library/bash:5
command : ["bash" ] # 模拟一个触发 FailJob 动作的错误的示例命令
args :
- -c
- echo "Hello world!" && sleep 5 && exit 42
backoffLimit : 6
podFailurePolicy :
rules :
- action : FailJob
onExitCodes :
containerName : main # 可选
operator : In # In 和 NotIn 二选一
values : [42 ]
- action : Ignore # Ignore、FailJob、Count 其中之一
onPodConditions :
- type : DisruptionTarget # 表示 Pod 失效
在上面的示例中,Pod 失效策略的第一条规则规定如果 main
容器失败并且退出码为 42,
Job 将被标记为失败。以下是 main
容器的具体规则:
退出码 0 代表容器成功
退出码 42 代表整个 Job 失败
所有其他退出码都代表容器失败,同时也代表着整个 Pod 失效。
如果重启总次数低于 backoffLimit
定义的次数,则会重新启动 Pod,
如果等于 backoffLimit
所设置的次数,则代表整个 Job 失效。
说明:
因为 Pod 模板中指定了 restartPolicy: Never
,
所以 kubelet 将不会重启 Pod 中的 main
容器。
Pod 失效策略的第二条规则,
指定对于状况为 DisruptionTarget
的失效 Pod 采取 Ignore
操作,
统计 .spec.backoffLimit
重试次数限制时不考虑 Pod 因干扰而发生的异常。
说明:
如果根据 Pod 失效策略或 Pod 回退失效策略判定 Pod 已经失效,
并且 Job 正在运行多个 Pod,Kubernetes 将终止该 Job 中仍处于 Pending 或 Running 的所有 Pod。
下面是此 API 的一些要求和语义:
如果你想在 Job 中使用 .spec.podFailurePolicy
字段,
你必须将 Job 的 Pod 模板中的 .spec.restartPolicy
设置为 Never
。
在 spec.podFailurePolicy.rules
中设定的 Pod 失效策略规则将按序评估。
一旦某个规则与 Pod 失效策略匹配,其余规则将被忽略。
当没有规则匹配 Pod 失效策略时,将会采用默认的处理方式。
你可能希望在 spec.podFailurePolicy.rules[*].onExitCodes.containerName
中通过指定的名称限制只能针对特定容器应用对应的规则。
如果不设置此属性,规则将适用于所有容器。
如果指定了容器名称,它应该匹配 Pod 模板中的一个普通容器或一个初始容器(Init Container)。
你可以在 spec.podFailurePolicy.rules[*].action
指定当 Pod 失效策略发生匹配时要采取的操作。
可能的值为:
FailJob
:表示 Pod 的任务应标记为 Failed,并且所有正在运行的 Pod 应被终止。
Ignore
:表示 .spec.backoffLimit
的计数器不应该增加,应该创建一个替换的 Pod。
Count
:表示 Pod 应该以默认方式处理。.spec.backoffLimit
的计数器应该增加。
FailIndex
:表示使用此操作以及逐索引回退限制 来避免就失败的 Pod
的索引进行不必要的重试。
说明:
当你使用 podFailurePolicy
时,Job 控制器只匹配处于 Failed
阶段的 Pod。
具有删除时间戳但不处于终止阶段(Failed
或 Succeeded
)的 Pod 被视为仍在终止中。
这意味着终止中的 Pod 会保留一个跟踪 Finalizer ,
直到到达终止阶段。
从 Kubernetes 1.27 开始,kubelet 将删除的 Pod 转换到终止阶段
(参阅 Pod 阶段 )。
这确保已删除的 Pod 的 Finalizer 被 Job 控制器移除。
说明:
自 Kubernetes v1.28 开始,当使用 Pod 失败策略时,Job 控制器仅在这些 Pod 达到终止的
Failed
阶段时才会重新创建终止中的 Pod。这种行为类似于 podReplacementPolicy: Failed
。
细节参阅 Pod 替换策略 。
Job 终止与清理
Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常 也不会被删除。
保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告或者其它诊断性输出。
Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。
在查看了 Job 状态之后删除老的 Job 的操作留给了用户自己。
你可以使用 kubectl
来删除 Job(例如,kubectl delete jobs/pi
或者 kubectl delete -f ./job.yaml
)。
当使用 kubectl
来删除 Job 时,该 Job 所创建的 Pod 也会被删除。
默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never
)
或者某个容器出错退出(restartPolicy=OnFailure
)。
这时,Job 基于前述的 spec.backoffLimit
来决定是否以及如何重试。
一旦重试次数到达 .spec.backoffLimit
所设的上限,Job 会被标记为失败,
其中运行的 Pod 都会被终止。
终止 Job 的另一种方式是设置一个活跃期限。
你可以为 Job 的 .spec.activeDeadlineSeconds
设置一个秒数值。
该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。
一旦 Job 运行时间达到 activeDeadlineSeconds
秒,其所有运行中的 Pod 都会被终止,
并且 Job 的状态更新为 type: Failed
及 reason: DeadlineExceeded
。
注意 Job 的 .spec.activeDeadlineSeconds
优先级高于其 .spec.backoffLimit
设置。
因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达
activeDeadlineSeconds
所设的时限即不再部署额外的 Pod,
即使其重试次数还未达到 backoffLimit
所设的限制。
例如:
apiVersion : batch/v1
kind : Job
metadata :
name : pi-with-timeout
spec :
backoffLimit : 5
activeDeadlineSeconds : 100
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
注意 Job 规约和 Job 中的
Pod 模板规约
都有 activeDeadlineSeconds
字段。
请确保你在合适的层次设置正确的字段。
还要注意的是,restartPolicy
对应的是 Pod,而不是 Job 本身:
一旦 Job 状态变为 type: Failed
,就不会再发生 Job 重启的动作。
换言之,由 .spec.activeDeadlineSeconds
和 .spec.backoffLimit
所触发的 Job
终结机制都会导致 Job 永久性的失败,而这类状态都需要手工干预才能解决。
自动清理完成的 Job
完成的 Job 通常不需要留存在系统中。在系统中一直保留它们会给 API 服务器带来额外的压力。
如果 Job 由某种更高级别的控制器来管理,例如
CronJob ,
则 Job 可以被 CronJob 基于特定的根据容量裁定的清理策略清理掉。
已完成 Job 的 TTL 机制
特性状态: Kubernetes v1.23 [stable]
自动清理已完成 Job (状态为 Complete
或 Failed
)的另一种方式是使用由
TTL 控制器 所提供的 TTL 机制。
通过设置 Job 的 .spec.ttlSecondsAfterFinished
字段,可以让该控制器清理掉已结束的资源。
TTL 控制器清理 Job 时,会级联式地删除 Job 对象。
换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。
注意,当 Job 被删除时,系统会考虑其生命周期保障,例如其 Finalizers。
例如:
apiVersion : batch/v1
kind : Job
metadata :
name : pi-with-ttl
spec :
ttlSecondsAfterFinished : 100
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
Job pi-with-ttl
在结束 100 秒之后,可以成为被自动删除的对象。
如果该字段设置为 0
,Job 在结束之后立即成为可被自动删除的对象。
如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。
说明:
建议设置 ttlSecondsAfterFinished
字段,因为非托管任务
(是你直接创建的 Job,而不是通过其他工作负载 API(如 CronJob)间接创建的 Job)
的默认删除策略是 orphanDependents
,这会导致非托管 Job 创建的 Pod 在该 Job 被完全删除后被保留。
即使控制面 最终在 Pod 失效或完成后
对已删除 Job 中的这些 Pod 执行垃圾收集 操作,
这些残留的 Pod 有时可能会导致集群性能下降,或者在最坏的情况下会导致集群因这种性能下降而离线。
你可以使用 LimitRange 和
ResourceQuota ,
设定一个特定名字空间可以消耗的资源上限。
Job 模式
Job 对象可以用来处理一组相互独立而又彼此关联的“工作条目”。
这类工作条目可能是要发送的电子邮件、要渲染的视频帧、要编解码的文件、NoSQL
数据库中要扫描的主键范围等等。
在一个复杂系统中,可能存在多个不同的工作条目集合。
这里我们仅考虑用户希望一起管理的工作条目集合之一:批处理作业 。
并行计算的模式有好多种,每种都有自己的强项和弱点。这里要权衡的因素有:
每个工作条目对应一个 Job 或者所有工作条目对应同一 Job 对象。
为每个工作条目创建一个 Job 的做法会给用户带来一些额外的负担,系统需要管理大量的 Job 对象。
用一个 Job 对象来完成所有工作条目的做法更适合处理大量工作条目的场景。
创建数目与工作条目相等的 Pod 或者令每个 Pod 可以处理多个工作条目。
当 Pod 个数与工作条目数目相等时,通常不需要在 Pod 中对现有代码和容器做较大改动;
让每个 Pod 能够处理多个工作条目的做法更适合于工作条目数量较大的场合。
有几种技术都会用到工作队列。这意味着需要运行一个队列服务,
并修改现有程序或容器使之能够利用该工作队列。
与之比较,其他方案在修改现有容器化应用以适应需求方面可能更容易一些。
当 Job 与某个无头 Service
之间存在关联时,你可以让 Job 中的 Pod 之间能够相互通信,从而协作完成计算。
下面是对这些权衡的汇总,第 2 到 4 列对应上面的权衡比较。
模式的名称对应了相关示例和更详细描述的链接。
当你使用 .spec.completions
来设置完成数时,Job 控制器所创建的每个 Pod
使用完全相同的 spec
。
这意味着任务的所有 Pod 都有相同的命令行,都使用相同的镜像和数据卷,
甚至连环境变量都(几乎)相同。
这些模式是让每个 Pod 执行不同工作的几种不同形式。
下表显示的是每种模式下 .spec.parallelism
和 .spec.completions
所需要的设置。
其中,W
表示的是工作条目的个数。
高级用法
挂起 Job
特性状态: Kubernetes v1.24 [stable]
Job 被创建时,Job 控制器会马上开始执行 Pod 创建操作以满足 Job 的需求,
并持续执行此操作直到 Job 完成为止。
不过你可能想要暂时挂起 Job 执行,或启动处于挂起状态的 Job,
并拥有一个自定义控制器以后再决定什么时候开始。
要挂起一个 Job,你可以更新 .spec.suspend
字段为 true,
之后,当你希望恢复其执行时,将其更新为 false。
创建一个 .spec.suspend
被设置为 true 的 Job 本质上会将其创建为被挂起状态。
当 Job 被从挂起状态恢复执行时,其 .status.startTime
字段会被重置为当前的时间。
这意味着 .spec.activeDeadlineSeconds
计时器会在 Job 挂起时被停止,
并在 Job 恢复执行时复位。
当你挂起一个 Job 时,所有正在运行且状态不是 Completed
的 Pod
将被终止 。
Pod 的体面终止期限会被考虑,不过 Pod 自身也必须在此期限之内处理完信号。
处理逻辑可能包括保存进度以便将来恢复,或者取消已经做出的变更等等。
Pod 以这种形式终止时,不会被记入 Job 的 completions
计数。
处于被挂起状态的 Job 的定义示例可能是这样子:
kubectl get job myjob -o yaml
apiVersion : batch/v1
kind : Job
metadata :
name : myjob
spec :
suspend : true
parallelism : 1
completions : 5
template :
spec :
...
你也可以使用命令行为 Job 打补丁来切换 Job 的挂起状态。
挂起一个活跃的 Job:
kubectl patch job/myjob --type= strategic --patch '{"spec":{"suspend":true}}'
恢复一个挂起的 Job:
kubectl patch job/myjob --type= strategic --patch '{"spec":{"suspend":false}}'
Job 的 status
可以用来确定 Job 是否被挂起,或者曾经被挂起。
kubectl get jobs/myjob -o yaml
apiVersion : batch/v1
kind : Job
# .metadata 和 .spec 已省略
status :
conditions :
- lastProbeTime : "2021-02-05T13:14:33Z"
lastTransitionTime : "2021-02-05T13:14:33Z"
status : "True"
type : Suspended
startTime : "2021-02-05T13:13:48Z"
Job 的 "Suspended" 类型的状况在状态值为 "True" 时意味着 Job 正被挂起;
lastTransitionTime
字段可被用来确定 Job 被挂起的时长。
如果此状况字段的取值为 "False",则 Job 之前被挂起且现在在运行。
如果 "Suspended" 状况在 status
字段中不存在,则意味着 Job 从未被停止执行。
当 Job 被挂起和恢复执行时,也会生成事件:
kubectl describe jobs/myjob
Name: myjob
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 12m job-controller Created pod: myjob-hlrpl
Normal SuccessfulDelete 11m job-controller Deleted pod: myjob-hlrpl
Normal Suspended 11m job-controller Job suspended
Normal SuccessfulCreate 3s job-controller Created pod: myjob-jvb44
Normal Resumed 3s job-controller Job resumed
最后四个事件,特别是 "Suspended" 和 "Resumed" 事件,都是因为 .spec.suspend
字段值被改来改去造成的。在这两个事件之间,我们看到没有 Pod 被创建,不过当
Job 被恢复执行时,Pod 创建操作立即被重启执行。
可变调度指令
特性状态: Kubernetes v1.27 [stable]
在大多数情况下,并行作业会希望 Pod 在一定约束条件下运行,
比如所有的 Pod 都在同一个区域,或者所有的 Pod 都在 GPU 型号 x 或 y 上,而不是两者的混合。
suspend 字段是实现这些语义的第一步。
suspend 允许自定义队列控制器,以决定工作何时开始;然而,一旦工作被取消暂停,
自定义队列控制器对 Job 中 Pod 的实际放置位置没有影响。
此特性允许在 Job 开始之前更新调度指令,从而为定制队列提供影响 Pod
放置的能力,同时将 Pod 与节点间的分配关系留给 kube-scheduler 决定。
这一特性仅适用于之前从未被暂停过的、已暂停的 Job。
控制器能够影响 Pod 放置,同时参考实际 pod-to-node 分配给 kube-scheduler。
这仅适用于从未暂停的 Job。
Job 的 Pod 模板中可以更新的字段是节点亲和性、节点选择器、容忍、标签、注解和
调度门控 。
指定你自己的 Pod 选择算符
通常,当你创建一个 Job 对象时,你不会设置 .spec.selector
。
系统的默认值填充逻辑会在创建 Job 时添加此字段。
它会选择一个不会与任何其他 Job 重叠的选择算符设置。
不过,有些场合下,你可能需要重载这个自动设置的选择算符。
为了实现这点,你可以手动设置 Job 的 spec.selector
字段。
做这个操作时请务必小心。
如果你所设定的标签选择算符并不唯一针对 Job 对应的 Pod 集合,
甚或该算符还能匹配其他无关的 Pod,这些无关的 Job 的 Pod 可能会被删除。
或者当前 Job 会将另外一些 Pod 当作是完成自身工作的 Pod,
又或者两个 Job 之一或者二者同时都拒绝创建 Pod,无法运行至完成状态。
如果所设置的算符不具有唯一性,其他控制器(如 RC 副本控制器)及其所管理的 Pod
集合可能会变得行为不可预测。
Kubernetes 不会在你设置 .spec.selector
时尝试阻止你犯这类错误。
下面是一个示例场景,在这种场景下你可能会使用刚刚讲述的特性。
假定名为 old
的 Job 已经处于运行状态。
你希望已有的 Pod 继续运行,但你希望 Job 接下来要创建的其他 Pod
使用一个不同的 Pod 模板,甚至希望 Job 的名字也发生变化。
你无法更新现有的 Job,因为这些字段都是不可更新的。
因此,你会删除 old
Job,但允许该 Job 的 Pod 集合继续运行 。
这是通过 kubectl delete jobs/old --cascade=orphan
实现的。
在删除之前,我们先记下该 Job 所使用的选择算符。
kubectl get job old -o yaml
输出类似于:
kind : Job
metadata :
name : old
...
spec :
selector :
matchLabels :
batch.kubernetes.io/controller-uid : a8f3d00d-c6d2-11e5-9f87-42010af00002
...
接下来你会创建名为 new
的新 Job,并显式地为其设置相同的选择算符。
由于现有 Pod 都具有标签
batch.kubernetes.io/controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002
,
它们也会被名为 new
的 Job 所控制。
你需要在新 Job 中设置 manualSelector: true
,
因为你并未使用系统通常自动为你生成的选择算符。
kind : Job
metadata :
name : new
...
spec :
manualSelector : true
selector :
matchLabels :
batch.kubernetes.io/controller-uid : a8f3d00d-c6d2-11e5-9f87-42010af00002
...
新的 Job 自身会有一个不同于 a8f3d00d-c6d2-11e5-9f87-42010af00002
的唯一 ID。
设置 manualSelector: true
是在告诉系统你知道自己在干什么并要求系统允许这种不匹配的存在。
使用 Finalizer 追踪 Job
特性状态: Kubernetes v1.26 [stable]
控制面会跟踪属于任何 Job 的 Pod,并通知是否有任何这样的 Pod 被从 API 服务器中移除。
为了实现这一点,Job 控制器创建的 Pod 带有 Finalizer batch.kubernetes.io/job-tracking
。
控制器只有在 Pod 被记入 Job 状态后才会移除 Finalizer,允许 Pod 可以被其他控制器或用户移除。
弹性索引 Job
特性状态: Kubernetes v1.27 [beta]
你可以通过同时改变 .spec.parallelism
和 .spec.completions
来扩大或缩小带索引 Job,
从而满足 .spec.parallelism == .spec.completions
。
当 API 服务器
上的 ElasticIndexedJob
特性门控被禁用时,.spec.completions
是不可变的。
弹性索引 Job 的使用场景包括需要扩展索引 Job 的批处理工作负载,例如 MPI、Horovord、Ray
和 PyTorch 训练作业。
延迟创建替换 Pod
特性状态: Kubernetes v1.28 [alpha]
说明:
你只有在启用了 JobPodReplacementPolicy
特性门控 后,
才能为 Job 设置 podReplacementPolicy
。
默认情况下,当 Pod 失败或正在终止(具有删除时间戳)时,Job 控制器会立即重新创建 Pod。
这意味着,在某个时间点上,当一些 Pod 正在终止时,为 Job 正运行中的 Pod 数量可以大于 parallelism
或超出每个索引一个 Pod(如果使用 Indexed Job)。
你可以选择仅在终止过程中的 Pod 完全终止(具有 status.phase: Failed
)时才创建替换 Pod。
为此,可以设置 .spec.podReplacementPolicy: Failed
。
默认的替换策略取决于 Job 是否设置了 podFailurePolicy
。对于没有定义 Pod 失败策略的 Job,
省略 podReplacementPolicy
字段相当于选择 TerminatingOrFailed
替换策略:
控制平面在 Pod 删除时立即创建替换 Pod(只要控制平面发现该 Job 的某个 Pod 被设置了 deletionTimestamp
)。
对于设置了 Pod 失败策略的 Job,默认的 podReplacementPolicy
是 Failed
,不允许其他值。
请参阅 Pod 失败策略 以了解更多关于 Job 的 Pod 失败策略的信息。
kind : Job
metadata :
name : new
...
spec :
podReplacementPolicy : Failed
...
如果你的集群启用了此特性门控,你可以检查 Job 的 .status.terminating
字段。
该字段值是当前处于终止过程中的、由该 Job 拥有的 Pod 的数量。
kubectl get jobs/myjob -o yaml
apiVersion : batch/v1
kind : Job
# .metadata 和 .spec 被省略
status :
terminating : 3 # 三个 Pod 正在终止且还未达到 Failed 阶段
替代方案
裸 Pod
当 Pod 运行所在的节点重启或者失败,Pod 会被终止并且不会被重启。
Job 会重新创建新的 Pod 来替代已终止的 Pod。
因为这个原因,我们建议你使用 Job 而不是独立的裸 Pod,
即使你的应用仅需要一个 Pod。
副本控制器
Job 与副本控制器 是彼此互补的。
副本控制器管理的是那些不希望被终止的 Pod (例如,Web 服务器),
Job 管理的是那些希望被终止的 Pod(例如,批处理作业)。
正如在 Pod 生命期 中讨论的,
Job
仅适合于 restartPolicy
设置为 OnFailure
或 Never
的 Pod。
注意:如果 restartPolicy
未设置,其默认值是 Always
。
单个 Job 启动控制器 Pod
另一种模式是用唯一的 Job 来创建 Pod,而该 Pod 负责启动其他 Pod,
因此扮演了一种后启动 Pod 的控制器的角色。
这种模式的灵活性更高,但是有时候可能会把事情搞得很复杂,很难入门,
并且与 Kubernetes 的集成度很低。
这种模式的实例之一是用 Job 来启动一个运行脚本的 Pod,脚本负责启动 Spark
主控制器(参见 Spark 示例 ),
运行 Spark 驱动,之后完成清理工作。
这种方法的优点之一是整个过程得到了 Job 对象的完成保障,
同时维持了对创建哪些 Pod、如何向其分派工作的完全控制能力,
接下来
了解 Pod 。
了解运行 Job 的不同的方式:
跟随自动清理完成的 Job 文中的链接,了解你的集群如何清理完成和失败的任务。
Job
是 Kubernetes REST API 的一部分。阅读
对象定义理解关于该资源的 API。
阅读 CronJob
,
它允许你定义一系列定期运行的 Job,类似于 UNIX 工具 cron
。
根据循序渐进的示例 ,
练习如何使用 podFailurePolicy
配置处理可重试和不可重试的 Pod 失效。
2.6 - 已完成 Job 的自动清理
一种用于清理已完成执行的旧 Job 的 TTL 机制。
特性状态: Kubernetes v1.23 [stable]
当你的 Job 已结束时,将 Job 保留在 API 中(而不是立即删除 Job)很有用,
这样你就可以判断 Job 是成功还是失败。
Kubernetes TTL-after-finished 控制器 提供了一种
TTL 机制来限制已完成执行的 Job 对象的生命期。
清理已完成的 Job
TTL-after-finished 控制器只支持 Job。你可以通过指定 Job 的 .spec.ttlSecondsAfterFinished
字段来自动清理已结束的 Job(Complete
或 Failed
),
如示例 所示。
TTL-after-finished 控制器假设 Job 能在执行完成后的 TTL 秒内被清理。一旦 Job
的状态条件发生变化表明该 Job 是 Complete
或 Failed
,计时器就会启动;一旦 TTL 已过期,该 Job
就能被级联删除 。
当 TTL 控制器清理作业时,它将做级联删除操作,即删除 Job 的同时也删除其依赖对象。
Kubernetes 尊重 Job 对象的生命周期保证,例如等待
Finalizer 。
你可以随时设置 TTL 秒。以下是设置 Job 的 .spec.ttlSecondsAfterFinished
字段的一些示例:
在 Job 清单(manifest)中指定此字段,以便 Job 在完成后的某个时间被自动清理。
手动设置现有的、已完成的 Job 的此字段,以便这些 Job 可被清理。
在创建 Job 时使用修改性质的准入 Webhook
动态设置该字段。集群管理员可以使用它对已完成的作业强制执行 TTL 策略。
使用修改性质的准入 Webhook
在 Job 完成后动态设置该字段,并根据 Job 状态、标签等选择不同的 TTL 值。
对于这种情况,Webhook 需要检测 Job 的 .status
变化,并且仅在 Job 被标记为已完成时设置 TTL。
编写你自己的控制器来管理与特定选择算符 匹配的
Job 的清理 TTL。
警告
更新已完成 Job 的 TTL
在创建 Job 或已经执行结束后,你仍可以修改其 TTL 周期,例如 Job 的
.spec.ttlSecondsAfterFinished
字段。
如果你在当前 ttlSecondsAfterFinished
时长已过期后延长 TTL 周期,
即使延长 TTL 的更新得到了成功的 API 响应,Kubernetes 也不保证保留此 Job,
时间偏差
由于 TTL-after-finished 控制器使用存储在 Kubernetes Job 中的时间戳来确定 TTL 是否已过期,
因此该功能对集群中的时间偏差很敏感,这可能导致控制平面在错误的时间清理 Job 对象。
时钟并不总是如此正确,但差异应该很小。
设置非零 TTL 时请注意避免这种风险。
接下来
2.7 - CronJob
CronJob 通过重复调度启动一次性的 Job。
特性状态: Kubernetes v1.21 [stable]
CronJob 创建基于时隔重复调度的 Job 。
CronJob 用于执行排期操作,例如备份、生成报告等。
一个 CronJob 对象就像 Unix 系统上的 crontab (cron table)文件中的一行。
它用 Cron 格式进行编写,
并周期性地在给定的调度时间执行 Job。
CronJob 有所限制,也比较特殊。
例如在某些情况下,单个 CronJob 可以创建多个并发任务。
请参阅下面的限制 。
当控制平面为 CronJob 创建新的 Job 和(间接)Pod 时,CronJob 的 .metadata.name
是命名这些 Pod 的部分基础。
CronJob 的名称必须是一个合法的
DNS 子域 值,
但这会对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签 规则。
即使名称是一个 DNS 子域,它也不能超过 52 个字符。这是因为 CronJob 控制器将自动在你所提供的 Job 名称后附加
11 个字符,并且存在 Job 名称的最大长度不能超过 63 个字符的限制。
示例
下面的 CronJob 示例清单会在每分钟打印出当前时间和问候消息:
apiVersion : batch/v1
kind : CronJob
metadata :
name : hello
spec :
schedule : "* * * * *"
jobTemplate :
spec :
template :
spec :
containers :
- name : hello
image : busybox:1.28
imagePullPolicy : IfNotPresent
command :
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy : OnFailure
使用 CronJob 运行自动化任务 一文会为你详细讲解此例。
编写 CronJob 声明信息
Cron 时间表语法
.spec.schedule
字段是必需的。该字段的值遵循 Cron 语法:
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
例如 0 0 13 * 5
表示此任务必须在每个星期五的午夜以及每个月的 13 日的午夜开始。
该格式也包含了扩展的 “Vixie cron” 步长值。
FreeBSD 手册 中解释如下:
步长可被用于范围组合。范围后面带有 /<数字>
可以声明范围内的步幅数值。
例如,0-23/2
可被用在小时字段来声明命令在其他数值的小时数执行
(V7 标准中对应的方法是 0,2,4,6,8,10,12,14,16,18,20,22
)。
步长也可以放在通配符后面,因此如果你想表达 “每两小时”,就用 */2
。
说明:
时间表中的问号 (?
) 和星号 *
含义相同,它们用来表示给定字段的任何可用值。
除了标准语法,还可以使用一些类似 @monthly
的宏:
输入
描述
相当于
@yearly (或 @annually)
每年 1 月 1 日的午夜运行一次
0 0 1 1 *
@monthly
每月第一天的午夜运行一次
0 0 1 * *
@weekly
每周的周日午夜运行一次
0 0 * * 0
@daily (或 @midnight)
每天午夜运行一次
0 0 * * *
@hourly
每小时的开始一次
0 * * * *
为了生成 CronJob 时间表的表达式,你还可以使用 crontab.guru 这类 Web 工具。
任务模板
.spec.jobTemplate
为 CronJob 创建的 Job 定义模板,它是必需的。它和
Job 的语法完全一样,
只不过它是嵌套的,没有 apiVersion
和 kind
。
你可以为模板化的 Job 指定通用的元数据,
例如标签 或注解 。
有关如何编写一个任务的 .spec
,
请参考编写 Job 规约 。
任务延迟开始的最后期限
.spec.startingDeadlineSeconds
字段是可选的。
它表示任务如果由于某种原因错过了调度时间,开始该任务的截止时间的秒数。
过了截止时间,CronJob 就不会开始该任务的实例(未来的任务仍在调度之中)。
例如,如果你有一个每天运行两次的备份任务,你可能会允许它最多延迟 8 小时开始,但不能更晚,
因为更晚进行的备份将变得没有意义:你宁愿等待下一次计划的运行。
对于错过已配置的最后期限的 Job,Kubernetes 将其视为失败的任务。
如果你没有为 CronJob 指定 startingDeadlineSeconds
,那 Job 就没有最后期限。
如果 .spec.startingDeadlineSeconds
字段被设置(非空),
CronJob 控制器将会计算从预期创建 Job 到当前时间的时间差。
如果时间差大于该限制,则跳过此次执行。
例如,如果将其设置为 200
,则 Job 控制器允许在实际调度之后最多 200 秒内创建 Job。
并发性规则
.spec.concurrencyPolicy
也是可选的。它声明了 CronJob 创建的任务执行时发生重叠如何处理。
spec 仅能声明下列规则中的一种:
Allow
(默认):CronJob 允许并发任务执行。
Forbid
: CronJob 不允许并发任务执行;如果新任务的执行时间到了而老任务没有执行完,CronJob 会忽略新任务的执行。
Replace
:如果新任务的执行时间到了而老任务没有执行完,CronJob 会用新任务替换当前正在运行的任务。
请注意,并发性规则仅适用于相同 CronJob 创建的任务。如果有多个 CronJob,它们相应的任务总是允许并发执行的。
调度挂起
通过将可选的 .spec.suspend
字段设置为 true
,可以挂起针对 CronJob 执行的任务。
这个设置不 会影响 CronJob 已经开始的任务。
如果你将此字段设置为 true
,后续发生的执行都会被挂起
(这些任务仍然在调度中,但 CronJob 控制器不会启动这些 Job 来运行任务),直到你取消挂起 CronJob 为止。
注意:
在调度时间内挂起的执行都会被统计为错过的任务。当现有的 CronJob 将 .spec.suspend
从 true
改为 false
时,
且没有开始的最后期限 ,错过的任务会被立即调度。
任务历史限制
.spec.successfulJobsHistoryLimit
和 .spec.failedJobsHistoryLimit
字段是可选的。
这两个字段指定应保留多少已完成和失败的任务。
默认设置分别为 3 和 1。将限制设置为 0
代表相应类型的任务完成后不会保留。
有关自动清理任务的其他方式,
请参见自动清理完成的 Job 。
时区
特性状态: Kubernetes v1.27 [stable]
对于没有指定时区的 CronJob,
kube-controller-manager
基于本地时区解释排期表(Schedule)。
你可以通过将 .spec.timeZone
设置为一个有效时区 的名称,
为 CronJob 指定一个时区。例如设置 .spec.timeZone: "Etc/UTC"
将告诉
Kubernetes 基于世界标准时间解读排期表。
Go 标准库中的时区数据库包含在二进制文件中,并用作备用数据库,以防系统上没有可用的外部数据库。
CronJob 的限制
不支持的时区规范
Kubernetes 1.29 中的 CronJob API 实现允许你设置
.spec.schedule
字段,在其中包括时区信息;
例如 CRON_TZ=UTC * * * * *
或 TZ=UTC * * * * *
。
以这种方式指定时区是 未正式支持的 (而且也从未正式支持过)。
如果你尝试设置包含 TZ
或 CRON_TZ
时区规范的排期表,
Kubernetes 会向客户端报告一条警告 。
后续的 Kubernetes 版本将完全阻止设置非正式的时区机制。
修改 CronJob
按照设计,CronJob 包含一个用于新 Job 的模板。
如果你修改现有的 CronJob,你所做的更改将应用于修改完成后开始运行的新任务。
已经开始的任务(及其 Pod)将继续运行而不会发生任何变化。
也就是说,CronJob 不 会更新现有任务,即使这些任务仍在运行。
Job 创建
CronJob 根据其计划编排,在每次该执行任务的时候大约会创建一个 Job。
我们之所以说 "大约",是因为在某些情况下,可能会创建两个 Job,或者不会创建任何 Job。
我们试图使这些情况尽量少发生,但不能完全杜绝。因此,Job 应该是 幂等的 。
如果 startingDeadlineSeconds
设置为很大的数值或未设置(默认),并且
concurrencyPolicy
设置为 Allow
,则作业将始终至少运行一次。
注意:
如果 startingDeadlineSeconds
的设置值低于 10 秒钟,CronJob 可能无法被调度。
这是因为 CronJob 控制器每 10 秒钟执行一次检查。
对于每个 CronJob,CronJob 控制器(Controller)
检查从上一次调度的时间点到现在所错过了调度次数。如果错过的调度次数超过 100 次,
那么它就不会启动这个任务,并记录这个错误:
Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
需要注意的是,如果 startingDeadlineSeconds
字段非空,则控制器会统计从
startingDeadlineSeconds
设置的值到现在而不是从上一个计划时间到现在错过了多少次 Job。
例如,如果 startingDeadlineSeconds
是 200
,则控制器会统计在过去 200 秒中错过了多少次 Job。
如果未能在调度时间内创建 CronJob,则计为错过。
例如,如果 concurrencyPolicy
被设置为 Forbid
,并且当前有一个调度仍在运行的情况下,
试图调度的 CronJob 将被计算为错过。
例如,假设一个 CronJob 被设置为从 08:30:00
开始每隔一分钟创建一个新的 Job,
并且它的 startingDeadlineSeconds
字段未被设置。如果 CronJob 控制器从
08:29:00
到 10:21:00
终止运行,则该 Job 将不会启动,
因为其错过的调度次数超过了 100。
为了进一步阐述这个概念,假设将 CronJob 设置为从 08:30:00
开始每隔一分钟创建一个新的 Job,
并将其 startingDeadlineSeconds
字段设置为 200 秒。
如果 CronJob 控制器恰好在与上一个示例相同的时间段(08:29:00
到 10:21:00
)终止运行,
则 Job 仍将从 10:22:00
开始。
造成这种情况的原因是控制器现在检查在最近 200 秒(即 3 个错过的调度)中发生了多少次错过的
Job 调度,而不是从现在为止的最后一个调度时间开始。
CronJob 仅负责创建与其调度时间相匹配的 Job,而 Job 又负责管理其代表的 Pod。
接下来
了解 CronJob 所依赖的 Pod 与
Job 的概念。
阅读 CronJob .spec.schedule
字段的详细格式 。
有关创建和使用 CronJob 的说明及 CronJob 清单的示例,
请参见使用 CronJob 运行自动化任务 。
CronJob
是 Kubernetes REST API 的一部分,
阅读
CronJob API 参考了解更多细节。
2.8 - ReplicationController
用于管理可水平扩展的工作负载的旧版 API。 被 Deployment 和 ReplicaSet API 取代。
ReplicationController 确保在任何时候都有特定数量的 Pod 副本处于运行状态。
换句话说,ReplicationController 确保一个 Pod 或一组同类的 Pod 总是可用的。
ReplicationController 如何工作
当 Pod 数量过多时,ReplicationController 会终止多余的 Pod。当 Pod 数量太少时,ReplicationController 将会启动新的 Pod。
与手动创建的 Pod 不同,由 ReplicationController 创建的 Pod 在失败、被删除或被终止时会被自动替换。
例如,在中断性维护(如内核升级)之后,你的 Pod 会在节点上重新创建。
因此,即使你的应用程序只需要一个 Pod,你也应该使用 ReplicationController 创建 Pod。
ReplicationController 类似于进程管理器,但是 ReplicationController 不是监控单个节点上的单个进程,而是监控跨多个节点的多个 Pod。
在讨论中,ReplicationController 通常缩写为 "rc",并作为 kubectl 命令的快捷方式。
一个简单的示例是创建一个 ReplicationController 对象来可靠地无限期地运行 Pod 的一个实例。
更复杂的用例是运行一个多副本服务(如 web 服务器)的若干相同副本。
运行一个示例 ReplicationController
这个示例 ReplicationController 配置运行 nginx Web 服务器的三个副本。
apiVersion : v1
kind : ReplicationController
metadata :
name : nginx
spec :
replicas : 3
selector :
app : nginx
template :
metadata :
name : nginx
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx
ports :
- containerPort : 80
通过下载示例文件并运行以下命令来运行示例任务:
kubectl apply -f https://k8s.io/examples/controllers/replication.yaml
输出类似于:
replicationcontroller/nginx created
使用以下命令检查 ReplicationController 的状态:
kubectl describe replicationcontrollers/nginx
输出类似于:
Name: nginx
Namespace: default
Selector: app=nginx
Labels: app=nginx
Annotations: <none>
Replicas: 3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- ---- ------ -------
20s 20s 1 {replication-controller } Normal SuccessfulCreate Created pod: nginx-qrm3m
20s 20s 1 {replication-controller } Normal SuccessfulCreate Created pod: nginx-3ntk0
20s 20s 1 {replication-controller } Normal SuccessfulCreate Created pod: nginx-4ok8v
在这里,创建了三个 Pod,但没有一个 Pod 正在运行,这可能是因为正在拉取镜像。
稍后,相同的命令可能会显示:
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
要以机器可读的形式列出属于 ReplicationController 的所有 Pod,可以使用如下命令:
pods = $( kubectl get pods --selector= app = nginx --output= jsonpath ={ .items..metadata.name} )
echo $pods
输出类似于:
nginx-3ntk0 nginx-4ok8v nginx-qrm3m
这里,选择算符与 ReplicationController 的选择算符相同(参见 kubectl describe
输出),并以不同的形式出现在 replication.yaml
中。
--output=jsonpath
选项指定了一个表达式,仅从返回列表中的每个 Pod 中获取名称。
编写一个 ReplicationController 清单
与所有其它 Kubernetes 配置一样,ReplicationController 需要 apiVersion
、kind
和 metadata
字段。
当控制平面为 ReplicationController 创建新的 Pod 时,ReplicationController
的 .metadata.name
是命名这些 Pod 的部分基础。ReplicationController 的名称必须是一个合法的
DNS 子域 值,
但这可能对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签 规则。
有关使用配置文件的常规信息,
参考对象管理 。
ReplicationController 也需要一个 .spec
部分 。
Pod 模板
.spec.template
是 .spec
的唯一必需字段。
.spec.template
是一个 Pod 模板 。
它的模式与 Pod 完全相同,只是它是嵌套的,没有 apiVersion
或 kind
属性。
除了 Pod 所需的字段外,ReplicationController 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。
对于标签,请确保不与其他控制器重叠。参考 Pod 选择算符 。
只允许 .spec.template.spec.restartPolicy
等于 Always
,如果没有指定,这是默认值。
对于本地容器重启,ReplicationController 委托给节点上的代理,
例如 Kubelet 。
ReplicationController 上的标签
ReplicationController 本身可以有标签 (.metadata.labels
)。
通常,你可以将这些设置为 .spec.template.metadata.labels
;
如果没有指定 .metadata.labels
那么它默认为 .spec.template.metadata.labels
。
但是,Kubernetes 允许它们是不同的,.metadata.labels
不会影响 ReplicationController 的行为。
Pod 选择算符
.spec.selector
字段是一个标签选择算符 。
ReplicationController 管理标签与选择算符匹配的所有 Pod。
它不区分它创建或删除的 Pod 和其他人或进程创建或删除的 Pod。
这允许在不影响正在运行的 Pod 的情况下替换 ReplicationController。
如果指定了 .spec.template.metadata.labels
,它必须和 .spec.selector
相同,否则它将被 API 拒绝。
如果没有指定 .spec.selector
,它将默认为 .spec.template.metadata.labels
。
另外,通常不应直接使用另一个 ReplicationController 或另一个控制器(例如 Job)
来创建其标签与该选择算符匹配的任何 Pod。如果这样做,ReplicationController 会认为它创建了这些 Pod。
Kubernetes 并没有阻止你这样做。
如果你的确创建了多个控制器并且其选择算符之间存在重叠,那么你将不得不自己管理删除操作(参考后文 )。
多个副本
你可以通过设置 .spec.replicas
来指定应该同时运行多少个 Pod。
在任何时候,处于运行状态的 Pod 个数都可能高于或者低于设定值。例如,副本个数刚刚被增加或减少时,
或者一个 Pod 处于优雅终止过程中而其替代副本已经提前开始创建时。
如果你没有指定 .spec.replicas
,那么它默认是 1。
使用 ReplicationController
删除一个 ReplicationController 以及它的 Pod
要删除一个 ReplicationController 以及它的 Pod,使用
kubectl delete
。
kubectl 将 ReplicationController 缩容为 0 并等待以便在删除 ReplicationController 本身之前删除每个 Pod。
如果这个 kubectl 命令被中断,可以重新启动它。
当使用 REST API 或客户端库 时,你需要明确地执行这些步骤(缩容副本为 0、
等待 Pod 删除,之后删除 ReplicationController 资源)。
只删除 ReplicationController
你可以删除一个 ReplicationController 而不影响它的任何 Pod。
使用 kubectl,为 kubectl delete
指定 --cascade=orphan
选项。
当使用 REST API 或客户端库 时,只需删除 ReplicationController 对象。
一旦原始对象被删除,你可以创建一个新的 ReplicationController 来替换它。
只要新的和旧的 .spec.selector
相同,那么新的控制器将领养旧的 Pod。
但是,它不会做出任何努力使现有的 Pod 匹配新的、不同的 Pod 模板。
如果希望以受控方式更新 Pod 以使用新的 spec,请执行滚动更新 操作。
从 ReplicationController 中隔离 Pod
通过更改 Pod 的标签,可以从 ReplicationController 的目标中删除 Pod。
此技术可用于从服务中删除 Pod 以进行调试、数据恢复等。以这种方式删除的 Pod
将被自动替换(假设复制副本的数量也没有更改)。
常见的使用模式
重新调度
如上所述,无论你想要继续运行 1 个 Pod 还是 1000 个 Pod,一个
ReplicationController 都将确保存在指定数量的 Pod,即使在节点故障或
Pod 终止(例如,由于另一个控制代理的操作)的情况下也是如此。
扩缩容
通过设置 replicas
字段,ReplicationController 可以允许扩容或缩容副本的数量。
你可以手动或通过自动扩缩控制代理来控制 ReplicationController 执行此操作。
滚动更新
ReplicationController 的设计目的是通过逐个替换 Pod 以方便滚动更新服务。
如 #1353 PR 中所述,建议的方法是使用 1 个副本创建一个新的 ReplicationController,
逐个扩容新的(+1)和缩容旧的(-1)控制器,然后在旧的控制器达到 0 个副本后将其删除。
这一方法能够实现可控的 Pod 集合更新,即使存在意外失效的状况。
理想情况下,滚动更新控制器将考虑应用程序的就绪情况,并确保在任何给定时间都有足够数量的
Pod 有效地提供服务。
这两个 ReplicationController 将需要创建至少具有一个不同标签的 Pod,
比如 Pod 主要容器的镜像标签,因为通常是镜像更新触发滚动更新。
多个版本跟踪
除了在滚动更新过程中运行应用程序的多个版本之外,通常还会使用多个版本跟踪来长时间,
甚至持续运行多个版本。这些跟踪将根据标签加以区分。
例如,一个服务可能把具有 tier in (frontend), environment in (prod)
的所有 Pod 作为目标。
现在假设你有 10 个副本的 Pod 组成了这个层。但是你希望能够 canary
(金丝雀
)发布这个组件的新版本。
你可以为大部分副本设置一个 ReplicationController,其中 replicas
设置为 9,
标签为 tier=frontend, environment=prod, track=stable
而为 canary
设置另一个 ReplicationController,其中 replicas
设置为 1,
标签为 tier=frontend, environment=prod, track=canary
。
现在这个服务覆盖了 canary
和非 canary
Pod。但你可以单独处理
ReplicationController,以测试、监控结果等。
和服务一起使用 ReplicationController
多个 ReplicationController 可以位于一个服务的后面,例如,一部分流量流向旧版本,
一部分流量流向新版本。
一个 ReplicationController 永远不会自行终止,但它不会像服务那样长时间存活。
服务可以由多个 ReplicationController 控制的 Pod 组成,并且在服务的生命周期内
(例如,为了执行 Pod 更新而运行服务),可以创建和销毁许多 ReplicationController。
服务本身和它们的客户端都应该忽略负责维护服务 Pod 的 ReplicationController 的存在。
编写多副本的应用
由 ReplicationController 创建的 Pod 是可替换的,语义上是相同的,
尽管随着时间的推移,它们的配置可能会变得异构。
这显然适合于多副本的无状态服务器,但是 ReplicationController 也可以用于维护主选、
分片和工作池应用程序的可用性。
这样的应用程序应该使用动态的工作分配机制,例如
RabbitMQ 工作队列 ,
而不是静态的或者一次性定制每个 Pod 的配置,这被认为是一种反模式。
执行的任何 Pod 定制,例如资源的垂直自动调整大小(例如,CPU 或内存),
都应该由另一个在线控制器进程执行,这与 ReplicationController 本身没什么不同。
ReplicationController 的职责
ReplicationController 仅确保所需的 Pod 数量与其标签选择算符匹配,并且是可操作的。
目前,它的计数中只排除终止的 Pod。
未来,可能会考虑系统提供的就绪状态 和其他信息,
我们可能会对替换策略添加更多控制,
我们计划发出事件,这些事件可以被外部客户端用来实现任意复杂的替换和/或缩减策略。
ReplicationController 永远被限制在这个狭隘的职责范围内。
它本身既不执行就绪态探测,也不执行活跃性探测。
它不负责执行自动扩缩,而是由外部自动扩缩器控制(如
#492 中所述),后者负责更改其 replicas
字段值。
我们不会向 ReplicationController 添加调度策略(例如,
spreading )。
它也不应该验证所控制的 Pod 是否与当前指定的模板匹配,因为这会阻碍自动调整大小和其他自动化过程。
类似地,完成期限、整理依赖关系、配置扩展和其他特性也属于其他地方。
我们甚至计划考虑批量创建 Pod 的机制(查阅 #170 )。
ReplicationController 旨在成为可组合的构建基元。
我们希望在它和其他补充原语的基础上构建更高级别的 API 或者工具,以便于将来的用户使用。
kubectl 目前支持的 "macro" 操作(运行、扩缩、滚动更新)就是这方面的概念示例。
例如,我们可以想象类似于 Asgard
的东西管理 ReplicationController、自动定标器、服务、调度策略、金丝雀发布等。
API 对象
在 Kubernetes REST API 中 Replication controller 是顶级资源。
更多关于 API 对象的详细信息可以在
ReplicationController API 对象 找到。
ReplicationController 的替代方案
ReplicaSet
ReplicaSet
是下一代 ReplicationController,
支持新的基于集合的标签选择算符 。
它主要被 Deployment
用来作为一种编排 Pod 创建、删除及更新的机制。
请注意,我们推荐使用 Deployment 而不是直接使用 ReplicaSet,除非你需要自定义更新编排或根本不需要更新。
Deployment (推荐)
Deployment
是一种更高级别的 API 对象,用于更新其底层 ReplicaSet 及其 Pod。
如果你想要这种滚动更新功能,那么推荐使用 Deployment,因为它们是声明式的、服务端的,并且具有其它特性。
裸 Pod
与用户直接创建 Pod 的情况不同,ReplicationController 能够替换因某些原因被删除或被终止的 Pod,
例如在节点故障或中断节点维护的情况下,例如内核升级。
因此,我们建议你使用 ReplicationController,即使你的应用程序只需要一个 Pod。
可以将其看作类似于进程管理器,它只管理跨多个节点的多个 Pod,而不是单个节点上的单个进程。
ReplicationController 将本地容器重启委托给节点上的某个代理(例如 Kubelet)。
Job
对于预期会自行终止的 Pod (即批处理任务),使用
Job
而不是 ReplicationController。
DaemonSet
对于提供机器级功能(例如机器监控或机器日志记录)的 Pod,
使用 DaemonSet
而不是
ReplicationController。
这些 Pod 的生命期与机器的生命期绑定:它们需要在其他 Pod 启动之前在机器上运行,
并且在机器准备重新启动或者关闭时安全地终止。
接下来