您现在的位置是:首页 >其他 >K8s核心知识原理网站首页其他
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,如:
metrics
、version
等。
可以说,自定义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了。
对于每个资源对象,只有三种事件(钩子):
- AddFunc:处理新对象添加事件。
- UpdateFunc:处理对象更新事件。
- 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 将被通知 |
调度流程
- 请求api-server,创建Pod。
- api-server将数据存储到etcd。
- 通过api-server(Watch API)监听未调度的Pod。
- 将未调度的Pod(spec.nodeName为空)放入调度队列,等待调度。
- 调度器监听api-server,查看未调度的Pod列表,遍历为Pod尝试分配节点,其中:
- 从调度队列pop一个Pod,开始标准调度周期。
- 断言过滤,根据预设规则去过滤掉不符合要求的Node。
- 优先级打分,将过滤后的Node打分。
- 将5中打分最高的Node和Pod绑定,改名spec.nodeName并将结果通过请求api-server存储到etcd。
- 在选出的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机制。
-
kubelet通过:
- List&Watch从api-server中获取Pod;
- 扫描本地的manifests获取静态Pod文件;
- HTTP Endpoint得到相关的Pod事件;
放入本地DeltaFIFO Queue中。
-
syncLoop通过对Queue进行消费,更新本地缓存同时将Key放入到workQueue中。
-
workers对workQueue进行消费,通过syncPod方法,根据key从本地缓存PLEG中取出Pod对象,并分析Pod清单与本地Pod信息进行比较,是进行创建、更新、还是删除。
-
过程中通过与CRI不断进行交互操作,达到我们理想的状态,过程中不断上报Pod的部署状态更新情况(通过PLEG定期relist正在Run的Pod,并通过Pod Lifestyle Event回报api-server),并且更新本地缓存。
PLEG:负责检测和生成Pod生命周期事件,会定期检查Pod和容器的状态。
Kubelet有Pod最大数量限制的原因:
如果Pod数量太大,会导致relist操作变慢,而relist是会上报全量的Pod对象,会给api-server造成压力。
对Pod的创建和修改
- 为该Pod创建数据目录。
- 从API Server读取该Pod清单。
- 为该Pod挂载外部卷。
- 下载Pod要用到的Secret。
- 检查已运行的Pod,若该Pod没有容器或Pause容器未启动,则停止Pod中所有容器进程。若Pod中有需要删除的容器,则删除它们。
- 为每个Pod创建一个Pause容器,接管Pod中所有其他容器的网络。每新建一个Pod,都会先创造一个Pause容器,再创建其他容器。
- 为Pod中每个容器计算hash值,再用容器名称去查对应CRI的hash值。若找到容器且hash值不同,则停止CRI中容器进程,并停止其关联的Pause容器;若相同则不处理。
- 若容器被终止,且未指定restartPolicy,则不做任何处理。
- 下载容器镜像(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,是通过:
- Pod-A节点的iptables转发给kube-proxy进程。
- kube-proxy建立与Pod-B的TCP/UDP连接。
- 请求转发到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插件的类型
- 桥接插件(Bridge):
- 创建一个Linux网桥,并将所有Pod接口连接到该网桥上。
- 常用于简单的网络配置。
- 覆盖网络插件(Overlay):
- 例如Flannel和Weave,创建一个覆盖网络,使得不同主机上的容器可以互相通信。
- 通过封装网络流量,提供跨主机的容器网络连接。
- 主机端口插件(HostPort):
- 管理主机端口和容器端口的映射,使外部流量可以访问容器内的服务。
- VLAN插件:
- 提供VLAN支持,使不同的Pod可以连接到不同的VLAN网络。
- 其他插件:
- 包括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的可用性状态。