ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes核心原理拆解:从声明式API到Pod调度全流程

Kubernetes核心原理拆解:从声明式API到Pod调度全流程 做了这么多年容器平台我面试过不少人发现很多人的Kubernetes知识是“背”出来的组件名称背得滚瓜烂熟一问到“kubelet 是怎么拿到 Pod 清单的”“调度器凭什么把 Pod 放到这台机器上”就卡壳。Kubernetes 从来不是一个靠背就能学会的东西它是一个由十来个组件通过 HTTP 请求互相协作、围绕“期望状态”不断收敛的分布式系统。这篇文章不打算给你画架构图而是直接用组件之间的调用关系、数据流向和实际排查经验把 Kubernetes 的原理一条条拆开。不管是刚入门想打通概念还是上了生产环境遇到诡异问题这篇都值得你花半小时仔细读一遍。1. Kubernetes 到底在解决什么问题很多人第一次接触 Kubernetes 是冲着“容器编排”这个词去的。但如果你只把它理解成一个“能跑容器的工具”那你很快就会迷失在一大堆抽象概念里。Kubernetes 真正解决的核心问题是把“我要跑什么”和“具体跑在哪、怎么跑”彻底分开。1.1 从容器到编排Kubernetes 诞生的逻辑Docker 出现之后单机跑容器变得极其简单一条docker run命令就能拉起一个隔离环境。但真实业务不是单机游戏。你有几十个微服务每个服务多副本还要做滚动更新、故障自愈、流量接入、配置管理靠人肉敲 docker 命令根本不现实。这时候就需要一个“管家”它知道集群里有哪些机器、每台机器还剩多少资源、你的服务需要几个副本、某个副本挂了该不该重启。这个管家就是 Kubernetes。Kubernetes 设计哲学的第一条是“声明式 API”你告诉它“我要跑 3 个 nginx 副本”它自己决定怎么达成这个状态。这跟传统的“命令式”操作有本质区别。命令式是“你告诉我每一步做什么”声明式是“你告诉我结果是什么”。Kubernetes 内部有一个永不疲倦的循环不停地把“当前状态”往“期望状态”拉近。这个理念贯穿所有组件理解了它你就理解了 Kubernetes 的一半。1.2 期望状态与控制循环Kubernetes 的心跳整个 Kubernetes 最核心的机制是“控制循环”Control Loop。这个概念其实不新鲜恒温器就是最朴素的例子你设定 26 度温度传感器读到 28 度空调就制冷读到 24 度空调就制热。Kubernetes 里的控制器就是无数个“恒温器”只不过它们控制的不是温度而是副本数、镜像版本、节点状态等等。举个例子你写了一个 Deployment声明“replicas: 3”。Deployment 控制器发现当前只有 2 个 Pod就会创建第 3 个发现某个 Pod 挂了就会重新创建一个。这个“发现-比较-修正”的过程会一直循环频率大约是每秒一次。这就是为什么 Kubernetes 能实现自愈——它不是在事故发生后被动响应而是每时每刻都在检查世界是不是符合你的期望。这段逻辑理解到位了后面看 kube-controller-manager 里那一堆控制器就不会乱。你要做的不是背控制器名字而是搞清楚“谁在比较什么状态谁负责修正”。2. 控制平面大脑是怎么思考和做决定的Kubernetes 的架构分两大部分控制平面和工作节点。控制平面是大脑负责做决策工作节点是手脚负责真正跑容器。我们先解剖大脑。2.1 API Server所有请求的唯一入口整个集群里只有 API Serverkube-apiserver会跟 etcd 直接通信。所有组件——kubectl、kubelet、调度器、控制器——想要读写集群状态都必须走 API Server。它干三件事认证你是谁、鉴权你能不能干这事、校验你提交的数据合不合理。三关过了请求才会被写入 etcd。很多人忽略 API Server 的“校验”功能其实它极其重要。你提交一个 Pod 定义API Server 会检查字段格式、检查资源名字是否合法、检查镜像名是否符合规范。这就好比机场安检API Server 是那道安检门不合格的请求根本进不了系统。这也是为什么很多安全攻击都把 API Server 当作首要目标——只要攻破它就等于拿到了整个集群的控制权。API Server 还有几个容易忽略的细节。第一它本身是无状态的可以水平扩展真正的状态都存在 etcd 里第二它支持 Watch 机制客户端可以长连接监听资源变化而不是反复轮询第三它内置了 Admission Control准入控制可以在请求被持久化之前做最后一道拦截。这几个特性组合起来才让 API Server 能支撑起整个集群的并发压力。2.2 etcd把状态变成键值对存储etcd 是 Kubernetes 的数据库所有集群状态都存在这里。它是一个分布式的键值存储基于 Raft 协议保证一致性。所谓“一致性”简单说就是多个节点上的数据永远相同不会出现一台机器说“有 3 个 Pod”、另一台说“有 2 个 Pod”的情况。在生产环境里etcd 是性能瓶颈的高发区。它要求磁盘延迟极低尤其是 fsync 操作网络抖动会导致 leader 选举频繁发生版本跨度太大的升级可能会触发数据迁移问题。我见过不少集群出故障根因都是 etcd 慢。比如某个 Pod 调度特别慢查了半天发现是 API Server 写 etcd 超时。所以真正搞 Kubernetes 的人一定会花时间研究 etcd 的监控指标尤其是etcd_server_leader_changes_seen_total和etcd_disk_wal_fsync_duration_seconds。这里有个容易误解的地方etcd 里存的不是容器本身而是描述容器的数据。容器的实际运行状态在 kubelet 手里etcd 只存“应该是什么样”。这就好比公司的人力系统存的是员工的职位和合同信息员工本人此刻在干什么人力系统最多只能记录个大概。2.3 Scheduler如何决定 Pod 落在哪台机器调度器kube-scheduler的职责是在新建 Pod 时挑一台最合适的节点。但它不是随机挑的背后是一套两阶段的算法过滤Predicates/Preselection和打分Priorities/Scoring。过滤阶段先把不合格的节点排除掉。比如 Pod 声明要 2 核 CPU、4G 内存那资源不够的节点直接被淘汰Pod 写了nodeSelector要求节点有diskssd标签不符合的直接淘汰Pod 声明要访问某块存储卷而这个卷只能挂载到特定区域其他节点也淘汰。过滤完之后剩下的节点才进入打分环节。打分环节考虑的因素更多节点上已有 Pod 的资源占用、端口冲突情况、Pod 分散性同一套服务的副本尽量打散到不同节点、亲和性和反亲和性规则等等。打分最高的节点就是最终的调度目标。调度器把结果写到 Pod 的spec.nodeName字段里这个动作本身就是一个 API 调用——它把 Pod 绑定到了某个节点。调度这件事看着简单实际坑很深。生产环境里最常见的调度问题是资源碎片化节点上剩的内存都是零零碎碎的几百兆但新 Pod 一口气要 8G调度器只能干瞪眼。这种问题没有银弹要么调整 requests 设置要么引入节点资源池把大内存的 Pod 固定调度到特定节点池。2.4 Controller Manager一屋子控制器的总管kube-controller-manager 不是一个控制器而是一堆控制器的集合。Deployment 控制器管副本数ReplicaSet 控制器管 Pod 数量Node 控制器管节点健康状态Service 控制器管负载均衡器的生命周期Namespace 控制器管命名空间清理。每个控制器都盯着自己的资源发现偏离期望状态就发起修正。以 Deployment 控制器为例它监听 Deployment 和 ReplicaSet 的变化。当你更新 Deployment 的镜像版本时Deployment 控制器会创建新的 ReplicaSet然后逐步缩容旧的 ReplicaSet、扩容新的 ReplicaSet这就是滚动更新的本质。整个过程不需要人工干预你只改了期望状态剩下的控制器自动搞定。控制器之间还会协作。比如 Node 控制器发现某台节点失联超过一定时间会先把节点标记为NotReady再把该节点上的 Pod 标记为Terminating最终由调度器在其他节点上重建这些 Pod。这个“发现-标记-驱逐-重建”的链条看着简单但每一步的时序和参数都会影响集群的故障恢复时间。3. 工作节点真正干活的人和他们的工具控制平面负责思考工作节点负责执行。每个工作节点上跑着三个关键进程kubelet、kube-proxy、容器运行时。很多人把这三个混为一谈其实它们各管一摊谁也替代不了谁。3.1 kubelet节点上的“监工”kubelet 是工作节点的核心它干两件大事第一通过 API Server 的 Watch 机制监听分配给本节点的 Pod 变化第二调用容器运行时把 Pod 里的容器真正拉起来。你可以把它看成节点上的“监工”——控制平面下达命令监工监督工人干活。kubelet 内部有一个非常重要的机制叫 SyncLoop同步循环。它会周期性地对比“期望状态”和“实际状态”如果发现容器没在运行就重新拉起如果发现镜像版本不对就重新拉镜像如果发现 Pod 被删了就清理容器。这个循环默认 10 秒一次但它也监听着 API Server 的事件做到“事件驱动 周期巡检”双保险。kubelet 还负责上报节点状态。它会周期性地把自己的资源容量、已分配资源、运行中的 Pod 数量汇报给 API Server。如果 kubelet 失联控制平面会把节点标记为 NotReady然后开始驱逐 Pod。这里有个生产环境中经常踩的坑如果 kubelet 因为磁盘满导致无法上报状态整个节点会被误判为故障Pod 会被重建到其他节点但实际上你只是日志没清理而已。3.2 容器运行时与 CRIkubelet 不直接调用 Docker而是通过 CRIContainer Runtime Interface容器运行时接口跟容器运行时通信。CRI 是一组 gRPC 接口定义“如何创建容器”“如何查询容器状态”“如何停止容器”。Docker 早期是 Kubernetes 默认运行时后来 containerd 因为更轻量、更稳定逐渐成为主流。生产环境中容器运行时的问题往往比 Kubernetes 本身的问题更难排查。镜像拉取慢、容器启动超时、孤儿容器堆积、镜像仓库认证失效这些都会表现为“Pod 一直 Pending 或 CrashLoopBackOff”。我个人的经验是遇到这类问题先看 kubelet 日志再看容器运行时的日志别一上来就重启节点。很多时候只是 containerd 的镜像存储目录满了清理一下就好了。3.3 kube-proxy把流量送到正确的 Podkube-proxy 负责实现 Service 的负载均衡功能。它监控 Service 和 Endpoint 的变化然后在本机设置 iptables 或 IPVS 规则让发往 Service VIP 的流量被转发到某个后端 Pod。很多初学者有个误解kube-proxy 是流量代理吗不是。默认条件下kube-proxy 不转发流量它只是“写规则的人”。真正的数据转发是内核里的 iptables 或 IPVS 完成的。这就好比 kube-proxy 是交通警察它在地上画好了线iptables/ipvs 规则车辆数据包按线走不需要警察亲自开车。不过 iptables 模式有一个众所周知的问题随着 Service 数量增长iptables 规则会变得极其庞大每条新连接都需要遍历大量规则转发性能会下降。生产环境通常建议切换到 IPVS 模式它使用哈希表存储规则性能更稳定。切换本身很简单在 kube-proxy 的启动参数里加--proxy-modeipvs就行但要注意内核模块支持。4. 一次 Pod 调度的完整旅程把组件分开讲完很多人还是会问这些东西到底是怎么串起来的下面我完整走一遍kubectl apply一条命令背后的旅程。这是理解 Kubernetes 原理最重要的一步。4.1 从 kubectl 到 etcd假设你执行kubectl apply -f deployment.yaml。kubectl 先读取本地文件把它转成一个 JSON 格式的 Deployment 对象然后通过 HTTPS 请求发送给 API Server。API Server 验证你的身份证书或 token检查权限校验字段合法性通过后这个 Deployment 对象就被写入 etcd。这里有个细节API Server 在写入之前会先处理 Admission Control准入控制。比如某个命名空间设置了镜像白名单准入控制器会拦下不符合要求的镜像请求。还有各种 Webhook比如校验规则、资源配额控制都是在这个阶段生效的。所以你的请求被拒不一定是权限问题可能只是某个准入规则没通过。4.2 控制器观测变化并创建 PodDeployment 对象写入 etcd 后API Server 会通过 Watch 机制通知 kube-controller-manager 里的 Deployment 控制器“新增了一个 Deployment”。Deployment 控制器拿到这个对象发现它期望 3 个副本但当前没有任何 ReplicaSet 对应它于是创建了一个 ReplicaSet 对象。ReplicaSet 对象又触发 ReplicaSet 控制器控制器发现当前 Pod 数量为 0于是创建 3 个 Pod 对象。这里的“创建 Pod 对象”仍然只是写数据不是真的跑容器。Pod 对象被写入 etcd 之后调度器通过 Watch 机制发现了它。调度器发现这个 Pod 的spec.nodeName还是空的就知道自己该出马了。它执行过滤和打分选定一个节点然后把 Pod 绑定到该节点——这个绑定动作依然是更新 etcd 里的 Pod 对象把nodeName字段填上。4.3 kubelet 拉起容器被选中的节点上的 kubelet通过 Watch 机制发现有一个 Pod 被分配给了自己。它开始干活先创建 Pod 的沙箱环境pause 容器负责持有网络命名空间然后拉取业务镜像启动业务容器。启动完成后kubelet 会周期性地上报 Pod 状态给 API Server直到 Pod 进入Running状态。整条链路有个很关键的特点每一环都是“事件驱动”的组件之间不直接调用而是都通过 API Server 和 etcd 通信。这种松耦合设计的好处是任何一个组件挂了其他组件都能继续独立工作。比如调度器挂了几分钟正在跑的 Pod 完全不受影响kubelet 挂了一会儿控制平面也只是标记节点异常不会把整个集群搞崩。5. 网络、存储与扩展生产环境绕不开的三道坎Kubernetes 本身只定义了接口真正的网络、存储、设备接入都靠插件实现。这也是 Kubernetes 生态最强大的地方——它不绑定任何厂商一切都可替换。5.1 CNIPod 之间怎么互通Kubernetes 要求所有 Pod 都在一个扁平网络里任意两个 Pod 可以直接通过 IP 通信不需要 NAT。这个“扁平网络”不是 Kubernetes 自己实现的而是通过 CNIContainer Network Interface插件完成的。常见的 CNI 插件有 Calico、Flannel、Cilium它们的实现方式各不相同但目标一致给每个 Pod 分配一个集群内唯一的 IP并保证跨节点通信畅通。选 CNI 插件是个典型的“看似简单实则复杂”的决定。Flannel 最轻量但功能也最简单只解决连通性Calico 基于 BGP性能好还支持网络策略Cilium 基于 eBPF性能和可观测性都更强但对内核版本有要求。我在生产环境里用过 Calico 和 Cilium如果团队对网络策略有强需求直接选 Calico如果追求极致的性能和新特性Cilium 值得考虑但要先确认所有节点的内核支持。5.2 CSI 与存储持久化数据怎么挂载Pod 是“一次性”的Pod 被删掉里面的容器数据就没了。但数据库、消息队列这类有状态服务需要持久化存储。Kubernetes 用 PVPersistentVolume和 PVCPersistentVolumeClaim来管理存储用户声明“我要 10G 存储”PVC 就是这个声明管理员预先准备好存储池PV 就是池子里的实际资源。CSIContainer Storage Interface则负责把 PV 真正挂载到 Pod 上。CSI 插件的选型直接跟你的基础设施绑定上云用云厂商的 CSI自建机房可以用 NFS、Ceph、Rook 等。生产环境的存储问题往往比网络问题更头痛尤其是性能调优。比如在云上买了一块云盘Pod 里跑 MySQL发现写入延迟很高——这时候要检查的不只是 CSI 插件还要看文件系统类型、挂载参数、IOPS 上限。存储这条路没有捷径只能一个个试、一个个调。5.3 Device PluginGPU 等特殊设备怎么接入Kubernetes 默认只能调度 CPU 和内存资源。如果你想让某个 Pod 用上 GPU、FPGA 或者其他特殊硬件就需要 Device Plugin 框架。设备插件向 kubelet 上报设备数量kubelet 再把这些设备作为可调度资源暴露给 API Server。比如你装好 NVIDIA 的 Device Plugin节点上就会多出一个nvidia.com/gpu资源调度器在调度时就知道这个节点有 4 块 GPUPod 申请多少就分配多少。Device Plugin 这个东西坑不少。最常见的问题是设备健康检查GPU 一旦发生 Xid 错误插件需要能够标记设备不健康并让 kubelet 把相关 Pod 重新调度到健康节点。很多自研插件在这块做得不够完善导致 GPU 卡坏了 Kubernetes 还不知道继续往上面调度任务。5.4 服务发现与负载均衡Service 与 IngressPod 的 IP 地址是动态的Pod 随时可能被重建所以直接拿 Pod IP 做服务发现根本不现实。Kubernetes 用 Service 搞定这件事。Service 有一个固定的虚拟 IPClusterIP它对一组 Pod 做负载均衡。Pod 可以随时变动但 Service 的 IP 和端口是稳定的调用方只需要记住 Service 的名字。Service 有几种类型ClusterIP 只在集群内部访问NodePort 把端口暴露到每个节点上LoadBalancer 对接云厂商的负载均衡器。集群边界上的流量入口通常是 Ingress它不是 Service 的一种而更像一个七层反向代理的总入口根据域名和路径把流量路由到不同的 Service 后面。Ingress 控制器比如 Nginx Ingress Controller是实际干活的进程Ingress 对象只是它读取的配置。6. 实战经验常见问题排查与避坑指南原理讲得再多最终都得落到“出了问题怎么查”。这一节的内容全是我在真实环境里踩过的坑跟你分享几条最有价值的排查路径。6.1 Pod 一直 Pending卡在哪一环Pod 处于 Pending 状态说明调度器还没完成调度或者调度完成后 kubelet 还没成功拉起容器。第一步用kubectl describe pod pod-name查看事件绝大多数问题都会直接显示出来节点资源不足、镜像拉取失败、端口冲突、PVC 未绑定。我最常遇到的情况是“0/1 nodes are available”要么是资源 requests 设置得太高节点上凑不出那么多资源要么是节点有 taint而 Pod 没有对应的 toleration。6.2 CrashLoopBackOff容器根本起不来Pod 正在重启说明容器起来之后又退出了。这通常是应用自身的问题启动报错、配置错误、依赖的数据库连不上。有效做法是看容器日志kubectl logs pod-name --previous。注意这个--previous参数它能让看到上一次容器退出前的日志经常能查到关键报错。如果日志为空多半是容器在启动早期就崩溃了此时得接上kubectl exec进去手动试命令。6.3 API Server 的安全性怎么守住Kubernetes 有一个默认的安全陷阱默认情况下API Server 如果允许匿名访问又没有正确的 RBAC 配置任何能接触到 API Server 端口的人都能管理集群。这就是“未授权访问”问题的根源。生产环境一定要做几件事关闭匿名认证--anonymous-authfalse、启用 RBAC、为 ServiceAccount 配置最小权限、开启审计日志。记住一条原则永远不要在生产环境用默认配置。6.4 这些监控指标你必须每天看我强烈建议给每个 Kubernetes 集群至少配一套监控。除了看 Pod 的 CPU 内存更要看这几个关键指标etcd 的 fsync 延迟、API Server 的请求延迟和错误码、kubelet 的同步周期耗时、节点的磁盘使用率。其中任何一个异常都可能引发连锁故障。比如节点磁盘使用率达到 90%kubelet 会开始驱逐 Pod达到 100%容器运行时直接报错整个节点上的 Pod 全部不可用。6.5 企业落地项目最容易被忽视的事情我见过很多企业项目从测试环境搬到生产环境时翻车根本原因不是 Kubernetes 本身而是周边配套没跟上。比如镜像仓库没有做高可用一次仓库故障导致整个发布流程停摆比如没有给命名空间配置 ResourceQuota某个团队把集群资源打爆影响所有人比如网络策略完全没配任何 Pod 都能访问数据库。要记住Kubernetes 提供的是“可能性”生产环境的稳定需要你自己补上所有细节。我个人在做集群稳定性优化时最深的体会是Kubernetes 的组件并不复杂复杂的是它们之间的依赖关系和故障传导路径。很多人遇到问题喜欢重启节点但实际上绝大多数故障都能从日志和事件里找到根因。把kubectl describe、kubectl logs、journalctl -u kubelet这三个命令用熟就能解决 80% 的日常问题。剩下的 20%通常要往 etcd 性能和网络插件上查——这也是最能体现一个工程师功力的地方。
返回列表