Kubernetes 常被简称为 K8s(K 和 s 之间省略了 8 个字母)。它是一个管理容器化应用的开源平台,负责把应用安排到集群里的机器上运行,并持续让实际状态接近期望状态。
学完 Docker 之后,我的理解是:Docker 解决的是“怎样把应用打包并运行起来”,Kubernetes 解决的是“应用跑在很多台机器上时,怎样持续管理这些容器”。
本文基于 Kubernetes 1.37,第六节的实验用 kind 在本地搭集群。
这是 K8s 学习笔记的第一篇,先把整体地图搭起来:
- 为什么有了 Docker 和 Compose,还需要 Kubernetes。
- 一个 Kubernetes 集群由哪些部分组成。
- 控制平面负责什么。
- Node 节点负责什么。
- Pod 为什么是 Kubernetes 调度和运行应用的基本单位。
后面再分篇记录 Deployment、Service、Gateway API、ConfigMap、Secret、存储、调度和故障排查。
一、为什么要用 Kubernetes
从一个容器到一组服务
在本地运行一个 Web 服务很简单:准备镜像,执行 docker run,映射一个端口即可。服务数量增加后,问题会同时出现:
- 容器应该运行在哪台机器上?这台机器资源够不够?
- 容器崩溃后,谁负责重新启动?机器故障后,谁负责迁移?
- 一个服务启动多个副本后,流量怎样分发?客户端应该连接哪个地址?
- 发布新版本时,怎样逐步替换旧版本,避免所有实例同时中断?
- 配置、密钥和持久化数据怎样与镜像分开管理?
- 集群有几十或几百台机器时,怎样统一查看状态和执行变更?
Compose 能很好地描述一台机器上的多容器应用,但它管的是“在这个环境里把这一组服务启动起来”。应用一旦需要跨机器调度、自动恢复和持续发布,就需要一个集群级的控制系统,这正是 Kubernetes 做的事。
Kubernetes 提供了什么
在我看来,Kubernetes 的核心不是“又一条启动容器的命令”,而是把运行应用所需的状态交给一组控制器持续维护。
| 能力 | Kubernetes 做什么 | 实际收益 |
|---|---|---|
| 调度 | 根据资源、约束和策略把 Pod 放到合适的 Node | 不需要手动挑选机器 |
| 自愈 | 发现容器或节点异常,重新创建或调度工作负载 | 减少人工值守 |
| 服务发现 | 为一组动态变化的 Pod 提供稳定的 Service 地址 | 服务之间不依赖 Pod IP |
| 水平扩展 | 修改副本数量,或根据指标自动扩缩容 | 应对流量变化 |
| 滚动更新 | 分批替换旧版本,控制不可用实例数量 | 降低发布风险 |
| 配置与资源管理 | 集中管理配置、密钥、CPU 和内存边界 | 镜像与运行配置分离 |
这些能力都建立在声明式模型上。我们提交的是“希望有 3 个副本、用哪个镜像、暴露哪个端口”,剩下的由控制器观察当前状态并不断修正差异。
flowchart LR
A[提交期望状态] --> B[API Server]
B --> C[控制器与调度器]
C --> D[选择 Node]
D --> E[kubelet 启动容器]
E --> F[实际运行状态]
F -->|持续观察并修正差异| B
Kubernetes 不会自动解决所有问题
Kubernetes 不会替应用修复代码,也不会自动保证数据库事务、备份和权限设计正确。它提供的是运行平台:
- 应用仍需要正确处理信号、超时和依赖重连。
- 数据库等有状态服务需要独立的备份、恢复和容量规划。
- 集群需要权限控制、镜像安全、日志、指标和审计。
- 小型应用如果只需要单机部署,Compose 或托管平台可能更简单。
所以上不上 K8s,要看集群管理、可用性和交付流程上有没有真实需求,而不是“所有服务都得上 Kubernetes”。
二、Kubernetes 集群的整体架构
一个集群通常分为两类节点:
- 控制平面(Control Plane):保存集群状态,接受操作请求,做出调度和控制决策。
- 工作节点(Worker Node):运行实际的 Pod 和业务容器。
控制平面在早期资料里叫 Master 节点,社区出于包容性命名已经统一改为 control-plane。新集群里 kubectl get nodes 的 ROLES 列显示的是 control-plane,旧教程里带 master 的标签和污点命令已经不适用了。
flowchart TB
Client[kubectl / CI / 外部系统] --> CP
subgraph CP[控制平面]
direction LR
Scheduler[kube-scheduler] --> API[kube-apiserver]
Controller[kube-controller-manager] --> API
Cloud[cloud-controller-manager(可选)] --> API
API <--> ETCD[(etcd)]
end
subgraph N1[Node 1]
direction TB
K1[kubelet] -->|CRI| R1[容器运行时] --> P1[Pod]
Proxy1[kube-proxy]
end
subgraph N2[Node 2]
direction TB
K2[kubelet] -->|CRI| R2[容器运行时] --> P2[Pod]
Proxy2[kube-proxy]
end
CP <-->|watch / 上报状态| N1
CP <-->|watch / 上报状态| N2
图中控制平面与 Node 之间的双向箭头,表示 Node 上的组件与 kube-apiserver 保持通信:kubelet 获取分配给本节点的 Pod 并上报状态,kube-proxy 监听 Service 和 EndpointSlice 的变化来更新转发规则。
一次部署发生了什么
执行下面的命令时,kubectl 并不会直接登录某台 Node 创建容器:
|
|
我把请求的流程整理成下面几步:
kubectl把 YAML 发送给kube-apiserver。- API Server 校验请求,并把持久化状态写入
etcd。 - Deployment 控制器发现期望状态发生变化,创建或更新 ReplicaSet;ReplicaSet 控制器再创建 Pod 对象,此时 Pod 还没有分配 Node。
- Scheduler 为没有绑定 Node 的 Pod 选择合适的工作节点,并通过 API Server 写回绑定结果。
- 目标 Node 上的 kubelet 观察到 Pod 规格,调用容器运行时拉取镜像并启动容器。
- kubelet 持续汇报状态;控制器根据状态决定是否重建、扩容或更新 Pod。
可以看出,各个组件都是通过 API Server 协作的。我们平时只跟 API 打交道,不需要登录每台 Node 去启动容器。
三、控制平面的组件
控制平面组件可以运行在一台或多台机器上。生产环境通常使用多个控制平面实例,并为 API Server 和 etcd 设计高可用方案;单机学习集群则常把它们运行在同一个节点。
1. kube-apiserver:集群的统一入口
kube-apiserver 是 Kubernetes API 的 HTTP 入口。kubectl、控制器、Scheduler、kubelet 以及外部系统都会通过它读写资源对象。
它主要负责:
- 认证:请求来自谁?
- 授权:这个身份能操作哪些资源?
- 准入控制:这次变更是否符合策略?
- API 版本与对象校验:字段类型是否正确?
- 把资源变更通知给观察者。
API Server 本身不运行容器。它接收并记录“应该是什么样”,具体动作由控制器、Scheduler 和 kubelet 完成。它也是唯一直接读写 etcd 的组件,其他组件都要经过它访问集群状态。
2. etcd:保存集群状态
etcd 是一个分布式键值存储,Kubernetes 用它保存 API 对象和集群状态,例如:
- Deployment 希望有多少个副本。
- Pod 被调度到了哪台 Node。
- Service 的端口和 Selector 是什么。
- Secret、ConfigMap、RBAC 和节点信息是什么。
etcd 可以理解成集群的状态数据库,但它不是给业务用的数据库。etcd 的数据需要备份和保护,丢失或损坏会影响控制平面的恢复能力。
etcd 通过 Raft 共识算法在多个成员之间复制数据(Raft 的原理我在分布式共识算法里整理过)。写入需要多数成员确认,所以生产环境通常部署 3 或 5 个成员:3 个成员可以容忍 1 个故障,5 个成员可以容忍 2 个。
3. kube-scheduler:为 Pod 选择 Node
Scheduler 负责处理还没有绑定 Node 的 Pod。它先过滤不满足条件的节点,再对候选节点打分,选择一个合适的目标。
它会考虑:
- Pod 的 CPU、内存 requests 与 Node 的可分配资源。
- Node 的 taint、Pod 的 toleration。
- 节点选择器、亲和性和反亲和性。
- 拓扑分布、卷约束和其他调度策略。
Scheduler 只做“放在哪里”的决定:选定 Node 后,它通过 API Server 把结果写回 Pod 的 spec.nodeName,并不直接联系目标 Node。Pod 被绑定到 Node 后,由该 Node 的 kubelet 负责实际创建容器。
4. kube-controller-manager:持续修正状态
Controller Manager 运行多个控制器。每个控制器都采用类似的循环:
|
|
例如,Deployment 控制器负责把 Deployment 转换为 ReplicaSet,ReplicaSet 控制器负责让 Pod 副本数达到目标。Pod 被删除后,控制器会发现副本数不足并创建替代 Pod。
控制器不是执行一次就结束,而是一直在观察,所以 Kubernetes 才能在运行期间自己处理故障和变化。
5. cloud-controller-manager:对接云平台(可选)
使用公有云时,cloud-controller-manager 可以把 Kubernetes 对象与云平台能力连接起来,例如:
- 云厂商的负载均衡器。
- 云主机节点生命周期。
- 云硬盘的创建和挂载。
- 云平台路由。
裸机或本地实验集群通常不需要这个组件。
控制平面组件的关系
| 组件 | 核心问题 | 是否直接运行业务容器 |
|---|---|---|
| kube-apiserver | 谁可以读取和修改集群对象? | 否 |
| etcd | 集群状态保存在哪里? | 否 |
| kube-scheduler | Pod 应该放到哪台 Node? | 否 |
| kube-controller-manager | 怎样让实际状态接近期望状态? | 否 |
| cloud-controller-manager | 怎样使用云平台资源? | 否,负责云资源协作 |
控制平面组件可以被部署为系统服务或静态 Pod,具体方式取决于安装工具和集群发行版。这里要把“组件怎么运行”和“组件负责什么”分开看。
四、Node 节点:运行 Pod 的地方
Node 是集群中真正承载工作负载的机器,可以是虚拟机或物理机。每台 Node 至少需要 kubelet 和容器运行时;网络组件通常由 CNI 插件和 kube-proxy 等组件提供。
1. kubelet:Node 上的代理
kubelet 运行在每台 Node 上,负责让这台机器上的 Pod 处于期望状态。它会:
- 从 API Server 获取分配给本节点的 Pod 规格。
- 调用容器运行时创建、启动和停止容器。
- 挂载卷,执行探针,并上报 Pod 状态。
- 发现容器退出或探针失败后,按 Pod 的重启策略处理。
注意 kubelet 不负责调度,它只管理被分配到本节点的 Pod。
2. 容器运行时:真正启动容器
Kubernetes 通过 CRI(Container Runtime Interface)与容器运行时通信。常见运行时包括 containerd 和 CRI-O。
运行时负责拉取镜像、创建容器并管理容器的生命周期。现在的集群一般直接用 containerd 或 CRI-O,Node 上不需要装 Docker(早期是通过内置的 dockershim 对接 Docker Engine,1.24 已移除)。
这不影响用 Docker 构建的镜像:Docker 入门与实战里 docker build 出来的镜像符合 OCI 规范,推到镜像仓库后,containerd 和 CRI-O 都能直接拉取运行。
3. kube-proxy:Service 流量转发
kube-proxy 在 Node 上维护 Service 相关的转发规则,把访问 Service 虚拟地址的流量转发到后端 Pod。目前默认是 iptables 模式,较新的 Linux 内核上可以改用 nftables 模式(1.33 起 GA)。以前常见的 IPVS 模式已在 1.35 弃用。
它不是完整的网络插件。Pod IP 分配、跨 Node 路由和网络策略通常由 CNI 插件负责,例如 Calico、Cilium 或 Flannel;容器运行时创建 Pod 时会调用 CNI 插件配置网络。有些 CNI(例如 Cilium)还能用 eBPF 直接接管 Service 转发,这时集群中可以不运行 kube-proxy。具体组件取决于集群安装方案。
4. Node 的资源与状态
Scheduler 根据 Node 的容量和可分配资源做决定。Node 不只是“能运行或不能运行”,还拥有一组状态和约束:
- CPU、内存和临时存储容量。
- Ready、MemoryPressure、DiskPressure 等条件。
- labels,用于节点选择和拓扑分布。
- taints,用于阻止不匹配的 Pod 被调度。
常用检查命令:
|
|
kubectl top 需要集群中安装 Metrics Server;没有它时,节点和 Pod 仍然可以运行,但不能直接通过这个命令查看资源使用率。
五、Pod:Kubernetes 的基本运行单位
Pod 不是“一个容器的别名”
Pod 是 Kubernetes 可以创建和管理的最小部署单位。一个 Pod 可以包含一个或多个紧密协作的容器:
- Pod 中的容器共享同一个网络命名空间。
- 它们通过
localhost访问同一个 Pod 内的其他容器端口。 - 它们可以共享挂载卷。
- Pod 拥有自己的 IP,但这个 IP 会随着 Pod 重建而变化。
最常见的做法是一个 Pod 运行一个主应用容器。多容器 Pod 适合日志代理、代理 sidecar 这类要和主容器紧密协作的进程,不相关的服务不应该塞进同一个 Pod。sidecar 从 1.33 起可以原生声明:写在 initContainers 里并设置 restartPolicy: Always,它会先于主容器启动,并在主容器退出后才停止。
flowchart LR
subgraph Pod[Pod:共享网络与生命周期]
App[应用容器<br>:8080]
Sidecar[Sidecar 容器<br>日志或代理]
Vol[(共享卷)]
App --- Vol
Sidecar --- Vol
end
App -.localhost.-> Sidecar
Service[Service 稳定地址] --> Pod
Pod 的生命周期
Pod 不是永久服务器。它可能因为发布、驱逐、节点故障或配置变化而被删除并重新创建。重新创建后的 Pod 通常拥有新的 IP 和 UID,所以 Pod IP 不能当作稳定地址,数据也不能只写在容器可写层里。
Pod 的阶段(status.phase)只有下面五种:
| 阶段 | 含义 |
|---|---|
| Pending | Pod 已创建,但还没有完成调度、镜像拉取或容器启动 |
| Running | Pod 已绑定 Node,至少有容器正在运行或启动中 |
| Succeeded | Pod 中的容器都以成功状态结束 |
| Failed | Pod 中的容器都已终止,且至少有一个以失败状态结束 |
| Unknown | 控制平面暂时无法取得 Pod 的状态 |
READY 列表示 Pod 中已经通过就绪检查的容器数量,不等于容器进程“还活着”。一个进程存活但尚未准备好接收请求时,应该保持未就绪,避免 Service 把流量发送过去。
Pod 的 IP 与 Service
Pod IP 用于集群内部通信,但它不是服务发现地址。扩容或重建会让后端 Pod 集合变化,客户端不应该写死某个 Pod IP。
Service 通过 Selector 找到一组标签匹配的 Pod,并提供稳定的虚拟地址与 DNS 名称:
|
|
在同一个 Namespace 中,通常可以直接使用 Service 名称,例如 http://web:80。Service 的 port 是客户端访问的端口,targetPort 是后端容器监听的端口,两者可以不同:上图中 Service 端口是 80,Pod 监听 8080;第六节的 Nginx 示例两者都是 80。
Pod 与工作负载对象的关系
生产环境通常不直接创建裸 Pod,而是使用更高层的工作负载对象:
- Deployment 管理无状态应用的 ReplicaSet 和 Pod。
- StatefulSet 管理需要稳定身份和持久存储的副本。
- DaemonSet 确保每个符合条件的 Node 运行一个 Pod 副本。
- Job 和 CronJob 管理一次性或定时任务。
这些对象最终都会创建 Pod,但它们负责的生命周期不同。删除由 Deployment 管理的 Pod,Deployment 会通过 ReplicaSet 补回副本;直接删除裸 Pod 就真的没了,没有上层对象会把它补回来。
六、用一个最小示例把对象连起来
下面用一个最小示例把前面的对象串起来:两个 Nginx Pod 加一个 Service,不依赖业务代码。
动手前需要一个集群。我这里用 kind:它用 Docker 容器模拟 Kubernetes 节点,正好复用之前装好的 Docker;用 Docker Desktop 的话,也可以直接在设置里打开内置的 Kubernetes。先按官方文档装好 kubectl 和 kind,再执行:
|
|
首次创建要拉取节点镜像,等节点的 STATUS 变成 Ready 就可以继续。kind 默认只有一个节点,ROLES 显示 control-plane,控制平面组件和业务 Pod 跑在同一个节点上,也就是前面说的单机学习集群。第四节的检查命令也可以在这个集群中执行。
保存为 web.yaml:
|
|
resources.requests 就是第三节提到的调度依据:Scheduler 按 requests 而不是实时用量判断 Node 能否容纳这个 Pod。limits.memory 是容器可用内存的上限,超出后容器会被 OOM Kill。
创建和观察
|
|
预期 Deployment 的 READY 最终为 2/2,两个 Pod 都进入 Running,Service 获得 ClusterIP。镜像首次拉取需要时间,等待期间可以执行:
|
|
kubectl get pods 的 STATUS 列显示的不只是上一节表格里的 Pod 阶段:拉取镜像、创建容器期间会显示 ContainerCreating,镜像拉取失败时显示 ErrImagePull 或 ImagePullBackOff,容器反复崩溃时显示 CrashLoopBackOff。这些是容器处于等待状态的原因,排查时先看 kubectl describe pod 输出末尾的 Events。
Service 未指定 type 时默认是 ClusterIP,只能在集群内部访问。在本地实验集群中,用端口转发从宿主机访问 Service:
|
|
保持这个命令运行,在另一个终端执行:
|
|
port-forward 是本地调试入口,不是生产环境的外部暴露方案。它指向 Service 时,kubectl 只会从中选择一个 Pod 建立连接,请求不经过 Service 的负载均衡;这个 Pod 被删除后,转发会直接断开。
生产环境一般通过 Gateway API 或云负载均衡器把流量引入集群。以前常用的 Ingress 已经冻结,不再增加新功能,社区版 ingress-nginx 控制器也在 2026 年 3 月退役了。
观察自愈行为
先在运行 port-forward 的终端按 Ctrl+C 结束转发(它连着的 Pod 可能正好是要删的那个),再删除一个 Pod:
|
|
Deployment 期望有两个副本。一个 Pod 被删除后,ReplicaSet 会创建替代 Pod。新 Pod 的名字和 UID 一定与旧 Pod 不同,IP 通常也会变化,但 Service 仍然通过标签找到可用副本。
清理实验
|
|
这个动作删除 Deployment、ReplicaSet、Pod 和 Service。它不会删除集群本身,也不会删除镜像仓库中的镜像。
不再需要实验集群时,再删除 kind 集群:
|
|
七、先记住这张关系图
flowchart TB
CP[控制平面<br>保存并协调集群状态]
CP -.控制器调谐.-> Deploy[Deployment]
CP -.调度器分配 Node.-> Kubelet[kubelet / Node]
Deploy -->|管理| RS[ReplicaSet]
RS -->|维持副本数| Pod
Kubelet -->|启动容器、上报状态| Pod
Svc[Service] -->|Selector 匹配标签| Pod
subgraph Pod[Pod]
direction TB
C[容器]
Net[共享网络]
Vol[(共享卷)]
end
最后用五句话总结一下:
- Kubernetes 用声明式 API 管理容器化应用的期望状态。
- 控制平面负责接收状态、保存状态、调度 Pod 和持续修正差异。
- Node 提供运行 Pod 所需的 kubelet、容器运行时和网络能力。
- Pod 是最小部署单位,容器在 Pod 内共享网络和生命周期边界。
- Deployment 管理 Pod 副本,Service 为变化的 Pod 提供稳定访问入口。