Kubernetes 核心概念与架构
本篇是 Kubernetes 系列的第一篇,先不装环境、不敲大段命令,把「K8s 是什么、解决什么问题、由哪些组件构成、整体怎么运转」这几件事讲清楚。学过前面 Docker 系列的话,可以把这篇看作从单机走向集群的第一步。
Kubernetes 是什么
Kubernetes(常简写为 K8s,因为 K 和 s 之间有 8 个字母)是一个开源的容器编排平台,用来自动化地部署、扩缩和管理容器化应用。名字来自希腊语「舵手、领航员」,这也解释了它的 Logo 为什么是一个船舵。
几个关键事实:
- 前身是 Google 内部用了十多年的集群管理系统 Borg,2014 年 Google 用 Go 重写并开源了 Kubernetes。
- 2015 年 Google 把它捐给 CNCF(云原生计算基金会),如今是 CNCF 的毕业项目,也是容器编排领域事实上的标准,基本没有竞品。
- 版本迭代很快,大约每 4 个月发布一个大版本,生产上一般用落后最新版一两个版本的稳定版。
K8s 最核心的工作方式是声明式 + 控制循环:我们用 YAML 描述「我想要什么状态」(比如跑 3 个 nginx 副本),提交给 K8s;K8s 内部持续观察实际状态,一旦发现和期望不一致就自动修正。我们不对集群下「现在去干什么」的命令,而是声明「我要的结果是什么」。
Docker 管「一台机器上怎么跑容器」,Kubernetes 管「一群机器上怎么跑容器」:容器放哪台机器、挂了怎么办、要几个副本、流量怎么进来,都由它统一负责。
为什么需要 Kubernetes
前面 Docker 系列的所有操作——docker run、docker compose up——都局限在一台机器上。单台机器时这套东西很好用,但应用一旦要部署到多台机器,问题就一个接一个冒出来:
- 容器放哪台机器,要自己定:哪台机器资源够用、哪台快满了,得自己盯着,再人肉登录上去操作。
- 容器挂了没人管:Docker 的 restart policy 只能处理本机容器退出;机器整个宕机时,上面的容器就彻底没了。
- 扩缩容靠手工:流量来了想多开几个副本,只能一台台机器去敲命令。
- 跨机器通信麻烦:容器 IP 是动态分配的、随时会变,服务发现和负载均衡得自己搭(比如手工维护 nginx 反代)。
- 发布靠手工:停旧版、起新版全靠自己编排顺序,稍有不慎就中断服务。
- 配置、密钥、存储散落各机:改个配置要逐台同步,数据也只绑在单机目录上。
Kubernetes 就是为解决这类「多机管容器」的问题而生的,对照关系如下:
| 单机 Docker 的痛点 | Kubernetes 的做法 |
|---|---|
| 容器放哪台机器靠人定 | Scheduler 根据资源、亲和性等规则自动调度 |
| 容器或节点挂了没人管 | 容器退出自动重启,节点宕机自动在其他节点重建 Pod(自愈) |
| 扩缩容手工操作 | 改一个副本数字即可,也可以接 HPA 按负载自动伸缩 |
| 跨机访问、负载均衡自己搭 | Service 提供固定访问入口并自动分发流量,DNS 自动服务发现 |
| 发布手工编排 | Deployment 滚动更新,出问题一键回滚 |
| 配置密钥散落各机 | ConfigMap / Secret 统一管理,随 Pod 注入 |
| 数据绑死单机目录 | PV / PVC 体系对接云盘、NFS、Ceph 等各种存储后端 |
如果只有一两台机器、几个容器,单机 Docker 加 Compose 就够用,没必要上 K8s。K8s 的价值要等规模上来之后才体现,而且它本身的学习和运维成本并不低,小团队不必为用而用。
Kubernetes 架构
一个 K8s 集群在逻辑上分为两块:Control Plane(控制平面) 和若干 Node(工作节点)。前者是「大脑」,后者是「干活的机器」。

| 对比项 | Control Plane(控制平面) | Node(工作节点) |
|---|---|---|
| 角色 | 集群的「大脑」,负责管理和决策 | 集群的「手脚」,负责真正运行容器 |
| 主要职责 | 接收请求、存储集群状态、给 Pod 选节点、持续调谐 | 按控制平面下发的指令起停容器、维护网络转发规则、上报状态 |
| 核心组件 | API Server、Scheduler、Controller Manager、etcd | kubelet、kube-proxy、容器运行时(如 containerd) |
| 是否跑业务负载 | 一般不跑(默认有污点限制,生产建议专机专用) | 跑,业务 Pod 全部落在 Node 上 |
| 典型数量 | 生产环境 3 或 5 台做高可用 | 几台到几千台,按需扩缩 |
| 故障影响 | 集群不可管理(已运行的 Pod 暂时不受影响) | 该节点上的 Pod 由控制器在其他节点重建 |
最小的集群可以只有 1 台机器(Control Plane 和 Node 合一),比如 kind、minikube、Docker Desktop 自带的单节点集群,学习和测试完全够用。下面会分节讲每个组件。
Control Plane
Control Plane 负责整个集群的管理和决策:接收用户请求、保存集群状态、决定 Pod 跑在哪、不断把实际状态拉向期望状态。它由四个核心组件构成:
| 组件 | 一句话职责 |
|---|---|
| API Server | 集群唯一入口,所有操作都经过它 |
| etcd | 存储集群全部状态的键值数据库 |
| Scheduler | 给新 Pod 挑选合适的 Node |
| Controller Manager | 运行各种控制器,持续调谐集群状态 |
如果集群跑在云厂商上,通常还有一个可选组件 cloud-controller-manager,负责对接云平台的负载均衡器、云盘、节点路由等能力,学习阶段可以先不管它。
生产环境的 Control Plane 至少部署 3 个节点:API Server 无状态、可以水平扩展,前面挂负载均衡;etcd 基于 Raft 协议,用 3 或 5 个奇数节点保证多数派可用。学习环境用单节点即可,不用考虑这些。
Node
Node 是集群里真正跑业务容器的机器,可以是物理机,也可以是虚拟机(云服务器)。每个 Node 上有三个必备组件:
| 组件 | 一句话职责 |
|---|---|
| kubelet | 节点上的「大管家」,负责起停容器、探活、上报状态 |
| kube-proxy | 维护本节点网络规则,实现 Service 的流量转发 |
| 容器运行时 | 真正干活的底层组件(如 containerd),负责拉镜像、跑容器 |
查看集群里有哪些节点:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
control-plane Ready control-plane 30d v1.31.0
node-1 Ready <none> 30d v1.31.0
node-2 Ready <none> 30d v1.31.0
ROLES 列显示 control-plane 的是控制平面节点,显示 <none> 的是普通工作节点。集群运行期间可以随时向里加新节点(扩容)或把节点上的 Pod 迁走后摘除(缩容),这是 K8s 弹性的基础。
API Server
API Server(kube-apiserver)是集群对外的唯一入口。kubectl、集群内部组件、外部系统(CI/CD、Dashboard、各种 Operator)对集群的一切操作,最终都是调用它的 REST API(默认 6443 端口,HTTPS)。
一个请求进来后,API Server 会依次处理:
- 认证(Authentication):确认调用者是谁(证书、Token、ServiceAccount 等)。
- 鉴权(Authorization):确认调用者有没有权限做这件事(主流是 RBAC)。
- 准入控制(Admission):对请求做校验和改写,比如检查字段合法性、自动注入 Sidecar。
- 校验并入库:全部通过后,把资源对象写入 etcd,返回结果。
关键点:
- 所有组件的唯一入口:Scheduler、Controller Manager、kubelet 之间互不直接通信,全部通过 watch/list API Server 获取信息、提交结果。整个集群是围绕 API Server 的星型结构。
- 唯一直接读写 etcd 的组件:其他组件想看状态、改状态,都得经过它。
- 无状态:本身不存数据(数据都在 etcd),所以可以多副本水平扩展。
统一入口意味着统一的安全和数据校验:认证、鉴权、审计、准入策略只需要在这一处实现,不用每个组件各搞一套。这也是为什么「能连上 API Server 且权限够」就等于「能管理整个集群」——kubeconfig 文件要保管好。
Scheduler
Scheduler(kube-scheduler)的职责只有一个:给还没分配节点的 Pod 选一个最合适的 Node。
它通过 watch API Server 发现 nodeName 为空的 Pod(即新创建、未调度的 Pod),然后分两步做决策:
- 过滤(Filtering):排除不满足硬性条件的节点,比如 CPU/内存不够、请求的端口被占用、节点带有 Pod 不能容忍的污点、不满足节点亲和性规则。
- 打分(Scoring):给剩下的候选节点按策略算分,比如资源越空闲分越高、尽量把同一个应用的副本打散到不同机器和可用区,最后选得分最高的节点。
选定后,Scheduler 把结果(Pod 的 nodeName)写回 API Server,这一步叫绑定(Binding)。
关键点:
- Scheduler 只负责「选」,不负责「起」:真正把容器拉起来的是目标节点上的 kubelet。
- 调度是一次性的:Pod 一旦绑定到节点就不会再被自动迁移,想挪位置只能删除重建。
- 调度器是可插拔的,可以写自定义调度器和默认调度器共存,一般场景用不到。
Controller Manager
Controller Manager(kube-controller-manager)以单进程形式运行着 K8s 内置的一堆控制器(Controller)。每个控制器盯住一类资源,不断执行调谐循环(Reconcile Loop):
观察当前状态 → 与期望状态对比 → 有差异就采取行动(创建/删除/更新)→ 再观察……
常见内置控制器:
| 控制器 | 负责的事 |
|---|---|
| Deployment 控制器 | 管理 ReplicaSet,实现滚动更新和回滚 |
| ReplicaSet 控制器 | 保证 Pod 副本数量始终等于期望值 |
| Node 控制器 | 监控节点心跳,节点失联时驱逐上面的 Pod |
| Job 控制器 | 管理一次性任务,直到跑完为止 |
| EndpointSlice 控制器 | 维护 Service 与后端 Pod 的对应关系 |
关键点:
- 逻辑上每个控制器是独立的,只是为了部署方便才编译进同一个二进制、跑在同一个进程里。
- 「副本数设为 2,挂了 1 个 Pod 能自动补回来」就是 ReplicaSet 控制器在调谐——自愈能力来自控制器,不是什么神秘机制。
- 高可用部署时多个副本之间通过 leader election 选主,同一时刻只有一个在真正干活。
etcd
etcd 是一个分布式键值数据库,集群里所有资源对象(Deployment、Pod、Service、ConfigMap、Secret……)的完整定义和状态都存在它里面。可以说 etcd 就是整个集群的「账本」。
关键点:
- 集群里唯一有状态的组件:其他组件挂了重启进程就行,etcd 的数据丢了,集群状态就没了。
- 只有 API Server 直接访问 etcd:其他组件(包括 kubelet)都不直接和 etcd 打交道。kubelet 上报状态也是先发给 API Server,再由 API Server 写入 etcd。
- 基于 Raft 一致性协议:多节点部署时要求多数派(过半)存活才能写入,所以生产环境用 3 或 5 个奇数节点,2 个节点没有任何意义。
- 对磁盘延迟非常敏感,生产环境建议放在 SSD 上,并且不要和其他高 IO 应用抢磁盘。
备份 etcd 约等于备份整个集群:只要 etcd 数据还在,集群出再大的问题也能恢复出全部资源对象。生产环境务必定期做 etcd 快照备份(具体做法见本系列下一篇「集群安装与生命周期管理」)。
kubelet
kubelet 是运行在每个 Node 上的 agent,是控制平面的指令在节点上的执行者,管理该节点上所有 Pod 的生命周期。
它具体干这些事:
- 通过 API Server watch「分配到自己节点」的 Pod,按 Pod 定义调用容器运行时(走 CRI 接口)拉镜像、创建和启动容器。
- 容器退出后按
restartPolicy决定是否重启。 - 周期性执行存活探针(livenessProbe)和就绪探针(readinessProbe):活不过来的重启,没就绪的从 Service 后端摘掉。
- 定期向 API Server 上报本节点和 Pod 的状态(相当于心跳),
kubectl get pod看到的状态都来自这里。 - 节点资源(磁盘、内存)紧张时按优先级驱逐 Pod。
关键点:
- kubelet 不直接和 etcd 打交道:它和其他组件一样,只和 API Server 通信。
- 除了管理 API Server 下发的 Pod,kubelet 还能管理 static Pod:读取本机指定目录下的清单文件直接起容器。kubeadm 部署的集群正是用这个方式,把 API Server、etcd 等控制平面组件本身也跑成容器交给 kubelet 管。
控制平面负责「想」,kubelet 负责「做」。Scheduler 决定 Pod 去哪个节点之后,拉镜像、起容器、探活、上报这些脏活累活,全是该节点的 kubelet 在干。
kube-proxy
kube-proxy 运行在每个 Node 上,负责实现 Service 的网络转发。
背景是这样的:Pod 的 IP 是临时分配的,重建就变;Service 则给一组 Pod 提供一个固定的虚拟 IP 和 DNS 名字。kube-proxy 的任务就是让发往 Service 虚拟 IP 的流量,能被正确转发到后端健康的 Pod 上。
它有两种主流工作模式:
| 模式 | 说明 |
|---|---|
| iptables(默认) | 在节点上写 iptables 规则做转发,实现简单稳定;Service 数量特别多时规则链变长,性能下降 |
| IPVS | 用内核的 IPVS 模块做负载均衡,支持多种调度算法,大规模场景性能更好,需要显式开启 |
(更早期还有个 userspace 模式,性能差,早已淘汰,知道有这回事就行。)
关键点:
- 转发规则的数据来源同样是 API Server:kube-proxy watch Service 和 EndpointSlice 的变化,动态更新本机规则。
- kube-proxy 管的是「集群内到 Service 的转发」;从集群外进来的 HTTP/HTTPS 流量路由是 Ingress 的事,后续「Service 与流量管理」一篇会专门展开。
CRI / CNI / CSI
K8s 不想把容器运行时、网络、存储的具体实现绑死在自己的核心里,于是定义了三套标准接口。各家厂商按接口实现插件,K8s 通过接口调用,核心代码和具体实现解耦。
| 接口 | 全称 | 解决的问题 | 主流实现 |
|---|---|---|---|
| CRI | Container Runtime Interface,容器运行时接口 | kubelet 怎么统一地拉起、停止容器,管理镜像 | containerd、CRI-O |
| CNI | Container Network Interface,容器网络接口 | Pod 怎么拿到 IP、跨节点的 Pod 之间怎么通信 | Calico、Flannel、Cilium |
| CSI | Container Storage Interface,容器存储接口 | Pod 的存储卷怎么对接各种存储后端 | Ceph CSI、AWS EBS CSI Driver、NFS CSI Driver |
CRI:容器运行时接口
调用方是 kubelet。kubelet 不直接操作容器,而是把「启动这个容器」翻译成 CRI 请求发给运行时执行。
- containerd:当前绝对主流,Docker 底层用的也是它,kubeadm 等主流部署工具默认对接 containerd。
- CRI-O:Red Hat 主导的轻量实现,只为 K8s 而生,常见于 OpenShift。
- 早期 K8s 通过 dockershim 适配 Docker Engine,1.24 版本起已移除。注意这不影响 Docker 的使用:Docker 构建的镜像符合 OCI 标准,照样能在 containerd 上跑(前面 Docker 系列附录「Docker 与 Kubernetes 的关系」专门讲过这段历史)。
CNI:容器网络接口
Pod 创建时由 CNI 插件负责分配 IP、打通网络。不同插件实现思路不同(overlay 隧道、BGP 路由、eBPF),按需选择。
- Flannel:最简单常用,基于 VXLAN overlay,适合学习和小规模集群。
- Calico:支持 BGP 直连路由和 NetworkPolicy 网络策略,生产环境常见。
- Cilium:基于 eBPF,性能强、自带可观测能力,近几年增长很快。
CSI:容器存储接口
存储厂商按 CSI 标准写驱动,K8s 就能通过 PV/PVC 体系对接各种存储:云盘、分布式存储、NFS 等。
- 云厂商官方驱动:AWS EBS CSI Driver、各云各自的云盘 CSI 插件。
- 开源自建:Ceph CSI、NFS CSI Driver。
排查问题时方向感很重要:容器起不来查运行时(CRI 这条链)、Pod 拿不到 IP 或网络不通查网络插件(CNI)、卷挂不上查存储驱动(CSI)。三个方向互不交叉,先定位到哪一层再深入。
Pod 生命周期
Pod 是 K8s 的最小调度单位:一个 Pod 里可以有一个或多个共享网络和存储的容器,但绝大多数情况一个 Pod 就一个容器。Pod 也是「用完即弃」的——它不会被原地修复,出问题通常是删掉重建。理解 Pod 的生命周期,是后面学故障排查的基础。
Pod 阶段(phase)
kubectl get pod 看到的 STATUS 大致对应 Pod 的 phase,一共五种:
| phase | 含义 |
|---|---|
| Pending | Pod 已被集群接受,但还没跑起来:可能在等调度,也可能已调度、节点上正在拉镜像建容器 |
| Running | Pod 已绑定到节点,至少有一个容器在运行(或正在启动、重启中) |
| Succeeded | Pod 里所有容器都正常退出(退出码 0)且不会再重启,Job 跑完就是这个状态 |
| Failed | 所有容器都已退出,且至少一个是非正常退出(退出码非 0 或被系统杀掉) |
| Unknown | API Server 联系不上 Pod 所在节点的 kubelet,拿不到状态,通常是节点失联或网络分区 |
kubectl get pod 的 STATUS 列还会出现 ContainerCreating、CrashLoopBackOff、ImagePullBackOff、OOMKilled 等更具体的状态,它们来自容器层面的原因(waiting reason 等),本质上仍归属上面五个 phase 之一。排查时用 kubectl describe pod 能看到完整细节。
Pod Conditions
phase 粒度太粗,Pod 的 .status.conditions 里还有几个布尔状态,能更细地看出 Pod 卡在哪一步:
| Condition | 含义 |
|---|---|
| PodScheduled | 是否已被调度到某个节点 |
| Initialized | 所有 Init 容器是否已执行完成 |
| ContainersReady | Pod 里所有容器是否都已就绪 |
| Ready | Pod 整体是否就绪——只有 Ready 的 Pod 才会被 Service 加进后端、接收流量 |
用 kubectl describe pod 的 Conditions 段就能逐项看到这些值。
restartPolicy
Pod 的 spec.restartPolicy 决定容器退出后 kubelet 怎么处理,三个取值:
| 取值 | 行为 | 典型场景 |
|---|---|---|
| Always(默认) | 容器退出就重启,不管退出码是什么 | 长期运行的服务;Deployment 管理的 Pod 必须是这个 |
| OnFailure | 只有异常退出(退出码非 0)才重启 | 跑批任务,失败自动重试 |
| Never | 退出了也不重启 | 一次性任务 |
几个容易搞混的点:
- 这里的重启是 kubelet 在原节点、原 Pod 内重启容器,不是换个节点新建 Pod。
- 重启次数多了会带退避延迟:10s、20s、40s 指数增加,封顶 5 分钟——这就是 CrashLoopBackOff 里 BackOff(退避)一词的由来。
- Pod 一旦被调度到某个节点,生命周期就绑在这个节点上。节点挂了,上面的 Pod 不会「漂走」,而是由控制器在其他节点新建 Pod,旧 Pod 停留在 Failed 等终止状态。
phase 告诉你 Pod 现在大致处于哪个阶段,Conditions 告诉你卡在哪一步,restartPolicy 决定容器退出后要不要救。三者配合 kubectl get、kubectl describe、kubectl logs 这三板斧,就是日常排查 Pod 问题的起点。
Kubernetes 工作流程
把前面所有组件串起来看:以 kubectl apply 一个 Deployment 为例,走一遍完整链路。
先准备一份清单 deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
执行:
kubectl apply -f deployment.yaml
deployment.apps/nginx created
接下来集群内部发生的事,按时间顺序是:
- kubectl 发请求:kubectl 解析 YAML,向 API Server 发起创建 Deployment 的 HTTPS 请求。
- API Server 处理并入库:依次做认证(确认调用者身份)、鉴权(RBAC 检查权限)、准入控制(校验和补全字段),全部通过后把 Deployment 对象写入 etcd,然后返回成功。注意:到这一步
apply命令就结束了,但此时一个容器都还没起。 - Controller 创建 Pod 对象:Deployment 控制器一直 watch 着 API Server,发现新 Deployment 后创建对应的 ReplicaSet;ReplicaSet 控制器发现实际副本数(0)少于期望值(2),于是创建出 2 个 Pod 对象。此时这些 Pod 的
nodeName还是空的,phase 为 Pending。 - Scheduler 选节点:Scheduler watch 到未调度的 Pod,走过滤和打分流程,为每个 Pod 选出最合适的 Node,把绑定结果写回 API Server,由 API Server 更新 etcd。
- kubelet 拉镜像起容器:目标节点的 kubelet watch 到分配给自己的 Pod,先通过 CRI 让容器运行时(containerd)拉取
nginx:1.27镜像,然后创建并启动容器。如果同时创建了 Service,各节点的 kube-proxy 也会更新转发规则。 - 状态上报:kubelet 持续向 API Server 上报容器状态。镜像拉完、容器就绪后,Pod 进入 Running,Ready 条件置为 True——这时
kubectl get pod就能看到 2 个 Running 的 Pod。 - 持续调谐:从此各控制器进入常驻循环:ReplicaSet 控制器盯副本数,少了补、多了删;kubelet 盯容器健康,按 restartPolicy 处理退出;节点失联时相关控制器负责在其他节点重建 Pod。整个集群不断把实际状态拉向 YAML 里声明的期望状态。
整体链路示意:

注意第 2 步:apply 返回成功只代表「期望状态已存进 etcd」,不代表应用已经跑起来了,之后的一切都是各控制器异步完成的。这也是 K8s 和 docker run 的本质区别:docker run 是「命令它现在就做」,kubectl apply 是「告诉它我要的结果,怎么做你看着办」。
最后可以用几条命令验证前面学的概念:
# 看 Pod 落在哪个节点、处于什么阶段
kubectl get pods -l app=nginx -o wide
# 看某个 Pod 的 Conditions、事件(调度、拉镜像、启动的完整记录)
kubectl describe pod -l app=nginx
describe 输出末尾的 Events 段,能看到 Scheduler 调度、kubelet 拉镜像、启动容器的逐条记录——正好对应上面第 4 到 6 步,第一次学的时候建议认真看一遍。