我在刚开始接触Kubernetes的时候,最头疼的一件事就是:明明照着教程把一个集群搭起来了,Pod也跑起来了,但一旦遇到问题,脑子里就一片空白,不知道从哪开始查。后来我才意识到,问题不在于命令记了多少,而在于我对K8S集群里最核心的"四大组件"理解得太表面。
所谓"四大组件",在不同资料里说法略有出入,但抛开细枝末节,真正撑起一个集群控制面的核心就是这四兄弟:kube-apiserver、kube-controller-manager、kube-scheduler,以及真正在节点上干活的kubelet。你可能还会听到etcd、kube-proxy、CoreDNS这些名字,它们确实重要,但如果把K8S比作一家公司,那apiserver就是前台,controller-manager是人事行政,scheduler是排班主管,kubelet就是一线员工——etcd则是全公司的档案室。这套分工逻辑搞清楚了,以后排查任何集群问题,你至少能知道该去找谁。
这篇文章我就用这套"公司运作"的思路,把四大组件的工作原理掰开揉碎讲一遍,重点放在它们各自管什么、相互之间怎么协作、以及实际运维中你能用到哪些排查手段上。不管你是准备K8S面试,还是自己搭过集群但一直"知其然不知其所以然",这篇文章都值得你花二十分钟看完。
1. 四大组件到底指哪四个,边界在哪里
先说个很多人争论的问题:K8S的"四大组件"到底包含etcd还是kube-proxy?不同培训机构、不同文档版本确实有不同说法。但如果你去翻官方文档,Master节点(控制平面)上的核心组件其实分得很清楚,控制平面组件主要包括kube-apiserver、kube-controller-manager、kube-scheduler,另外etcd作为分布式键值存储虽然独立于组件之外,但它是整个集群状态的唯一来源,缺了它一切归零。而kubelet是每个工作节点上最核心的代理,负责真正把Pod跑起来。
所以为了让边界更清晰,这篇文章把"四大组件"定义为控制平面的四个核心角色:kube-apiserver、kube-scheduler、kube-controller-manager,以及工作节点上的kubelet。etcd我会把它当作基础设施来讲,因为它不是"组件"而是"存储层",但它和apiserver的关系密不可分,讲原理时绕不开。
1.1 组件分工的底层逻辑:声明式API
理解四大组件之前,必须先理解K8S的设计哲学——声明式API。你告诉集群"我想要什么",而不是"你应该怎么一步步做"。比如你写一个Deployment说"我要3个nginx副本",这个声明会被提交给apiserver,然后由controller-manager负责确保集群里有3个副本,如果没有就创建,多了就删掉。整个过程中,没有人去写脚本说"先查一下现在有几个Pod,如果是2个就再创建1个",这些逻辑全部内建在控制器里。
这套设计带来的最大好处是集群具备自愈能力。任何组件挂掉、节点宕机、Pod被误删,控制器都会自动发现并修复,因为期望状态始终存在etcd里。这也是为什么K8S集群经得起折腾——只要apiserver和etcd还在,控制平面就能持续工作,把集群拉回期望状态。
1.2 一次完整的Pod创建请求,组件之间是怎么协作的
光说分工太抽象,我们直接看一次完整的Pod创建流程,你会发现四兄弟的协作关系一目了然:
- 你通过kubectl向apiserver发送一个创建Deployment的POST请求。
- apiserver验证你的身份和权限(认证、鉴权),然后把Deployment对象校验后写入etcd。
- controller-manager中的Deployment控制器watch到有新Deployment对象,发现期望副本数3,但当前ReplicaSet数量为0,于是创建对应的ReplicaSet对象。
- ReplicaSet控制器watch到新的ReplicaSet,发现Pod副本数为0,于是创建3个Pod对象(此时Pod处于Pending状态)。
- scheduler watch到新的未调度的Pod,经过过滤和打分,选出最合适的节点,把Pod的nodeName字段写上,然后更新到apiserver,etcd随之更新。
- 目标节点上的kubelet watch到该Pod被调度到自己节点上,于是调用容器运行时(containerd或dockershim)拉取镜像、启动容器。
- kubelet持续上报Pod状态给apiserver,最终Pod变成Running。
这个流程里,每一个步骤都由对应的组件独立完成,组件之间不直接通信,而是通过apiserver间接协作。所以apiserver成了整个系统的"总线",地位极其重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. kube-apiserver:一切流量入口,集群的"中枢神经"
先讲apiserver,因为它是四大组件里最容易理解却被低估的一个。很多人只知道apiserver是RESTful API入口,但它实际上承担的职责远比想象中多:认证、鉴权、准入控制、请求限流、对象校验、etcd读写、事件分发。你每执行一条kubectl命令,背后都是一连串apiserver的处理逻辑。
2.1 请求从进入到落地的完整链路
我经常跟团队里新人说,理解apiserver最好的方式就是开一个debug日志,看一次创建Pod的请求到底经历了什么。完整链路大致是:
- 认证(Authentication):apiserver先看请求方是谁。常用的认证方式有客户端证书、Bearer Token(ServiceAccount)、Basic Auth。如果是kubeconfig里配的证书,apiserver会通过CA校验客户端证书是否受信任。
- 鉴权(Authorization):确认"你是谁"之后,再确认"你有权做什么"。K8S默认的RBAC模式会检查请求者的角色绑定是否允许对某个资源执行某种操作。
- 准入控制(Admission Control):这是最容易被忽略的一环。准入控制器会在对象持久化之前拦截请求,做校验、修改甚至拒绝。比如PodSecurityPolicy(新版本叫Pod Security Admission)、NamespaceLifecycle、ResourceQuota、LimitRanger等。很多"莫名其妙的创建失败"都是准入控制导致的。
前三步都通过之后,apiserver才会把对象做序列化、校验,然后通过raft协议写入etcd,并响应客户端:"创建成功"。整个过程是同步的,也就是说请求方拿到成功响应时,对象已经持久化到etcd了。
2.2 为什么说apiserver是集群唯一的"写入口"
有一个关键点必须强调:所有组件对集群状态的任何修改,都必须通过apiserver。controller-manager修正副本数,scheduler标记节点调度,kubelet上报状态,全部是走的apiserver的API。没有任何一个组件能直接访问etcd去改数据(除非你手动指定etcd地址去操作,那是极度危险且官方不推荐的)。
这个设计带来的好处是:写入一致性得到了保证,etcd只在apiserver这一层被写入,不会有多个写入方互相冲突。同时,你可以通过apiserver做统一的权限控制、审计日志、限流熔断。这也是为什么生产环境你永远要保证api-server的高可用,它一挂,集群就变成了"看得见摸不着"的状态——节点上的Pod还在跑,但没人能管理它们了。
2.3 实操心得:etcd写入那几个隐藏的坑
我自己在运维中踩过几个和apiserver写入相关的坑,这里分享给有需要的朋友。
第一个是etcd的存储大小和碎片问题。etcd默认的compact间隔内会积累大量历史版本,如果事件很多(比如频繁重启Pod,大量驱逐事件),db文件会膨胀到几GB,最后导致apiserver写入超时、集群不可用。解决办法是监控etcd db大小,超过1GB就该考虑执行defrag操作,同时用--auto-compaction-retention控制历史版本保留时间。
第二个是apiserver的QPS限制。大量并发操作(比如一次性删除几百上千个Pod,或者几百个节点同时注册)可能会触发apiserver的max-inflight限流,表现为请求超时或503。生产环境建议根据集群规模合理配置--max-requests-inflight和--max-mutating-requests-inflight,不要用默认值硬扛。
第三个和客户端有关,kubectl命令偶尔会报 the server has asked for the client to provide credentials,这种未必是证书真的过期了,可能是kubeconfig里配置的context发生了切换,或者集群的token过期。排查时先确认在用的context是不是你以为的那个。
3. kube-scheduler:集群里的"房产中介"
scheduler的职责一句话就能说清:为新创建的Pod找到最合适的工作节点。但"最合适"三个字背后,是一整套过滤加打分机制,以及大量可调参数。这套机制用得好,能直接提升集群的资源利用率和稳定性。
3.1 过滤阶段:先把不合格的候选人刷掉
过滤阶段,也叫Predicates(旧版叫法),核心逻辑是把不满足Pod硬性要求的节点剔除掉。常用的过滤条件包括:
- 资源是否充足:节点上剩余的可分配CPU和内存是否大于Pod的requests值。这里要注意,过滤用的是requests,不是limits,limits超了只会导致Pod被驱逐,不会影响调度判断。
- 端口冲突:Pod声明的hostPort是否和节点上已被占用的端口冲突。
- 节点选择器(nodeSelector):集群节点上的标签是否能匹配Pod指定的标签。
- 节点亲和性(nodeAffinity):是否满足requiredDuringSchedulingIgnoredDuringExecution的硬性规则。
- 污点容忍(taints & tolerations):节点上有污点(taint),而Pod没有对应容忍(toleration),这个节点就会被刷掉。
经过这一步,正常的集群通常还会剩下多个可用节点,这时候就轮到打分阶段登场。
3.2 打分阶段:优中选优的"评分模型"
打分阶段,也叫Priorities,会对每个通过过滤的节点打一个0到100的分数,最后选总分最高的节点。常用打分策略包括:
- LeastRequestedPriority:优先选择资源利用率较低的节点,让负载更均匀。
- BalancedResourceAllocation:优先选择CPU和内存利用率平衡的节点,避免一个节点CPU用满但内存还有大量剩余。
- ImageLocalityPriority:如果节点上已经有Pod所需镜像,这个节点会得到更高分,省去拉镜像的时间。
- NodeAffinityPriority:满足软性亲和规则的节点会加分。
实际调度时这些策略的权重可以调整,如果集群有特殊需求(比如更倾向于把Pod堆到少数节点上),可以修改scheduler的policy配置。
3.3 调度器的两个常见应用场景
第一个是自定义调度器。K8S允许你给Pod指定spec.schedulerName字段,当你不想用默认调度器(default-scheduler)时,可以写一个自己的调度器。我看到有些团队为了实现更精细的调度逻辑(比如按GPU拓扑、按地域距离)会这么做。但我的建议是:能用默认调度器解决的就别自己造轮子,除非你确实需要默认调度器实现不了的约束。
第二个是抢占式调度(Preemption)。当高优先级Pod无法在任何节点上调度成功时,默认调度器会尝试抢占(驱逐)低优先级Pod来腾出资源。这个功能在生产环境要慎用,因为它会导致低优先级Pod被无辜杀死。可以通过调整全局默认优先级或给不同业务设置不同的PriorityClass来控制影响范围。
实操中要特别注意:调度器如果崩溃或者配置出错,集群的表现不是"节点全挂了",而是"新创建的Pod一直Pending"。排查Pending Pod时,第一步先看事件 kubectl describe pod <pod-name>,如果Events里出现了 0/3 nodes are available 或者 pod didn't trigger scheduling,基本就能确定是调度层面的问题。
4. kube-controller-manager:集群的"恒温器"
controller-manager是四大组件里最容易被忽视的,因为它深居简出,平时不太刷存在感。但它的重要性不亚于apiserver——没有它,你的Deployment里面宣告的"3个副本"永远只是一个期望值,不会变成现实。我习惯把它比作恒温器:你用空调设定了26度,温度传感器时刻监测房间温度,高了就停机,低了就启动,循环往复。controller-manager的每个控制器,本质上就是一个这样的闭环系统。
4.1 控制循环的三步曲:观察、分析、行动
控制器的核心逻辑可以用三步概括:观察实际状态(Observe)、对比期望状态(Diff)、采取行动(Act)。以ReplicaSet控制器为例:
- 观察:从apiserver(经过etcd)持续watch当前有多少个Pod带有指定label,且它们的owner是哪个ReplicaSet。
- 分析:当前运行的Pod数量是否等于ReplicaSet的replicas字段设定的数量。
- 行动:如果实际数量少于期望数量,通过apiserver创建新的Pod对象;多则删除多余的Pod对象。
这个循环会一直持续,所以即使某个Pod被误删,控制器也会在几秒内创建一个新的顶上,直到ReplicaSet被删除。这套机制在K8S里被反复使用,Deployment、StatefulSet、DaemonSet、Job等高级工作负载底层全部基于控制循环。
4.2 控制器不止一个:常见的控制器组合
controller-manager内部由几十个控制器打包组成,常用的有:
- Deployment Controller:管理Deployment对象的生命周期,创建/更新/回滚。
- ReplicaSet Controller:维持指定数量的Pod副本。
- StatefulSet Controller:保证有状态应用Pod的标识符稳定、网络标识稳定、存储卷持久。
- DaemonSet Controller:确保每个节点上运行一个实例。
- Node Controller:监控节点状态,负责节点失联后的Pod驱逐(taint-driven eviction)。
- ServiceAccount Controller:为Namespace自动创建默认的ServiceAccount。
- Namespace Controller:处理Namespace的删除,以及其中资源的清理。
正是因为控制器太多,controller-manager通常在架构上是按主备模式部署的,通过选主(leader election)机制保证同一时刻只有一个实例在真正干活,另一个standby。为什么?因为多个controller-manager同时运行会导致多个控制器对同一资源进行竞争操作,产生冲突。如果你在多个controller-manager的日志中看到类似的选主信息,不要慌,这是正常的。
4.3 实操心得:如何判断是不是控制器出了问题
控制器如果挂了,最常见的一个表现是:新建的Deployment一直没有任何Pod被创建出来。这时kubectl get deployment能看到Deployment存在,kubectl get rs却看不到对应的ReplicaSet。
排查方式很直接:
- 检查controller-manager的Pod是否健康:
kubectl -n kube-system get pod -l component=kube-controller-manager - 看日志:
kubectl -n kube-system logs <controller-manager-pod> --tail=100,重点看有没有leader election
有段时间我碰过一个诡异的现象,创建所有Deployment都不生成Pod,反复看controller-manager日志发现它在频繁重启,选主一直没有完成。排查下来是因为etcd的租约(lease)时间太短,加上controller-manager和etcd之间网络抖动,导致所有控制器都拿不到leader权限。把etcd的网络延迟问题解决后,controller-manager立刻恢复了正常。这也说明一个道理:控制器出问题,根因往往不在控制器本身,而在它依赖的etcd和网络链路。
5. kubelet:真正在现场干活的"一线工人"
前面三个组件都在控制平面上,kubelet则是奔走在生产一线的那个。它运行在每个工作节点上,直接和容器运行时(CRI)、Pod生命周期打交道,是集群一切指令的最终执行者。可以这么说:没有kubelet,你的Pod只是一堆存在etcd里的数据,永远变不成真正的容器。
5.1 kubelet的一揽子职责
kubelet的职责远不止"根据spec启动容器"这么简单,我列一下它日常要干的事:
- Pod生命周期管理:通过CRI(容器运行时接口)调用containerd、CRI-O或老的dockershim,创建/启动/终止容器。
- 健康检查:执行livenessProbe和readinessProbe。如果liveness探测失败,kubelet会杀掉容器并重启;readiness失败则把Pod从Service的Endpoints中摘掉。
- 资源监控:通过cAdvisor采集节点、容器的CPU/内存/网络/磁盘指标,暴露给metrics-server或Prometheus。
- 节点状态上报:定期把节点的状况(Ready/NotReady、磁盘压力、内存压力等)上报给apiserver,Controller Manager里的Node Controller看这个状态决定是否驱逐Pod。
- 镜像管理:拉取Pod需要的镜像,管理节点上的镜像缓存。
- Volume管理:挂载Pod声明的emptyDir、hostPath、PV/PVC等卷。
5.2 kubelet和容器运行时之间怎么对话
如果只记住一个词,记住CRI(Container Runtime Interface)。CRI是K8S和容器运行时之间的桥梁,本质是一套gRPC接口,定义了kubelet怎么调用容器运行时去创建、删除、管理容器。containerd和CRI-O都实现了CRI,所以能直接被K8S使用。在早期版本里,Docker是通过一个叫dockershim的中间层来完成CRI适配的,后来K8S在1.24版本移除了dockershim,所以新集群你基本见不到Docker作为底层运行时了。
实操中你可能会遇到一个迷惑行为:用docker ps看不到K8S创建的容器。不是说docker不存在了,而是kubelet通过CRI调用的并不归Docker管理,你只能用crictl ps查看。这也是很多人从Docker切换到containerd后踩的第一个坑。
5.3 kubelet证书轮换:一个容易爆的雷
生产环境跑久了,很多集群会突然出现节点上Pod的API调用权限失效问题,比如某个组件报403 Forbidden。一种典型原因是kubelet的客户端证书过期了。K8S支持证书自动轮换(--rotate-certificates=true和kubelet配置里的rotateCertificates: true),但不是所有集群默认都开着,尤其一些早期kubeadm搭起来的集群可能没有正确配置。
怎么排查?
- 登录异常节点,看kubelet证书:通常位于
/var/lib/kubelet/pki/,用openssl x509 -in kubelet-client-current.pem -noout -text -enddate查看过期时间。 - 确认kubelet配置里是否启用了证书轮换:
kubectl get csr,如果集群有大量Pending的CSR,说明轮换机制在干活但需要管理员审批。 - kubeadm创建的集群通常默认开了
serverTLSBootstrap,但如果没有额外的approve脚本,CSR会一直Pending,最终导致证书过期。
我建议:生产环境一定要配置自动签发CSR的机制,可以用kubelet-rubber-stamp这类工具,或者自己写个cron去approve CSR,别让证书过期成为"半夜三点把你叫醒"的元凶。
6. 高可用部署要点与问题排查实录
前面把四个组件各自的原理讲完了,最后落到实际部署和运维上。不管你是用kubeadm初始化集群,还是用二进制搭建,有几个点几乎是所有踩坑的人都反复强调的。
6.1 控制平面高可用:不是把所有组件塞在一台机器上
很多初学者理解"高可用"就是多复制几份,但K8S控制平面的高可用有讲究。kube-apiserver可以做毫秒级的无状态负载均衡,前面用HAProxy或云负载均衡把流量分发到多台Master。etcd是分布式存储,内部通过raft协议选主,奇数节点投票,所以生产环境一般部署3或5副本。scheduler和controller-manager则通过选主机制实现主备,同一时刻只有一个实例在干活。
所以完整的高可用控制平面架构是:多台Master上的apiserver水平扩展,etcd集群独立部署(可以和Master在同一批机器上,也可以完全独立),scheduler和controller-manager各跑一个主选中互不干扰,但最终所有流量还是通过apiserver汇聚。
6.2 节点NotReady:最经典也最让人上头的排查题
节点NotReady是群里被问烂的问题,处理思路其实很固定:
- 第一步,看节点状态和条件:
kubectl describe node <node-name>,重点看Conditions里哪一项是False。如果KubeletReady是Unknown或False,继续往下。 - 第二步,登录节点,看kubelet服务状态:
systemctl status kubelet,如果服务挂了,查日志journalctl -u kubelet -f。 - 第三步,看kubelet日志里的关键错误。常见的包括:
- "failed to get nodes from etcd"或"connection refused":apiserver网络不通或者证书过期。
- "failed to check network"或CNI插件问题:节点上的网络组件(比如Calico、Flannel)异常,导致kubelet认为节点网络没准备好。
- "PLEG is not healthy":容器运行时假死,containerd卡住,需要重启containerd再重启kubelet。
- 第四步,确认磁盘和负载。如果根分区满了,kubelet会直接报
disk pressure,节点状态也会异常。清理磁盘空间通常是见效最快的操作。
6.3 一条命令快速看组件状态的思路
搭建集群时,我习惯初始化完之后马上跑一次kubectl get pods -n kube-system -o wide,观察每个核心组件的Pod数量,正常情况应该是:
- kube-apiserver:Master数量对应的副本数,比如3台Master有3个。
- etcd:Master数量对应的副本数(或者独立的etcd集群节点数)。
- kube-controller-manager:和Master数量一致。
- kube-scheduler:和Master数量一致。
- kube-proxy和CNI的Pod:每个工作节点各有一个。
如果发现某一类组件Loading或CrashLoopBackOff,就用kubectl -n kube-system logs去看对应Pod日志,排查思路和上面是一致的。另外强烈建议开启metrics-server或者采集kube-state-metrics,这样你在仪表盘上就能提前看到Pod、节点状态的趋势,而不是等业务告警了才去被动排查。
最后分享一点我自己的实际感受
这套组件讲了那么多,落到实际里,我最深的体会是:K8S排障,本质上是定位"期望状态"和"实际状态"之间为什么产生了差距。知道了四大组件各管一块,你在排查时就能快速缩小范围——Pod创建不出来,先怀疑调度器和准入控制;Pod反复重启,先看kubelet和健康检查;副本数对不上,直接查controller-manager日志。这比记住一百个kubectl命令有用得多。
如果你正在准备K8S相关的面试,我的建议是把"一次Pod从提交到Running的完整链路"背熟,面试官从任何点切入你都能接得上。如果你是在维护集群,那就像我前面说的,先把kubectl describe、kubectl logs、journalctl -u kubelet几个命令练到肌肉记忆,再复杂的故障也基本能走出第一步。希望这篇文章能帮你看清K8S的骨架,以后踩起坑来,心里不慌。
