Kubernetes ReplicaSet深度解析:从Pod副本管理到故障自愈机制
发布时间:2026/9/28 15:12:12 作者:尧图编辑部 阅读量:1,286

1. 手动管理Pod的那段日子ReplicaSet到底在解决什么问题1.1 只创建Pod的暴力做法应用挂了就只能认栽刚玩Kubernetes那阵子我干过一件特别傻的事写了一个nginx的Pod YAMLkubectl apply -f起了三个Pod然后一整天就盯着终端看生怕哪个Pod突然退出。那时候运维群里天天有人喊节点宕了容器OOM了应用半夜挂了我第一反应都是回去重新apply一遍YAML。这种手动重建的方式在实验环境还能撑一下放到企业环境基本就是灾难。你自己想一套订单服务要跑20个副本凌晨两点挂掉3个服务降级你能忍就算你能忍谁深更半夜爬起来执行那段重建命令这种操作背后本质上暴露了一个关键问题——Pod是一次性资源它的宿主机没救回来这个Pod就没了它本身崩溃退出也没有任何东西会把它拉起来。Kubernetes叫容器编排平台如果连最基本的维持副本数量都做不到那编排两个字就名不副实了。ReplicaSet就是来解决这个问题的。它做的事情通俗地讲就一句话你告诉它我要3个满足条件的Pod它就不分昼夜地保证集群里始终有恰好3个这样的Pod。多了一个就删掉少了一个就补上死了一个就再拉一个起来。这个机制奠定了Kubernetes里所有无状态工作负载的基础。你平时用的Deployment、StatefulSet、DaemonSet底层其实都躲不开ReplicaSet这套管理逻辑。1.2 一个管家该有的三样东西副本数、选择器、Pod模板ReplicaSet的YAML看起来结构简单但三个核心字段缺一不可我分别说一下它们在企业环境里各自承担什么角色。spec.replicas期望的Pod副本数量。它只是一个数字控制器会以这个数为基准不断比对集群里的实际数量。spec.selector选择器。它决定了ReplicaSet管哪些Pod。ReplicaSet靠Pod上的label来识别自己的手下。你只要给Pod打上对应的标签它就会被这个ReplicaSet接管哪怕这个Pod根本不是这个ReplicaSet创建的它也会被收编。spec.templatePod模板。这是ReplicaSet创建新Pod时用的图纸——每次需要补齐副本时都照着这张图纸捏一个Pod出来。这三个字段的关系我用一个不太严谨但很好理解的类比说明ReplicaSet像一个带工头的施工队replicas是甲方要求的楼层数selector是工头认人的标准戴红帽子的归我管template是盖楼用的图纸。楼层不够就按图纸盖楼盖多了就拆掉不戴红帽子的楼工头每天都在工地数人头。1.3 ReplicaSet在企业集群中的真实定位在企业生产集群里你直接见到的ReplicaSet往往不是自己写出来的而是Deployment创建出来的。很多人查完Pod之后会发现一堆名字带随机后缀的rs-xxxxx第一反应是我什么时候创建了这玩意儿。其实Deployment本身不直接管Pod它管的是ReplicaSet由ReplicaSet去管Pod。这个层级关系保证了Deployment可以做滚动更新、可以回滚——每次发版本Deployment都新建一个ReplicaSet新RS的Pod慢慢顶上旧RS的Pod慢慢撤下回滚的时候直接把旧RS的副本数扩回来就行。所以我的建议是只要你不是在做控制器本身的开发日常工作中请把ReplicaSet当作一个底层机制来理解而不是当作一个操作入口。但是底层机制一定要吃透否则你排查Deployment滚动更新卡住、Pod数量异常这些问题时会像无头苍蝇一样乱撞。2. 拆开RS的黑盒子控制循环与选择器匹配的底层逻辑2.1 控制循环怎么把期望状态变成集群里的现实状态ReplicaSet能维持副本数靠的是Kubernetes里最经典的控制器模式Control Loop。我调试的集群版本是v1.26.0这套机制从早期版本到现在基本没变过后面我会提到的大多数命令在1.20到1.30系列都能通用。控制循环大致是这样的流程ReplicaSet控制器运行在kube-controller-manager这个组件里通过informer机制持续监听ReplicaSet和Pod的增删改事件。一旦发现某个ReplicaSet的期望副本数和实际匹配Pod数不一致控制器就把这个ReplicaSet放进自己的同步队列。控制器从队列里取出这个对象计算当前的Pod数量、检查每个Pod的状态然后调用kube-apiserver创建或删除Pod。这个机制最巧妙的一点是事件驱动而不是定时轮询。也就是说Pod挂了、Pod被删了、节点漂移了这些事件会马上触发控制器去校正。你不太需要担心控制器反应迟钝的问题生产环境里Pod删掉之后几秒钟内就会被重新创建出来。不过这里有个很容易被忽视的细节控制器判断要不要创建Pod看的不是这个ReplicaSet以前创建过几个Pod而是集群中还有多少Pod的标签匹配这个ReplicaSet的selector。换句话说它只认标签不认亲爹。这既是一个很灵活的设计也是一颗埋在配置错误里的雷。2.2 选择器的匹配规则标签不是装饰品ReplicaSet的selector支持两种写法matchLabels和matchExpressions。matchLabels最常用写法就是键值对精确匹配多个标签之间是AND关系。举个例子selector: matchLabels: app: nginx tier: frontend这段代码要求匹配的Pod必须同时带appnginx和tierfrontend两个标签。只满足其中一个是不行的控制器算副本数的时候不会把它算进去。matchExpressions更灵活支持In、NotIn、Exists、DoesNotExist这几种操作符。比如下面的写法表示挑出所有环境标签不是prod的Pod来管理selector: matchExpressions: - key: env operator: NotIn values: - prodmatchExpressions和matchLabels可以同时存在多个条件之间同样是AND关系。这里有一个我在企业排障中反复强调的要点ReplicaSet的selector一旦创建就不可修改这是API设计的硬限制。你想改selector删掉这个ReplicaSet重新建一个吧。为什么这么设计因为允许运行中的控制器随意改匹配规则会造成Pod管理归属的混乱——一个RS原本管着10个Podselector一改那10个Pod瞬间变成没人要的孤儿又有10个别人的Pod可能被它强行接管这是生产环境里绝对不能接受的。2.3 ReplicaSet的边界只管数量不管死活之外的事很多人对ReplicaSet有一个误解以为它像一个自动恢复机器人Pod里的容器挂了它会去重启。其实不是的ReplicaSet管不了这么多。它关注的只有一件事匹配这个selector的Pod总数是否等于期望副本数。如果容器里的进程崩溃了但Pod本身还处于Running状态ReplicaSet数人头的时候发现数量没变它就不会做任何动作。容器进程崩溃后的重启通常要交给Pod里的restartPolicy来处理默认的Always策略会让kubelet尝试重启容器这跟ReplicaSet没有关系。而当Pod整个被删除、节点宕机导致Pod被驱逐、或者有人手动把Pod标签改掉了导致它不再匹配selector时ReplicaSet才会介入。也正是因为这个边界的存在生产环境里你才需要搭配存活探针和就绪探针让Pod在看似活着但实际上已经无法服务的时候被标记为不健康触发重启或从Service后端摘除这个话题我在后面的踩坑章节会展开聊。3. 动手实验从第一份YAML到故障自愈全流程3.1 第一份RS的YAML应该怎么写纸上谈兵说得再多不如自己敲一遍。下面这份YAML我建议你直接存下来作为实验基准apiVersion: apps/v1 kind: ReplicaSet metadata: name: nginx-rs-demo namespace: default spec: replicas: 3 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80注意一下版本apiVersion用的是apps/v1早期版本的extensions/v1beta1早就废弃了新集群里不要再写。selector部分模板里的Pod标签一定要能匹配上selector这是一个新手经常犯的低级错误——写完selector和template之后两台对不上导致ReplicaSet创建半天Pod数量还是0。这个文件创建之后控制器会发现集群里没有任何Pod带appnginx-demo这个标签于是按照template的图纸连续创建3个Pod。3.2 创建与验证常用观测命令大盘点创建命令没什么好说的kubectl apply -f rs-demo.yaml验证的时候别只看一眼Pod列表就完事我建议养成一套标准的观测顺序# 看ReplicaSet整体状态 kubectl get rs nginx-rs-demo -o wide # 看控制器当前期望数量和实际数量 kubectl describe rs nginx-rs-demo # 看它管理的Pod以及Pod归属 kubectl get pods -l appnginx-demokubectl describe rs输出的下半部分非常关键里面有ReplicaSet控制器在运行过程中记录的Events比如成功创建Pod成功删除Pod副本数不满足目的地状态等信息。这些事件是排障的起点但很多同学一上来就盯着Pod日志看方向就反了。3.3 扩缩容的三种姿势在ReplicaSet上做扩缩容有三种常见方式我逐一列一下企业里都会用到第一种原地修改YAML然后用applyvim rs-demo.yaml kubectl apply -f rs-demo.yaml把spec.replicas改成5apply之后控制器会立刻把Pod数量补齐到5。第二种用scale命令直接指定副本数kubectl scale rs nginx-rs-demo --replicas5这种方式的本质是修改ReplicaSet的/scale子资源。HPA横向自动扩缩容在底层调用的也是这个接口后面我会展开说。第三种用patch做局部修改kubectl patch rs nginx-rs-demo -p {spec:{replicas:5}}三种方式的效果完全一样选哪种取决于你当时的工作习惯。需要写成基础设施即代码IaC时用第一种应急扩缩容时用第二种脚本自动化时用第三种。3.4 亲测Pod故障自愈把Pod删掉看会发生什么实验做到这一步最爽的时刻来了。随便删一个ReplicaSet管理的Podkubectl delete pod nginx-rs-demo-xxxxx几秒钟内你再执行kubectl get pods会发现一个全新的Pod出现在列表里名字后缀跟之前那个完全不一样。ReplicaSet通过控制器循环感知到了Pod数量从3掉到了2立刻按图施工补了一个新的。这个自愈动作不需要任何人工介入我在生产环境里验证过很多次把Pod删了新Pod生成的速度通常在12秒级别如果集群本身负载比较重可能会慢一些但不至于等很久。这里还可以做一个更极端的实验把Pod上匹配selector的标签改掉比如把appnginx-demo改成appnginx-demo-other。你会发现ReplicaSet根本不跟你商量直接再创建一个新Pod。而被你改掉标签的那个Pod因为它已经不再匹配任何ReplicaSet的selector就变成了一个孤儿Pod孤零零地留在集群里没人管理它。这种孤儿Pod在生产故障中经常出现后面我会专门讲它的危害。3.5 修改模板之后的一个大坑旧Pod不会被自动替换很多第一次使用ReplicaSet的人会踩一个隐蔽的坑改了template里的镜像版本然后apply发现Pod列表毫无变化。这其实不是bug而是ReplicaSet的天然行为template只影响以后新建的Pod已经存在的Pod统统一律不动。你想让新镜像生效手动把这几个Pod挨个删掉让ReplicaSet按新模板重建或者直接把整个ReplicaSet删掉重建。你可以自己实验一下把image从nginx:1.25改成nginx:1.26然后apply再观察Pod的镜像版本你会发现老Pod纹丝不动。这个体验跟Deployment的滚动更新相比简直天差地别这也是为什么真实企业环境里几乎没人直接裸用ReplicaSet——它真的只负责保数量至于如何平滑升级这件事它压根不擅长。4. 企业环境里的RSDeployment的“幕后管家”与发布策略4.1 Deployment与RS的父子关系你看到的那堆RS才是发布本体前面提到过Deployment不直接管Pod它管ReplicaSet。这句抽象的话在kubectl get rs的输出里是能直观看到的kubectl get rs -n your-namespace你会看到每个Deployment下面挂着好几个ReplicaSet名字通常是deployment名称-模板哈希。模板哈希是根据Pod template算出来的template一变哈希就变Deployment就会新建一个RS。这里有一个很多人在面试或者实际排障中翻车的问题为什么Deployment要设计成Deployment管RS、RS管Pod这样的两层级式结构我的理解是Deployment需要版本化发布过程。每一次YAML变更、每一次滚动更新都是基于一个模板的变更。如果把Pod模板直接绑在Deployment上就没有办法保留上一个版本的Pod长什么样这个信息。有了RS这一层中介Deployment的每次发布都会留下一个历史RS里面冻结了当时那份Pod模板。回滚的时候Deployment只需要把流量从当前RS切回历史RS把旧RS的副本数扩起来、把新RS的副本数缩下去整个发布状态就退回去了。这是非常聪明的设计。4.2 滚动更新过程中的RS数量变化一升一降的节奏美学默认情况下Deployment的滚动更新是先建新RS再缩旧RS的渐进过程。我以一批业务服务为例假设副本数10、maxSurge25%、maxUnavailable25%新RS先被创建出来期望副本数先从0变成某个低于10的过渡值。新RS的Pod逐步Ready之后旧RS的副本数开始逐步下调。整个过程中总Pod数会维持在1012之间maxSurge允许临时超出不可用的Pod数控制在2个以内maxUnavailable允许临时减少。如果你在滚动更新过程中不断执行kubectl get rs你会看到新旧两个RS的副本数像跷跷板一样一个往上走另一个往下走。这时候如果哪个RS卡住了比如新Pod一直Pending、一直ImagePullBackOff滚动更新就会卡在原地不动Deployment会一直维持这个新旧并存的状态。这时候排查目标就非常明确了问题出在新RS而不是Deployment本身。4.3 为什么企业不建议直接操作RS版本管理的底线我在企业里做分享的时候经常说一句话Deployment是正规军裸RS是游击队。直接操作ReplicaSet意味着你放弃了几个企业级的关键能力第一放弃了滚动升级能力。前面实验里验证过改RS的template不会影响旧Pod你要手动删Pod或者整个重建业务会短暂中断。第二放弃了版本记录和回滚能力。Deployment天然保留历史RS你可以一条命令回滚到上一个版本裸RS没有这个机制你改坏了只能凭记忆恢复。第三放弃了发布策略的精细控制。Deployment的strategy字段里支持RollingUpdate、Recreate两种策略配合maxSurge和maxUnavailable可以精确控制发布节奏。裸RS完全不存在这些概念。所以在生产环境里我的建议非常明确除非你写的是一个自定义控制器或者明确知道自己在干什么否则永远不要手写ReplicaSet来承载业务。让Deployment去创建RS你在旁边做监督和排障就够了。4.4 资源配额、探针与HPA联动RS在企业中的配套玩法ReplicaSet虽然不管资源配额和健康检查但它创建的Pod必须满足这些约束才能正常存活。企业环境里我会强调三件事第一件事是requests和limits必须配好。RS创建Pod时如果template里没有写资源请求调度器会按零请求处理。生产中常见的情况是节点上明明有资源Pod却一直Pending因为requests超出了节点的可分配量。反过来limits不设容器OOM了ReplicaSet也只能按流程重建业务闪断还是会照常发生。第二件事是readinessProbe和livenessProbe必须配好。我见过无数案例RS的副本数一直是正常的3/3但Service访问就是报错因为容器进程活着但内部服务已经不可用。就绪探针失败时Pod不会从Service的Endpoints里摘除吗会前提是你配置了readinessProbe没配置的话Kubernetes会默认Pod进入Ready状态流量照打故障自然就看起来正常。第三件事是HPA按需扩缩容落地在Scale子资源上。HorizontalPodAutoscaler通过调ReplicaSet的/scale子资源动态修改副本数。这里有一个经典冲突要提醒你如果运维同事手动kubectl scale把副本数改成了10而HPA的期望副本数是3两边的期望状态打架最终会以HPA的控制周期为准被拉回。解决方案是别手动scale由HPA管控的资源副本要调也是调HPA的min/max值。5. 踩坑实录副本数显示正常但Pod就是不Ready5.1 排障第一原则先看Event再看日志最后看配置每次有同事跑过来跟我说集群出问题了我第一句话都是先把Event贴出来。很多人习惯先去看Pod日志、看容器状态这其实绕了远路。ReplicaSet相关的故障事件里通常写得非常清楚。执行这条命令kubectl describe rs rs-name -n namespace我见过太多的事故排查是在Event里一眼就能定位的比如Failed to pull image0/5 nodes are available: 2 Insufficient cpu, 3 Insufficient memoryReadiness probe failed每一条都指向非常具体的故障类别。Event看完再去kubectl logs或者kubectl get pod -o yaml深入分析效率会高很多。5.2 镜像拉取失败最没有技术含量但最高频的企业问题这一类故障在Event里会看到ImagePullBackOff或ErrImagePull单纯看Pod状态的话它表现为ContainerCreating卡住不动ReplicaSet一直在报副本数不满足。我总结过几个最常用的原因按出现频率排序镜像Tag不存在。把nginx:1.26写成了nginx:latest后来又改成nginx:1.26.0但镜像仓库里没有1.26.0这个Tag。私有仓库认证没配好。imagePullSecrets没写或者secret过期了kubelet拉私有镜像拿不到凭证一直403。镜像仓库网络不通。企业在内网拉镜像时节点访问不到仓库地址或DNS解析失败。Tag漂移。生产环境最忌讳写latest这种可变Tag因为每次拉取到的镜像内容可能都不一样ReplicaSet重建Pod时可能会拉到不同版本版本一致性直接被打乱。排查方式很简单kubectl describe pod pod-name看一下Events或者直接docker pull那个镜像在节点上手动验证。企业里的规范做法是把镜像Tag钉死到具体版本号并且把私有仓库的拉取凭证提前配置好。5.3 资源不足导致PendingEvent会直接告诉你差多少ReplicaSet副本数正常但执行kubectl get pods时能看到一个Pod状态卡在Pending。Event里通常会写0/6 nodes are available: 2 Insufficient cpu, 4 Insufficient memory.注意这组数字它意味着调度器已经把集群里所有节点都算了一遍没有一个节点同时满足CPU和内存的剩余资源。这种故障在早高峰流量上涨或者多个应用同时发布时尤其常见。遇到这类问题排障思路是看节点真实资源使用情况kubectl top node需要metrics-server没有就kubectl describe node看Allocated resources。看是不是某个节点被taint污点标记调度器在默认情况下不会把Pod调度到带污点的节点上。评估requests是不是设置过高。有些开发在写资源请求时喜欢拍脑袋给个8核16G一个Pod就把节点压满了浪费又危险。5.4 就绪探针失败副本数达标但流量异常的幕后黑手这个坑我单独拎出来说因为它最具迷惑性。现象是kubectl get rs显示的副本数是3/3kubectl get pods也是3/3 Running但访问Service就是间歇性报错。问题通常出在readinessProbe上。如果探针配置不合理比如超时时间太短、初始等待时间不够、探针路径写错那么Pod的READY列可能是0/1跟Running状态共存。kubectl get endpoints看Service后端时会发现Available的Pod少于3个甚至有0个。ReplicaSet只关心Pod的数量并不会因为就绪探针失败就去删除或重建Pod它认为我该管的3个Pod都在那里数量是对的剩下的事交给Service和下探针机制去处理。你要修正的是探针参数不是指责ReplicaSet。我在生产里常用的做法是先把探针的initialDelaySeconds稍微调大一点确保应用启动完成后再开始探测。5.5 选择器误配引发“同室操戈”RS接管了别人的Pod这一类坑属于配置层面的低级错误但破坏力很大。场景是这样的团队A部署了一个带有apporder-service标签的Deployment团队B后来也写了一个ReplicaSetselector里也写了matchLabels: {app: order-service}。结果B的ReplicaSet一创建就把A的Deployment下的一部分Pod当成了自己的手下开始按自己的副本数删减它们。A的Pod突然少了A的Deployment控制器会立刻补建B的ReplicaSet也会因为数量不对而继续调整。两个控制器为了同一批Pod打架整个命名空间里Pod数量上下跳服务时好时坏这就是典型的控制器同室操戈。我在前面的原理章节里说过ReplicaSet只认标签不认亲爹这就是它在实际环境里的负面体现。企业里要避免这个问题核心手段是严格管控label命名空间和selector。各业务团队用自己专属的前缀作为标签名例如app.kubernetes.io/name、app.kubernetes.io/instance这类语义化标签而不是人人通用的appxxx。还有一个兜底办法创建ReplicaSet之前先查一下当前集群中是否已有Pod带相同标签kubectl get pods -l apporder-service --all-namespaces如果查出来一堆不是你的Pod那你的selector一定要改不要往枪口上撞。5.6 孤儿Pod的危害删了RS但保留Pod的诡异状态还有一种企业里常见的场景有人删除了一个ReplicaSet但使用了--cascadeorphan参数比如kubectl delete rs rs-name --cascadeorphan这个命令的意思是删除ReplicaSet本身但保留它下面那些Pod不让垃圾回收机制级联删除。执行完之后你会发现一堆Pod仍然在Running但它们的ownerReference指向的那个RS已经不存在了变成了一堆无人认领的Pod。这种孤儿Pod非常危险它们不受任何控制器管理你手动删掉就删掉了永远不会被重建。如果这些Pod恰好带了旧镜像、带着错误的标签混在Service后端里流量还是会被派到它们头上造成难以排查的诡异故障。遇到孤儿Pod处理办法也简单要么手动标注好标签新建一个ReplicaSet或Deployment把它们接管起来要么直接删掉它们让真正的Deployment按模板重建。反正我的建议是别留这种三不管的在集群里清理干净再睡踏实觉。6. 哪些场景能直接绕开Deployment只用ReplicaSet6.1 我认为可以直接裸用RS的少数场景前面章节反复强调企业里不要直接操作ReplicaSet但这话不能说死。在我实际接触过的项目里有几种场景裸用ReplicaSet是合适的甚至比用Deployment更干净。一是自定义控制器场景。如果你自己写了一个Kubernetes Operator或Controller需要管理一组无状态Pod并且你打算自己实现发布策略和版本管理那你不依赖Deployment直接用ReplicaSet更纯粹。比如某些定时任务系统或者分布式计算框架它们希望Pod生命周期完全受自己掌控Deployment那套自动滚动反而不是它们想要的。二是只关心数量、完全不关心版本的临时服务。比如一个性能压测集群压力机不需要滚动升级坏了就按模板重建。这种场景创建一个裸RS副本数写10完全不关心镜像变不变化因为它本来就不需要发布变更。三是作为CRD的底层实现。有些自定义资源在API层对外暴露的是CRD控制器把用户请求转换成ReplicaSet的创建与伸缩逻辑。这时候ReplicaSet就像一张低调但可靠的内核帮自定义控制器完成最基础的Pod运维。除了这几类日常业务我还是那句话老老实实用Deployment省心。6.2 操作RS时必须养成的几个职业习惯如果你确实要在测试环境或者特定场景操作ReplicaSet我建议养成下面几个习惯都是我在群里看别人踩坑、自己也踩过坑之后总结出来的创建之前先检查标签冲突。用kubectl get pods -l 标签扫一遍全局确认没有别人在用同一组标签。这一步花费不到10秒但能避免同室操戈级别的事故。删除之前想清楚是否要级联。默认删除RS会级联删除Pod这是绝大多数时候想要的行为。如果你用了--cascadeorphan删完RS一定要立刻处理遗留的Pod要么接管要么清除别让孤儿Pod在集群里游荡。改模板之前先确认旧Pod是否需要保留。记住前面那个实验改RS的template不重建旧Pod这意味着你可能要手动删Pod才能让新配置生效。动手之前把这波操作对业务的影响评估清楚。把RS当作只读对象来观察。生产环境里我把kubectl get rs和kubectl describe rs当作日常巡检标配但几乎不会去写RS的YAML。看数量、看事件、看历史revison这些就够了。6.3 最后再分享一个我实测有效的排障技巧用Deployment发布业务时如果滚动更新卡住了别急着点回滚。先找到当前卡住的那个新RS看它的事件比如FailedCreate、ImagePullBackOff、Insufficient cpu大概率问题就锁定了。如果你发现新RS本身没有任何事件Pod状态也正常但Deployment就是不往前推进那问题可能出在maxUnavailable和maxSurge的配置上——比如你设置了maxUnavailable为0而集群里又没有足够的资源创建临时的新Pod滚动更新就会僵持。这时临时把maxUnavailable调大一点比如从0改成1让旧Pod先释放一个位置出来更新就又能往前走了。我在几套环境里用这个办法救过好几次发布事故。ReplicaSet这套机制说破天也就是维持副本数五个字但它背后牵出来的控制器模式、标签选择器、发布策略这些概念却是理解整个Kubernetes工作负载体系的钥匙。你把它的脾气摸透了以后再看到Deployment滚动更新、StatefulSet有序扩缩容、DaemonSet节点级部署这些机制都会有一种殊途同归的通透感。