这是本节的多页打印视图。
点击此处打印 .
返回本页常规视图 .
Pod
Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod (就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个)
容器 ;
这些容器共享存储、网络、以及怎样运行这些容器的声明。
Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。
Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器,
这些容器相对紧密地耦合在一起。
在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的
Init 容器 。
你也可以在集群支持临时性容器 的情况下,
为调试的目的注入临时性容器。
什么是 Pod?
说明: 除了 Docker 之外,Kubernetes 支持很多其他
容器运行时 ,
Docker 是最有名的运行时,
使用 Docker 的术语来描述 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 容器会在启动应用容器之前运行并完成。
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.26 中,为此字段设置的值对 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
属性值相同。
网络 部分提供了更多有关此内容的信息。
容器的特权模式
在 Linux 中,Pod 中的任何容器都可以使用容器规约中的
安全性上下文 中的
privileged
(Linux)参数启用特权模式。
这对于想要使用操作系统管理权能(Capabilities,如操纵网络堆栈和访问设备)的容器很有用。
如果你的集群启用了 WindowsHostProcessContainers
特性,你可以使用 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 服务器来控制。
容器探针
Probe 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 可以执行三种动作:
ExecAction
(借助容器运行时执行)
TCPSocketAction
(由 kubelet 直接检测)
HTTPGetAction
(由 kubelet 直接检测)
你可以参阅 Pod 的生命周期文档中的探针 部分。
接下来
要了解为什么 Kubernetes 会在其他资源
(如 StatefulSet
或 Deployment )
封装通用的 Pod API,相关的背景信息可以在前人的研究中找到。具体包括:
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
会实施一种策略,将失去的节点上运行的所有 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 中的所有容器。restartPolicy
仅针对同一节点上
kubelet
的容器重启动作。当 Pod 中的容器退出时,kubelet
会按指数回退方式计算重启的延迟(10s、20s、40s、...),其最长延迟为 5 分钟。
一旦某容器执行了 10 分钟并且没有出现问题,kubelet
对该容器的重启回退计时器执行重置操作。
Pod 状况
Pod 有一个 PodStatus 对象,其中包含一个
PodConditions
数组。Pod 可能通过也可能未通过其中的一些状况测试。
Kubelet 管理以下 PodCondition:
PodScheduled
:Pod 已经被调度到某节点;
PodHasNetwork
:Pod 沙箱被成功创建并且配置了网络(Alpha 特性,必须被显式启用 );
ContainersReady
:Pod 中所有容器都已就绪;
Initialized
:所有的 Init 容器 都已成功完成;
Ready
:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
字段名称
描述
type
Pod 状况的名称
status
表明该状况是否适用,可能的取值有 "True
"、"False
" 或 "Unknown
"
lastProbeTime
上次探测 Pod 状况时的时间戳
lastTransitionTime
Pod 上次从一种状态转换到另一种状态时的时间戳
reason
机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因
message
人类可读的消息,给出上次状态转换的详细信息
Pod 就绪态
特性状态: Kubernetes v1.14 [stable]
你的应用可以向 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]
在 Pod 被调度到某节点后,它需要被 Kubelet 接受并且挂载所需的卷。
一旦这些阶段完成,Kubelet 将与容器运行时(使用容器运行时接口(Container Runtime Interface;CRI) )
一起为 Pod 生成运行时沙箱并配置网络。
如果启用了 PodHasNetworkCondition
特性门控 ,
kubelet 会通过 Pod 的 status.conditions
字段中的 PodHasNetwork
状况来报告
Pod 是否达到了初始化里程碑。
当 kubelet 检测到 Pod 不具备配置了网络的运行时沙箱时,PodHasNetwork
状况将被设置为 False
。
以下场景中将会发生这种状况:
在 Pod 生命周期的早期阶段,kubelet 还没有开始使用容器运行时为 Pod 设置沙箱时。
在 Pod 生命周期的末期阶段,Pod 的沙箱由于以下原因被销毁时:
节点重启时 Pod 没有被驱逐
对于使用虚拟机进行隔离的容器运行时,Pod 沙箱虚拟机重启时,需要创建一个新的沙箱和全新的容器网络配置。
在运行时插件成功完成 Pod 的沙箱创建和网络配置后,
kubelet 会将 PodHasNetwork
状况设置为 True
。
当 PodHasNetwork
状况设置为 True
后,
Kubelet 可以开始拉取容器镜像和创建容器。
对于带有 Init 容器的 Pod,kubelet 会在 Init 容器成功完成后将 Initialized
状况设置为 True
(这发生在运行时成功创建沙箱和配置网络之后),
对于没有 Init 容器的 Pod,kubelet 会在创建沙箱和网络配置开始之前将
Initialized
状况设置为 True
。
容器探针
probe 是由 kubelet 对容器执行的定期诊断。
要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。
检查机制
使用探针来检查容器有四种不同的方法。
每个探针都必须准确定义为这四种机制中的一种:
exec
在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
grpc
使用 gRPC 执行一个远程过程调用。
目标应该实现
gRPC健康检查 。
如果响应的状态是 "SERVING",则认为诊断成功。
gRPC 探针是一个 Alpha 特性,只有在你启用了
"GRPCContainerProbe" 特性门控 时才能使用。
httpGet
对容器的 IP 地址上指定端口和路径执行 HTTP GET
请求。如果响应的状态码大于等于 200
且小于 400,则诊断被认为是成功的。
tcpSocket
对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。
如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。
探测结果
每次探测都将获得以下三种结果之一:
Success
(成功)
容器通过了诊断。
Failure
(失败)
容器未通过诊断。
Unknown
(未知)
诊断失败,因此不会采取任何行动。
探测类型
针对运行中的容器,kubelet
可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器,
并且容器将根据其重启策略 决定未来。如果容器不提供存活探针,
则默认状态为 Success
。
readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败,
端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。
初始延迟之前的就绪态的状态值默认为 Failure
。
如果容器不提供就绪态探针,则默认状态为 Success
。
startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被
禁用,直到此探针成功为止。如果启动探测失败,kubelet
将杀死容器,
而容器依其重启策略 进行重启。
如果容器没有提供启动探测,则默认状态为 Success
。
如欲了解如何设置存活态、就绪态和启动探针的进一步细节,
可以参阅配置存活态、就绪态和启动探针 。
何时该使用存活态探针?
特性状态: Kubernetes v1.0 [stable]
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针;
kubelet
将根据 Pod 的 restartPolicy
自动执行修复操作。
如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针,
并指定 restartPolicy
为 "Always
" 或 "OnFailure
"。
何时该使用就绪态探针?
特性状态: Kubernetes v1.0 [stable]
如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。
在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着
Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。
如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针,
检查某个特定于就绪态的因此不同于存活态探测的端点。
如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。
当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。
这可以帮助你避免将流量导向只能返回错误信息的 Pod。
如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移,
你可以使用启动探针 。
然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。
说明:
请注意,如果你只是想在 Pod 被删除时能够排空请求,则不一定需要使用就绪态探针;
在删除 Pod 时,Pod 会自动将自身置于未就绪状态,无论就绪态探针是否存在。
等待 Pod 中的容器停止期间,Pod 会一直处于未就绪状态。
何时该使用启动探针?
特性状态: Kubernetes v1.20 [stable]
对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。
你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定,
对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds
总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。
periodSeconds
的默认值是 10 秒。你应该将其 failureThreshold
设置得足够高,
以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。
这一设置有助于减少死锁状况的发生。
Pod 的终止
由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时允许其体面地终止是很重要的。
一般不应武断地使用 KILL
信号终止它们,导致这些进程没有机会完成清理操作。
设计的目标是令你能够请求删除进程,并且知道进程何时被终止,同时也能够确保删除操作终将完成。
当你请求删除某个 Pod 时,集群会记录并跟踪 Pod 的体面终止周期,
而不是直接强制地杀死 Pod。在存在强制关闭设施的前提下,
kubelet 会尝试体面地终止
Pod。
通常情况下,容器运行时会发送一个 TERM 信号到每个容器中的主进程。
很多容器运行时都能够注意到容器镜像中 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` 回调所需要的时间长于默认的体面终止限期,你必须修改
`terminationGracePeriodSeconds` 属性值来使其正常工作。
kubelet
接下来触发容器运行时发送 TERM 信号给每个容器中的进程 1。
说明:
Pod 中的容器会在不同时刻收到 TERM 信号,接收顺序也是不确定的。
如果关闭的顺序很重要,可以考虑使用 `preStop` 回调逻辑来协调。
在 kubelet
启动体面关闭逻辑的同时,控制面会将关闭的 Pod 从对应的
EndpointSlice(和 Endpoints)对象中移除,过滤条件是 Pod
被对应的服务 以某
选择算符 选定。
ReplicaSet
和其他工作负载资源不再将关闭进程中的 Pod 视为合法的、能够提供服务的副本。
关闭动作很慢的 Pod 也无法继续处理请求数据,
因为负载均衡器(例如服务代理)已经在终止宽限期开始的时候将其从端点列表中移除。
超出终止宽限期限时,kubelet
会触发强制关闭过程。容器运行时会向 Pod
中所有容器内仍在运行的进程发送 SIGKILL
信号。
kubelet
也会清理隐藏的 pause
容器,如果容器运行时使用了这种容器的话。
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 干扰状况 )。
接下来
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。
如下为创建这些 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 容器的名称必须唯一;
与任何其它容器共享同一个名称,会在校验时抛出错误。
资源
在给定的 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,可查阅你所使用的版本对应的文档。
接下来
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
中配置的。
当使用驱逐 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]
说明:
如果你正使用的 Kubernetes 版本早于 1.26,请参阅对应版本的文档。
说明:
要使用此行为,你必须在集群中启用 PodDisruptionConditions
特性门控 。
启用后,会给 Pod 添加一个 DisruptionTarget
状况 ,
用来表明该 Pod 因为发生干扰 而被删除。
状况中的 reason
字段进一步给出 Pod 终止的原因,如下:
PreemptionByKubeScheduler
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。
不停机。
最小的资源重复。
允许更多的集群管理自动化。
编写可容忍干扰的应用是棘手的,但对于支持容忍自愿干扰所做的工作,和支持自动扩缩和容忍非
自愿干扰所做工作相比,有大量的重叠
接下来
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
命令进行故障排查。
使用临时容器时,
启用进程名字空间共享 很有帮助,
可以查看其他容器中的进程。
接下来
5 - 用户命名空间
特性状态: Kubernetes v1.25 [alpha]
本页解释了在 Kubernetes pods 中如何使用用户命名空间。
用户命名空间允许将容器内运行的用户与主机内的用户隔离开来。
在容器中以 root 身份运行的进程可以在主机中以不同的(非 root)用户身份运行;
换句话说,该进程在用户命名空间内的操作具有完全的权限,
但在命名空间外的操作是无特权的。
你可以使用这个功能来减少被破坏的容器对主机或同一节点中的其他 Pod 的破坏。
有几个安全漏洞 被评为 高 或 重要 ,
当用户命名空间处于激活状态时,这些漏洞是无法被利用的。
预计用户命名空间也会减轻一些未来的漏洞。
准备开始
🛇 本条目指向第三方项目或产品,而该项目(产品)不是 Kubernetes 的一部分。
更多信息
这是一个只对 Linux 有效的功能特性。此外,需要在容器运行时 提供支持,
才能在 Kubernetes 无状态 Pod 中使用这一功能:
CRI-O:v1.25 版已经支持用户命名空间。
containerd:计划在 1.7 版本中支持。更多细节请参见 containerd 问题 #7063 。
目前 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
Pod 完全不使用卷是被允许的;如果使用卷,只允许使用以下卷类型:
configmap
secret
projected
downwardAPI
emptyDir
为了保证 Pod 可以读取这些卷中的文件,卷的创建操作就像你为 Pod 指定了 .spec.securityContext.fsGroup
为 0
一样。
如果该属性被设定为不同值,那么这个不同值当然也会被使用。
作为一个副产品,这些卷的文件夹和文件将具有所给组的权限,
即使 defaultMode
或 volumes 的特定项目的 mode
被指定为没有组的权限。
例如,不可以在挂载这些卷时使其文件只允许所有者访问。
6 - 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']
)
spec.serviceAccountName
Pod 的服务账号 名称
spec.nodeName
Pod 运行时所处的节点 名称
status.hostIP
Pod 所在节点的主 IP 地址
status.podIP
Pod 的主 IP 地址(通常是其 IPv4 地址)
此外,以下信息可以通过 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-*
容器的巨页限制值(前提是启用了 DownwardAPIHugePages
特性门控 )
resource: requests.hugepages-*
容器的巨页请求值(前提是启用了 DownwardAPIHugePages
特性门控 )
resource: limits.ephemeral-storage
容器的临时存储的限制值
resource: requests.ephemeral-storage
容器的临时存储的请求值
如果没有为容器指定 CPU 和内存限制时尝试使用 Downward API 暴露该信息,那么 kubelet 默认会根据
节点可分配资源
计算并暴露 CPU 和内存的最大可分配值。
接下来
你可以阅读有关 downwardAPI
卷 的内容。
你可以尝试使用 Downward API 暴露容器或 Pod 级别的信息: