《 九 阴 真 经 卷 六 》云原生:Kubernetes ETCD Docker
文章目录
一、Kubernetes
Kubernetes的核心组件:
- kube-apiserver
- etcd
- kube-scheduler
- kube-proxy
- kubelet
Pod
- Pod是一组容器的集合,同一个Pod中的容器共享存储、网络等资源。Pod内的容器可以localhost通信,与外部通信时必须为Pod分配网络资源,比如宿主机的端口映射
- pause容器
- Pod会创建一个pause容器,让下属的所有容器共享pause容器的network namespace。pause容器在Pod的生命周期内一直存活
- init容器
- init容器先于业务容器启动,完成后才运行业务容器。有多个init容器时会顺序执行
- init容器没有探针、生命周期钩子
- kube-scheduler在调度时,pod的资源总量的计算为max(所有init容器的资源消耗之和, 所有业务容器资源消耗之和)
pause容器的作用
Pause 容器(通常称为 Pause 容器或 Infra 容器)是 Kubernetes 中一个特殊的容器,它在每个 Pod 中都存在。Pause 容器的作用是作为 Pod 中所有容器的共享父容器,它为 Pod 中的容器提供了如下功能:
-
共享命名空间
Pause 容器使得 Pod 中的所有容器可以共享同一个网络命名空间和 PID 命名空间。这意味着,Pod 中的容器可以通过 Pause 容器进行网络通信,并且它们可以看到彼此在 PID 命名空间中的进程。 -
初始容器
Pause 容器还作为 Pod 中其他容器的初始容器。在 Kubernetes 1.18 版本之前,pause 容器作为第一个运行的容器,为 Pod 中的其他容器设置共享的网络和资源隔离环境。 -
稳定性保证
Pause 容器的设计非常简洁,它不包含任何进程,因此不会占用太多资源,且不容易出现故障。这使得 Pause 容器成为 Pod 中其他容器稳定运行的基石。 -
资源预留
Pause 容器可以被 Kubernetes 用来实现资源预留。在 Kubernetes 中,资源配额(ResourceQuotas)和优先级(PriorityClass)等功能都是借助 Pause 容器来实现的。 -
Pod 生命周期管理
Kubernetes 使用 Pause 容器来跟踪 Pod 的生命周期。当 Pod 被创建时,Pause 容器首先被创建,之后其他容器在 Pause 容器的环境中被启动。当 Pod 被删除时,Pause 容器被删除,Kubernetes 知道所有容器都已经停止。
- 结论
Pause 容器是 Kubernetes 中一个不可或缺的组件,它为 Pod 中的容器提供了一个共享的命名空间和稳定的运行环境。没有 Pause 容器,Kubernetes 将无法有效地管理 Pod 和容器。尽管 Pause 容器在 Pod 中不可见,但它确实对 Kubernetes 集群的功能和性能有着重要的影响。
同一个Pod中的容器共享哪些资源
在同一个 Pod 中,容器可以共享以下资源:
-
网络和 IPC 命名空间:
容器共享同一个网络命名空间,这意味着它们可以彼此通信,并且它们监听的端口不会有冲突。此外,容器还共享 IPC(Inter-Process Communication)命名空间,这意味着它们可以使用 SystemV IPC 或 POSIX 消息队列、信号量以及共享内存进行通信。 -
PID 命名空间:
容器共享同一个 PID 命名空间,这意味着它们可以看到彼此的进程。每个容器中的进程都作为该命名空间中的进程存在,并且可以被其他容器查看。 -
UTS 命名空间:
在 Pod 中,容器共享同一个 UTS(UNIX Time-Sharing)命名空间,这允许它们共享主机名和域名,但 Pod 中的每个容器可以有不同的 hostname。 -
Mount 命名空间:
容器可以共享某些卷(Volumes),这些卷被挂载到 Pod 的所有容器上。这允许容器之间共享文件和其他存储资源。 -
用户命名空间(User Namespace):
在 Kubernetes 1.7 版本之后,用户命名空间被引入。Pod 中的容器可以共享同一个用户命名空间,这意味着它们可以共享用户的 ID 和权限。 -
Cgroup 命名空间:
容器还可以共享某些 Cgroup 资源限制,如内存、CPU 和磁盘 I/O。这意味着可以对 Pod 作为一个整体进行资源限制,而不是单独对每个容器进行限制。
要注意的是,虽然容器可以共享这些资源,但 Kubernetes 也允许为 Pod 中的容器设置资源限制。例如,可以为 Pod 中的容器设置不同的资源请求(requests)和资源限制(limits),Kubernetes 会根据这些设置来分配资源。
总之,Pod 中的容器共享网络和 IPC 命名空间,以及其他一些命名空间和 Cgroup 资源限制。这使得 Pod 中的容器可以相互协作,并且共享某些资源,从而提高了资源的利用率和应用的可用性。
Pod中容器的启动顺序
在 Kubernetes 中,Pod 中的容器启动顺序通常遵循以下规则:
-
初始化容器(Init Containers):
如果 Pod 定义了初始化容器,它们将首先按顺序启动并运行直至完成。初始化容器的主要目的是在启动应用容器之前执行一些初始化任务。 -
主容器(Main Containers):
初始化容器完成后,Pod 中的主容器(通常是指应用的容器)将按照定义的顺序依次启动。 -
就绪探针和存活探针(Liveness and Readiness Probes):
当主容器启动后,Kubernetes 会根据就绪探针和存活探针来检测容器的状态。就绪探针用于确定容器何时可以接受流量,而存活探针则用来检测容器是否还在运行。 -
就绪状态:
一旦所有容器都通过了就绪探针,Pod 将变为 Ready 状态,这意味着 Pod 可以对外提供服务。 -
启动延迟:
容器启动之间可能会有延迟,特别是当涉及到初始化容器时。Pod 中容器的启动顺序和时间可能会受到各种因素的影响,如调度延迟、资源限制、网络设置等。
需要注意的是,Pod 中的容器启动顺序是通过 Pod 规范中的 containers
部分来定义的,在 containers
列表中的第一个容器将首先启动。如果要定义初始化容器,则需要在 initContainers
列表内定义它们。
Pod 的启动过程是由 kubelet 组件监控的,kubelet 负责启动 Pod 中的容器,并监控它们的状态。如果容器启动失败或状态异常,kubelet 将会采取相应的行动,例如重启容器或重启整个 Pod。
Sandbox
在 Kubernetes 中,Sandbox(沙箱)通常指的是 Pod 中容器运行环境的基础结构。在 Pod 的上下文中,沙箱通常指的是:
-
pause容器:在 Kubernetes 中,每个 Pod 都包含一个特殊的容器,名为 pause,该容器是 Pod 中所有容器的父容器。pause 容器的作用是提供网络名称空间和存储卷给 Pod 中的其他容器共享。pause 容器不运行任何应用,它只是作为其他容器的基础运行环境。
-
容器运行时(Container Runtime):Sandbox 还包含容器运行时,如 Docker 或者 containerd,它是负责管理容器生命周期的软件。容器运行时负责下载容器镜像,运行容器,监控容器的状态等。
-
Pod 网络:沙箱还涉及到 Pod 的网络配置,包括 Pod 内部的容器之间的网络通信以及 Pod 与 Pod 之间的通信。这通常涉及到 CNI(容器网络接口)插件,负责配置容器的网络接口。
-
资源管理:沙箱还包括对容器资源使用的管理,如 CPU 和内存限制。这通常涉及到 cgroups 的使用,它是 Linux 内核的一项功能,用于限制、记录、隔离进程组所使用的物理资源(包括 CPU、内存、磁盘 I/O 等)。
在 Kubernetes 的术语中,Sandbox 通常指的是 pause 容器及其环境,它给 Pod 中的容器提供了一个共有的运行环境。沙箱环境是 Kubernetes 管理和调度容器的基础,它确保容器能够正确地运行并与其他容器或 Pod 交互。
在 Kubernetes 中,Sandbox(沙箱)通常指的是 pause 容器以及它提供的共享环境。pause 容器是 Pod 中所有容器的基础,它为 Pod 中的容器提供了一个共享的命名空间,包括网络名称空间,IPC 命名空间和 PID 命名空间等。
但是,Sandbox 的概念不仅仅局限于 pause 容器。它还可以包括 Pod 网络配置和容器运行时环境,例如容器运行时(如 Docker 或者 containerd)、Pod 网络配置(如 CNI 插件)和资源管理等。
所以,虽然 pause 容器是 Sandbox 的重要组成部分,但 Sandbox 的概念更广泛,它指的是 pause 容器及其提供的整个运行环境,包括网络、资源管理等。
kube-sheduler源码
- 调度器缓存
- kube-sheduler通过list-watch机制维护一个调度器缓存,存储全量的Node信息、Pod信息和一些影响调度的信息,例如存储资源
- kube-sheduler只从缓存中读取这些信息,降低了kube-apiserver和etcd的压力,提升的读取速度
- kube-sheduler的结果会以乐观假设的方式先写入缓存,再由异步流程进行绑定。这样可以保证之前的调度过程影响后续的调度过程,并且无需等待绑定结束
- 调度队列
- kube-sheduler共有三个调度队列
- active:一个按照Pod优先级排序的堆,优先级越高的Pod会被越优先调度
- unschedulable:已经尝试过不可调度的Pod
- podBackoff:重试等待中的Pod
- unschedulable、podBackoff队列中的Pod会被异步goroutine定期的放回active
- 针对调度过的和未调度过的Pod,kube-sheduler注册的informer不同。未调度的会直接加入active,调度过的会将与其具有相同亲和性的unschedulable队列中的Pod全部捞出,加入active或unschedulable
- kube-sheduler共有三个调度队列
- 扩展点和插件
- 调度器的整个调度流程又若干个扩展点组成,每个扩展点都可以注册多个插件
- 核心扩展点:
- sort:影响Pod在active中的排序
- filter:过滤掉不可被调度的Node
- score:为filter出来的Node打分
- bind:绑定Pod到Node
- 抢占
- 调度器通过检查Pod的nominated字段检查是否有抢占,
- 调度
- 标签与选择器
- 通过给Node打标签,给Pod配置NodeSelector实现指定调度到带有某种标签的机器上
- 污点和容忍
- 通过给Node打污点,可以避免Pod调度到这台Node上;Pod可以通过配置容忍来去除污点的影响
- 污点等级:
- NoSchedule:不会调度到该Node
- PreferNoSchedule:尽量不会调度到该Node
- NoExecute:不会调度到该Node,已调度上的也会被驱逐
- kubernetes原生也会使用污点,在节点出现某些情况时,做避免调度和驱逐
- 亲和和反亲和
- 亲和和反亲和都分为prefer和require。require类似NodeSelector,不满足的话就无法调度;prefer会在没有其他选择时,仍然允许调度
- nodeAffinity:Pod倾向于调度到哪些Node上
- podAffinity:Pod倾向于和哪些Pod被调度到一起
- podAntiAffinity:Pod不倾向于和哪些Pod被调度到一起
- 这些亲和策略都依赖标签生效
- 标签与选择器
- 开放接口
- CRI:容器运行时接口 R = Runtime,kubelet通过gPPC和容器运行时交互。容器运行时包括RuntimeService和ImageService,RuntimeService负责管理容器和Sandbox运行时,ImageService负责管理镜像
- CNI:容器网络接口 N = Network,CNI负责容器的网络分配和删除。
- CSI:容器存储接口 S = Storage,借助这个接口可以把任意的存储接口暴露给容器工作负载。
- Deployment和StatefulSet
- StatefulSet面对有状态服务设计
- 其管理的Pod是有稳定序号的,升级时会按照序号进行
- Pod重调度后,网络标识PodName、HostName不变
- StatefulSet面对有状态服务设计
- HPA VPA
- HPA Pod数量水平缩放。Kubernetes原生支持Deployment和StatefulSet根据一些Metric(原生或自定义的)进行自动扩缩容
kube-proxy
- 每个Node都会运行一个kube-proxy进程,kube-proxy为Service实现了虚拟IP
- userspace代理:kube-proxy监听Service和Endpoints的增删,为每个Service在Node上随机打开一个端口
- iptables代理:kube-proxy为每个Service安装iptables
- ipvs代理:ipvs基于netfilter的hook,在内核中使用哈希表工作,支持配置负载均衡算法
QoS
- QoS是服务质量等级,每个Pod都会有自己的QoS
- Guaranteed:每个容器都必须有内存/CPU Limit和Request,而且值必须相等
- Burstable:每个容器都必须有内存/CPU Limit和Request,而且值必须不同
- BestEffort:容器必须没有内存/CPU Limit或Request
kubelet
kubelet启动一个pod的流程
当一个 Pod 被调度到一个节点上后,kubelet 会负责这个 Pod 的生命周期管理。下面是 kubelet 在接收到调度到其节点的 Pod 后进行的操作:
-
Pod 同步:
kubelet 会通过 Kubernetes API 服务器定期同步其节点上的所有 Pod 列表。一旦发现新的 Pod 已被调度到节点上,kubelet 就会更新本地状态。 -
Pod 状态确认:
kubelet 会确认 Pod 已经被调度到节点上,并确认 Pod 是否满足运行条件。这可能包括检查 Pod 请求的硬件资源和节点是否满足这些请求。 -
准备 Pod 网络:
kubelet 准备 Pod 网络,这通常是通过调用网络插件(如 CNI 插件)来完成的。网络插件负责配置容器的网络接口,分配 IP 地址,设置路由规则等。 -
准备 Pod 存储:
kubelet 根据 Pod 的存储卷配置准备持久存储或临时存储。这可能包括在本地文件系统上创建一个卷,或者与远程存储系统(如 GCP 的 PD、AWS 的 EBS)通信来配置持久存储。 -
启动容器:
kubelet 使用容器运行时(如 Docker)来创建 Pod 中的容器。这涉及到运行容器的镜像,设置环境变量,挂载卷等。 -
执行就绪检查和存活检查:
kubelet 执行 Pod 中定义的就绪探针(Readiness Probe)和存活探针(Liveness Probe),以确保容器在准备好接收请求之前不会向 Service 公开,并在必要时重启不健康的容器。 -
Pod 状态更新:
kubelet 会更新 Pod 的状态为Running
。这个状态会被报告给 Kubernetes API 服务器,使得其他组件(如 Service 控制器、HPA 等)可以基于这个状态做出决策。 -
事件监听和日志记录:
kubelet 持续监听 Pod 的生命周期事件,并记录日志。这些事件和日志对于调试和监控 Pod 的行为非常有用。 -
Pod 生命周期管理:
kubelet 负责响应 Pod 生命周期中的各种事件,如容器重启、容器退出、Pod 删除等。 -
资源监控和节点状态报告:
kubelet 还会监控节点上的资源使用情况,如 CPU、内存等,并将这些信息报告给 Kubernetes 控制平面。此外,kubelet 也会报告节点状态,比如节点是否健康、是否准备好接收新的 Pod 等。
kubelet 的这些操作确保了 Pod 可以在 Kubernetes 集群中正确地启动、运行
kubelet的作用
kubelet 是 Kubernetes 集群中的一个关键组件,它是 Kubernetes 控制平面的一个部分。kubelet 是运行在集群中的每个节点上的守护进程,主要负责执行和监控节点上的容器化应用程序。
以下是 kubelet 的一些核心职责和功能:
- 核心职责
-
Pod 管理:
kubelet 负责启动、停止、监控和重启 Pod 中的容器。它确保 Pod 定义的状态(如副本数量、运行状态等)得到满足。 -
容器健康检查:
kubelet 实施存活探针(Liveness Probe)和就绪探针(Readiness Probe),以确定容器是否健康,并在需要时进行重启。 -
容器日志和监控:
kubelet 收集容器日志,并通过 Kubernetes API 暴露它们,供日志收集系统(如 fluentd)使用。同时,kubelet 也负责将容器和节点的指标发送给监控系统。 -
节点资源管理:
kubelet 负责监控节点的资源使用情况,并根据需要向 Kubernetes 控制平面报告。此外,kubelet 也负责执行驱逐策略,以确保节点资源的有效分配。 -
容器存储:
kubelet 管理 Pod 存储卷,包括配置持久卷(Persistent Volumes)以及绑定到 Pod 的卷。
- 关键功能
-
容器运行时接口(CRI):
kubelet 使用容器运行时接口与容器运行时(如 Docker、containerd、CRI-O 等)交互,以创建和管理容器。 -
容器网络接口(CNI):
kubelet 通过容器网络接口来管理 Pod 网络,它调用 CNI 插件来为容器配置网络。 -
节点和 Pod 状态:
kubelet 定期报告节点和 Pod 的状态信息给 Kubernetes 控制平面,以便调度器和其它控制平面组件做出决策。 -
认证和授权:
kubelet 使用 kubeconfig 文件进行身份认证,并且仅执行被授权的操作。 -
API 扩展:
kubelet 支持扩展 API 服务器,通过 HTTP 端点提供对节点和 Pod 信息的直接访问。 -
节点问题处理:
kubelet 有能力处理节点上的问题,如网络中断、文件系统满等,并尝试进行自我修复。
二、ETCD
三、Docker
四、Operator
Informer
Client-go 的 Informer 是 Kubernetes 客户端库中的一个关键组件,用于监视 Kubernetes API 服务器的变更,并通过事件驱动的方式提供对这些变更的访问。Informer 提供了一种高效的方式来监听 Kubernetes 资源的变更,并管理本地缓存的资源对象。
Informer 的工作原理通常包括以下步骤:
-
初始化:创建一个 informer 实例,指定要监视的资源类型(如 Deployment、Pod 等)。
-
同步(List & Watch):Informer 会向 Kubernetes API 服务器发送一个 List 请求来获取资源的当前状态,并随后通过一个 Watch 通道来接收资源的变更事件。
-
本地缓存:Informer 使用本地缓存来存储资源对象的当前状态。当收到资源变更事件时,Informer 会将变更反映到本地缓存中,并触发相应的回调函数。
-
事件处理:Informer 允许开发者注册事件处理函数,这些函数会在资源创建、更新或删除时被执行。
-
Reflector:Reflector 负责通过 Watch API 监听 Kubernetes API 服务器上的资源变更,并将这些变更通知给 Informer。
-
Indexer:Indexer 是本地缓存的实现,它允许按标签(labels)或其他索引键快速查找资源对象。
Informer 的优势包括:
- 高效性:通过本地缓存和批处理 List 请求,Informer 减少了与 Kubernetes API 服务器的交互次数,从而降低了网络负载和 API 服务器的负载。
- 实时性:通过 Watch 机制,Informer 可以实时接收到资源变更的事件。
- 事件驱动:开发者可以通过注册事件处理函数,以事件驱动的方式响应资源变更,这简化了资源管理逻辑。
- 可扩展性:Informer 允许开发者扩展其功能,例如通过自定义 Indexers 实现更复杂的缓存查询。
Client-go 的 Informer 是在 Kubernetes 生态系统中广泛使用的工具,尤其是在 Operator 和自动化工具的开发中。通过使用 Informer,开发者可以更加高效和可靠地管理 Kubernetes 集群中的资源。
Informer 在 Kubernetes 的 client-go 客户端库中扮演了非常重要的角色,它提供了获取 Kubernetes API 资源变更的高效方式。Informer 组件主要包括以下几个部分:
-
Lister: Lister 是一个工具,用于从 Kubernetes API 检索资源对象。Lister 通过 Informer 维护的本地缓存提供资源对象的快照。
-
Controller: Informer 中的 Controller 负责维护本地缓存与 Kubernetes API 资源状态的一致性。它通过 List 和 Watch 机制与 API 服务器通信,以便在发生变更时更新本地缓存。
-
Reflector: Reflector 负责监视 Kubernetes API 服务器上的资源变更。当 Reflector 检测到资源更新时,它将变更通知发送给 Informer,以便更新本地缓存。
-
Delta FIFO Queue: Delta FIFO Queue 是 Informer 中的一个队列,用于存储接收到的变更事件。队列保证了事件处理的顺序,并按照接收到的顺序进行处理。
-
Indexer: Indexer 提供了基于标签(labels)或其他索引键的本地缓存索引。这使得 Informer 能够高效地查询本地缓存中的资源对象。
-
Resource Event Handlers: 用户可以为不同的资源事件(如 Add、Update、Delete)定义事件处理函数,以便在资源状态变更时执行特定的逻辑。
Informer 的工作流程如下:
- Reflector 通过 API 服务器的 Watch API 监听资源变更。
- 当资源发生变更时,Reflector 将变更事件放入 Delta FIFO Queue。
- Informer 的 Controller 从队列中取出事件,并更新本地缓存。
- Lister 利用 Indexer 查询本地缓存,提供资源对象的快照。
- 用户定义的事件处理函数在资源变更时被触发,以执行相关的业务逻辑。
通过这样的组件和流程设计,Informer 提供了一种非常高效和可靠的方式来监听和处理 Kubernetes API 资源的变更,这对于 Kubernetes 中的自动化和监控任务至关重要。
Informer怎么实现事件去重
queue push操作
watcher监控的资源变更时,会调用deltaFIFO中Added、Updated、Deleted、Replaced、Sync方法,最终它们都会通过queueActionLocked 方法往deltaFIFO队列中加入对应类型的delta对象。
queueActionLocked 也就是deltaFIFO的入队操作。
和一般的入队不同的是,新加入的delta不是直接加入到队尾,队列queue数组中保存的是delta的key。所以入队的操作是这样的
获取delta对应的key值(还记得keyfunc吗,又是它)
如果delta所属的资源key已经在队列中,直接将delta添加到key对应到deltas数组末尾。更新已存在的资源delta并不会影响他的key在队列中的位置。
如果delta所属的资源key不在队列中,就将key添加到队列末尾,并在items中关联key和delta
Informer工作流程
以 Pod 资源为例,介绍下 informer 的关键逻辑(与下图步骤一一对应):
- Informer 在初始化时,Reflector 会先调用 List 获得所有的 Pod,同时调用 Watch 长连接监听 kube-apiserver。
- Reflector 拿到全部 Pod 后,将 Add Pod 这个事件发送到 DeltaFIFO。
- DeltaFIFO 随后 pop 这个事件到 Informer 处理。
- Informer 向 Indexer 发布 Add Pod 事件。
- Indexer 接到通知后,直接操作 Store 中的数据(key->value 格式)。
- Informer 触发 EventHandler 回调。
- 将 key 推到 Workqueue 队列中。
- 从 WorkQueue 中 pop 一个 key。
- 然后根据 key 去 Indexer 取到 val。根据当前的 EventHandler 进行 Add Pod 操作(用户自定义的回调函数)。
- 随后当 Watch 到 kube-apiserver 资源有改变的时候,再重复 2-9 步骤。
Informer 关键设计
• 本地缓存:Informer 只会调用 K8s List 和 Watch 两种类型的 API。Informer 在初始化的时,先调用 List 获得某种 resource 的全部 Object,缓存在内存中; 然后,调用 Watch API 去 watch 这种 resource,去维护这份缓存; 最后,Informer 就不再调用 kube-apiserver。Informer 抽象了 cache 这个组件,并且实现了 store 接口,后续获取资源直接通过本地的缓存来进行获取。
• 无界队列:为了协调数据生产与消费的不一致状态,在客户端中通过实现了一个无界队列 DeltaFIFO 来进行数据的缓冲,当 reflector 获取到数据之后,只需要将数据推到到 DeltaFIFO 中,则就可以继续 watch 后续事件,从而减少阻塞时间,如上图 2-3 步骤所示。
• 事件去重:在 DeltaFIFO 中,如果针对某个资源的事件重复被触发,则就只会保留相同事件最后一个事件作为后续处理,有 resourceVersion 唯一键保证,不会重复消费。
• 复用连接:每一种资源都实现了 Informer 机制,允许监控不同的资源事件。为了避免同一个资源建立多个 Informer,每个 Informer 使用一个 Reflector 与 apiserver 建立链接,导致 kube-apiserver 负载过高的情况,K8s 中抽象了 sharedInformer 的概念,即共享的 Informer, 可以使同一类资源 Informer 共享一个 Reflector。内部定义了一个 map 字段,用于存放所有 Infromer 的字段。针对同一资源只建立一个连接,减小 kube-apiserver 的负载。
https://blog.csdn.net/susu_xi/article/details/132297951
Delta
在 Kubernetes 的 client-go 客户端库中,Delta FIFO Queue 是一个用于存储和处理变更事件的队列。队列中的每个条目称为 “Delta”,它代表了一个 Kubernetes 资源的状态变更。Delta 通常包含以下信息:
- Type:变更的类型,可以是 Added(添加)、Updated(更新)、Deleted(删除)或 Sync(同步)。
- Object:变更的对象,即 Kubernetes 资源对象本身或其一部分。
- Object.Key:对象的唯一标识,通常是资源的名称。
- Object.Namespace:对象所属的命名空间。
- Object.UID:对象的唯一标识(UID)。
Delta FIFO Queue 的设计是为了确保变更事件按照它们发生的顺序来处理,并且每个 Delta 只被处理一次。这意味着,即使 API 服务器在短时间内发送了多个相同类型的变更事件,Delta FIFO Queue 也会确保它们只被处理一次,从而避免了重复的操作。
在 Delta FIFO Queue 中,Deltas 是按照以下规则处理的:
- Add After Delete:如果先收到了一个删除事件,随后收到了同一个对象的添加事件,那么添加事件会在删除事件之后进行处理。
- Replace:如果 Delta 表明一个资源对象被更新,但对象的 Key(通常是指资源名称)没有改变,则被视为 Replace 事件。这意味着先前的对象将被标记为已删除,并在队列中删除,同时新的对象将被添加到队列中。
- Sync:同步事件通常用于处理资源对象在 Informer 本地缓存中的状态与 API 服务器中的状态不一致的情况。
Delta FIFO Queue 保证了 Kubernetes 控制器的操作幂等性,也就是说,对于相同的 Delta,不管处理多少次,最终的结果应该是一样的。这种机制对于保证 Kubernetes 控制器的稳定性和准确性是非常重要的。
Reflector
在 Kubernetes 的 client-go 客户端库中,Reflector 是一个负责监视 Kubernetes API 服务器中资源变更的组件。Reflector 通过 kube-apiserver 提供的 watch API 监听资源的变动,并将这些变更通知给 Informer。
Reflector 的主要职责包括:
-
开始监听:Reflector 开始通过 kube-apiserver 监听指定类型的资源(例如 Deployment、Pod 等)。
-
将事件传递给 Delta FIFO Queue:当 Reflector 检测到资源变更时,它会将这些变更封装成 Delta(资源变更事件),并将其添加到 Delta FIFO Queue 中。
-
处理 watch 断开:如果 Reflector 与 kube-apiserver 的连接断开,Reflector 将尝试重新连接并继续监听。
-
处理 watch 对象的更新:当 Reflector 收到关于 watch 对象的更新时(例如,资源类型、标签选择器等),Reflector 会相应地更新其配置,并可能重新启动监听过程。
-
处理资源对象的删除:当 Reflector 检测到资源对象被删除时,它会将相应的 Delta 添加到 Delta FIFO Queue 中。
Reflector 使用一个称为 “Lister” 的接口来获取所有匹配的 Kubernetes 资源对象,以便在启动监听之前初始化本地缓存。此外,Reflector 使用一个称为 “Watcher” 的接口来与 kube-apiserver 交互,以接收资源变更事件。
在 Kubernetes 控制器的设计中,Reflector 是一个非常重要的组件,因为它确保控制器能够及时地接收到资源的变更信息,进而可以执行相关的业务逻辑,如同步本地缓存、处理事件等。通过 Reflector,Kubernetes 控制器能够保持对集群状态的实时响应,从而实现对集群的自动化管理。
Workqueue
在 Kubernetes 的 client-go 客户端库中,WorkQueue 是一个用于处理异步任务的工具。它可以被用在各种需要异步执行的任务中,例如 Kubernetes 中的控制器(Controllers)在处理资源对象时。WorkQueue 提供了一种机制来缓存待处理的任务,以便控制器可以在资源状态发生变化时处理这些任务,而不是立即执行。
WorkQueue 的主要目的是:
- 异步处理:允许任务在后台异步执行,不阻塞主线程。
- 缓冲和调度:为任务提供缓冲空间,以便在任务执行器(通常是控制器)忙或不可用的时候,任务可以被存储起来。
- 重试和延迟:提供机制来处理失败的任务,例如通过重试机制,以及通过延迟来避免在短期内对同一任务进行连续的重试。
WorkQueue 的主要组件包括:
-
Rate Limiter:Rate Limiter 用来限制任务处理的速度,以避免任务处理者过载。
-
Delaying Queue:Delaying Queue 是一个用于存储任务的队列,当任务被放入队列时,它可能会延迟一定时间后再被处理,这有助于减轻对资源状态的连续变更。
-
Processing Function:用户定义的函数,用于处理任务。在 Kubernetes 中,这通常是一个处理资源对象变更的函数。
-
Rate Limiting Queue:Rate Limiting Queue 是一个普通的队列,它限制了任务的插入速度,以避免对 WorkQueue 的过度负载。
-
Workers:工作进程(Worker)从队列中取出任务并交给 Processing Function 处理。在 Kubernetes 中,这些工作进程通常是控制器的一部分。
WorkQueue 的工作流程通常如下:
- 任务被放入 Rate Limiting Queue 中。
- 如果延迟功能被激活,任务可能会被放入 Delaying Queue 中等待一段设定的时间。
- 工作进程(Worker)从 Rate Limiting Queue 或 Delaying Queue 中取出任务。
- 工作进程将任务传递给 Processing Function 处理。
- 如果任务处理失败,工作进程可能会根据策略(如指数退避)重试任务。
WorkQueue 使得 Kubernetes 中的控制器能够有效地处理资源变更,并且能够处理各种复杂的任务执行场景,例如控制并发、处理失败的任务、避免过载等。