跳到主要内容

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(工作节点)。前者是「大脑」,后者是「干活的机器」。

Kubernetes 集群架构

对比项Control Plane(控制平面)Node(工作节点)
角色集群的「大脑」,负责管理和决策集群的「手脚」,负责真正运行容器
主要职责接收请求、存储集群状态、给 Pod 选节点、持续调谐按控制平面下发的指令起停容器、维护网络转发规则、上报状态
核心组件API Server、Scheduler、Controller Manager、etcdkubelet、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 会依次处理:

  1. 认证(Authentication):确认调用者是谁(证书、Token、ServiceAccount 等)。
  2. 鉴权(Authorization):确认调用者有没有权限做这件事(主流是 RBAC)。
  3. 准入控制(Admission):对请求做校验和改写,比如检查字段合法性、自动注入 Sidecar。
  4. 校验并入库:全部通过后,把资源对象写入 etcd,返回结果。

关键点:

  • 所有组件的唯一入口:Scheduler、Controller Manager、kubelet 之间互不直接通信,全部通过 watch/list API Server 获取信息、提交结果。整个集群是围绕 API Server 的星型结构。
  • 唯一直接读写 etcd 的组件:其他组件想看状态、改状态,都得经过它。
  • 无状态:本身不存数据(数据都在 etcd),所以可以多副本水平扩展。
为什么所有组件都要经过 API Server

统一入口意味着统一的安全和数据校验:认证、鉴权、审计、准入策略只需要在这一处实现,不用每个组件各搞一套。这也是为什么「能连上 API Server 且权限够」就等于「能管理整个集群」——kubeconfig 文件要保管好。

Scheduler​

Scheduler(kube-scheduler)的职责只有一个:给还没分配节点的 Pod 选一个最合适的 Node。

它通过 watch API Server 发现 nodeName 为空的 Pod(即新创建、未调度的 Pod),然后分两步做决策:

  1. 过滤(Filtering):排除不满足硬性条件的节点,比如 CPU/内存不够、请求的端口被占用、节点带有 Pod 不能容忍的污点、不满足节点亲和性规则。
  2. 打分(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 通过接口调用,核心代码和具体实现解耦。

接口全称解决的问题主流实现
CRIContainer Runtime Interface,容器运行时接口kubelet 怎么统一地拉起、停止容器,管理镜像containerd、CRI-O
CNIContainer Network Interface,容器网络接口Pod 怎么拿到 IP、跨节点的 Pod 之间怎么通信Calico、Flannel、Cilium
CSIContainer 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含义
PendingPod 已被集群接受,但还没跑起来:可能在等调度,也可能已调度、节点上正在拉镜像建容器
RunningPod 已绑定到节点,至少有一个容器在运行(或正在启动、重启中)
SucceededPod 里所有容器都正常退出(退出码 0)且不会再重启,Job 跑完就是这个状态
Failed所有容器都已退出,且至少一个是非正常退出(退出码非 0 或被系统杀掉)
UnknownAPI Server 联系不上 Pod 所在节点的 kubelet,拿不到状态,通常是节点失联或网络分区
STATUS 不等于 phase

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 容器是否已执行完成
ContainersReadyPod 里所有容器是否都已就绪
ReadyPod 整体是否就绪——只有 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

接下来集群内部发生的事,按时间顺序是:

  1. kubectl 发请求:kubectl 解析 YAML,向 API Server 发起创建 Deployment 的 HTTPS 请求。
  2. API Server 处理并入库:依次做认证(确认调用者身份)、鉴权(RBAC 检查权限)、准入控制(校验和补全字段),全部通过后把 Deployment 对象写入 etcd,然后返回成功。注意:到这一步 apply 命令就结束了,但此时一个容器都还没起。
  3. Controller 创建 Pod 对象:Deployment 控制器一直 watch 着 API Server,发现新 Deployment 后创建对应的 ReplicaSet;ReplicaSet 控制器发现实际副本数(0)少于期望值(2),于是创建出 2 个 Pod 对象。此时这些 Pod 的 nodeName 还是空的,phase 为 Pending。
  4. Scheduler 选节点:Scheduler watch 到未调度的 Pod,走过滤和打分流程,为每个 Pod 选出最合适的 Node,把绑定结果写回 API Server,由 API Server 更新 etcd。
  5. kubelet 拉镜像起容器:目标节点的 kubelet watch 到分配给自己的 Pod,先通过 CRI 让容器运行时(containerd)拉取 nginx:1.27 镜像,然后创建并启动容器。如果同时创建了 Service,各节点的 kube-proxy 也会更新转发规则。
  6. 状态上报:kubelet 持续向 API Server 上报容器状态。镜像拉完、容器就绪后,Pod 进入 Running,Ready 条件置为 True——这时 kubectl get pod 就能看到 2 个 Running 的 Pod。
  7. 持续调谐:从此各控制器进入常驻循环:ReplicaSet 控制器盯副本数,少了补、多了删;kubelet 盯容器健康,按 restartPolicy 处理退出;节点失联时相关控制器负责在其他节点重建 Pod。整个集群不断把实际状态拉向 YAML 里声明的期望状态。

整体链路示意:

Deployment 创建调谐流程

这就是「声明式」的含义

注意第 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 步,第一次学的时候建议认真看一遍。