Kubernetes架构深度拆解:从控制面到Pod调度的核心原理与实战
1. 从“一台机器跑所有服务”说起为什么是Kubernetes我第一次听到“Kubernetes架构”这个词的时候脑子里冒出来的是“一艘货轮在海上指挥一堆集装箱”——后来发现这个比喻虽然粗糙但方向没错。Kubernetes解决的核心问题就是当你的服务从一个变成一百个、从一台机器变成几十台机器的时候谁来替你做调度、做容灾、做网络打通、做配置下发。你说我用脚本自己写可以但你很快就会发现你写的不再是业务代码而是一套运维系统而且这套系统还没法处理节点宕机、进程漂移、配置变更这些突发状况。Kubernetes把这一切固化成了平台能力。它把物理机抽象成“节点”Node把业务的运行单元抽象成“Pod”把调度决策交给控制面Control Plane把分布式系统的复杂度封装在组件之间的协作关系里。你可以把整个集群想象成一支乐队节点是乐手Pod是乐手演奏的每一个音符控制面是指挥etcd是乐谱——所有动作都必须对得上拍子任何声部出了错指挥必须在毫秒级内做出调整。这篇文章适合三类人第一类是刚接触Kubernetes、被一大堆名词kube-apiserver、etcd、kubelet、kube-proxy、调度器砸晕的新手第二类是已经能跑通kubeadm部署、但遇到问题只能搜报错、搞不清组件之间关系的进阶者第三类是准备做分布式系统设计、想从Kubernetes里借鉴架构思想的开发者和架构师。我会从全局架构拆解开始逐步深入到Pod、调度、网络的核心逻辑再给一套可复现的三节点集群实操方案最后把我踩过的坑和排查思路整理成速查表。全程说人话不堆概念尽量让你读完就能在脑子里画出一张完整的架构图。2. 全局架构拆解控制面和工作节点的分工2.1 控制面组件各自在干什么Kubernetes集群从物理视角看就两类角色控制面节点Control Plane和工作节点Worker Node。控制面跑四个核心组件kube-apiserver、etcd、kube-scheduler、kube-controller-manager。kube-apiserver是唯一对外的入口。所有请求——不管是kubectl命令、Pod创建请求、还是kubelet上报状态——都走它。它的职责不是执行而是“鉴权和转发”像一个严格的前台验证你的身份检查你有没有权限然后把请求写到etcd里或者从etcd里读数据返回给你。任何组件之间都不直接通信全部通过apiserver中转这个设计保证了整个集群的状态流转只有一条可信路径。etcd是分布式键值存储是整个集群的“乐谱”。它保存了所有期望状态Desired State哪个Pod应该跑在哪个节点上、哪个Service应该关联哪些Pod、哪个ConfigMap存了什么配置。etcd挂了整个集群就瞎了因为所有组件都靠它来同步信息。正因为它是单点中的单点生产环境必须做etcd的集群化部署和高可用隔离绝不能让etcd和apiserver在同一台机器上裸奔。kube-scheduler负责调度决策。它持续监听etcd里“待调度的Pod”Pending状态的Pod然后综合节点的资源余量、污点容忍、亲和性规则、数据本地性等因素给Pod挑一个最合适的工作节点。注意scheduler只做决策不执行——真实的启动动作由目标节点上的kubelet完成。kube-controller-manager是一组控制器的集合我习惯叫它“纠偏员”。它内部跑着Node Controller、Replication Controller、Endpoint Controller等一堆循环每个控制器都在做同一件事对比期望状态和当前状态不一致就触发动作把当前状态拉回期望状态。比如节点挂了Node Controller发现心跳超时就会把该节点上的Pod标记为异常并触发重新调度。这个“声明式状态循环”是整个Kubernetes最核心的运作原理。2.2 工作节点上的三个常驻角色工作节点上的核心组件是kubelet、kube-proxy和容器运行时比如containerd或CRI-O。kubelet是节点上的“监工”它会定期向apiserver汇报节点的资源情况和Pod状态同时接收apiserver下发的Pod定义确保本机容器真正跑起来。可以这么理解apiserver说“这个Pod该在这个节点上启动”真正把容器拉起来、把健康检查跑起来的是kubelet。kube-proxy管的是集群内网络规则。它维护节点上的iptables或IPVS规则让访问Service的流量能转发到对应的Pod IP上。没有kube-proxyService这个抽象层就不成立负载均衡和DNS服务都会失效。容器运行时没什么好说的就是一个标准化的容器运行环境。Kubernetes通过CRIContainer Runtime Interface统一操作它不管底下是containerd还是其他兼容运行时Kubernetes都一视同仁。2.3 组件之间如何协作一次Pod创建的全过程我建议每个初学者都完整推演一遍Pod创建流程这是理解架构的钥匙。你在终端敲下kubectl create deployment nginx --imagenginx:1.25完整链路是这样的kubectl把请求发给kube-apiserverapiserver校验身份、权限后把Deployment对象写入etcd。Deployment本身不做任何事是deployment controllercontroller-manager里的一环发现“期望的副本数是1当前是0”于是创建了一个ReplicaSet对象。ReplicaSet controller发现“期望1个Pod当前0个”于是创建了一个Pod对象写入etcd。scheduler监听到这个新Pod处于Pending状态综合各项条件选好目标节点把Pod的nodeName字段填上回写etcd。kubelet在apiserver上发现“有一个Pod被分配给了我”于是调用容器运行时拉镜像、启动容器然后持续上报状态。如果Pod配置了Serviceendpoint controller会更新Service背后的Endpoints列表kube-proxy在所有节点上刷新转发规则。这串流程里最值得学习的是“分工状态回写”的协作模式每个组件都只做自己那一环做完就把结果写回etcd下一环的组件通过监听变化继续接力。这就是分布式系统里经典的“事件驱动状态机”思路也是Kubernetes集群看似复杂却能保持稳定的根本原因。3. 为什么必须加一层Pod从编排到最小调度单元3.1 Pod的本质是“共享资源的一组容器”直接调度容器不行吗为什么非得多包一层Pod这是新手最容易困惑的地方。我的理解是Pod是Kubernetes对“进程组”概念的一种建模。某些场景下两个容器必须跑在同一台机器上共享同一个网络命名空间和存储卷——比如一个Sidecar容器负责日志采集主容器负责业务它们必须通过localhost通信、必须读同一份文件。如果只调度单个容器你就没法表达这种“绑在一起、生死与共”的关系。Pod内部的容器共享IP、共享hostname、共享端口空间彼此之间用localhost就能访问。Pod与Pod之间则通过独立的Pod IP通信即使它们调度到同一台机器上网络也是隔离的。这个设计让Pod成为集群里最小、最原子的调度单位要么整个Pod调度成功要么整个Pod失败重来不存在“只调度其中几个容器”的中间状态。还有一点值得注意Pod里的容器可以被kill、重启但Pod作为一个逻辑单元不会迁移。如果节点宕了这个Pod就没了由控制器比如Deployment创建出一个全新的Pod来替补。这就是分布式系统里“不迁移进程只消灭和重建”的思想面对不可靠的物理环境它比进程迁移更简单可靠。3.2 调度器是怎么给Pod找“座位”的调度器的决策过程分两步过滤Filtering和打分Ranking。过滤阶段会把不满足硬性条件的节点排除掉比如资源不足、端口冲突、节点taint不允许调度、Pod的nodeSelector要求不符合。打分阶段再对剩余节点按软性规则计分比如资源配比更均衡的得分更高、有数据本地性优势的得分更高、符合Pod亲和性规则的得分更高。这一步容易踩的坑是资源计算模型。调度器做资源判断时用的不是你节点上的总容量而是“分配量预留量”后的可分配量。如果节点上跑了很多系统进程或者存在静态Pod占用了内存那么调度器认为的可用资源和实际可用资源可能不一致。这时候就得看kubelet上报的allocatable而不是盲目看机器的free命令输出。另外调度器还会考虑服务质量QoS等级。Limit设得越大Pod的优先级可能越低而你要保证核心业务不被抢占就得合理配置request和limit避免所有Pod都在“Burstable”级别里互相挤压。3.3 网络抽象Service、kube-proxy和Pod IP的生命周期Pod IP是动态的Pod一重建IP就变了。如果让业务直接访问Pod IP分布式系统根本没法定址。Service层就是为解决这个问题存在的Service像一个稳定的虚拟IP入口它通过selector关联一组Pod把流量转发到Pod上。kube-proxy负责在每个节点上维护Service到Pod的转发规则最常见的方式是iptables或IPVS。这个设计的核心难点在“转发规则如何感知Pod变化”。Pod重建、扩缩容、节点迁移都会导致Endpoints变化。Kubernetes的应对方式是让endpoint controller持续监听Pod和Service的关联变化更新Endpoints对象然后kube-proxy再把规则刷到内核里。整个过程是异步的所以在滚动发布或扩缩容瞬间流量可能会有短暂的不一致——这就是为什么需要Readiness Probe只有就绪的Pod才会被加到Endpoints里。生产环境里还经常会遇到一个问题Service流量到Pod后Pod需要知道客户端的真实IP。默认SNAT模式下Pod看到的源IP是节点IP不是真实客户端。如果业务需要真实IP做审计或个性化服务就得配置externalTrafficPolicy: Local代价是可能产生跨节点负载不均。这类取舍在分布式系统里很常见你要么接受复杂度要么接受偏差。4. 实操从零搭建一个三节点Kubernetes集群4.1 环境准备和版本选型我以Kubernetes v1.26.0为例因为这是目前资料最全、坑相对较少的版本。准备三台Linux服务器虚拟机也行内存建议至少2GB生产至少8GB以上一台作为控制面两台作为工作节点。系统用Ubuntu 22.04或CentOS 7.9都行关键是各节点系统版本保持一致否则排查问题的时候会多出很多干扰因素。部署前必须做三件事关闭swap、加载br_netfilter模块、配置IP转发。这些是Kubernetes官方preflight检查必查项少一个都过不了。# 所有节点执行 swapoff -a sed -i /swap/d /etc/fstab cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sudo sysctl --system接下来安装容器运行时。我建议用containerd而不是docker。理由很简单Docker已经不再是Kubernetes的直接运行时中间隔了一层Dockershim多一层就多一个故障点。装containerd的时候记得改一下默认配置把SystemdCgroup设为true否则kubelet做资源统计时会出警告。# 安装containerd以Ubuntu为例 sudo apt-get update sudo apt-get install -y containerd sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml sudo sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml sudo systemctl restart containerd4.2 初始化控制面节点安装kubeadm、kubelet、kubectl。这里有个关键点三者版本必须匹配装完千万不要随便升级否则版本错位会让你怀疑人生。sudo apt-get update sudo apt-get install -y kubelet1.26.0-00 kubeadm1.26.0-00 kubectl1.26.0-00 sudo apt-mark hold kubelet kubeadm kubectl然后初始化控制面。先说重要参数--apiserver-advertise-address要填控制面节点的内网IP--pod-network-cidr要根据你选的CNI插件来定。我用Calico时习惯填10.244.0.0/16用Flannel时也常配合这个网段如果你多个集群不同集群的Pod网段绝对不能重叠否则路由表一乱流量就不知道往哪走了。kubeadm init \ --apiserver-advertise-address192.168.31.10 \ --pod-network-cidr10.244.0.0/16 # 看到类似 [init] using Kubernetes version: v1.26.0 和 [preflight] running pre-flight checks 输出说明检查阶段已通过初始化成功后会输出一段kubeadm join命令务必保存。控制面节点的kubectl配置也要处理mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config这时候执行kubectl get nodes只会看到一台NotReady的控制面节点——别慌这是正常的因为还没有装CNI网络插件。CNI不装Pod之间无法通信kubelet自然报NotReady。安装Calico网络插件我习惯直接apply官方manifestkubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml等一小会再执行kubectl get nodes控制面节点应该变成Ready。如果是Flannel的方案命令不同但原理一样核心都是给集群打通Pod网络。4.3 加入工作节点工作节点只需要安装kubelet、kubeadm和containerd然后执行之前保存的join命令kubeadm join 192.168.31.10:6443 --token 你的token \ --discovery-token-ca-cert-hash sha256:你的hash加入成功后在控制面节点执行kubectl get nodes能看到三个节点都处于Ready状态角色列分别是control-plane和none如果版本够新会显示worker。我在这一步遇到过最典型的坑是节点长时间NotReady查看kubelet日志发现连不上apiserver。排查思路很简单——先在节点上telnet控制面IP的6443端口如果能通再看防火墙和云安全组如果端口不通大概率是安全组没放行。别一上来就去查kubelet配置从网络链路一层层往上排查反而最快。4.4 跑通第一个应用并观察分布式调度集群就绪后部署一个测试应用验证整个调度链路是否正常。我建议直接拉一个带资源request的Deployment故意制造资源分布然后用kubectl get pod -o wide看Pod落在哪台节点上kubectl create deployment nginx --imagenginx:1.25 --replicas3 kubectl scale deployment nginx --replicas6 kubectl get pods -o wide你会看到Pod分散在三个节点上。这就是调度器根据资源余量做的均衡分布。为了验证selector、Service和kube-proxy的联动再加一个Servicekubectl expose deployment nginx --port80 --target-port80 --typeNodePort kubectl get svc curl http://任意节点IP:NodePort能正常返回Nginx欢迎页说明Service到Pod的转发链路是通的。如果curl不通先检查Pod是否Running、Readiness是否通过再检查kube-proxy日志、节点防火墙大概率能定位到问题。5. 分布式交响中最容易跑调的几个地方5.1 控制面单点etcd和apiserver的可用性我把控制面高可用放在第一个讲因为它最容易被忽略。很多入门教程只教你搭单控制面集群但生产环境里控制面一挂整个集群就失去调度能力了——已经运行的Pod还能继续跑但新Pod起不来、故障Pod没人替换、配置变更全部失效。一个可靠的控制面至少需要三台控制面节点组成高可用集群etcd也至少三节点并且控制面组件和etcd最好单独部署在专用节点上避免资源争抢。如果你只是试验环境单控制面可以接受但一定要清楚这属于“能跑但不可靠”的状态。挑环境的时候别省这一步踩一次掉线事故你就明白了。5.2 节点NotReady的三大常见原因和排查路径节点NotReady是我被问得最多的问题。归纳下来主要有三类第一是kubelet没起来或版本不对日志里会报版本不匹配、证书过期之类的错误第二是CNI插件出问题比如Calico的Pod没有Running导致节点网络异常第三是资源耗尽比如磁盘满了或者PID耗尽kubelet的驱逐逻辑会把节点标记为压力状态严重时直接进入NotReady。排查顺序我建议这样先kubectl describe node 节点名看Condition部分的输出再把kubelet的日志调出来journalctl -u kubelet -f --no-pager如果是资源耗尽日志里会出现类似eviction manager: attempting to reclaim ephemeral-storage的信息处理办法是清理docker/containerd日志和镜像缓存。如果是CNI问题则要看对应Pod的日志kubectl get pods -n kube-system -o wide | grep calico kubectl logs -n kube-system calico-pod-name5.3 Pod一直Pending调度失败的常见原因Pod不Running而一直是Pending基本就是调度器没找到合适节点。使用kubectl describe pod能直接看到调度失败的原因最常见的几类是节点资源不足request申请的内存比所有节点的可分配量都大。有污点没有容忍节点被打了TaintPod没有对应的Toleration。selector和亲和性条件不满足nodeSelector选的标签在现网节点上不存在。存储卷无法挂载比如Standalone的local PV无法跨节点调度。如果是资源不足加节点或者调低request如果是污点问题要么去掉Taint要么给Pod加Toleration。这里提一句给节点加Taint是一种很有效的隔离手段比如把专门跑数据库的节点打上污点只允许特定Pod调度上去这样核心业务和一般业务在物理上就隔离开了。5.4 常见问题速查表现象常见原因快速排查命令解决思路Node NotReadyCNI异常 / kubelet异常 / 资源耗尽journalctl -u kubelet按日志分层排查优先看网络与磁盘Pod Pending资源不足 / 污点 / 亲和性不满足kubectl describe pod看调度事件按事件内容定位ImagePullBackOff镜像名错误 / 仓库认证失败 / 网络不通kubectl describe pod检查镜像地址与私有仓库凭据CrashLoopBackOff启动命令报错 / 配置缺失kubectl logs看业务日志重点查环境变量和存储挂载Service不通kube-proxy规则未更新 / Pod未Readykubectl get endpoints确认Endpoints里有IP再看kube-proxy状态证书过期集群运行超过一年未更新kubeadm certs check-expiration用kubeadm手动续期或升级集群我特别想说一下KerneIssue和iptables规则冲突这个问题。如果节点上手动改过iptables或者有其他软件比如Docker也写iptables规则kube-proxy的规则可能被覆盖Service忽然就不通了。排查的时候记得看一眼iptables -t nat -L -n里有没有KUBE-SVC链没有的话大概率就是规则被冲掉了重启kube-proxy或重新生成规则能救回来。6. 从Kubernetes架构里能带走什么Kubernetes对我最大的启发不是“技术多牛”而是它把分布式系统的复杂度做了极其优雅的拆解所有组件通过一个共享状态层etcd协作靠的是“声明期望状态”和“持续纠偏”而不是直接下发指令调度器只做决策不执行每个节点的kubelet只对自己负责的Pod负责。这套模式放在微服务架构里同样适用——你用注册中心解决服务发现用配置中心管理环境差异用消息队列做异步解耦本质上都是一次“状态收敛”的游戏。另外我想强调的是Kubernetes的架构思维对做任何复杂系统都有借鉴意义组件职责要单一通信路径要收敛状态存储要可靠扩缩容要可预期。在实际使用Kubernetes时我建议所有学习路线都遵循“手动搭一遍集群 观察一次完整调度流程 人为制造几种故障”这个组合比刷一百遍文档都管用。手动搭过一遍你对“节点—组件—Pod—Service”这条链路的感知就不一样了以后遇到任何分布式系统的架构问题脑海里都有一张清晰的协作图可以对照。