k8s架构详解:Master 与 Node 各组件的作用和交互
k8s架构分两部分:控制平面负责保存集群状态和做决策,工作节点负责把容器真正跑起来。所有组件都只和 kube-apiserver 通信,由它读写 etcd;看懂这条通信路径,就能理解创建一个 Pod 时各组件的先后顺序,也知道机房防火墙该放行哪些端口。
k8s架构可以用一句话概括:一组控制平面节点负责记录状态和做决策,一组工作节点负责执行。控制平面(早期叫 Master)有四个核心组件:kube-apiserver、etcd、kube-scheduler、kube-controller-manager;工作节点(Node)上有三个:kubelet、kube-proxy 和容器运行时。这些组件彼此不直接对话,全部通过 kube-apiserver 读写集群状态,再各自监听(watch)自己关心的变化去动手。
k8s架构图:控制平面与工作节点
kubectl、CI 系统、监控 ──HTTPS 6443──► kube-apiserver
控制平面(Control Plane,旧称 Master)
├── kube-apiserver 唯一入口:认证 → 鉴权 → 准入 → 写入 etcd,并向其他组件推送变化
├── etcd 分布式键值数据库,保存全部集群状态(2379 客户端,2380 成员间)
├── kube-scheduler 监听还没分配节点的 Pod,为它选一个节点
└── kube-controller-manager 运行几十个控制器,让实际状态不断向期望状态靠拢
▲ 6443:节点上的组件主动连接 apiserver,监听分给自己的任务
│ 10250:apiserver 反向访问 kubelet,用于 logs、exec、port-forward
工作节点(Node)× N
├── kubelet 节点代理:接收分到本节点的 Pod,调用容器运行时创建容器,上报状态
├── 容器运行时 containerd 或 CRI-O,通过 CRI 接口被 kubelet 调用
│ └── CNI 网络插件 创建 Pod 网络、分配 Pod IP(Calico、Flannel、Cilium 等)
├── kube-proxy 把 Service 翻译成本机的 iptables、IPVS 或 nftables 规则
└── Pod pause 容器持有网络命名空间,同一 Pod 里的业务容器共享它
读这张图抓住三点:
- apiserver 是唯一的枢纽。scheduler、controller-manager、kubelet、kube-proxy 都是 apiserver 的客户端,只有 apiserver 直接读写 etcd。任何组件想知道“发生了什么”,都是向 apiserver 发起 watch。
- 通信方向有两条。节点组件主动连 apiserver 的 6443;apiserver 也会反向连 kubelet 的 10250,
kubectl logs、kubectl exec就走这条路。防火墙只放了 6443 时,Pod 能正常运行,但看日志和进容器会失败。 - 插件不在核心组件里,但几乎必装。CoreDNS 提供集群内的服务名解析,网络插件负责 Pod 之间互通,Ingress 控制器负责对外的 HTTP 入口,metrics-server 提供
kubectl top和自动扩缩用的指标。
“Master”和“控制平面”指的是同一组组件。新版本里已统一改称 control-plane,节点角色标签和污点都写作 node-role.kubernetes.io/control-plane,老教程里的 master 标签不再使用。
控制平面四个组件分别做什么
| 组件 | 职责 | 是否保存状态 | 多实例时怎么工作 |
|---|---|---|---|
| kube-apiserver | 集群唯一的 API 入口。每个请求依次经过认证(证书、Token)、鉴权(通常是 RBAC)、准入控制(先改写、再校验),通过后写入 etcd | 不保存,状态都在 etcd | 多个实例同时工作,前面放负载均衡或虚拟 IP |
| etcd | 分布式键值数据库,保存 Deployment、Pod、Service、Secret 等全部对象 | 保存,是集群里唯一必须备份的数据 | Raft 多数派,写入要过半数成员确认 |
| kube-scheduler | 监听 spec.nodeName 为空的 Pod,先过滤掉资源不够、有污点、不满足亲和性的节点,再给剩下的节点打分,把得分最高的节点写回 Pod |
不保存 | 主备模式,靠 Lease 对象选主,同一时刻只有一个在工作 |
| kube-controller-manager | 一个进程里运行多个控制器:Deployment、ReplicaSet、节点生命周期、Job、EndpointSlice、ServiceAccount 等,每个控制器循环比较期望状态与实际状态,发现差异就通过 apiserver 修正 | 不保存 | 主备模式,同样靠 Lease 选主 |
几个容易误解的地方:
- scheduler 只负责“选节点”,不启动容器;它做的唯一写操作是把节点名绑定到 Pod 上,真正起容器的是那台节点上的 kubelet。
- 控制器不直接操作服务器。ReplicaSet 控制器发现少了一个副本,只是在 apiserver 里新建一个 Pod 对象,后面的事交给 scheduler 和 kubelet。
- 控制平面节点也运行 kubelet 和容器运行时。kubeadm 把这四个组件做成静态 Pod,清单放在
/etc/kubernetes/manifests/,由本机的 kubelet 直接拉起,不经过 scheduler。 - cloud-controller-manager 只在云上需要。它负责对接云厂商的负载均衡、云盘和节点信息,自有机房的物理机集群一般不部署。
查看这些组件的状态:
# kubeadm 集群:控制平面组件的静态 Pod 清单
ls /etc/kubernetes/manifests/
# etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
kubectl -n kube-system get pods -o wide # 各组件运行在哪个节点
kubectl get --raw='/readyz?verbose' # apiserver 逐项自检,包含 etcd 连接
kubectl -n kube-system get lease kube-scheduler kube-controller-manager # HOLDER 列是当前的主
kubectl get componentstatuses 从 1.19 起已标记为废弃,结果不可靠,检查控制平面用上面的 /readyz 和 Pod 状态。
工作节点上的三个组件
| 组件 | 运行方式 | 主要工作 | 排查入口 |
|---|---|---|---|
| kubelet | systemd 服务,不是 Pod | 把本节点注册到集群;监听分配给本节点的 Pod;调用容器运行时创建容器、挂载存储卷;执行存活、就绪、启动探针;定期上报节点和 Pod 状态 | journalctl -u kubelet -f |
| kube-proxy | DaemonSet,每个节点一个 Pod | 监听 Service 和 EndpointSlice,把“访问某个 ClusterIP”翻译成本机的转发规则 | kubectl -n kube-system logs ds/kube-proxy |
| 容器运行时 | systemd 服务(containerd 或 CRI-O) | 接收 kubelet 经 CRI 发来的指令:创建 Pod 沙箱、调用 CNI 插件配置网络、拉镜像、启停容器 | crictl pods、crictl ps -a |
补充三点:
- 节点心跳靠 Lease。kubelet 默认每 10 秒更新一次
kube-node-lease命名空间里属于自己的 Lease 对象,控制器据此判断节点是否还活着。 - kube-proxy 不在数据路径上转发流量。iptables 模式下它只负责写规则,包由内核按规则直接转发,所以 kube-proxy 进程短暂退出时已有的 Service 照样能访问。
- 每个 Pod 都有一个 pause 容器。它先创建并持有网络命名空间,CNI 插件给它分配 IP,业务容器随后加入这个命名空间,所以同一 Pod 里的容器可以用 localhost 互相访问。
创建一个 Pod 时,各组件按什么顺序工作
以执行 kubectl apply 创建一个 2 副本的 Deployment 为例,从命令发出到流量进入新 Pod,经过七步:
- apiserver 受理:kubectl 把清单发给 apiserver 的 6443 端口,依次通过认证、鉴权、准入,Deployment 对象写入 etcd。此时集群里还没有任何 Pod。
- Deployment 控制器:controller-manager 通过 watch 看到新的 Deployment,创建一个 ReplicaSet。
- ReplicaSet 控制器:发现这个 ReplicaSet 期望 2 个 Pod、实际 0 个,创建 2 个 Pod 对象,它们的
nodeName为空,状态是 Pending。 - scheduler 选节点:看到 2 个未调度的 Pod,过滤、打分后选出节点,向 apiserver 写入绑定关系。
- kubelet 起容器:目标节点上的 kubelet 看到有 Pod 分给自己,调用容器运行时:先创建 pause 沙箱并由 CNI 插件分配 Pod IP,再拉取镜像,最后创建并启动业务容器。
- 就绪上报:kubelet 执行就绪探针,通过后把 Pod 标记为 Ready 并上报 apiserver。
- 接入流量:如果有 Service 的选择器匹配这些 Pod,EndpointSlice 控制器会把就绪 Pod 的 IP 作为可用端点写进 EndpointSlice,各节点的 kube-proxy 收到变化后更新转发规则,访问 Service 的请求开始分到新 Pod 上。
每一步都会在集群里留下可以观察的痕迹:
| 阶段 | 由谁完成 | 在哪里看 |
|---|---|---|
| 写入 Deployment | apiserver、etcd | kubectl get deploy 出现新对象 |
| 创建 ReplicaSet 和 Pod | controller-manager | Deployment 的事件 ScalingReplicaSet,ReplicaSet 的事件 SuccessfulCreate |
| 选节点 | scheduler | Pod 事件 Scheduled;资源不够时是 FailedScheduling |
| 配网络、拉镜像、起容器 | kubelet、容器运行时、CNI | Pod 状态 ContainerCreating;事件 Pulling、Pulled、Created、Started |
| 就绪并接入流量 | kubelet、EndpointSlice 控制器、kube-proxy | READY 列变为 1/1;建了 Service 时,kubectl get endpointslices 里出现 Pod IP |
kubectl create deployment web --image=nginx --replicas=2
kubectl get pods -l app=web -o wide -w # Pending → ContainerCreating → Running
kubectl get events --sort-by=.metadata.creationTimestamp | tail -n 20
kubectl describe pod -l app=web | sed -n '/Events:/,$p'
这套机制的好处是排障有章可循:Pod 卡在哪个阶段,就查负责那个阶段的组件。一直 Pending 且没有 Scheduled 事件,看 scheduler 和节点资源;长时间 ContainerCreating,看镜像拉取和网络插件;Running 但 READY 是 0/1,看就绪探针和应用本身。
组件故障时会出现什么现象
因为每个组件只对状态变化做出反应,某个组件停了,流程就停在它负责的那一步。按现象反推故障组件:
| 故障组件 | 现象 | 已在运行的业务 |
|---|---|---|
| kube-apiserver 全部不可用 | kubectl 报连接被拒绝或超时;扩缩容、发版、自愈全部停止 | Pod 继续运行,已有的 Service 规则继续生效 |
| etcd 失去多数成员 | apiserver 无法写入,读请求也可能失败,集群相当于被冻结 | 同上 |
| kube-scheduler | 新 Pod 一直 Pending,事件里没有 Scheduled | 不受影响 |
| kube-controller-manager | 改副本数不生效,删掉的 Pod 不会补,节点宕机后上面的 Pod 不会被驱逐重建 | 不受影响 |
| 某节点的 kubelet | 该节点过一段宽限期后变为 NotReady;默认约 5 分钟后,由控制器管理的 Pod 在其他节点重建 | 该节点上的容器还在跑,但不再受管理 |
| kube-proxy | 新建或变更的 Service、端点不再同步到该节点的规则里 | 已有规则继续工作 |
| CNI 插件 | 新 Pod 卡在 ContainerCreating,事件里是网络配置相关的报错 | 已分配 IP 的 Pod 通常不受影响 |
| CoreDNS | 按服务名访问失败,按 IP 访问正常 | 依赖域名解析的调用失败 |
这张表也说明了高可用该保护什么:apiserver 和 etcd 一停,整个集群就失去管理能力。kubeadm 的高可用方案有两种拓扑:堆叠 etcd(etcd 和其他控制平面组件跑在同一批节点上,至少 3 台)和外置 etcd(etcd 单独部署,至少 3 台 etcd 加 3 台控制平面,共 6 台)。中小规模用堆叠更省机器,代价是一台节点故障同时损失一个 etcd 成员和一个 apiserver 实例。
组件与端口对照表:机房防火墙与网络规划
下表按 Kubernetes 官方文档列出的端口整理,并补充了常见网络插件的端口:
| 端口 | 协议 | 组件 | 监听位置 | 谁来访问 | 放行建议 |
|---|---|---|---|---|---|
| 6443 | TCP | kube-apiserver | 控制平面 | 所有节点、运维人员、负载均衡 | 只对节点网段和运维网段开放,不对公网开放 |
| 2379 | TCP | etcd 客户端接口 | 控制平面或独立 etcd 节点 | kube-apiserver | 只在控制平面之间放行 |
| 2380 | TCP | etcd 成员间通信 | 同上 | 其他 etcd 成员 | 只在 etcd 成员之间放行 |
| 10250 | TCP | kubelet API | 所有节点 | apiserver、metrics-server | 只允许控制平面和集群内部访问 |
| 10259 | TCP | kube-scheduler | 控制平面 | 本机 | kubeadm 默认只监听 127.0.0.1,无需对外放行 |
| 10257 | TCP | kube-controller-manager | 控制平面 | 本机 | 同上 |
| 10256 | TCP | kube-proxy 健康检查 | 所有节点 | 外部负载均衡 | 用外部负载均衡探测节点时放行 |
| 30000–32767 | TCP、UDP | NodePort 类型的 Service | 所有节点 | 客户端 | 只开放实际用到的端口 |
| 179 | TCP | Calico BGP | 所有节点 | 其他节点或接入交换机 | 使用 Calico BGP 模式时 |
| IP 协议号 4 | IPIP | Calico IPIP 封装 | 所有节点 | 其他节点 | 这是协议号不是端口,ACL 要按协议放行 |
| 4789 | UDP | Calico VXLAN | 所有节点 | 其他节点 | 使用 Calico VXLAN 模式时 |
| 8472 | UDP | Flannel、Cilium 的 VXLAN | 所有节点 | 其他节点 | 使用对应插件时 |
| 4240 | TCP | Cilium 健康检查 | 所有节点 | 其他节点 | 使用 Cilium 时 |
老教程里常见的 kubelet 只读端口 10255,kubeadm 默认已经关闭,不要再打开。10250 尤其不能暴露到公网:拿到它的访问权限就能在节点上的容器里执行命令。
做网络规划时,除了端口还要注意三件事:
- apiserver 用虚拟 IP 对外。3 台控制平面前面用 keepalived 加 HAProxy 提供一个虚拟 IP,节点和 kubectl 都连这个地址;这个地址要事先在机房的地址台账里预留,避免被分配给其他服务器。
- etcd 怕延迟和慢盘。etcd 默认心跳间隔 100 毫秒、选举超时 1000 毫秒,网络抖动或磁盘写入慢都可能触发重新选主,表现为 apiserver 间歇性变慢。控制平面放在同一机房的低延迟网络里,etcd 数据目录放 SSD。
- 封装会吃掉 MTU。VXLAN 封装多出 50 字节,物理网络 MTU 1500 时 Pod 网卡要设为 1450;IPIP 多出 20 字节,对应 1480。网络插件和物理网络的 MTU 不匹配时,常见症状是小请求正常、大文件或大响应卡住。交换机统一开了 9000 字节巨帧的,可以相应调大。
节点上开着 firewalld 时,控制平面可以这样按来源放行(示例中控制平面为 10.10.20.11–13,节点网段 10.10.20.0/24,Pod 网段 172.20.0.0/16):
firewall-cmd --permanent --new-ipset=k8s-cp --type=hash:ip
for ip in 10.10.20.11 10.10.20.12 10.10.20.13; do
firewall-cmd --permanent --ipset=k8s-cp --add-entry=$ip
done
# etcd 与 kubelet 只允许控制平面访问
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="k8s-cp" port port="2379-2380" protocol="tcp" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="k8s-cp" port port="10250" protocol="tcp" accept'
# apiserver 允许整个节点网段访问
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.10.20.0/24" port port="6443" protocol="tcp" accept'
# Pod 网段放进信任区,否则跨节点的 Pod 流量可能被拦截
firewall-cmd --permanent --zone=trusted --add-source=172.20.0.0/16
firewall-cmd --reload
工作节点按同样思路放行 10250(来源为控制平面)、所用网络插件的端口和需要对外的 NodePort。
在机房里怎么落地
架构图上的高可用,要落到机柜和供电上才算数。3 台控制平面节点如果装在同一个机柜、接同一路 PDU,一次断电就会同时带走 3 个 etcd 成员,多数派设计形同虚设。上架前在 机柜与资产 的 U 位视图里确认 3 台控制平面分属不同机柜和供电回路,扩容时新节点按同样原则摆放。
节点出现 NotReady、SSH 又连不上时,问题往往出在操作系统层:内核崩溃、根分区写满或变成只读、网卡驱动异常。这时可以通过 IPMI 远程管理 打开节点的 VNC 控制台看屏幕输出,需要时远程重启或进入救援模式,确认是单节点故障后再决定修复还是把节点移出集群重装。
常见问题
控制平面节点上能跑业务 Pod 吗?
能。kubeadm 默认给控制平面节点加上 node-role.kubernetes.io/control-plane:NoSchedule 污点,普通 Pod 不会调度上去,去掉污点即可。小集群常这样节省机器,但业务把节点资源吃满时会拖慢 apiserver 和 etcd,生产环境要给控制平面组件预留资源,或者让控制平面只做控制平面。
etcd 为什么要部署奇数个?
etcd 用 Raft 协议,写入要超过半数成员确认。3 个成员能容忍坏 1 个,4 个成员同样只能容忍坏 1 个,多出的一个不增加容错,反而多一份同步开销,所以一般部署 3 个或 5 个。
kube-proxy 可以不装吗?
可以由网络插件接管。例如 Cilium 能用 eBPF 实现 Service 转发,开启替代模式后集群里不再需要 kube-proxy;用 kubeadm 部署时,init 时加 --skip-phases=addon/kube-proxy 跳过它。具体步骤以所用插件的文档为准。
k3s 的架构和标准 k8s 一样吗?
组件和职责一样,打包方式不同:k3s 把 apiserver、scheduler、controller-manager、kubelet 等打进一个二进制,以 server 和 agent 两种进程运行;单 server 时默认用 SQLite 存状态,高可用模式常用内置 etcd,也可以接外部数据库。apiserver 端口同样是 6443。