摘要: Kubernetes 的核心概念常被拆成资源清单来记忆,但真正决定行为的是两个底层机制:Spec 与 Status 的持续收敛,以及调度承诺与运行时约束的分离。本文从"资源即对象"的模型出发,梳理无状态与有状态服务的分野,拆解 Pod、Deployment、StatefulSet、DaemonSet、Job 五类工作负载的控制器逻辑,以及 Service 四层负载与 Ingress 七层路由的边界。最后聚焦两个最容易踩坑的实践点:声明式管理与命令式操作的适用边界,以及 requests/limits 如何分别影响调度和运行时行为,帮助你把"概念"转化为可预期的集群行为。
Kubernetes 的学习曲线陡峭,很大程度上是因为它引入了一整套新的词汇:Pod、Deployment、Service、ConfigMap、StatefulSet......如果把这些当成并列的名词来背,很快会陷入"知道定义但不知道怎么组合"的状态。
更有效的路径是抓住两个底层机制,让其余概念各归其位。
一、资源即对象:Spec 是承诺,Status 是现实

K8s 把集群中的一切抽象为资源,Pod、Service、Node 都是资源,而对象是资源的一个具体实例。理解一个对象,只需要看两个字段。
Spec 是期望状态,由你提供。创建对象时必须给出 spec,它描述"这个对象应该长什么样"。Status 是实际状态,由 K8s 自己维护,控制器持续观测并努力让实际状态向 spec 收敛。一个 Deployment 的 spec 写 replicas: 3,但当前只有 2 个 Pod 在运行,控制器就会创建一个新的 Pod 来补齐。某个 Pod 异常退出,控制器会重新拉起。这个"声明---收敛"的循环不是一次性的,而是持续运行的控制回路。
这个模型有一个容易被低估的推论:如果 spec 写错了,K8s 会忠实地收敛到一个错误的状态。资源定义的准确性,比命令执行的效率更重要。
二、无状态与有状态:不是部署方式的区别,是约束条件的区别
在进入具体工作负载之前,先区分两类服务形态,因为它们决定了你该选择哪一种控制器。

无状态服务的核心特征是对客户端透明、实例之间无依赖。Nginx 或 Apache 的任意一个副本处理请求的方式完全相同,没有本地状态需要保留。这使得扩容、缩容、迁移都可以随意进行,副本数可以按需增减。

有状态服务则相反,MySQL、Redis 的每个实例持有独立的数据,实例之间有主从关系或数据分片。在集群环境中,有状态服务需要处理主从选举、数据同步、备份和水平扩容的复杂性,这些约束直接决定了 StatefulSet 与 Deployment 的设计差异。
三、工作负载:五种控制器,五种对 Pod 的管理策略
Pod 是 K8s 中最小的可部署单元:一个或多个共享网络和存储资源的容器集合,被一同调度、一同运行在共享上下文中。但通常不直接创建 Pod,而是通过工作负载资源来管理。选择哪一种工作负载,取决于你要管理的对象具有什么特征。
Deployment 管理无状态副本。它封装了 ReplicaSet,由 ReplicaSet 保证实际副本数与声明一致。Deployment 额外提供滚动升级、回滚、暂停与恢复能力。滚动升级的核心机制是创建一个新的 ReplicaSet,逐步将流量从旧 Pod 迁移到新 Pod,而不是一次性替换所有实例。
StatefulSet 管理有状态副本。它与 Deployment 的关键差异在于稳定性保证:每个 Pod 拥有稳定的网络标识(DNS 格式为 statefulSetName-0...N-1.serviceName.namespace.svc.cluster.local)和稳定的持久化存储。Pod 按序号有序部署、有序扩展、有序收缩、有序删除。这些约束不是"额外功能",而是有状态服务数据一致性要求的直接映射。
DaemonSet 保证每个节点运行一个副本,典型用途包括日志收集(fluentd、logstash)、节点级监控(Prometheus Node Exporter)和系统组件(kube-proxy、kube-dns)。它的调度逻辑与副本数驱动的控制器不同:节点数量变化时,DaemonSet 自动在新节点上创建 Pod,在移除的节点上清理 Pod。
Job 和 CronJob 处理一次性任务和定时任务。Job 运行完成后 Pod 不再重启新容器;CronJob 在 Job 之上增加了定时触发能力。
五种控制器共享同一个底层逻辑:比较实际状态与期望状态,发起变更使二者收敛。差异在于它们对"什么算作期望"和"什么算作收敛"的定义不同。
四、服务发现:Service 解决"找到谁",Ingress 解决"怎么进来"

Pod 的 IP 是短暂的,重建后会变化,因此不能让调用方直接依赖 Pod IP。Service 为后端 Pod 组提供一个稳定的虚拟 IP 和 DNS 名称,将请求负载均衡到健康的 Pod 上。它是四层(TCP/UDP)的抽象,不感知应用层协议。
Ingress 解决的是另一个问题:集群外部的流量如何进入内部服务。它工作在七层,通过域名和路径规则将外部请求路由到不同的 Service,通常由 Ingress Controller(如 ingress-nginx)实现反向代理和负载均衡。

一句话区分:Service 管集群内部的"找到谁",Ingress 管外部流量的"怎么进来"。两者处于不同的网络层级,解决的问题不重叠。
五、配置与存储:把"环境相关"和"数据持久"从镜像中剥离
ConfigMap 存放非敏感的键值对配置,Secret 存放密码、Token、密钥等敏感数据,支持 Volume 挂载或环境变量两种注入方式。两者的设计目标一致:让同一个镜像可以在不同环境中以不同配置运行,而不需要重新构建。Secret 的三种常用类型包括 ServiceAccount(自动挂载到 Pod 用于访问 API)、Opaque(base64 编码的通用密钥)和 kubernetes.io/dockerconfigjson(私有镜像仓库认证)。
Volume 和 PersistentVolume 解决容器无状态假设的边界问题。容器本身是临时的,但数据库等有状态服务需要数据在 Pod 重建后仍然存在。PV/PVC 将存储资源的申请与挂载从 Pod 生命周期中解耦,CSI(Container Storage Interface)则提供了标准化的存储插件接口,使不同存储系统可以以统一方式接入。
六、两个值得在"概念"阶段就建立的习惯
6.1 声明式管理与命令式操作的边界
kubectl create 和 kubectl apply 看起来只是命令不同,但背后的管理模型差异很大。命令式操作直接作用于存活对象,没有历史记录,难以审计和复现;声明式管理基于资源清单文件,适合进入 Git 流程,支持变更评审、回滚和多人协作。
一个实用的边界是:临时调试和只读诊断(kubectl get、describe、logs)可以用命令式;生产环境的资源变更走声明式清单。同一个对象不应该混用两种管理方式,否则行为会变得不可预期。
6.2 requests 和 limits 是两个不同的问题,不是一个滑块的粗细
很多初学者把 requests 和 limits 当成同一个"资源上限"来填,但它们控制的是两套机制。
requests 是调度承诺:调度器根据 requests 之和判断节点是否有足够容量放置 Pod。它决定了 Pod 能不能被调度上去,以及 QoS 等级。limits 是运行时约束:CPU 超过 limit 会被节流(throttling),内存超过 limit 会被 OOMKill。
一个常见的误区是 requests 设得很小、limits 设得很大,以为"先占住位置再借用资源"。实际后果是调度层认为节点还有空间,把更多 Pod 塞了进来,运行时这些 Pod 争抢实际算力,长尾延迟上升。另一个方向是内存 limit 不设,内存泄漏的 Pod 会吃掉整个节点的内存,触发系统级 OOM,被杀的是谁取决于运气。
在概念阶段就建立这个区分,比等到生产事故之后再回头补课要便宜得多。
七、总结
K8s 的核心概念可以收敛为一条主线:Spec 声明期望,控制器驱动 Status 向 Spec 收敛,工作负载的差异在于它们对"期望"和"收敛"的定义不同。
Pod 是最小执行单元,但不建议直接管理;Deployment 管理无状态副本,StatefulSet 为有状态服务提供稳定标识与存储,DaemonSet 按节点铺开,Job/CronJob 处理批处理。Service 在四层提供内部稳定的访问入口,Ingress 在七层管理外部流量的进入方式。ConfigMap/Secret 把配置从镜像中剥离,PV/PVC 和 CSI 把存储从 Pod 生命周期中剥离。
两个实践习惯值得在入门阶段就建立:生产变更走声明式清单,临时诊断用命令式操作;requests 管调度,limits 管运行时。概念的真正价值不在于能背出定义,而在于看到一份 YAML 或一次调度结果时,能判断系统接下来会做什么、以及可能在哪里出问题。