您现在的位置是:首页 >其他 >K8s核心知识原理网站首页其他

K8s核心知识原理

加文阿波多 2025-02-10 12:01:02
简介K8s核心知识原理

Kubernetes核心知识原理

在这里插入图片描述

以下组件,可以称为Master节点的必备组件。

API-Server

  • 是整个K8s集群操作etcd的唯一入口(资源访问入口)。
  • 负责K8s各类资源的认证、鉴权、CRUD等。
  • 提供REST API,给其他组件调用。(内部可能用protobuf、gprc等)

所有对K8s的操作,都必须经由API-Server。

API-Server的逻辑结构如下。

在这里插入图片描述

其资源请求大致分为三类:

  • Core Group:主要在api/v1下,内含:Pod、Service等核心API资源。
  • named group:其path在api/$GROUP/$VERSION,允许API随时间扩展,增加新功能而不影响核心组。
  • 系统状态API,如:metricsversion等。

可以说,自定义API时,指定GVK,也是为了指定请求资源的路径。

至于自定义API的功能,肯定就是Event Handler实现,其会被注册在Router中。

简单地说,功能主要就是:认证用户身份、授权用户、资源对象准入

Controller Manager

是一个守护进程,管理和协调K8s中的内部Controller,保证其正常工作。

它的内部有Pod Controller、DeploymentController、NodeController等。

若通过Operator扩展机制创建的Controller,其实是Deployment!

如果想要Kube Controller Manager去接管,需要修改源码,但显然这样不太可取。

其提供事件分发能力,这样不同的Controller只需要注册对应的Handler等待和接收事件即可。

如此,Controller中,只需要实现对应的EventHandler,就可以处理被分发的事件了。

(事实上也是Controller自己做的)

因此其实这里讲Controller Manager,更多的是讲其内部Controller了。

对于每个资源对象,只有三种事件(钩子):

  1. AddFunc:处理新对象添加事件。
  2. UpdateFunc:处理对象更新事件。
  3. DeleteFunc:处理对象删除事件。

每当相应的资源发生变化时,Informer会调用这些方法。

管理Controller

是Controller Manager做的。

kube-controller-manager通常会采用高可用部署,通过首领选举,保证只有一个Manager去操作Controller。

管理K8s资源状态

Controller Manager提供事件分发的能力,将K8s资源变更的事件分发给对应Controller处理。

(事实是Controller自己做的)

当Controller收到资源变更事件时,触发对应已经注册好的EventHandler去调整状态。

而在Controller Manager中,使用了client-go去做事件分发,涉及List&Watch机制和Informer模块。

其实这种能力是各Controller自己去实现的。Controller Manager的作用只是正常启动了Controller。

Controller启动之后 ,再去通过client-go使用List&Watch机制处理事件。

在这里插入图片描述

List&Watch机制

无非就是一个处理K8s资源变更的一种“生产消费者模式”。

Controller-Manager中的各Controller,通过client-go提供的Informer去监听其管控的资源的变化。

在Informer中,一旦观察到资源变化,则触发Controller的Event Handler去处理。

在这里插入图片描述

Scheduler

主要负责整个集群资源的调度功能。

根据特定的调度算法和策略,将Pod调度到最优的Worker上面去。

Scheduler可以使用自定义的,也可以使用默认的kube-scheduler。

在调度周期中,也有相当多的扩展点事件可以被我们自定义,协助最终调度决策。

事件名称事件作用
PreFilter预处理 Pod
Filter过滤 Nodes
PostFilter仅在该 Pod 没有可行的 Node 时调用
PreScore打分预处理
Score对 Nodes 进行打分
NormalizeScore在调度器计算 Node 排名之前修改分数
Reserve调度器实际将一个 Pod 绑定到其指定节点之前,调用该扩展点
Permit每个 Pod 调度周期的最后调用,用于防止或延迟 Pod 的绑定
PreBind执行 Pod 绑定前所需的所有工作
Bind将 Pod 绑定到 Node 上
PostBind在 Pod 成功绑定后被调用
Unreserve如果 Pod 被保留,然后在后面的阶段中被拒绝,则 Unreserve 将被通知

调度流程

  1. 请求api-server,创建Pod。
  2. api-server将数据存储到etcd。
  3. 通过api-server(Watch API)监听未调度的Pod。
  4. 将未调度的Pod(spec.nodeName为空)放入调度队列,等待调度。
  5. 调度器监听api-server,查看未调度的Pod列表,遍历为Pod尝试分配节点,其中:
    • 从调度队列pop一个Pod,开始标准调度周期。
    • 断言过滤,根据预设规则去过滤掉不符合要求的Node。
    • 优先级打分,将过滤后的Node打分。
  6. 将5中打分最高的Node和Pod绑定,改名spec.nodeName并将结果通过请求api-server存储到etcd。
  7. 在选出的Node中,通过Kubelet去创建并管理Pod。

当然,调度决策也会有亲和性等方面的考量,实际上也是通过调度评估的插件去做的。

下面两张图,展示了调度的流程以及一个标准调度周期的流程。

在标准调度周期中,并发度为1(串行),而后续的等待和绑定周期,是异步并行执行。

在这里插入图片描述

在这里插入图片描述

自定义一个调度器

其实就是针对扩展点去做逻辑控制,主要调整的可以是:打分策略、断言策略。

Kubelet

Kubelet是在每个节点上运行的主要 “节点代理”。可认为是节点的“管家”。

每个节点都会启动kubelet进程,用来处理Master节点下发到本节点的任务,按照podSpec描述来管理Pod和其中的容器。

主要功能

  • 监视本节点的Node
  • 挂载Pod所需Volumes
  • 下载Pod的Secret
  • 通过CR运行Pod容器
  • 周期地执行Pod中为容器定义的liveness探针
  • 上报Pod的状态给系统其他组件
  • 上报Node状态

常用端口

10250 - kubelet API端口

允许集群中的其他组件与kubelet进行通信。

用于执行命令、获取节点和Pod的状态、以及管理Pod生命周期。

10248 - 健康检查端口

访问该端口可以判断kubelet 是否正常工作。

通过kubelet的启动参数 --healthz-port--healthz-bind-address 来指定监听的地址和端口。

10255 - Read-Only Kubelet API端口

提供一个只读的HTTP端口,用于公开节点和Pod的状态信息。

默认启用,不用验证。

4194 - cAdvisor端口

用于收集和提供容器的资源使用情况,如CPU、内存、文件系统等。

工作过程与原理

依然是使用了List&Watch机制。

  1. kubelet通过:

    • List&Watch从api-server中获取Pod;
    • 扫描本地的manifests获取静态Pod文件;
    • HTTP Endpoint得到相关的Pod事件;

    放入本地DeltaFIFO Queue中。

  2. syncLoop通过对Queue进行消费,更新本地缓存同时将Key放入到workQueue中。

  3. workers对workQueue进行消费,通过syncPod方法,根据key从本地缓存PLEG中取出Pod对象,并分析Pod清单与本地Pod信息进行比较,是进行创建、更新、还是删除。

  4. 过程中通过与CRI不断进行交互操作,达到我们理想的状态,过程中不断上报Pod的部署状态更新情况(通过PLEG定期relist正在Run的Pod,并通过Pod Lifestyle Event回报api-server),并且更新本地缓存。

PLEG:负责检测和生成Pod生命周期事件,会定期检查Pod和容器的状态。

Kubelet有Pod最大数量限制的原因:

如果Pod数量太大,会导致relist操作变慢,而relist是会上报全量的Pod对象,会给api-server造成压力。
在这里插入图片描述

对Pod的创建和修改

  1. 为该Pod创建数据目录。
  2. 从API Server读取该Pod清单。
  3. 为该Pod挂载外部卷。
  4. 下载Pod要用到的Secret。
  5. 检查已运行的Pod,若该Pod没有容器或Pause容器未启动,则停止Pod中所有容器进程。若Pod中有需要删除的容器,则删除它们。
  6. 为每个Pod创建一个Pause容器,接管Pod中所有其他容器的网络。每新建一个Pod,都会先创造一个Pause容器,再创建其他容器。
  7. 为Pod中每个容器计算hash值,再用容器名称去查对应CRI的hash值。若找到容器且hash值不同,则停止CRI中容器进程,并停止其关联的Pause容器;若相同则不处理。
  8. 若容器被终止,且未指定restartPolicy,则不做任何处理。
  9. 下载容器镜像(Docker的话调用Docker Client),并运行容器。

List&Watch机制

与Controller的List&Watch不同,kubelet监听的是api-server分配过来的Pod及其他资源。

(其实本质上都是监听自己需要处理的事件的变化罢了)

其他的与Controller的本质上没什么区别。

Kube-Proxy

kube-proxy负责为Service提供cluster内部的服务发现和负载均衡

由于Service只是一种概念,因此kube-proxy是这个service的重要的一部分实现。

它运行在每个Node计算节点上,负责Pod网络代理。

它会定时从etcd服务获取到service信息来做相应的策略,维护网络规则和四层负载均衡工作。

第一代(iptables + 代理转发)

一开始就是一个TCP/UDP代理,转发从SVC到Pod的访问流量,称为userspace模式

其使用节点的iptables去做转发。例如Pod-A访问Pod-B,是通过:

  1. Pod-A节点的iptables转发给kube-proxy进程。
  2. kube-proxy建立与Pod-B的TCP/UDP连接。
  3. 请求转发到Pod-B。

此时若Pod是多副本的,则转发过程会负载均衡。

但是iptables是针对本地kube-proxy端口,因此每个Node都需要运行kube-proxy。

第二代(iptables + watch)

取消了第一代的转发到kube-proxy再转发的步骤。

kube-proxy现在通过api-server监听service和endpoint信息,有变化直接更新到对应Node的iptables。

这样,每个Node直接根据本地iptables即可通信。

第三代(IPVS模式)

iptables可认为是链表的结构,在大规模集群中,网络性能会显著下降。

而IPVS结构式hash表,查询效率高。

并且,IPVS模式下,使用了iptables的扩展ipset,假设需要屏蔽网段,直接添加进这个集合即可,而不用像操作链表一样一个个加入iptables。

CNI

容器网络接口,为每个Pod配置网络,存在于各个节点上。

在K8s中,CNI用于实现容器网络的配置和管理(使用具体的CNI插件,如Calico)。

其仅关心容器创建时的网络分配,和当容器被删除时释放网络资源。

可在下面目录查看CNI的配置文件。

cd /etc/cni/net.d

规范概述

CNI中只涉及两个概念:容器网络

  • 容器:必须是拥有独立Linux网络namespace的环境,如Docker创建的容器。
  • 网络:可以互连的一组实体,这些实体拥有各自独立、唯一的IP地址,可以是容器、物理机等。
    可将容器添加或删除到若干个网络中。

对容器网络的设置和操作,都由插件进行。

插件类型:

  • CNI Plugin:为容器配置网络资源。
  • IPAM:对容器IP地址进行分配和管理。

工作流程

创建网络

  • 当一个新的 Pod 被创建时,K8s调用CNI插件来为该Pod配置网络
  • 插件根据配置文件中定义的网络参数,创建相应的网络接口,并将其连接到 Pod。

删除网络

  • 当 Pod 被删除时,K8s调用CNI插件来清理该Pod的网络配置。
  • 插件删除相应的网络接口,并释放相关的资源。

Runtime按先后顺序依次调用各CNI Plugin的二进制文件并执行。

若某个插件执行失败,Runtime必须停止后续执行,并返回错误信息。

CNI插件的类型

  1. 桥接插件(Bridge)
    • 创建一个Linux网桥,并将所有Pod接口连接到该网桥上。
    • 常用于简单的网络配置。
  2. 覆盖网络插件(Overlay)
    • 例如Flannel和Weave,创建一个覆盖网络,使得不同主机上的容器可以互相通信。
    • 通过封装网络流量,提供跨主机的容器网络连接。
  3. 主机端口插件(HostPort)
    • 管理主机端口和容器端口的映射,使外部流量可以访问容器内的服务。
  4. VLAN插件
    • 提供VLAN支持,使不同的Pod可以连接到不同的VLAN网络。
  5. 其他插件
    • 包括Calico、Cilium等,提供高级网络功能,如网络策略、加密、负载均衡等。

NetworkPolicy

对Pod或Namespace之间的网络通信进行限制和准入控制。

但是它只是策略,需要相应的Policy Controller实现,如Calico等。

方式:将目标对象的Label作为查询条件,设置允许访问或禁止访问的客户端列表。

工作原理

Policy Controller需要实现一个API Listener,监听用户设置的NetworkPolicy定义,并将网络访问规则通过各节点的Agent(CNI插件实现)进行设置。

假设CNI用Calico,那么就需要一个Policy Controller去监听NetworkPolicy,再传给各节点的Calico做具体的网络限制。

DNS

用于为集群中的Service和Pod提供名字解析,使得它们可以通过名字而不是IP地址来相互访问。

目前,通常用CoreDNS作为默认的DNS。

DNS 服务部署

  • DNS 服务通常作为K8s集群中的一个Service运行,部署在kube-system命名空间中。

服务发现

  • 当一个服务在K8s中创建时,K8s自动为该服务分配一个DNS名称。这个名称可以在集群内的任何地方解析。
  • DNS名称的格式通常为 <service-name>.<namespace>.svc.cluster.local

DNS 解析

  • 集群中的每个 Pod都配置了一个DNS解析器,通常是CoreDNS或kube-dns。
  • 当一个Pod尝试通过服务名访问另一个服务时,DNS解器会将该名称解析为服务的ClusterIP或Pod IP地址。

Pod的DNS策略

Pod的DNS策略可以逐个Pod设置,字段为dnsPolicy,默认clusterFirst

策略举例:

  • Default:Pod从运行所在的节点继承名称解析配置。
  • ClusterFirst:与配置的集群域后缀不匹配的任何DNS查询(例如 “www.kubernetes.io”)都会由 DNS 服务器转发到上游名称服务器。集群管理员可能配置了额外的存根域和上游 DNS 服务器。
  • ClusterFirstWithHostNet:和ClusterFirst相同,对于以hostNetwork模式运行的Pod应明确知道使用该策略。
  • None:忽略K8s环境的DNS配置,通过spec.dnsConfig自定义DNS配置。

策略见官网文档:Pod的DNS策略

NodeDNS

是本地缓存的DNS。

优势:

  • 减小跨主机查询的延时。
  • 跳过iptables DNAT和链接跟踪,有助于减少conntrack竞争,并避免UDP DNS记录填满conntrack表。
  • 本地缓存连接到集群DNS,协议可升级为TCP,减少了conntrack条目以及较少UDP丢包造成的尾延。

Pod生命周期

这里将介绍Pod的生命周期及状态。

生命周期

Pod在一次生命周期中,只会被调度一次

可以用门控去延迟调度,但是不能让已被调度的Pod再被调度,除非Pod停止。

对于一个Pod而言,以下是从kubectl apply开始的生命周期描述:

在这里插入图片描述

Pod阶段(状态)

官网给的几个阶段(状态)如下,不应该存在除了下面几种状态之外的状态:

取值描述
Pending(悬决)Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。
Running(运行中)Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
Succeeded(成功)Pod 中的所有容器都已成功终止,并且不会再重启。
Failed(失败)Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止,且未被设置为自动重启。
Unknown(未知)因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。

在这里插入图片描述

重启策略

即restartPolicy,有三种:

  • Always:只要容器失效,kubelet自动重启该容器。
  • OnFailure:当容器终止运行且exit code不为0,kubelet自动重启该容器。
  • Never:无论什么情况,kubelet都不重启该容器。

容器状态

一旦Pod被分配到节点,Kubelet就通过Runtime开始为节点创建容器。有三种状态:

Waiting

只要容器不在Running或Terminating,它就在Waiting。

此时,容器仍在运行它完成启动所需要做的操作,如拉取镜像等。

Running

此时容器正在执行状态,且无任何问题。

若配置了postStart回调,则该回调已经执行并且完成。

Terminating

此时容器已经开始执行,且正常结束或因为某些原因失败。

若配置了preStop回调,则该回调会在容器进入Terminating状态前执行。

Pod健康检查

可用三类探针检查:

LivenessProbe

判断容器是否存活(Running态)。

若其探测到容器不健康,则kubelet会kill该容器,并根据容器重启策略作相应处理。

若一个容器不包含LivenessProbe探针,则kubelet认为该容器的LivenessProbe永远返回success。

ReadinessProbe

判断容器服务是否可用(Ready态)。

Ready态:表明Pod或容器是否已经准备好处理请求的状态。

当所有容器的Readiness Probe都成功时,Pod会被标记为"Ready"。

Pod必须达到Ready态才能接收请求。


防止服务转发到不可用的Pod

若Pod被Service管理,Service、Pod、Endpoint的关联也会与Pod的Ready态有关。

若运行过程中,Ready态变为False,则系统自动将其从Service的Endpoint列表隔离出去。

再把恢复到Ready态的Pod加回Endpoint。


StartupProbe

对于慢启动、长延时等情况,造成容器启动缓慢,可用该探针。

Pod Readiness Gate(Ready++)

给予Pod之外的组件控制某个Pod就绪的能力。

通过该机制,用户可以设置自定义的Pod可用性探测方式,告诉K8s某个Pod是否可用。

具体:写一个外部Controller,设置相应Pod的可用性状态。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。