Kubernetes 架构
- 1: 节点
- 2: 节点与控制面之间的通信
- 3: 控制器
- 4: 租约(Lease)
- 5: 云控制器管理器
- 6: 关于 cgroup v2
- 7: 容器运行时接口(CRI)
- 8: 垃圾收集
- 9: 混合版本代理
1 - 节点
Kubernetes 通过将容器放入在节点(Node)上运行的 Pod 中来执行你的工作负载。 节点可以是一个虚拟机或者物理机器,取决于所在的集群配置。 每个节点包含运行 Pod 所需的服务; 这些节点由控制面负责管理。
通常集群中会有若干个节点;而在一个学习所用或者资源受限的环境中,你的集群中也可能只有一个节点。
节点上的组件包括 kubelet、 容器运行时以及 kube-proxy。
管理
向 API 服务器添加节点的方式主要有两种:
- 节点上的
kubelet
向控制面执行自注册; - 你(或者别的什么人)手动添加一个 Node 对象。
在你创建了 Node 对象或者节点上的
kubelet
执行了自注册操作之后,控制面会检查新的 Node 对象是否合法。
例如,如果你尝试使用下面的 JSON 对象来创建 Node 对象:
{
"kind": "Node",
"apiVersion": "v1",
"metadata": {
"name": "10.240.79.157",
"labels": {
"name": "my-first-k8s-node"
}
}
}
Kubernetes 会在内部创建一个 Node 对象作为节点的表示。Kubernetes 检查 kubelet
向 API 服务器注册节点时使用的 metadata.name
字段是否匹配。
如果节点是健康的(即所有必要的服务都在运行中),则该节点可以用来运行 Pod。
否则,直到该节点变为健康之前,所有的集群活动都会忽略该节点。
Kubernetes 会一直保存着非法节点对应的对象,并持续检查该节点是否已经变得健康。
你,或者某个控制器必须显式地删除该 Node 对象以停止健康检查操作。
Node 对象的名称必须是合法的 DNS 子域名。
节点名称唯一性
节点的名称用来标识 Node 对象。 没有两个 Node 可以同时使用相同的名称。 Kubernetes 还假定名字相同的资源是同一个对象。 就 Node 而言,隐式假定使用相同名称的实例会具有相同的状态(例如网络配置、根磁盘内容) 和类似节点标签这类属性。这可能在节点被更改但其名称未变时导致系统状态不一致。 如果某个 Node 需要被替换或者大量变更,需要从 API 服务器移除现有的 Node 对象, 之后再在更新之后重新将其加入。
节点自注册
当 kubelet 标志 --register-node
为 true(默认)时,它会尝试向 API 服务注册自己。
这是首选模式,被绝大多数发行版选用。
对于自注册模式,kubelet 使用下列参数启动:
--kubeconfig
- 用于向 API 服务器执行身份认证所用的凭据的路径。--cloud-provider
- 与某云驱动 进行通信以读取与自身相关的元数据的方式。--register-node
- 自动向 API 服务器注册。--register-with-taints
- 使用所给的污点列表 (逗号分隔的<key>=<value>:<effect>
)注册节点。当register-node
为 false 时无效。
-
--node-ip
- 可选的以英文逗号隔开的节点 IP 地址列表。你只能为每个地址簇指定一个地址。 例如在单协议栈 IPv4 集群中,需要将此值设置为 kubelet 应使用的节点 IPv4 地址。 参阅配置 IPv4/IPv6 双协议栈了解运行双协议栈集群的详情。如果你未提供这个参数,kubelet 将使用节点默认的 IPv4 地址(如果有); 如果节点没有 IPv4 地址,则 kubelet 使用节点的默认 IPv6 地址。
--node-labels
- 在集群中注册节点时要添加的标签。 (参见 NodeRestriction 准入控制插件所实施的标签限制)。--node-status-update-frequency
- 指定 kubelet 向 API 服务器发送其节点状态的频率。
当 Node 鉴权模式和 NodeRestriction 准入插件被启用后, 仅授权 kubelet 创建/修改自己的 Node 资源。
正如节点名称唯一性一节所述,当 Node 的配置需要被更新时,
一种好的做法是重新向 API 服务器注册该节点。例如,如果 kubelet 重启时其 --node-labels
是新的值集,但同一个 Node 名称已经被使用,则所作变更不会起作用,
因为节点标签是在 Node 注册时完成的。
如果在 kubelet 重启期间 Node 配置发生了变化,已经被调度到某 Node 上的 Pod 可能会出现行为不正常或者出现其他问题,例如,已经运行的 Pod 可能通过污点机制设置了与 Node 上新设置的标签相排斥的规则,也有一些其他 Pod, 本来与此 Pod 之间存在不兼容的问题,也会因为新的标签设置而被调到同一节点。 节点重新注册操作可以确保节点上所有 Pod 都被排空并被正确地重新调度。
手动节点管理
你可以使用 kubectl 来创建和修改 Node 对象。
如果你希望手动创建节点对象时,请设置 kubelet 标志 --register-node=false
。
你可以修改 Node 对象(忽略 --register-node
设置)。
例如,你可以修改节点上的标签或并标记其为不可调度。
你可以结合使用 Node 上的标签和 Pod 上的选择算符来控制调度。 例如,你可以限制某 Pod 只能在符合要求的节点子集上运行。
如果标记节点为不可调度(unschedulable),将阻止新 Pod 调度到该 Node 之上, 但不会影响任何已经在其上的 Pod。 这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。
要标记一个 Node 为不可调度,执行以下命令:
kubectl cordon $NODENAME
更多细节参考安全地腾空节点。
被 DaemonSet 控制器创建的 Pod 能够容忍节点的不可调度属性。 DaemonSet 通常提供节点本地的服务,即使节点上的负载应用已经被腾空, 这些服务也仍需运行在节点之上。
节点状态
一个节点的状态包含以下信息:
你可以使用 kubectl
来查看节点状态和其他细节信息:
kubectl describe node <节点名称>
更多细节参见 Node Status。
节点心跳
Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。
对于节点,有两种形式的心跳:
节点控制器
节点控制器是 Kubernetes 控制面组件, 管理节点的方方面面。
节点控制器在节点的生命周期中扮演多个角色。 第一个是当节点注册时为它分配一个 CIDR 区段(如果启用了 CIDR 分配)。
第二个是保持节点控制器内的节点列表与云服务商所提供的可用机器列表同步。 如果在云环境下运行,只要某节点不健康,节点控制器就会询问云服务是否节点的虚拟机仍可用。 如果不可用,节点控制器会将该节点从它的节点列表删除。
第三个是监控节点的健康状况。节点控制器负责:
- 在节点不可达的情况下,在 Node 的
.status
中更新Ready
状况。 在这种情况下,节点控制器将 NodeReady 状况更新为Unknown
。 - 如果节点仍然无法访问:对于不可达节点上的所有 Pod 触发
API 发起的逐出操作。
默认情况下,节点控制器在将节点标记为
Unknown
后等待 5 分钟提交第一个驱逐请求。
默认情况下,节点控制器每 5 秒检查一次节点状态,可以使用 kube-controller-manager
组件上的 --node-monitor-period
参数来配置周期。
逐出速率限制
大部分情况下,节点控制器把逐出速率限制在每秒 --node-eviction-rate
个(默认为 0.1)。
这表示它每 10 秒钟内至多从一个节点驱逐 Pod。
当一个可用区域(Availability Zone)中的节点变为不健康时,节点的驱逐行为将发生改变。
节点控制器会同时检查可用区域中不健康(Ready
状况为 Unknown
或 False
)
的节点的百分比:
- 如果不健康节点的比例超过
--unhealthy-zone-threshold
(默认为 0.55), 驱逐速率将会降低。 - 如果集群较小(意即小于等于
--large-cluster-size-threshold
个节点 - 默认为 50), 驱逐操作将会停止。 - 否则驱逐速率将降为每秒
--secondary-node-eviction-rate
个(默认为 0.01)。
在逐个可用区域中实施这些策略的原因是, 当一个可用区域可能从控制面脱离时其它可用区域可能仍然保持连接。 如果你的集群没有跨越云服务商的多个可用区域,那(整个集群)就只有一个可用区域。
跨多个可用区域部署你的节点的一个关键原因是当某个可用区域整体出现故障时,
工作负载可以转移到健康的可用区域。
因此,如果一个可用区域中的所有节点都不健康时,节点控制器会以正常的速率
--node-eviction-rate
进行驱逐操作。
在所有的可用区域都不健康(也即集群中没有健康节点)的极端情况下,
节点控制器将假设控制面与节点间的连接出了某些问题,它将停止所有驱逐动作
(如果故障后部分节点重新连接,节点控制器会从剩下不健康或者不可达节点中驱逐 Pod)。
节点控制器还负责驱逐运行在拥有 NoExecute
污点的节点上的 Pod,
除非这些 Pod 能够容忍此污点。
节点控制器还负责根据节点故障(例如节点不可访问或没有就绪)
为其添加污点。
这意味着调度器不会将 Pod 调度到不健康的节点上。
资源容量跟踪
Node 对象会跟踪节点上资源的容量(例如可用内存和 CPU 数量)。 通过自注册机制生成的 Node 对象会在注册期间报告自身容量。 如果你手动添加了 Node, 你就需要在添加节点时手动设置节点容量。
Kubernetes 调度器
保证节点上有足够的资源供其上的所有 Pod 使用。
它会检查节点上所有容器的请求的总和不会超过节点的容量。
总的请求包括由 kubelet 启动的所有容器,但不包括由容器运行时直接启动的容器,
也不包括不受 kubelet
控制的其他进程。
如果要为非 Pod 进程显式保留资源。 请参考为系统守护进程预留资源。
节点拓扑
Kubernetes v1.27 [stable]
如果启用了 TopologyManager
特性门控,
kubelet
可以在作出资源分配决策时使用拓扑提示。
参考控制节点上拓扑管理策略了解详细信息。
节点体面关闭
Kubernetes v1.21 [beta]
kubelet 会尝试检测节点系统关闭事件并终止在节点上运行的所有 Pod。
在节点终止期间,kubelet 保证 Pod 遵从常规的 Pod 终止流程, 且不接受新的 Pod(即使这些 Pod 已经绑定到该节点)。
节点体面关闭特性依赖于 systemd,因为它要利用 systemd 抑制器锁机制, 在给定的期限内延迟节点关闭。
节点体面关闭特性受 GracefulNodeShutdown
特性门控控制,
在 1.21 版本中是默认启用的。
注意,默认情况下,下面描述的两个配置选项,shutdownGracePeriod
和
shutdownGracePeriodCriticalPods
都是被设置为 0 的,因此不会激活节点体面关闭功能。
要激活此功能特性,这两个 kubelet 配置选项要适当配置,并设置为非零值。
一旦 systemd 检测到或通知节点关闭,kubelet 就会在节点上设置一个
NotReady
状况,并将 reason
设置为 "node is shutting down"
。
kube-scheduler 会重视此状况,不将 Pod 调度到受影响的节点上;
其他第三方调度程序也应当遵循相同的逻辑。这意味着新的 Pod 不会被调度到该节点上,
因此不会有新 Pod 启动。
如果检测到节点关闭过程正在进行中,kubelet 也会在 PodAdmission
阶段拒绝 Pod,即使是该 Pod 带有 node.kubernetes.io/not-ready:NoSchedule
的容忍度。
同时,当 kubelet 通过 API 在其 Node 上设置该状况时,kubelet 也开始终止在本地运行的所有 Pod。
在体面关闭节点过程中,kubelet 分两个阶段来终止 Pod:
- 终止在节点上运行的常规 Pod。
- 终止在节点上运行的关键 Pod。
节点体面关闭的特性对应两个
KubeletConfiguration
选项:
shutdownGracePeriod
:- 指定节点应延迟关闭的总持续时间。此时间是 Pod 体面终止的时间总和,不区分常规 Pod 还是关键 Pod。
shutdownGracePeriodCriticalPods
:- 在节点关闭期间指定用于终止关键 Pod
的持续时间。该值应小于
shutdownGracePeriod
。
- 在节点关闭期间指定用于终止关键 Pod
的持续时间。该值应小于
在某些情况下,节点终止过程会被系统取消(或者可能由管理员手动取消)。
无论哪种情况下,节点都将返回到 Ready
状态。然而,已经开始终止进程的
Pod 将不会被 kubelet 恢复,需要被重新调度。
例如,如果设置了 shutdownGracePeriod=30s
和 shutdownGracePeriodCriticalPods=10s
,
则 kubelet 将延迟 30 秒关闭节点。
在关闭期间,将保留前 20(30 - 10)秒用于体面终止常规 Pod,
而保留最后 10 秒用于终止关键 Pod。
当 Pod 在正常节点关闭期间被驱逐时,它们会被标记为关闭。
运行 kubectl get pods
时,被驱逐的 Pod 的状态显示为 Terminated
。
并且 kubectl describe pod
表示 Pod 因节点关闭而被驱逐:
Reason: Terminated
Message: Pod was terminated in response to imminent node shutdown.
基于 Pod 优先级的节点体面关闭
Kubernetes v1.24 [beta]
为了在节点体面关闭期间提供更多的灵活性,尤其是处理关闭期间的 Pod 排序问题, 节点体面关闭机制能够关注 Pod 的 PriorityClass 设置,前提是你已经在集群中启用了此功能特性。 此功能特性允许集群管理员基于 Pod 的优先级类(Priority Class) 显式地定义节点体面关闭期间 Pod 的处理顺序。
前文所述的节点体面关闭特性能够分两个阶段关闭 Pod, 首先关闭的是非关键的 Pod,之后再处理关键 Pod。 如果需要显式地以更细粒度定义关闭期间 Pod 的处理顺序,需要一定的灵活度, 这时可以使用基于 Pod 优先级的体面关闭机制。
当节点体面关闭能够处理 Pod 优先级时,节点体面关闭的处理可以分为多个阶段, 每个阶段关闭特定优先级类的 Pod。kubelet 可以被配置为按确切的阶段处理 Pod, 且每个阶段可以独立设置关闭时间。
假设集群中存在以下自定义的 Pod 优先级类。
Pod 优先级类名称 | Pod 优先级类数值 |
---|---|
custom-class-a |
100000 |
custom-class-b |
10000 |
custom-class-c |
1000 |
regular/unset |
0 |
在 kubelet 配置中,
shutdownGracePeriodByPodPriority
可能看起来是这样:
Pod 优先级类数值 | 关闭期限 |
---|---|
100000 | 10 秒 |
10000 | 180 秒 |
1000 | 120 秒 |
0 | 60 秒 |
对应的 kubelet 配置 YAML 将会是:
shutdownGracePeriodByPodPriority:
- priority: 100000
shutdownGracePeriodSeconds: 10
- priority: 10000
shutdownGracePeriodSeconds: 180
- priority: 1000
shutdownGracePeriodSeconds: 120
- priority: 0
shutdownGracePeriodSeconds: 60
上面的表格表明,所有 priority
值大于等于 100000 的 Pod 会得到 10 秒钟期限停止,
所有 priority
值介于 10000 和 100000 之间的 Pod 会得到 180 秒钟期限停止,
所有 priority
值介于 1000 和 10000 之间的 Pod 会得到 120 秒钟期限停止,
所有其他 Pod 将获得 60 秒的时间停止。
用户不需要为所有的优先级类都设置数值。例如,你也可以使用下面这种配置:
Pod 优先级类数值 | 关闭期限 |
---|---|
100000 | 300 秒 |
1000 | 120 秒 |
0 | 60 秒 |
在上面这个场景中,优先级类为 custom-class-b
的 Pod 会与优先级类为 custom-class-c
的 Pod 在关闭时按相同期限处理。
如果在特定的范围内不存在 Pod,则 kubelet 不会等待对应优先级范围的 Pod。 kubelet 会直接跳到下一个优先级数值范围进行处理。
如果此功能特性被启用,但没有提供配置数据,则不会出现排序操作。
使用此功能特性需要启用 GracefulNodeShutdownBasedOnPodPriority
特性门控,
并将 kubelet 配置
中的 shutdownGracePeriodByPodPriority
设置为期望的配置,
其中包含 Pod 的优先级类数值以及对应的关闭期限。
在节点体面关闭期间考虑 Pod 优先级的能力是作为 Kubernetes v1.23 中的 Alpha 功能引入的。 在 Kubernetes 1.29 中该功能是 Beta 版,默认启用。
kubelet 子系统中会生成 graceful_shutdown_start_time_seconds
和
graceful_shutdown_end_time_seconds
度量指标以便监视节点关闭行为。
处理节点非体面关闭
Kubernetes v1.28 [stable]
节点关闭的操作可能无法被 kubelet 的节点关闭管理器检测到, 是因为该命令不会触发 kubelet 所使用的抑制锁定机制,或者是因为用户错误的原因, 即 ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPod 配置不正确。 请参考以上节点体面关闭部分了解更多详细信息。
当某节点关闭但 kubelet 的节点关闭管理器未检测到这一事件时, 在那个已关闭节点上、属于 StatefulSet 的 Pod 将停滞于终止状态,并且不能移动到新的运行节点上。 这是因为已关闭节点上的 kubelet 已不存在,亦无法删除 Pod, 因此 StatefulSet 无法创建同名的新 Pod。 如果 Pod 使用了卷,则 VolumeAttachments 不会从原来的已关闭节点上删除, 因此这些 Pod 所使用的卷也无法挂接到新的运行节点上。 所以,那些以 StatefulSet 形式运行的应用无法正常工作。 如果原来的已关闭节点被恢复,kubelet 将删除 Pod,新的 Pod 将被在不同的运行节点上创建。 如果原来的已关闭节点没有被恢复,那些在已关闭节点上的 Pod 将永远滞留在终止状态。
为了缓解上述情况,用户可以手动将具有 NoExecute
或 NoSchedule
效果的
node.kubernetes.io/out-of-service
污点添加到节点上,标记其无法提供服务。
如果在 kube-controller-manager
上启用了 NodeOutOfServiceVolumeDetach
特性门控,
并且节点被通过污点标记为无法提供服务,如果节点 Pod 上没有设置对应的容忍度,
那么这样的 Pod 将被强制删除,并且该在节点上被终止的 Pod 将立即进行卷分离操作。
这样就允许那些在无法提供服务节点上的 Pod 能在其他节点上快速恢复。
在非体面关闭期间,Pod 分两个阶段终止:
- 强制删除没有匹配的
out-of-service
容忍度的 Pod。 - 立即对此类 Pod 执行分离卷操作。
- 在添加
node.kubernetes.io/out-of-service
污点之前, 应该验证节点已经处于关闭或断电状态(而不是在重新启动中)。 - 将 Pod 移动到新节点后,用户需要手动移除停止服务的污点, 并且用户要检查关闭节点是否已恢复,因为该用户是最初添加污点的用户。
交换内存管理
Kubernetes v1.28 [beta]
要在节点上启用交换内存,必须启用 kubelet 的 NodeSwap
特性门控,
同时使用 --fail-swap-on
命令行参数或者将 failSwapOn
配置设置为 false。
当内存交换功能被启用后,Kubernetes 数据(如写入 tmpfs 的 Secret 对象的内容)可以被交换到磁盘。
用户还可以选择配置 memorySwap.swapBehavior
以指定节点使用交换内存的方式。例如:
memorySwap:
swapBehavior: UnlimitedSwap
UnlimitedSwap
(默认):Kubernetes 工作负载可以根据请求使用尽可能多的交换内存, 一直到达到系统限制为止。LimitedSwap
:Kubernetes 工作负载对交换内存的使用受到限制。 只有具有 Burstable QoS 的 Pod 可以使用交换空间。
如果启用了特性门控但是未指定 memorySwap
的配置,默认情况下 kubelet 将使用与
UnlimitedSwap
设置相同的行为。
采用 LimitedSwap
时,不属于 Burstable QoS 分类的 Pod
(即 BestEffort
/Guaranteed
QoS Pod)
被禁止使用交换内存。为了保持上述的安全性和节点健康性保证,
在 LimitedSwap
生效时,不允许这些 Pod 使用交换内存。
在详细介绍交换限制的计算之前,有必要定义以下术语:
nodeTotalMemory
:节点上可用的物理内存总量。totalPodsSwapAvailable
:节点上可供 Pod 使用的交换内存总量 (一些交换内存可能被保留由系统使用)。containerMemoryRequest
:容器的内存请求。
交换限制被配置为 (containerMemoryRequest / nodeTotalMemory) * totalPodsSwapAvailable
的值。
需要注意的是,位于 Burstable QoS Pod 中的容器可以通过将内存请求设置为与内存限制相同来选择不使用交换空间。 以这种方式配置的容器将无法访问交换内存。
只有 Cgroup v2 支持交换空间,Cgroup v1 不支持。
如需了解更多信息、协助测试和提交反馈,请参阅关于 Kubernetes 1.28:NodeSwap 进阶至 Beta1 的博客文章、 KEP-2400 及其设计提案。
接下来
进一步了解以下资料:
- 构成节点的组件。
- Node 的 API 定义。
- 架构设计文档中有关 Node 的章节。
- 污点和容忍度。
- 节点资源管理器。
- Windows 节点的资源管理。
2 - 节点与控制面之间的通信
本文列举控制面节点(确切地说是 API 服务器)和 Kubernetes 集群之间的通信路径。 目的是为了让用户能够自定义他们的安装,以实现对网络配置的加固, 使得集群能够在不可信的网络上(或者在一个云服务商完全公开的 IP 上)运行。
节点到控制面
Kubernetes 采用的是中心辐射型(Hub-and-Spoke)API 模式。 所有从节点(或运行于其上的 Pod)发出的 API 调用都终止于 API 服务器。 其它控制面组件都没有被设计为可暴露远程服务。 API 服务器被配置为在一个安全的 HTTPS 端口(通常为 443)上监听远程连接请求, 并启用一种或多种形式的客户端身份认证机制。 一种或多种客户端鉴权机制应该被启用, 特别是在允许使用匿名请求 或服务账户令牌的时候。
应该使用集群的公共根证书开通节点, 这样它们就能够基于有效的客户端凭据安全地连接 API 服务器。 一种好的方法是以客户端证书的形式将客户端凭据提供给 kubelet。 请查看 kubelet TLS 启动引导 以了解如何自动提供 kubelet 客户端证书。
想要连接到 API 服务器的 Pod
可以使用服务账号安全地进行连接。
当 Pod 被实例化时,Kubernetes 自动把公共根证书和一个有效的持有者令牌注入到 Pod 里。
kubernetes
服务(位于 default
名字空间中)配置了一个虚拟 IP 地址,
用于(通过 kube-proxy
)转发请求到
API 服务器的 HTTPS 末端。
控制面组件也通过安全端口与集群的 API 服务器通信。
这样,从集群节点和节点上运行的 Pod 到控制面的连接的缺省操作模式即是安全的, 能够在不可信的网络或公网上运行。
控制面到节点
从控制面(API 服务器)到节点有两种主要的通信路径。 第一种是从 API 服务器到集群中每个节点上运行的 kubelet 进程。 第二种是从 API 服务器通过它的代理功能连接到任何节点、Pod 或者服务。
API 服务器到 kubelet
从 API 服务器到 kubelet 的连接用于:
- 获取 Pod 日志。
- 挂接(通过 kubectl)到运行中的 Pod。
- 提供 kubelet 的端口转发功能。
这些连接终止于 kubelet 的 HTTPS 末端。 默认情况下,API 服务器不检查 kubelet 的服务证书。这使得此类连接容易受到中间人攻击, 在非受信网络或公开网络上运行也是 不安全的。
为了对这个连接进行认证,使用 --kubelet-certificate-authority
标志给
API 服务器提供一个根证书包,用于 kubelet 的服务证书。
如果无法实现这点,又要求避免在非受信网络或公共网络上进行连接,可在 API 服务器和 kubelet 之间使用 SSH 隧道。
最后,应该启用 Kubelet 认证/鉴权 来保护 kubelet API。
API 服务器到节点、Pod 和服务
从 API 服务器到节点、Pod 或服务的连接默认为纯 HTTP 方式,因此既没有认证,也没有加密。
这些连接可通过给 API URL 中的节点、Pod 或服务名称添加前缀 https:
来运行在安全的 HTTPS 连接上。
不过这些连接既不会验证 HTTPS 末端提供的证书,也不会提供客户端证书。
因此,虽然连接是加密的,仍无法提供任何完整性保证。
这些连接 目前还不能安全地 在非受信网络或公共网络上运行。
SSH 隧道
Kubernetes 支持使用 SSH 隧道来保护从控制面到节点的通信路径。 在这种配置下,API 服务器建立一个到集群中各节点的 SSH 隧道(连接到在 22 端口监听的 SSH 服务器) 并通过这个隧道传输所有到 kubelet、节点、Pod 或服务的请求。 这一隧道保证通信不会被暴露到集群节点所运行的网络之外。
SSH 隧道目前已被废弃。除非你了解个中细节,否则不应使用。 Konnectivity 服务是 SSH 隧道的替代方案。
Konnectivity 服务
Kubernetes v1.18 [beta]
作为 SSH 隧道的替代方案,Konnectivity 服务提供 TCP 层的代理,以便支持从控制面到集群的通信。 Konnectivity 服务包含两个部分:Konnectivity 服务器和 Konnectivity 代理, 分别运行在控制面网络和节点网络中。 Konnectivity 代理建立并维持到 Konnectivity 服务器的网络连接。 启用 Konnectivity 服务之后,所有控制面到节点的通信都通过这些连接传输。
请浏览 Konnectivity 服务任务 在你的集群中配置 Konnectivity 服务。
接下来
- 阅读 Kubernetes 控制面组件
- 进一步了解 Hubs and Spoke model
- 进一步了解如何保护集群
- 进一步了解 Kubernetes API
- 设置 Konnectivity 服务
- 使用端口转发来访问集群中的应用
- 学习如何检查 Pod 的日志 以及如何使用 kubectl 端口转发
3 - 控制器
在机器人技术和自动化领域,控制回路(Control Loop)是一个非终止回路,用于调节系统状态。
这是一个控制环的例子:房间里的温度自动调节器。
当你设置了温度,告诉了温度自动调节器你的期望状态(Desired State)。 房间的实际温度是当前状态(Current State)。 通过对设备的开关控制,温度自动调节器让其当前状态接近期望状态。
在 Kubernetes 中,控制器通过监控集群 的公共状态,并致力于将当前状态转变为期望的状态。控制器模式
一个控制器至少追踪一种类型的 Kubernetes 资源。这些
对象
有一个代表期望状态的 spec
字段。
该资源的控制器负责确保其当前状态接近期望状态。
控制器可能会自行执行操作;在 Kubernetes 中更常见的是一个控制器会发送信息给 API 服务器,这会有副作用。 具体可参看后文的例子。
通过 API 服务器来控制
Job 控制器是一个 Kubernetes 内置控制器的例子。 内置控制器通过和集群 API 服务器交互来管理状态。
Job 是一种 Kubernetes 资源,它运行一个或者多个 Pod,
来执行一个任务然后停止。
(一旦被调度了,对 kubelet
来说 Pod
对象就会变成期望状态的一部分)。
在集群中,当 Job 控制器拿到新任务时,它会保证一组 Node 节点上的 kubelet
可以运行正确数量的 Pod 来完成工作。
Job 控制器不会自己运行任何的 Pod 或者容器。Job 控制器是通知 API 服务器来创建或者移除 Pod。
控制面中的其它组件
根据新的消息作出反应(调度并运行新 Pod)并且最终完成工作。
创建新 Job 后,所期望的状态就是完成这个 Job。Job 控制器会让 Job 的当前状态不断接近期望状态:创建为 Job 要完成工作所需要的 Pod,使 Job 的状态接近完成。
控制器也会更新配置对象。例如:一旦 Job 的工作完成了,Job 控制器会更新 Job 对象的状态为 Finished
。
(这有点像温度自动调节器关闭了一个灯,以此来告诉你房间的温度现在到你设定的值了)。
直接控制
相比 Job 控制器,有些控制器需要对集群外的一些东西进行修改。
例如,如果你使用一个控制回路来保证集群中有足够的 节点,那么控制器就需要当前集群外的 一些服务在需要时创建新节点。
和外部状态交互的控制器从 API 服务器获取到它想要的状态,然后直接和外部系统进行通信 并使当前状态更接近期望状态。
(实际上有一个控制器 可以水平地扩展集群中的节点。)
这里的重点是,控制器做出了一些变更以使得事物更接近你的期望状态, 之后将当前状态报告给集群的 API 服务器。 其他控制回路可以观测到所汇报的数据的这种变化并采取其各自的行动。
在温度计的例子中,如果房间很冷,那么某个控制器可能还会启动一个防冻加热器。 就 Kubernetes 集群而言,控制面间接地与 IP 地址管理工具、存储服务、云驱动 APIs 以及其他服务协作,通过扩展 Kubernetes 来实现这点。
期望状态与当前状态
Kubernetes 采用了系统的云原生视图,并且可以处理持续的变化。
在任务执行时,集群随时都可能被修改,并且控制回路会自动修复故障。 这意味着很可能集群永远不会达到稳定状态。
只要集群中的控制器在运行并且进行有效的修改,整体状态的稳定与否是无关紧要的。
设计
作为设计原则之一,Kubernetes 使用了很多控制器,每个控制器管理集群状态的一个特定方面。 最常见的一个特定的控制器使用一种类型的资源作为它的期望状态, 控制器管理控制另外一种类型的资源向它的期望状态演化。 例如,Job 的控制器跟踪 Job 对象(以发现新的任务)和 Pod 对象(以运行 Job,然后查看任务何时完成)。 在这种情况下,新任务会创建 Job,而 Job 控制器会创建 Pod。
使用简单的控制器而不是一组相互连接的单体控制回路是很有用的。 控制器会失败,所以 Kubernetes 的设计正是考虑到了这一点。
可以有多个控制器来创建或者更新相同类型的对象。 在后台,Kubernetes 控制器确保它们只关心与其控制资源相关联的资源。
例如,你可以创建 Deployment 和 Job;它们都可以创建 Pod。 Job 控制器不会删除 Deployment 所创建的 Pod,因为有信息 (标签)让控制器可以区分这些 Pod。
运行控制器的方式
Kubernetes 内置一组控制器,运行在 kube-controller-manager 内。 这些内置的控制器提供了重要的核心功能。
Deployment 控制器和 Job 控制器是 Kubernetes 内置控制器的典型例子。 Kubernetes 允许你运行一个稳定的控制平面,这样即使某些内置控制器失败了, 控制平面的其他部分会接替它们的工作。
你会遇到某些控制器运行在控制面之外,用以扩展 Kubernetes。 或者,如果你愿意,你也可以自己编写新控制器。 你可以以一组 Pod 来运行你的控制器,或者运行在 Kubernetes 之外。 最合适的方案取决于控制器所要执行的功能是什么。
接下来
- 阅读 Kubernetes 控制平面组件
- 了解 Kubernetes 对象 的一些基本知识
- 进一步学习 Kubernetes API
- 如果你想编写自己的控制器,请查看 Kubernetes 扩展模式 以及控制器样例。
4 - 租约(Lease)
分布式系统通常需要租约(Lease);租约提供了一种机制来锁定共享资源并协调集合成员之间的活动。
在 Kubernetes 中,租约概念表示为 coordination.k8s.io
API 组中的
Lease 对象,
常用于类似节点心跳和组件级领导者选举等系统核心能力。
节点心跳
Kubernetes 使用 Lease API 将 kubelet 节点心跳传递到 Kubernetes API 服务器。
对于每个 Node
,在 kube-node-lease
名字空间中都有一个具有匹配名称的 Lease
对象。
在此基础上,每个 kubelet 心跳都是对该 Lease
对象的 update 请求,更新该 Lease 的 spec.renewTime
字段。
Kubernetes 控制平面使用此字段的时间戳来确定此 Node
的可用性。
更多细节请参阅 Node Lease 对象。
领导者选举
Kubernetes 也使用 Lease 确保在任何给定时间某个组件只有一个实例在运行。
这在高可用配置中由 kube-controller-manager
和 kube-scheduler
等控制平面组件进行使用,
这些组件只应有一个实例激活运行,而其他实例待机。
API 服务器身份
Kubernetes v1.26 [beta]
从 Kubernetes v1.26 开始,每个 kube-apiserver
都使用 Lease API 将其身份发布到系统中的其他位置。
虽然它本身并不是特别有用,但为客户端提供了一种机制来发现有多少个 kube-apiserver
实例正在操作
Kubernetes 控制平面。kube-apiserver 租约的存在使得未来可以在各个 kube-apiserver 之间协调新的能力。
你可以检查 kube-system
名字空间中名为 kube-apiserver-<sha256-hash>
的 Lease 对象来查看每个
kube-apiserver 拥有的租约。你还可以使用标签选择算符 apiserver.kubernetes.io/identity=kube-apiserver
:
kubectl -n kube-system get lease -l apiserver.kubernetes.io/identity=kube-apiserver
NAME HOLDER AGE
apiserver-07a5ea9b9b072c4a5f3d1c3702 apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05 5m33s
apiserver-7be9e061c59d368b3ddaf1376e apiserver-7be9e061c59d368b3ddaf1376e_84f2a85d-37c1-4b14-b6b9-603e62e4896f 4m23s
apiserver-1dfef752bcb36637d2763d1868 apiserver-1dfef752bcb36637d2763d1868_c5ffa286-8a9a-45d4-91e7-61118ed58d2e 4m43s
租约名称中使用的 SHA256 哈希基于 API 服务器所看到的操作系统主机名生成。
每个 kube-apiserver 都应该被配置为使用集群中唯一的主机名。
使用相同主机名的 kube-apiserver 新实例将使用新的持有者身份接管现有 Lease,而不是实例化新的 Lease 对象。
你可以通过检查 kubernetes.io/hostname
标签的值来查看 kube-apisever 所使用的主机名:
kubectl -n kube-system get lease apiserver-07a5ea9b9b072c4a5f3d1c3702 -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
creationTimestamp: "2023-07-02T13:16:48Z"
labels:
apiserver.kubernetes.io/identity: kube-apiserver
kubernetes.io/hostname: master-1
name: apiserver-07a5ea9b9b072c4a5f3d1c3702
namespace: kube-system
resourceVersion: "334899"
uid: 90870ab5-1ba9-4523-b215-e4d4e662acb1
spec:
holderIdentity: apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05
leaseDurationSeconds: 3600
renewTime: "2023-07-04T21:58:48.065888Z"
kube-apiserver 中不再存续的已到期租约将在到期 1 小时后被新的 kube-apiserver 作为垃圾收集。
你可以通过禁用 APIServerIdentity
特性门控来禁用 API 服务器身份租约。
工作负载
你自己的工作负载可以定义自己使用的 Lease。例如,
你可以运行自定义的控制器,
让主要成员或领导者成员在其中执行其对等方未执行的操作。
你定义一个 Lease,以便控制器副本可以使用 Kubernetes API 进行协调以选择或选举一个领导者。
如果你使用 Lease,良好的做法是为明显关联到产品或组件的 Lease 定义一个名称。
例如,如果你有一个名为 Example Foo 的组件,可以使用名为 example-foo
的 Lease。
如果集群操作员或其他终端用户可以部署一个组件的多个实例, 则选择名称前缀并挑选一种机制(例如 Deployment 名称的哈希)以避免 Lease 的名称冲突。
你可以使用另一种方式来达到相同的效果:不同的软件产品不相互冲突。
5 - 云控制器管理器
Kubernetes v1.11 [beta]
使用云基础设施技术,你可以在公有云、私有云或者混合云环境中运行 Kubernetes。 Kubernetes 的信条是基于自动化的、API 驱动的基础设施,同时避免组件间紧密耦合。
组件 cloud-controller-manager 是指云控制器管理器, 一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager) 允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
cloud-controller-manager
组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。
cloud-controller-manager
组件是基于一种插件机制来构造的,
这种机制使得不同的云厂商都能将其平台与 Kubernetes 集成。
设计
云控制器管理器以一组多副本的进程集合的形式运行在控制面中,通常表现为 Pod
中的容器。每个 cloud-controller-manager
在同一进程中实现多个控制器。
你也可以用 Kubernetes 插件 的形式而不是控制面中的一部分来运行云控制器管理器。
云控制器管理器的功能
云控制器管理器中的控制器包括:
节点控制器
节点控制器负责在云基础设施中创建了新服务器时为之更新节点(Node)对象。 节点控制器从云提供商获取当前租户中主机的信息。节点控制器执行以下功能:
- 使用从云平台 API 获取的对应服务器的唯一标识符更新 Node 对象;
- 利用特定云平台的信息为 Node 对象添加注解和标签,例如节点所在的区域 (Region)和所具有的资源(CPU、内存等等);
- 获取节点的网络地址和主机名;
- 检查节点的健康状况。如果节点无响应,控制器通过云平台 API 查看该节点是否已从云中禁用、删除或终止。如果节点已从云中删除, 则控制器从 Kubernetes 集群中删除 Node 对象。
某些云驱动实现中,这些任务被划分到一个节点控制器和一个节点生命周期控制器中。
路由控制器
Route 控制器负责适当地配置云平台中的路由,以便 Kubernetes 集群中不同节点上的容器之间可以相互通信。
取决于云驱动本身,路由控制器可能也会为 Pod 网络分配 IP 地址块。
服务控制器
服务(Service)与受控的负载均衡器、 IP 地址、网络包过滤、目标健康检查等云基础设施组件集成。 服务控制器与云驱动的 API 交互,以配置负载均衡器和其他基础设施组件。 你所创建的 Service 资源会需要这些组件服务。
鉴权
本节分别讲述云控制器管理器为了完成自身工作而产生的对各类 API 对象的访问需求。
节点控制器
节点控制器只操作 Node 对象。它需要读取和修改 Node 对象的完全访问权限。
v1/Node
:
- get
- list
- create
- update
- patch
- watch
- delete
路由控制器
路由控制器会监听 Node 对象的创建事件,并据此配置路由设施。 它需要读取 Node 对象的 Get 权限。
v1/Node
:
- get
服务控制器
服务控制器监测 Service 对象的 create、update 和 delete 事件, 并配置对应服务的 Endpoints 对象 (对于 EndpointSlices,kube-controller-manager 按需对其进行管理)。
为了访问 Service 对象,它需要 list 和 watch 访问权限。 为了更新 Service 对象,它需要 patch 和 update 访问权限。
为了能够配置 Service 对应的 Endpoints 资源, 它需要 create、list、get、watch 和 update 等访问权限。
v1/Service
:
- list
- get
- watch
- patch
- update
其他
在云控制器管理器的实现中,其核心部分需要创建 Event 对象的访问权限, 并创建 ServiceAccount 资源以保证操作安全性的权限。
v1/Event
:
- create
- patch
- update
v1/ServiceAccount
:
- create
用于云控制器管理器 RBAC 的 ClusterRole 如下例所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cloud-controller-manager
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- update
- apiGroups:
- ""
resources:
- nodes
verbs:
- '*'
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
- apiGroups:
- ""
resources:
- services
verbs:
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- create
- apiGroups:
- ""
resources:
- persistentvolumes
verbs:
- get
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- create
- get
- list
- watch
- update
接下来
-
云控制器管理器的管理 给出了运行和管理云控制器管理器的指南。
-
要升级 HA 控制平面以使用云控制器管理器, 请参见将复制的控制平面迁移以使用云控制器管理器。
-
想要了解如何实现自己的云控制器管理器,或者对现有项目进行扩展么?
- 云控制器管理器使用 Go 语言的接口(具体指在
kubernetes/cloud-provider
项目中
cloud.go
文件中所定义的CloudProvider
接口),从而使得针对各种云平台的具体实现都可以接入。
- 本文中列举的共享控制器(节点控制器、路由控制器和服务控制器等)的实现以及其他一些生成具有
CloudProvider 接口的框架的代码,都是 Kubernetes 的核心代码。
特定于云驱动的实现虽不是 Kubernetes 核心成分,仍要实现
CloudProvider
接口。
- 关于如何开发插件的详细信息, 可参考开发云控制器管理器文档。
- 云控制器管理器使用 Go 语言的接口(具体指在
kubernetes/cloud-provider
项目中
6 - 关于 cgroup v2
在 Linux 上,控制组约束分配给进程的资源。
kubelet 和底层容器运行时都需要对接 cgroup 来强制执行为 Pod 和容器管理资源, 这包括为容器化工作负载配置 CPU/内存请求和限制。
Linux 中有两个 cgroup 版本:cgroup v1 和 cgroup v2。cgroup v2 是新一代的 cgroup
API。
什么是 cgroup v2?
Kubernetes v1.25 [stable]
cgroup v2 是 Linux cgroup
API 的下一个版本。cgroup v2 提供了一个具有增强资源管理能力的统一控制系统。
cgroup v2 对 cgroup v1 进行了多项改进,例如:
- API 中单个统一的层次结构设计
- 更安全的子树委派给容器
- 更新的功能特性, 例如压力阻塞信息(Pressure Stall Information,PSI)
- 跨多个资源的增强资源分配管理和隔离
- 统一核算不同类型的内存分配(网络内存、内核内存等)
- 考虑非即时资源变化,例如页面缓存回写
一些 Kubernetes 特性专门使用 cgroup v2 来增强资源管理和隔离。 例如,MemoryQoS 特性改进了内存 QoS 并依赖于 cgroup v2 原语。
使用 cgroup v2
使用 cgroup v2 的推荐方法是使用一个默认启用 cgroup v2 的 Linux 发行版。
要检查你的发行版是否使用 cgroup v2,请参阅识别 Linux 节点上的 cgroup 版本。
要求
cgroup v2 具有以下要求:
- 操作系统发行版启用 cgroup v2
- Linux 内核为 5.8 或更高版本
- 容器运行时支持 cgroup v2。例如:
- containerd v1.4 和更高版本
- cri-o v1.20 和更高版本
- kubelet 和容器运行时被配置为使用 systemd cgroup 驱动
Linux 发行版 cgroup v2 支持
有关使用 cgroup v2 的 Linux 发行版的列表, 请参阅 cgroup v2 文档。
- Container-Optimized OS(从 M97 开始)
- Ubuntu(从 21.10 开始,推荐 22.04+)
- Debian GNU/Linux(从 Debian 11 Bullseye 开始)
- Fedora(从 31 开始)
- Arch Linux(从 2021 年 4 月开始)
- RHEL 和类似 RHEL 的发行版(从 9 开始)
要检查你的发行版是否使用 cgroup v2, 请参阅你的发行版文档或遵循识别 Linux 节点上的 cgroup 版本中的指示说明。
你还可以通过修改内核 cmdline 引导参数在你的 Linux 发行版上手动启用 cgroup v2。
如果你的发行版使用 GRUB,则应在 /etc/default/grub
下的 GRUB_CMDLINE_LINUX
中添加 systemd.unified_cgroup_hierarchy=1
,
然后执行 sudo update-grub
。不过,推荐的方法仍是使用一个默认已启用 cgroup v2 的发行版。
迁移到 cgroup v2
要迁移到 cgroup v2,需确保满足要求,然后升级到一个默认启用 cgroup v2 的内核版本。
kubelet 能够自动检测操作系统是否运行在 cgroup v2 上并相应调整其操作,无需额外配置。
切换到 cgroup v2 时,用户体验应没有任何明显差异,除非用户直接在节点上或从容器内访问 cgroup 文件系统。
cgroup v2 使用一个与 cgroup v1 不同的 API,因此如果有任何应用直接访问 cgroup 文件系统, 则需要将这些应用更新为支持 cgroup v2 的版本。例如:
- 一些第三方监控和安全代理可能依赖于 cgroup 文件系统。你要将这些代理更新到支持 cgroup v2 的版本。
- 如果以独立的 DaemonSet 的形式运行 cAdvisor 以监控 Pod 和容器, 需将其更新到 v0.43.0 或更高版本。
- 如果你部署 Java 应用程序,最好使用完全支持 cgroup v2 的版本:
- OpenJDK / HotSpot: jdk8u372、11.0.16、15 及更高的版本
- IBM Semeru Runtimes: 8.0.382.0、11.0.20.0、17.0.8.0 及更高的版本
- IBM Java: 8.0.8.6 及更高的版本
- 如果你正在使用 uber-go/automaxprocs 包, 确保你使用的版本是 v1.5.1 或者更高。
识别 Linux 节点上的 cgroup 版本
cgroup 版本取决于正在使用的 Linux 发行版和操作系统上配置的默认 cgroup 版本。
要检查你的发行版使用的是哪个 cgroup 版本,请在该节点上运行 stat -fc %T /sys/fs/cgroup/
命令:
stat -fc %T /sys/fs/cgroup/
对于 cgroup v2,输出为 cgroup2fs
。
对于 cgroup v1,输出为 tmpfs
。
接下来
7 - 容器运行时接口(CRI)
CRI 是一个插件接口,它使 kubelet 能够使用各种容器运行时,无需重新编译集群组件。
你需要在集群中的每个节点上都有一个可以正常工作的容器运行时, 这样 kubelet 能启动 Pod 及其容器。
容器运行时接口(CRI)是 kubelet 和容器运行时之间通信的主要协议。
Kubernetes 容器运行时接口(Container Runtime Interface;CRI)定义了主要 gRPC 协议, 用于节点组件 kubelet 和容器运行时之间的通信。
API
Kubernetes v1.23 [stable]
当通过 gRPC 连接到容器运行时,kubelet 将充当客户端。运行时和镜像服务端点必须在容器运行时中可用,
可以使用 --image-service-endpoint
命令行标志在 kubelet 中单独配置。
对 Kubernetes v1.29,kubelet 偏向于使用 CRI v1
版本。
如果容器运行时不支持 CRI 的 v1
版本,那么 kubelet 会尝试协商较老的、仍被支持的所有版本。
v1.29 版本的 kubelet 也可协商 CRI v1alpha2
版本,但该版本被视为已弃用。
如果 kubelet 无法协商出可支持的 CRI 版本,则 kubelet 放弃并且不会注册为节点。
升级
升级 Kubernetes 时,kubelet 会尝试在组件重启时自动选择最新的 CRI 版本。 如果失败,则将如上所述进行回退。如果由于容器运行时已升级而需要 gRPC 重拨, 则容器运行时还必须支持最初选择的版本,否则重拨预计会失败。 这需要重新启动 kubelet。
接下来
- 了解更多有关 CRI 协议定义
8 - 垃圾收集
垃圾收集(Garbage Collection)是 Kubernetes 用于清理集群资源的各种机制的统称。 垃圾收集允许系统清理如下资源:
- 终止的 Pod
- 已完成的 Job
- 不再存在属主引用的对象
- 未使用的容器和容器镜像
- 动态制备的、StorageClass 回收策略为 Delete 的 PV 卷
- 阻滞或者过期的 CertificateSigningRequest (CSR)
- 在以下情形中删除了的节点对象:
- 当集群使用云控制器管理器运行于云端时;
- 当集群使用类似于云控制器管理器的插件运行在本地环境中时。
- 节点租约对象
属主与依赖
Kubernetes 中很多对象通过属主引用 链接到彼此。属主引用(Owner Reference)可以告诉控制面哪些对象依赖于其他对象。 Kubernetes 使用属主引用来为控制面以及其他 API 客户端在删除某对象时提供一个清理关联资源的机会。 在大多数场合,Kubernetes 都是自动管理属主引用的。
属主关系与某些资源所使用的标签和选择算符不同。
例如,考虑一个创建 EndpointSlice
对象的 Service。
Service 使用标签来允许控制面确定哪些 EndpointSlice
对象被该 Service 使用。
除了标签,每个被 Service 托管的 EndpointSlice
对象还有一个属主引用属性。
属主引用可以帮助 Kubernetes 中的不同组件避免干预并非由它们控制的对象。
根据设计,系统不允许出现跨名字空间的属主引用。名字空间作用域的依赖对象可以指定集群作用域或者名字空间作用域的属主。 名字空间作用域的属主必须存在于依赖对象所在的同一名字空间。 如果属主位于不同名字空间,则属主引用被视为不存在,而当检查发现所有属主都已不存在时,依赖对象会被删除。
集群作用域的依赖对象只能指定集群作用域的属主。 在 1.20 及更高版本中,如果一个集群作用域的依赖对象指定了某个名字空间作用域的类别作为其属主, 则该对象被视为拥有一个无法解析的属主引用,因而无法被垃圾收集处理。
在 1.20 及更高版本中,如果垃圾收集器检测到非法的跨名字空间 ownerReference
,
或者某集群作用域的依赖对象的 ownerReference
引用某名字空间作用域的类别,
系统会生成一个警告事件,其原因为 OwnerRefInvalidNamespace
,involvedObject
设置为非法的依赖对象。你可以通过运行
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace
来检查是否存在这类事件。
级联删除
Kubernetes 会检查并删除那些不再拥有属主引用的对象,例如在你删除了 ReplicaSet 之后留下来的 Pod。当你删除某个对象时,你可以控制 Kubernetes 是否去自动删除该对象的依赖对象, 这个过程称为级联删除(Cascading Deletion)。 级联删除有两种类型,分别如下:
- 前台级联删除
- 后台级联删除
你也可以使用 Kubernetes Finalizers 来控制垃圾收集机制如何以及何时删除包含属主引用的资源。
前台级联删除
在前台级联删除中,正在被你删除的属主对象首先进入 deletion in progress 状态。 在这种状态下,针对属主对象会发生以下事情:
- Kubernetes API 服务器将某对象的
metadata.deletionTimestamp
字段设置为该对象被标记为要删除的时间点。 - Kubernetes API 服务器也会将
metadata.finalizers
字段设置为foregroundDeletion
。 - 在删除过程完成之前,通过 Kubernetes API 仍然可以看到该对象。
当属主对象进入删除过程中状态后,控制器删除其依赖对象。控制器在删除完所有依赖对象之后, 删除属主对象。这时,通过 Kubernetes API 就无法再看到该对象。
在前台级联删除过程中,唯一可能阻止属主对象被删除的是那些带有
ownerReference.blockOwnerDeletion=true
字段的依赖对象。
参阅使用前台级联删除
以了解进一步的细节。
后台级联删除
在后台级联删除过程中,Kubernetes 服务器立即删除属主对象,控制器在后台清理所有依赖对象。 默认情况下,Kubernetes 使用后台级联删除方案,除非你手动设置了要使用前台删除, 或者选择遗弃依赖对象。
参阅使用后台级联删除 以了解进一步的细节。
被遗弃的依赖对象
当 Kubernetes 删除某个属主对象时,被留下来的依赖对象被称作被遗弃的(Orphaned)对象。 默认情况下,Kubernetes 会删除依赖对象。要了解如何重载这种默认行为,可参阅 删除属主对象和遗弃依赖对象。
未使用容器和镜像的垃圾收集
kubelet 会每两分钟对未使用的镜像执行一次垃圾收集, 每分钟对未使用的容器执行一次垃圾收集。 你应该避免使用外部的垃圾收集工具,因为外部工具可能会破坏 kubelet 的行为,移除应该保留的容器。
要配置对未使用容器和镜像的垃圾收集选项,可以使用一个
配置文件,基于
KubeletConfiguration
资源类型来调整与垃圾收集相关的 kubelet 行为。
容器镜像生命周期
Kubernetes 通过其镜像管理器(Image Manager) 来管理所有镜像的生命周期, 该管理器是 kubelet 的一部分,工作时与 cadvisor 协同。 kubelet 在作出垃圾收集决定时会考虑如下磁盘用量约束:
HighThresholdPercent
LowThresholdPercent
磁盘用量超出所配置的 HighThresholdPercent
值时会触发垃圾收集,
垃圾收集器会基于镜像上次被使用的时间来按顺序删除它们,首先删除的是最近未使用的镜像。
kubelet 会持续删除镜像,直到磁盘用量到达 LowThresholdPercent
值为止。
未使用容器镜像的垃圾收集
Kubernetes v1.29 [alpha]
这是一个 Alpha 特性,不论磁盘使用情况如何,你都可以指定本地镜像未被使用的最长时间。 这是一个可以为每个节点配置的 kubelet 设置。
请为 kubelet 启用 ImageMaximumGCAge
特性门控,
并在 kubelet 配置文件中为 ImageMaximumGCAge
字段赋值来配置该设置。
该值应遵循 Kubernetes 持续时间(Duration) 格式;例如,你可以将配置字段设置为 3d12h
,
代表 3 天 12 小时。
容器垃圾收集
kubelet 会基于如下变量对所有未使用的容器执行垃圾收集操作,这些变量都是你可以定义的:
MinAge
:kubelet 可以垃圾回收某个容器时该容器的最小年龄。设置为0
表示禁止使用此规则。MaxPerPodContainer
:每个 Pod 可以包含的已死亡的容器个数上限。设置为小于0
的值表示禁止使用此规则。MaxContainers
:集群中可以存在的已死亡的容器个数上限。设置为小于0
的值意味着禁止应用此规则。
除以上变量之外,kubelet 还会垃圾收集除无标识的以及已删除的容器,通常从最近未使用的容器开始。
当保持每个 Pod 的最大数量的容器(MaxPerPodContainer
)会使得全局的已死亡容器个数超出上限
(MaxContainers
)时,MaxPerPodContainer
和 MaxContainers
之间可能会出现冲突。
在这种情况下,kubelet 会调整 MaxPerPodContainer
来解决这一冲突。
最坏的情形是将 MaxPerPodContainer
降格为 1
,并驱逐最近未使用的容器。
此外,当隶属于某已被删除的 Pod 的容器的年龄超过 MinAge
时,它们也会被删除。
kubelet 仅会回收由它所管理的容器。
配置垃圾收集
你可以通过配置特定于管理资源的控制器来调整资源的垃圾收集行为。 下面的页面为你展示如何配置垃圾收集:
接下来
- 进一步了解 Kubernetes 对象的属主关系。
- 进一步了解 Kubernetes finalizers。
- 进一步了解 TTL 控制器, 该控制器负责清理已完成的 Job。
9 - 混合版本代理
Kubernetes v1.28 [alpha]
Kubernetes 1.29 包含了一个 Alpha 特性,可以让 API 服务器代理指向其他对等 API 服务器的资源请求。当一个集群中运行着多个 API 服务器,且各服务器的 Kubernetes 版本不同时 (例如在上线 Kubernetes 新版本的时间跨度较长时),这一特性非常有用。
此特性通过将(升级过程中所发起的)资源请求引导到正确的 kube-apiserver 使得集群管理员能够配置高可用的、升级动作更安全的集群。 该代理机制可以防止用户在升级过程中看到意外的 404 Not Found 错误。
这个机制称为 Mixed Version Proxy(混合版本代理)。
启用混合版本代理
当你启动 API 服务器时,
确保启用了 UnknownVersionInteroperabilityProxy
特性门控:
kube-apiserver \
--feature-gates=UnknownVersionInteroperabilityProxy=true \
# 需要为此特性添加的命令行参数
--peer-ca-file=<指向 kube-apiserver CA 证书的路径>
--proxy-client-cert-file=<指向聚合器代理证书的路径>,
--proxy-client-key-file=<指向聚合器代理密钥的路径>,
--requestheader-client-ca-file=<指向聚合器 CA 证书的路径>,
# requestheader-allowed-names 可设置为空以允许所有 Common Name
--requestheader-allowed-names=<验证代理客户端证书的合法 Common Name>,
# 此特性的可选标志
--peer-advertise-ip=`应由对等方用于代理请求的 kube-apiserver IP`
--peer-advertise-port=`应由对等方用于代理请求的 kube-apiserver 端口`
# ... 和其他常规标志
API 服务器之间的代理传输和身份验证
-
源 kube-apiserver 重用现有的 API 服务器客户端身份验证标志
--proxy-client-cert-file
和--proxy-client-key-file
来表明其身份,供对等(目标 kube-apiserver)验证。 目标 API 服务器根据你使用--requestheader-client-ca-file
命令行参数指定的配置来验证对等连接。 -
要对目标服务器所用的证书进行身份验证,必须通过指定
--peer-ca-file
命令行参数来为源 API 服务器配置一个证书机构包。
对等 API 服务器连接的配置
要设置 kube-apiserver 的网络位置以供对等方来代理请求,
使用为 kube-apiserver 设置的 --peer-advertise-ip
和 --peer-advertise-port
命令行参数,
或在 API 服务器配置文件中指定这些字段。如果未指定这些参数,对等方将使用 --advertise-address
或 --bind-address
命令行参数的值。如果这些也未设置,则使用主机的默认接口。
混合版本代理
启用混合版本代理时, 聚合层会加载一个特殊的过滤器, 完成以下操作:
- 当资源请求到达无法提供该 API 的 API 服务器时 (可能的原因是服务器早于该 API 的正式引入日期或该 API 在 API 服务器上被关闭), API 服务器会尝试将请求发送到能够提供所请求 API 的对等 API 服务器。 API 服务器通过发现本地服务器无法识别的 API 组/版本/资源来实现这一点, 并尝试将这些请求代理到能够处理这些请求的对等 API 服务器。
- 如果对等 API 服务器无法响应,则源 API 服务器将以 503("Service Unavailable")错误进行响应。
内部工作原理
当 API 服务器收到一个资源请求时,它首先检查哪些 API 服务器可以提供所请求的资源。
这个检查是使用内部的
StorageVersion
API
进行的。
- 如果资源被收到请求(例如
GET /api/v1/pods/some-pod
)的 API 服务器所了解,则请求会在本地处理。
- 如果没有找到适合所请求资源(例如
GET /my-api/v1/my-resource
)的内部StorageVersion
对象, 并且所配置的 APIService 设置了指向扩展 API 服务器的代理,那么代理操作将按照扩展 API 的常规流程进行。
-
如果找到了对应所请求资源(例如
GET /batch/v1/jobs
)的合法的内部StorageVersion
对象, 并且正在处理请求的 API 服务器(处理中的 API 服务器)禁用了batch
API, 则正处理的 API 服务器使用已获取的StorageVersion
对象中的信息, 获取提供相关 API 组/版本/资源(在此情况下为api/v1/batch
)的对等 API 服务器。 处理中的 API 服务器随后将请求代理到能够理解所请求资源且匹配的对等 kube-apiserver 之一。-
如果没有对等方了解所给的 API 组/版本/资源,则处理请求的 API 服务器将请求传递给自己的处理程序链, 最终应返回 404("Not Found")响应。
-
如果处理请求的 API 服务器已经识别并选择了一个对等 API 服务器,但该对等方无法响应 (原因可能是网络连接问题或正接收的请求与向控制平面注册对等信息的控制器之间存在数据竞争等), 则处理请求的 API 服务器会以 503("Service Unavailable")错误进行响应。
-