资讯动态

Kubernetes Pod控制器完全拆解:从ReplicaSet到Deployment的可靠性机制

发布时间:2026/10/5 11:03:55 来源:尧图企业网站定制
如果你用Kubernetes的时间超过一年手里一定攒着不少“祖传”的YAML。我最开始入门K8s的时候图省事经常直接kubectl run nginx --imagenginx几十秒拉一个Pod起来感觉一切都很简单。直到有一次线上节点因为硬件问题被回收整批Pod瞬间消失我才突然意识到一个一直被自己绕开的问题裸Pod在Kubernetes里几乎不具备自愈能力真正让人睡得着觉的是它背后的Pod控制器。这篇文章我想把Kubernetes的Pod控制器从头到尾拆一遍包括最常见的ReplicaSet、Deployment、StatefulSet、DaemonSet、Job和CronJob配套滚动更新、回滚、优雅停机以及一个用nginx跑通的完整部署实例最后再聊几个我在生产环境里踩过的坑。内容适合两类读者一是刚把K8s用过一阵、想彻底理解“为什么Pod不会自己复活”的同学二是已经会用Deployment但没系统梳理过控制器机制或者被StatefulSet、Job细节坑过的兄弟。看完你至少能回答一个问题同样一个YAML为什么用控制器包一层之后可靠性立刻就不一样了。1. 当我们绕过控制器直接建Pod时到底在悖离什么1.1 裸Pod不是不能用而是没有“重生”能力先聊一个最容易被新同学忽略的事实Pod本身是被设计成短生命周期对象的。你直接用kubectl apply -f pod.yaml创建的Pod其实是Kubernetes里的“最小调度单元”它的宿主机上的kubelet只负责一件事——保证这个Pod里的容器在本节点上处于运行状态。也就是说Pod被删除、节点宕机、或者容器异常退出都不会有人主动帮你重建。很多从传统运维转过来的朋友会觉得重启容器、拉起服务这些事不应该是K8s的基本操作吗结果发现裸Pod挂了一小时没人管第一反应是“Kubernetes是不是有问题”。其实问题不在K8s而在我们没搞清楚它的分工调度、恢复、发布这类有状态管护逻辑全部属于控制器的工作范围。Deployment管理的Pod和裸Pod最大的区别就是前者有一个人在外面盯着副本数量只要实际数量低于期望值马上把缺的Pod补出来补出来的Pod虽然名字和IP都变了但它照样能接管流量。这也解释了为什么K8s官方推荐所有工作负载都尽量用Deployment等控制器去管理而不是直接丢出一个裸Pod。做一个不恰当的类比裸Pod像你托邻居照看的猫邻居只是每天喂一次猫跑丢了不负责找控制器则像一个宠物管家时刻清点数量、确保每只都在位少一只就立刻去领养同品种的补上。在虚拟化时代虚拟机是“宠物”坏了要修在容器时代Pod是“牲口”坏了直接换一头。理解这句话基本就理解了一半的控制器思想。1.2 控制循环期望状态与当前状态不断“对账”控制器的底层机制并不神秘本质上就是一个无限循环拿对象的期望状态spec和集群里的实际状态status不停对比发现不一致就做操作直到调谐完成。这个模式在K8s里面叫Reconcile Loop。你可以把它理解成类似数据库的主从同步控制器就是消费变更事件的消费者。从一次kubectl apply开始链路大致是这样的kubectl把YAML发给API ServerAPI Server校验通过后存入etcd然后触发一次watch通知。控制器管理器kube-controller-manager里对应的控制器通过informer监听这些事件拿到最新的Deployment对象读它的期望副本数再去查当前相关的ReplicaSet和Pod数量发现少了就发出“创建Pod”的指令。调度器看到新Pod后为它选一台合适的节点节点上的kubelet接着拉镜像、启动容器。整个流程是事件驱动加状态对比的双重机制好处是即使某一次消息丢了控制器在定期重新list时依然能发现状态漂移再调谐一轮。所以你会发现K8s本身就是声明式API的典范我们根本不需要告诉系统“先把旧Pod停掉再启动新Pod保持3个副本”我们只需要声明“我的Deployment要有3个副本使用nginx镜像”剩下怎么达成、什么时候补偿控制器自己会安排。反过来这也提醒我们所有写在spec里的期望值都极其重要因为它们就是控制器的“对账单”。有些同学在一个Deployment里只写replicas和template其他交给默认值其实很多坑就藏在那些默认值里。2. 六类控制器的本职分工用场景选工具而不是凭感觉K8s里的Pod控制器远不止一个Deployment。官方文档里归为工作负载资源的六类控制器分别覆盖了无状态服务、有状态服务、节点级守护进程、一次性任务和定时任务。很多人一上来就用Deployment顶一切结果有状态服务被Deployment搞得数据混乱、一次性迁移也被滚动重启折磨问题不在于K8s而是选型没对。控制器职责定位典型场景核心特征ReplicaSet维护固定副本数很少直接使用多被Deployment调用只保数量不做发布Deployment无状态工作负载发布Web服务、API服务、微服务滚动更新、回滚、暂停StatefulSet有状态工作负载数据库、消息队列、缓存集群固定名字、确定性顺序、独立存储DaemonSet每节点运行一个Pod日志采集、节点监控、网络插件节点增删自动联动Job完成即退出的任务数据迁移、批量计算、压测运行到成功次数达标CronJob定时执行的Job定时备份、定期清理标准cron表达式调度2.1 ReplicaSet最朴素的Pod副本保底机制ReplicaSet这个名字本身就说明了一切它的工作就是确保标签匹配的Pod副本数始终保持为期望值。核心字段就三个replicas、selector、template。selector用来圈定“哪些Pod归我管”template则是“如果不够照这个模板扩”。当副本数多了它删除多余的少了它创建缺的。但实际生产里直接操作ReplicaSet的人非常少。为什么因为它只解决了“数量保底”的问题不解决版本升级。你想换个镜像版本ReplicaSet本身没有任何滚动发布的能力你得手改template然后重新apply它会先删掉旧的一组再建新的高级点的发布策略全得自己实现。所以Deployment才在ReplicaSet外面又包了一层用一个新ReplicaSet承载新版本Pod再逐步缩掉承载旧版本的ReplicaSet。这也是为什么你在执行kubectl get rs时会看到好几个ReplicaSet它们其实是一个Deployment的不同“代际”。新同学看这个现象经常一脸懵以为是垃圾资源其实它们是滚动更新的历史版本快照很有用。2.2 Deployment无状态服务的发布首选Deployment是生产环境里使用频率最高的控制器。它管理ReplicaSet而ReplicaSet管理Pod两层结构的价值在于每次变更Pod模板时Deployment会创建一个新的ReplicaSet让新一代Pod先起来然后按滚动策略慢慢缩旧代际整个过程可回滚、可暂停、可审计。无状态服务是Deployment的主战场因为它天然不关心某个请求落在哪个“副本”身上。Web服务、API网关、定时任务的前端、纯计算逻辑的worker只要你接受“Pod重启后IP变了、名字变了、本地数据丢了也无所谓”Deployment就是首选。它配合Service使用时访问方只需要稳定记住Service的虚拟IP或DNS名后端Pod怎么换都无所谓。这也是为什么几乎所有K8s入门教程第一个示例都是Deployment加nginx因为它把声明式发布、健康检查、扩容缩容这些最核心的能力全部展示出来了。2.3 StatefulSet为有序、稳定、持久而存在StatefulSet是唯一一个能在K8s里把Pod当“有状态”对象对待的控制器它的三个核心保证是稳定且唯一的网络标识、稳定且独立的存储、以及确定性的部署和缩放顺序。什么是稳定唯一它的Pod名字不是随机字符串而是类似web-0、web-1这种带序号的固定名字即使Pod被删除重建新Pod还是叫web-1并且可以通过无头服务解析到同一个DNS名。很多人问我要部署一个三节点数据库为什么不能用Deployment加PVC因为你没法保证每次重建后哪个Pod挂哪块卷也没法保证节点启动顺序。数据库集群往往要求每个实例知道自己是谁、谁是主、谁是从名字一变就全乱了。StatefulSet的volumeClaimTemplates会为每个副本自动创建独立PVC并且绑定关系跟着Pod名字走web-0永远挂>readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 20 periodSeconds: 10initialDelaySeconds要按业务的启动时间留足余量。之前同事把Java服务的liveness探针配成1秒一次、initialDelay设2秒结果容器还没启动完就连续探测失败直接被K8s杀掉陷入反复CrashLoopBackOff。教训就是探针不是越灵敏越好。3.3 progressDeadlineSeconds、暂停发布与回滚Deployment还自带一套“超时叫停”机制叫progressDeadlineSeconds默认600秒。如果滚动更新在600秒内没有任何进展Deployment控制器会把状态标记为ProgressingFalse然后告诉你“更新卡住了”。常见原因是新Pod一直启动失败或者PVC创建不出来、资源配额不够。我很建议在CI/CD流水线里专门检查kubectl rollout status deployment/xxx --timeout300s超时就失败而不是等它无限挂在那里。暂停发布也很有用kubectl rollout pause deployment/xxx会让当前滚动更新停在半路新版本Pod已经在运行但旧版本Pod不会被继续缩容流量也只进部分新Pod。配合Service的流量配置可以实现很简单的金丝雀验证。确认没问题后执行kubectl rollout resume deployment/xxx让更新继续走完。如果验证不通过直接用kubectl rollout undo deployment/xxx --to-revision上一个版本号回滚。这里要注意回滚本身也是一次Deployment模板变更它不会去恢复已经停止的旧ReplicaSet而是把旧的模板重新圆成一个新ReplicaSet来发布。4. 一个nginx Deployment从零到上线完整演示控制器的常规操作4.1 最小可用的Deployment YAML一份能跑起来的模板理论聊了一堆不如直接上手验一遍。我习惯用nginx来做这套演示因为它镜像小、启动快、行为好观察。先准备一个最简DeploymentapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80这里有个必须严格注意的点selector里的label必须和template.metadata.labels完全匹配。Deployment创建时API Server就会校验这一点不匹配直接拒绝创建。很多人以为Selector只是给人看的注释实际它是ReplicaSet找Pod的唯一依据。执行kubectl apply -f nginx-deploy.yaml kubectl get deploy,rs,po你会看到Deployment、一个ReplicaSet、三个Pod依次出现。Pod名形如nginx-deploy-xxxxx-yyyyy中间那段是ReplicaSet的随机后缀最后那段才是Pod自己的随机标识。4.2 apply之后发生了什么控制器在背后做了四件事趁着刚才的apply我们把这条链路上每个角色分别做了什么串一遍。API Server收到YAML后先做准入校验比如检查Deployment是否合法、镜像名是否规范然后写进etcd。Deployment控制器watch到新Deployment发现没有对应的ReplicaSet于是按模板创建一个。ReplicaSet控制器看到ReplicaSet期望3个副本当前Pod数量是0就去创建3个Pod对象。Pod创建出来后Pod调度器从候选节点里选出合适的机器kubelet负责在对应节点上拉镜像、起容器。我们平时一条kubectl apply好像很轻松背后其实是四个组件接力完成的。这个流程也解释了为什么控制器能自愈当某个Pod被删除时ReplicaSet控制器只需要watch到“数量变少”这条事件就会重新创建一个一模一样的Pod出来。整个过程完全不需要人工介入。4.3 模拟故障手动删掉一个Pod看系统怎么自愈现在我们来验证一下。执行kubectl delete pod nginx-deploy-xxxxx紧接着开另一个终端跑kubectl get pods -w你会看到被删的这个Pod状态变成Terminating但与此同时一个新的Pod名字已经进入ContainerCreating再等一会儿变成Running。这说明自愈背后其实不是“原地复活”而是“照模板重建”。新的Pod换了名字、换了IP对外部访问方来说唯一不变的只有Deployment和Service。如果你在YAML里写死了某个Pod的IP去访问它发布和故障恢复后就会断连这也是我们必须配合Service使用的原因。4.4 通过Service暴露nginx控制器和稳定入口的配合既然Pod IP不可靠我们就要给Deployment配一个稳定入口。创建一个ServiceapiVersion: v1 kind: Service metadata: name: nginx-svc spec: selector: app: nginx ports: - port: 80 targetPort: 80 type: NodePortService通过selector里的app: nginx找到所有由Deployment管理的Pod然后把集群内访问nginx-svc:80的流量转发到某个Pod的targetPort。这里再强调一次标签是整个控制系统的“挂载点”Service找Pod靠它ReplicaSet圈养Pod也靠它所以千万不要随便改动由控制器管理Pod的标签否则会出现“系统里明明有Pod但Service怎么都选不中”的诡异问题。4.5 常用的扩容、升级、回滚命令速记日常操作里最常用的就是下面这几个命令我整理成一个速查表操作命令扩容/缩容kubectl scale deployment/nginx-deploy --replicas5查看滚动状态kubectl rollout status deployment/nginx-deploy更新镜像版本kubectl set image deployment/nginx-deploy nginxnginx:1.26查看发布历史kubectl rollout history deployment/nginx-deploy回滚到上一版kubectl rollout undo deployment/nginx-deploy回滚到指定版本kubectl rollout undo deployment/nginx-deploy --to-revision1暂停/恢复更新kubectl rollout pause / resume deployment/nginx-deploy注意set image命令里那个nginx是容器名它必须和Deployment里定义的name: nginx一致。如果你忘了容器名可以先kubectl get deploy nginx-deploy -o yaml看一眼或者直接改YAML再apply效果一样。5. StatefulSet当Pod不再可替换它如何保证“三稳定”5.1 稳定标识固定名字和无头服务Deployment管理下的Pod是“出厂即失忆”的——名字随机、IP随机、身份全靠标签。但有些系统需要Pod拥有稳定身份比如Kafka的broker要用序号标识自己是几号节点etcd集群要能通过固定域名找到彼此。StatefulSet就是为了解决这类问题诞生的。先看一个最小的StatefulSet长什么样apiVersion: v1 kind: Service metadata: name: nginx-svc spec: clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: nginx-svc replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25serviceName指定了一个无头服务clusterIP为None它不提供虚拟IP而是直接把DNS解析到每个Pod的IP。这样集群内访问web-0.nginx-svc.default.svc.cluster.local时就能稳定到达编号为0的那个实例。即使Pod被删掉重建新Pod还会叫web-0DNS记录自动恢复指向新IP。这种固定DNS名配合各节点的成员探测是有状态集群做节点发现的基础。5.2 有序部署和逆序回收StatefulSet的默认管理策略是OrderedReady。什么意思如果你创建了一个replicas3的StatefulSet它不会像Deployment那样一次性并行拉起3个Pod而是先启动web-0等它达到Ready状态后再启动web-1最后才启动web-2。缩容则反过来先删web-2再删web-1最后删web-0。这个顺序保证了对“初始化依赖顺序”的集群比如数据库主从里0号节点可能是主库1、2号是从库它希望主库先起来不能让从库先去连一个不存在的节点。如果业务不需要这种严格的顺序可以把podManagementPolicy改成Parallel让它像Deployment一样并行创建和删除提升扩容速度。但对大部分有状态中间件默认的OrderedReady才是安全的选择。5.3 volumeClaimTemplates存储跟着Pod名字走StatefulSet另一大杀手锏是volumeClaimTemplates。它可以像模板一样为每个Pod生成一个独立的PVC并且PVC命名规则直接和Pod名字绑定volumeClaimTemplates: - metadata: name: data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 1Gi创建后web-0会自动挂载名为>

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑