【Kubernetes从入门到精通】第07篇:K8s架构全景图——Master和Node的天作之合

【Kubernetes从入门到精通】第07篇:K8s架构全景图——Master和Node的天作之合 上一篇【第06篇】第一个K8s应用——3分钟部署你的Hello World下一篇【第08篇】Pod的前世今生——K8s最小调度单元深度解析摘要如果你在上一篇文章里成功把nginx跑在了K8s上那你心里一定有个疑问我就敲了个kubectl run nginx --imagenginx背后到底发生了什么这个问题问到点子上了。K8s表面上就一个命令行背后却是一套精密得像瑞士手表的分布式系统——Master节点坐镇中枢发号施令Node节点在一线冲锋陷阵中间还有一整套通信和存储机制在暗中运转。本文就从你敲下kubectl apply那一瞬间开始跟着这个请求走完K8s的全流程流水线把API Server、etcd、Scheduler、Controller Manager、kubelet、kube-proxy这六大主角一个个揪出来审一遍。看完这篇K8s对你就不再是黑盒了。一、先看全貌——K8s架构的两层金字塔K8s集群说白了就是一个中央指挥部 前线战斗部队的双层结构。指挥部叫控制平面Control Plane前线叫工作节点Worker Node。我个人特别喜欢把控制平面想象成物流调度中心把工作节点想象成仓库流水线——前者只管调度决策后者只管干活。【K8s 集群架构全貌】 ┌──────────────────────────────────────────────────┐ │ 控制平面 (Control Plane) │ │ │ │ ┌──────────────┐ ┌──────────────────────┐ │ │ │ API Server │◄──►│ etcd │ │ │ │ (唯一入口) │ │ (集群的记忆) │ │ │ └──────┬───────┘ └──────────────────────┘ │ │ │ │ │ ┌────┴────────────────────┐ │ │ ▼ ▼ │ │ ┌──────────────────┐ ┌────────────────────┐ │ │ │ Scheduler │ │ Controller Manager │ │ │ │ (包工头/媒婆) │ │ (监工/纠察队) │ │ │ └──────────────────┘ └────────────────────┘ │ └──────────────────────────────────────────────────┘ │ ┌────────────────────┼────────────────────┐ ▼ ▼ ▼ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ │ Worker Node 1 │ │ Worker Node 2 │ │ Worker Node N │ │ │ │ │ │ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ │ kubelet │ │ │ │ kubelet │ │ │ │ kubelet │ │ │ │ (工头/现场经理)│ │ │ │ (工头/现场经理)│ │ │ │ (工头/现场经理)│ │ │ └──────┬───────┘ │ │ └──────┬───────┘ │ │ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ ┌──────┴───────┐ │ │ ┌──────┴───────┐ │ │ ┌──────┴───────┐ │ │ │ 容器运行时 │ │ │ │ 容器运行时 │ │ │ │ 容器运行时 │ │ │ │ containerd │ │ │ │ containerd │ │ │ │ containerd │ │ │ └──────┬───────┘ │ │ └──────┬───────┘ │ │ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ ┌──────┴───────┐ │ │ ┌──────┴───────┐ │ │ ┌──────┴───────┐ │ │ │ Pod│Pod│Pod │ │ │ │ Pod│Pod│Pod │ │ │ │ Pod│Pod│Pod │ │ │ └──────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ │ kube-proxy │ │ │ │ kube-proxy │ │ │ │ kube-proxy │ │ │ │ (网络交通警察) │ │ │ │ (网络交通警察) │ │ │ │ (网络交通警察) │ │ │ └──────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │ └────────────────────┘ └────────────────────┘ └────────────────────┘要点控制平面是大脑负责想事情调度、决策、存储状态工作节点是手脚负责干事情启动容器、转发流量。大脑和手脚之间通过API Server通信没有API Server整个集群就是瞎子。二、控制平面四大金刚——谁是话事人控制平面有四兄弟每一位都是K8s集群不可或缺的角色。我见过太多新手把它们当成一个大杂烩黑盒出了问题根本不知道该找谁所以咱们一个一个掰开看。2.1 API Serverkube-apiserver——唯一的入口也是唯一的神API Server是K8s的唯一前门。你敲的每一条kubectl命令、每一个内部组件之间的通信统统都要经过它。它不存数据、不调度Pod、不监控节点但它连接了一切。【API Server——所有操作的十字路口】 kubectl │ ▼ ┌────────────────┐ │ │ etcd ◄──►│ API Server │◄──► Scheduler │ │ │ (验证/鉴权/ │◄──► Controller Manager │ 准入控制) │ │ │◄──► kubelet (各节点) └────────────────┘ ▲ │ 外部工具/Prometheus/Web UI要点API Server是整个集群的单点真理——只有它有权限直接写etcd其他组件想读状态或写状态都得通过它。你永远不会直接连etcd也不应该kubectl也不会直接连kubelet。API Server做了三件关键的事认证Authentication你是谁拿kubeconfig来验明正身授权Authorization你有权限干这事吗RBAC查一下准入控制Admission Control你这请求合规不Webhook检查一下这三关过了你的请求才真正开始生效。2.2 etcd——集群的记忆/脑如果把K8s集群比作一个人etcd就是他的大脑皮层——所有记忆都存这儿。集群里有什么Pod、什么Service、什么配置全在etcd里。etcd挂了K8s就成了失忆症患者。要点etcd不是K8s发明的它是CoreOS开源的分布式KV存储基于Raft一致性协议。高可用部署通常需要3个或5个etcd节点奇数个这是Raft的要求它能容忍(N-1)/2个节点挂掉。etcd里存的东西大概长这样逻辑视图# etcd中的数据结构概念示意实际是扁平的key-value/registry/namespaces/default# namespace定义/registry/pods/default/nginx-7b5f8c9d-abc12# Pod定义/registry/services/endpoints/default/nginx-service# Service的endpoints/registry/deployments/default/nginx-deployment# Deployment定义/registry/secrets/default/my-secret# Secret/registry/configmaps/default/my-config# ConfigMap一个小知识etcd的可靠性和性能直接决定了K8s集群的可靠性和性能。如果你装K8s时用了--data-dir把etcd数据目录放在了个慢磁盘上那你的集群响应就会像乌龟爬。2.3 Schedulerkube-scheduler——“包工头和媒婆”Scheduler的工作就一句话给新Pod找一个合适的Node嫁过去。它不创建Pod不启动容器只负责选地方。【Scheduler的工作流程】 新Pod出现Pending状态 │ ▼ ┌─────────────────────────────────────┐ │ Scheduler 开始相亲 │ │ │ │ Step 1: 过滤Filtering │ │ ├── 节点挂了 │ ❌ 淘汰 │ ├── 节点有足够CPU/内存吗 │ ❌ 淘汰 │ ├── Pod要求的端口被占了吗 │ ❌ 淘汰 │ ├── 节点有Pod要的GPU吗 │ ❌ 淘汰 │ └── nodeSelector/亲和性能满足吗│ ❌ 淘汰 │ │ │ Step 2: 打分Scoring │ │ ├── 优先选资源最空闲的 │ ⭐⭐⭐ │ ├── 优先选镜像已经缓存的 │ ⭐⭐ │ ├── 优先分散同一个应用的Pod │ ⭐ │ └── 各种自定义优先级规则 │ 综合评分 │ │ │ 最终选定得分最高的Node → 更新Pod的nodeName字段 │ └─────────────────────────────────────┘要点Scheduler只决定Pod去哪不负责把Pod搬过去。它干的事是——从所有候选Node里筛出一批合格的然后给它们打分选得分最高的那个。你可以把Scheduler理解为分配工位的那位具体干活的是kubelet。2.4 Controller Managerkube-controller-manager——“监工大队长”Controller Manager不是什么一个Controller管所有而是一群Controller合成的一个二进制文件。你可以把它想象成一个监工大队Controller职责通俗比喻Node Controller监控节点心跳节点挂了就标记NotReady巡楼保安Replication Controller保证Pod副本数符合期望不断数羊的牧羊犬Endpoint Controller把Service和Pod的IP/端口对应起来接线员Service Account Controller给每个Namespace创建默认ServiceAccount自动发工牌Deployment Controller管理滚动更新和回滚总工程师Job Controller管理一次性任务跑完就收工临时工管理【Controller Manager 内部结构】 ┌──────────────────────────────────────────────────────┐ │ kube-controller-manager (一个进程) │ │ │ │ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │ │ │Node │ │Replication │ │Deployment │ │ │ │Controller │ │Controller │ │Controller │ │ │ └────────────┘ └────────────┘ └────────────────┘ │ │ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │ │ │Endpoint │ │Service │ │Job │ │ │ │Controller │ │Account Ctl │ │Controller │ │ │ └────────────┘ └────────────┘ └────────────────┘ │ │ ┌────────────┐ ┌────────────┐ ┌────────────────┐ │ │ │Namespace │ │StatefulSet │ │DaemonSet │ │ │ │Controller │ │Controller │ │Controller │ │ │ └────────────┘ └────────────┘ └────────────────┘ │ │ ...还有更多 │ └──────────────────────────────────────────────────────┘要点所有Controller共用一个控制循环模式——观察Watch API Server→ 比较当前状态 vs 期望状态→ 行动调一调让实际向期望靠拢。这既是K8s最天才的设计也是初学者最容易懵的地方。如果用大白话解释Controller Manager就干一件事——**不停地问API Server现在的样子跟我要求的还差多少然后动手消除差距。**你把Deployment的replicas从3改成5Controller Manager看到了差异立刻告诉API Server“给我多建俩Pod”——Scheduler接手选址kubelet接手启容器一气呵成。三、工作节点三剑客——前线干活的底层打工仔3.1 kubelet——工头/现场经理Node的一把手kubelet是每个工作节点上的现场一把手。它负责看门到API Server注册自己这个Node定期报告我还活着接单从API Server获取分配到这个节点的Pod清单干活调用容器运行时containerd等把容器启起来盯梢持续监控Pod状态挂了就重启【kubelet的工作日常】 API Server │ │ ① Watch /api/v1/nodes/node-name/pods ▼ ┌─────────────────────────────────────────┐ │ kubelet │ │ │ │ ② 收到Pod列表 → 对比本地已运行的Pod │ │ ├── 新增的Pod → 创建 │ │ ├── 缺失的Pod → 重新创建 │ │ └── 多余的Pod不该在这 → 干掉 │ │ │ │ ③ 调用 CRIContainer Runtime Interface)│ │ └── containerd/cri-o 实际启动容器 │ │ │ │ ④ 持续上报状态心跳10秒一次 │ └─────────────────────────────────────────┘要点kubelet是唯一不通过API Server就能启动容器的组件——它直接跟容器运行时对话。但容器该不该启动、启动什么仍然由API Server决定。kubelet和容器运行时之间通过CRIContainer Runtime Interface交互这就是为什么K8s能同时支持containerd、CRI-O等多种运行时——只要实现了CRI接口就能用。3.2 kube-proxy——网络交通警察Service的Cluster IP是个虚拟IP数据包到了Node上怎么转发到实际的Pod这就是kube-proxy的活。kube-proxy在每个Node上跑维护一套网络规则iptables规则或者IPVS规则确保访问Service IP的流量能被正确转发到Pod IP。【kube-proxy的两种模式】 iptables 模式默认 IPVS 模式推荐 ┌──────────────────────┐ ┌──────────────────────┐ │ Service ClusterIP │ │ Service ClusterIP │ │ 10.96.0.1:80 │ │ 10.96.0.1:80 │ └──────────┬───────────┘ └──────────┬───────────┘ │ │ ▼ ▼ ┌──────────────────────┐ ┌──────────────────────┐ │ iptables 规则链 │ │ IPVS 虚拟服务器 │ │ 随机选一个 Pod IP │ │ 支持轮询/最小连接等 │ └──────────┬───────────┘ └──────────┬───────────┘ │ │ ┌──────┴──────┐ ┌────────┴────────┐ ▼ ▼ ▼ ▼ Pod IP:10.244.1.5 Pod IP:10.244.2.3 Pod IP:10.244.1.5 Pod IP:10.244.2.3要点iptables模式在大规模集群下几千个Service规则膨胀得吓人规则更新时性能也差。IPVS模式解决了这个问题——它用Linux内核的IPVS模块做负载均衡性能更好算法更灵活。如果你的集群规模大一定要切到IPVS模式。3.3 容器运行时Container Runtime——真正干活的那个容器运行时就是真正启动和管理容器的那个东西。早期K8s只支持Docker后来觉得被Docker绑定不是好事就抽象出了CRIContainer Runtime Interface接口# 早期已废弃K8s 1.24后彻底移除# 直接集成 dockershim → docker → containerd → runc# 现在# CRI → containerd/cri-o → runc# 或 CRI → containerd → runc推荐最常用的容器运行时现在是containerd它已经是CNCF的毕业项目稳定得很。K8s 1.24之后彻底移除了对Docker的原生支持dockershim但别慌——Docker构建的镜像containerd一样能跑因为它们共用OCI标准。四、一个kubectl命令的完整旅程——“一个请求的一生”好了理论知识够了。现在咱们跟着一个真实的请求走一遍全流程把前面学的串起来。假设你执行kubectl create deployment nginx--imagenginx:1.25--replicas3这条命令大概需要12秒执行完但这12秒里K8s内部发生的事比你想象的精彩得多【一个 kubectl 请求的完整旅程】 ┌─────────┐ │ kubectl │ Step 1: kubectl 读取 kubeconfig找到 API Server 地址 └────┬────┘ 用你的证书做认证 │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 2: API Server 收到请求 │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ ① 认证: 你是技术博客作者证书拿来瞅瞅 │ │ │ │ ② 授权: 你有权限在 default 空间创建 Deployment 吗 │ │ │ │ → RBAC 检查 → ✅ 有权限 │ │ │ │ ③ 准入控制: 这个 Deployment 配置有没有违规 │ │ │ │ → 各种 Webhook 检查 → ✅ 通过 │ │ │ │ ④ 写入 etcd: Deployment 对象持久化 │ │ │ │ ⑤ 返回 kubectl: 搞定了 │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 3: Deployment Controller (Controller Manager 内) │ │ │ │ 哎etcd里多了个新Deploymentreplicas3 │ │ 我看看... 现在集群里有几个 nginx Pod │ │ 0个那不行我得建 3 个 │ │ │ │ → 创建 3 个 ReplicaSet │ │ → ReplicaSet Controller: Pod 数不够 │ │ → 创建 3 个 Pod 对象写入 etcd │ │ → Pod 诞生时有 nodeName 没人给它找房子 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 4: Scheduler │ │ │ │ etcd里多了3个待调度的PodnodeName为空 │ │ → 过滤哪些节点能跑 │ │ → 打分哪个节点最合适 │ │ → 绑定把 Pod 的 nodeName 改成选中的节点名 │ │ → 写入 etcd │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 5: kubelet (在选中的 Node 上) │ │ │ │ 咦分配给我这个节点的 Pod 列表更新了 │ │ → 调用 CRI 让 containerd 拉镜像 nginx:1.25 │ │ → 创建 Pause 容器先搭网络框架 │ │ → 创建 nginx 容器 │ │ → 启动容器上报状态: Pod 跑起来了 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Step 6: Endpoint Controller kube-proxy │ │ │ │ Endpoint Controller: Pod有IP了更新Service的Endpoints │ │ kube-proxy: Endpoints变了更新 iptables/IPVS 规则 │ │ │ │ 现在外部可以访问 nginx 了 │ └─────────────────────────────────────────────────────────────┘要点注意整个过程中没有任何一个组件是发号施令的——API Server只是记录Controller只是观察差异并修正Scheduler只是填字段kubelet只是看自己的任务清单。这就是声明式系统的精髓没人指挥但一切井然有序。每个组件各司其职通过etcd这个共享白板协作。五、核心组件对比——一张表了结聊了这么多组件你可能已经有点晕了。下面这张表帮你快速回忆组件位置核心职责坏了会怎样通俗比喻API Server控制平面集群唯一入口验证请求写入etcd整个集群瘫痪所有操作不可用海关 传达室etcd控制平面存储集群所有状态数据现有Pod还能跑但没法创建/修改任何东西大脑/记忆Scheduler控制平面给新Pod选一个合适的Node新Pod永远Pending现有Pod不受影响媒婆/包工头Controller Manager控制平面维持集群状态与期望一致Deployment扩缩容失效Node状态不更新监工大队kubelet每个Node接收Pod定义启动/监控容器该Node上的Pod全部失联工头/现场经理kube-proxy每个Node维护网络规则把流量转到PodService不可用Pod间无法通信交警容器运行时每个Node真正启动/停止容器Pod无法运行当然流水线工人六、组件间通信——它们是怎么串供的看着上面那么多组件互联你可能好奇它们到底怎么通信的。答案是——几乎全走API Server【K8s 组件通信模式】 ┌───────────────────────────────────────────────────────┐ │ 通信模式一览 │ │ │ │ 模式1: 所有组件 → API Server ← 所有组件 │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Controller Manager ──Watch──► API Server │ │ │ │ Scheduler ──Watch──► API Server │ │ │ │ kubelet ──Watch──► API Server │ │ │ │ kube-proxy ──Watch──► API Server │ │ │ │ │ │ │ │ 只有API Server能直接写etcd │ │ │ │ API Server ──读/写──► etcd │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 模式2: kubelet → 容器运行时CRI, 本地gRPC │ │ ┌─────────────────────────────────────────────────┐ │ │ │ kubelet ──gRPC──► containerd (本地socket调用) │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ 模式3: Pod 之间集群网络 CNI │ │ ┌─────────────────────────────────────────────────┐ │ │ │ Pod A (10.244.1.5) ──CNI──► Pod B (10.244.2.3) │ │ │ └─────────────────────────────────────────────────┘ │ └───────────────────────────────────────────────────────┘要点这个设计有一个巨大的好处——解耦。API Server是唯一的中间商任何组件升级、重启、替换都不影响其他组件。Scheduler和Controller Manager甚至可以完全重写而不影响现有Pod的运行。你说这设计妙不妙本篇小结K8s的架构看似复杂但拆开来看其实就是一个分层协作系统API Server是唯一对外接口——所有操作、所有组件都通过它通信它是集群的单点真理etcd是唯一的数据库——状态存在这儿组件丢了可以重新从这儿读自愈的底气来源于此Scheduler只管选址——它不创建Pod不启动容器只负责这Pod去哪最合适Controller Manager是纠察队——不停对比你想要的和现在有的发现差异就动手修正kubelet是现场执行者——它在每个Node上盯着自己的任务清单该启就启该停就停下一篇咱们深入K8s最小的调度单元——Pod看看为什么K8s不直接管容器而是搞了个Pod的概念出来。这背后有个叫Pause容器的隐藏主角绝对让你大开眼界。上一篇【第06篇】第一个K8s应用——3分钟部署你的Hello World下一篇【第08篇】Pod的前世今生——K8s最小调度单元深度解析