【Kubernetes从入门到精通】第14篇:ReplicaSet——Deployment背后的“影子武士“
上一篇【第13篇】Deployment——无状态应用的“自动档“
下一篇【第15篇】Service——K8s的服务发现和负载均衡
摘要
上一篇文章咱们把Deployment夸成了一朵花——版本管理、滚动更新、一键回滚,听着就跟"应用管家"似的。但你有没有想过一个问题:这些花活背后,谁在埋头苦干保证Pod的数量?答案是ReplicaSet。Deployment发号施令,ReplicaSet负责执行——“我需要3个v1.25的Pod”、“现在开始逐步替换成v1.27的Pod”……这些命令最终都是ReplicaSet在落地。
ReplicaSet的身世比Deployment早得多——它的前身叫ReplicationController(RC),是K8s社区最早的控制器之一。本文从RC到RS的进化讲起,拆解RS的三大看家本事,然后聊聊一个让人纠结的问题:什么时候跳过Deployment直接撸RS?
一、从RC到RS——一段进化史
K8s最早的副本控制器叫ReplicationController(RC),诞生于K8s 1.0时代。后来社区发现RC的Label选择器太死板——只能用等值匹配(app=nginx、env=prod),不支持"集合操作"。于是ReplicaSet(RS)在K8s 1.2诞生了,基本取代了RC。
【RC → RS 进化史】 ┌─────────────────────────────────────────┐ │ ReplicationController (RC) —— 老前辈 │ │ ───────────────────────────────── │ │ • 选择器只支持等值匹配 │ │ selector: │ │ app: nginx ✅ 支持 │ │ env: production ✅ 支持 │ │ │ │ • 不支持集合表达式 │ │ matchExpressions: ❌ 不支持 │ │ - {key: env, operator: In, │ │ values: [prod,staging]} │ └────────────────┬────────────────────────┘ │ K8s 1.2 大升级 ▼ ┌─────────────────────────────────────────┐ │ ReplicaSet (RS) —— 新一代接班人 │ │ ───────────────────────────────── │ │ • 等值匹配 ✅ 完全兼容RC │ │ matchLabels: │ │ app: nginx │ │ │ │ • 集合表达式 ✅ 新增能力 │ │ matchExpressions: │ │ - {key: env, operator: In, │ │ values: [prod,staging]} │ │ - {key: version, operator: Exists} │ └─────────────────────────────────────────┘要点:虽然ReplicaSet已经基本取代了RC,但RC还没被废弃——你在
kubectl里还是能看到rc这个缩写。不过除非你在维护老古董集群,否则永远选RS。记不住区别也没关系,记住一句话:RS能做RC的所有事,还支持集合选择器,没有理由再用RC。
二、ReplicaSet的三大职责——“说几就是几,一个不能少”
ReplicaSet所有代码的核心逻辑,就围绕三件事:
【ReplicaSet 的三大职责】 ┌──────────────────────────────────────────────────────┐ │ ReplicaSet │ │ │ │ 职责1:确保Pod数量 │ │ ┌───────────────────────────────────────────────┐ │ │ │ "我要3个Pod,现在只有2个?立刻补1个!" │ │ │ │ "我要3个Pod,跑了5个?立刻杀2个!" │ │ │ │ │ │ │ │ 期望3个 ─────► ReplicaSet ─────► 实际3个 │ │ │ │ "不够补,多了杀" │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ 职责2:故障替换 │ │ ┌───────────────────────────────────────────────┐ │ │ │ Pod-1 挂了(Node崩了/OOM/被误删) │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ReplicaSet 立刻检测到:"少了一个!" │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ 在健康的Node上创建一个新Pod替代它 │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ 职责3:弹性伸缩 │ │ ┌───────────────────────────────────────────────┐ │ │ │ kubectl scale --replicas=5 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ "现在要5个了,给我多起2个Pod" │ │ │ │ kubectl scale --replicas=1 │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ "现在只要1个了,杀4个Pod" │ │ │ └───────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘2.1 职责一:确保Pod数量——"调谐循环"在行动
ReplicaSet的核心是个调谐循环(Reconciliation Loop)——它每隔一段时间就检查:现在跑着的Pod数量跟我期望的一样吗?不一样就调到一样。
# 来做个实验:手动删掉一个RS管理的Podkubectl get pods-lapp=myapp# NAME READY STATUS# myapp-rs-abc12 1/1 Running# myapp-rs-def34 1/1 Running# myapp-rs-ghi56 1/1 Running# 手动删一个Podkubectl delete pod myapp-rs-abc12# 立刻再查——新的Pod已经起来了!kubectl get pods-lapp=myapp# NAME READY STATUS AGE# myapp-rs-def34 1/1 Running 5m# myapp-rs-ghi56 1/1 Running 5m# myapp-rs-xyz78 1/1 Running 3s ← 自动补上了!要点:ReplicaSet不是靠"Pod挂了发通知"来补Pod的——它是主动轮询。每隔一会儿就数一遍自己管着的Pod:少了就建,多了就杀。这种"不断检查不断纠正"的模式叫"控制循环"(Control Loop),是K8s所有控制器的核心工作机制。
2.2 职责二:故障替换——“无缝抢修队”
当Pod所在的Node宕机了,kubelet停止向API Server上报心跳,K8s会把该Node标记为NotReady。ReplicaSet发现Node上的Pod状态异常后,会在其他健康Node上重新创建Pod:
【故障替换过程】 Node-1 (健康) Node-2 (宕机!) Node-3 (健康) ┌───────────┐ ┌───────────┐ ┌───────────┐ │ Pod-A │ │ Pod-B 💀 │ │ Pod-C │ │ Running │ │ 挂了! │ │ Running │ └───────────┘ └───────────┘ └───────────┘ │ │ Node-2心跳超时 ▼ RS检测到:Pod-B状态异常,实际可用Pod = 2,期望 = 3 │ ▼ ┌───────────┐ │ ┌───────────┐ │ Pod-A │ │ │ Pod-C │ │ Running │ │ Running │ │ │ │ Pod-D 🆕 │ │ │ │ Running │ └───────────┘ └───────────┘ RS在健康的Node-3上创建Pod-D,替代挂掉的Pod-B2.3 职责三:弹性伸缩——“说几就是几”
# HPA(自动伸缩器)最终也是通过调RS的replicas来实现的# 大促来了,HPA说:RS,给老子扩到20个!kubectl scale replicaset myapp-rs--replicas=20# 大促结束,HPA说:RS,留3个就行kubectl scale replicaset myapp-rs--replicas=3三、ReplicaSet YAML——完整拆解
apiVersion:apps/v1kind:ReplicaSetmetadata:name:myapp-rslabels:app:myappversion:v1.25spec:replicas:3# ★ 期望副本数selector:# ★ 选择器——怎么找到"自己的Pod"matchLabels:# 等值匹配(AND逻辑)app:myappversion:v1.25matchExpressions:# 集合匹配(更灵活)-{key:environment,operator:In,values:[production,staging]}-{key:tier,operator:NotIn,values:[backend]}-{key:monitoring,operator:Exists}# 只要有monitoring标签就行template:# ★ Pod模板metadata:labels:app:myappversion:v1.25environment:productionmonitoring:enabledspec:containers:-name:appimage:myapp:v1.25ports:-containerPort:80803.1 selector.matchLabels vs matchExpressions——什么时候用哪个?
这是RS(以及所有用Label Selector的控制器)里最让人纠结的地方。一张表讲清楚:
| 场景 | 用 matchLabels | 用 matchExpressions |
|---|---|---|
| “选所有 app=nginx 的Pod” | ✅app: nginx | 也能,但没必要 |
| “选 env=prod 或 env=staging 的Pod” | ❌ 做不到(AND逻辑) | ✅{key: env, operator: In, values: [prod, staging]} |
| “排除 tier=backend 的Pod” | ❌ 做不到 | ✅{key: tier, operator: NotIn, values: [backend]} |
| “只要有 monitoring 标签的Pod都算” | ❌ 做不到 | ✅{key: monitoring, operator: Exists} |
| “选 app=nginx 且 version=v1.25” | ✅ 写两行 | 也能,但两行expression是AND关系 |
# matchLabels 和 matchExpressions 的关系是 AND:# 下面这个配置的意思是:# 必须满足 matchLabels 里的所有条件(app=myapp AND version=v1.25)# AND# 必须满足 matchExpressions 里的所有条件(environment在[prod,staging] AND tier不在[backend])selector:matchLabels:app:myappversion:v1.25matchExpressions:-{key:environment,operator:In,values:[production,staging]}-{key:tier,operator:NotIn,values:[backend]}要点:如果你只是想精准选Pod(“app=nginx,env=prod”),
matchLabels就够用了。matchExpressions的真正价值在于模糊匹配——“所有带monitoring标签的Pod”、“env是prod或staging都算”、“只要不是tier=backend的都要”。
3.2 selector的"不可变性"——一个坑
# ReplicaSet创建后,selector不允许修改!# 如果你必须改选择器的逻辑,只能:# 1. 删除旧的RS# 2. 创建新的RS(用新的selector)# 这也是为什么Deployment每次更新都创建新RS的原因之一四、被遗忘的Pod——"孤儿Pod"是怎么来的
ReplicaSet通过Label Selector找到"自己管的Pod",而不是通过某种"父子关系"。这就引出一个有趣的现象——如果你的Pod的Label恰好匹配了某RS的Selector,这个Pod就会被RS"收养"。
【孤儿Pod被收养】 场景:你手动创建了一个Pod,标签是 app=myapp, version=v1.25 ┌───────────────────┐ │ ReplicaSet │ selector: {app: myapp, version: v1.25} │ myapp-rs │ 期望副本:3 │ │ 实际在跑的Pod:当前有2个 └────────┬──────────┘ │ │ RS检查:匹配我的selector的Pod有几个? │ 发现:2个自己创建的 + 1个手动创建的 = 3个! │ "够了,不用补了" │ ▼ ┌────────┐ ┌────────┐ ┌──────────────┐ │ Pod-1 │ │ Pod-2 │ │ Pod-手动创建 │ ← 被RS"收养"了 │ (RS建) │ │ (RS建) │ │ (你自己建的) │ └────────┘ └────────┘ └──────────────┘ 如果你删了这个手动创建的Pod, RS发现:只剩2个了,补1个!——又恢复了3个# 实验:看RS能不能"收养"手动创建的Pod# 先创建一个RS,replicas=2kubectl apply-fmyapp-rs.yaml# 手动创建一个标签匹配的Podkubectl run orphan-pod--image=myapp:v1.25--labels="app=myapp,version=v1.25"# RS发现已经有3个匹配的Pod了!不会再多建kubectl get pods-lapp=myapp# NAME READY STATUS# myapp-rs-abc12 1/1 Running ← RS创建的# myapp-rs-def34 1/1 Running ← RS创建的# orphan-pod 1/1 Running ← 手动创建的,但被RS"收养"# 你删掉手动创建的Pod,RS立刻补一个kubectl delete pod orphan-pod kubectl get pods-lapp=myapp# myapp-rs-abc12 1/1 Running# myapp-rs-def34 1/1 Running# myapp-rs-ghi56 1/1 Running ← 自动补的要点:RS不认Pod的"出生证明",只看Label。这意味着你需要严格管理Pod的标签——如果有多个资源(Service、RS、NetworkPolicy)用同一个Label选择Pod,改标签可能触发连锁反应。Pod标签管理是K8s运维的基本功。
五、什么时候跳过Deployment直接操作ReplicaSet?
Deployment是好用,但并不是所有场景都需要它。直接操作RS的情况总结如下:
【决策树:用Deployment还是直接用RS?】 "你的应用需要滚动更新/回滚吗?" │ ┌──┴──┐ │ 是 │──► 用 Deployment ✅ └─────┘ │ ┌──┴──┐ │ 否 │ ──► "你用的是什么部署工具?" └──┬──┘ │ ┌────┴────────────────────┐ │ │ Helm/ArgoCD/ │ Kustomize/CD工具 │ 手动 kubectl 管理 │ │ ▼ ▼ 工具自己可能直接 Deployment 有回滚能力, 创建RS(不经过 更安全 ✅ Deployment)✅ 但更常见:工具还是 用Deployment + 自己 的版本管理 ✅以下是真正直接操作RS的典型场景:
| 场景 | 为什么直接操作RS | 注意事项 |
|---|---|---|
| 学习/调试 | 理解RS本质,了解Deployment底层 | 仅限实验环境 |
| 部署工具内部实现 | Helm/ArgoCD等工具可能自己管理RS | 工具负责版本管理 |
| 静态副本集 | 永远不改镜像、不需要回滚的"一次性"任务 | 极少见 |
| 自定义Operator | 自己写控制器,RS是你代码里管理Pod的"原语" | 高级用法 |
# 手动创建一个独立的RS(不通过Deployment)kubectl apply-f-<<EOF apiVersion: apps/v1 kind: ReplicaSet metadata: name: standalone-rs spec: replicas: 3 selector: matchLabels: app: standalone template: metadata: labels: app: standalone spec: containers: - name: nginx image: nginx:1.25 EOF# 查看RS(包括被Deployment管理的和独立的)kubectl get rs# NAME DESIRED CURRENT READY AGE# nginx-deployment-7d8f9c6a5b 3 3 3 1h ← Deployment的RS# standalone-rs 3 3 3 10s ← 独立RS# 扩容独立RSkubectl scale rs standalone-rs--replicas=5# 改镜像——但RS本身不支持滚动更新!所有Pod会被同时杀掉重建kubectlsetimage rs/standalone-rsnginx=nginx:1.27# ⚠️ RS直接改镜像会导致所有Pod同时重建——没有滚动更新的保护!要点:直接操作RS的最大坑在于——RS没有滚动更新和回滚能力。你直接改RS的Pod模板,所有匹配的Pod会立刻被同步杀掉重建,没有maxSurge/maxUnavailable的保护。除非你知道自己在干什么,否则永远通过Deployment操作。
六、ReplicaSet vs ReplicationController——对比总结
| 维度 | ReplicationController (RC) | ReplicaSet (RS) |
|---|---|---|
| API版本 | v1 | apps/v1 |
| 选择器类型 | 仅等值匹配 | 等值匹配 + 集合表达式 |
matchExpressions | ❌ 不支持 | ✅ 支持 In/NotIn/Exists/DoesNotExist |
| 是否被推荐 | ❌ 不推荐(遗留) | ✅ 推荐 |
| Deployment使用 | ❌ 不用RC | ✅ Deployment内部用RS |
| kubectl缩写 | rc | rs |
七、RS的常用操作命令
# 查看所有RSkubectl get rs# 查看RS详情kubectl describe rs myapp-rs-7d8f9c6a5b# 查看RS管理的Podkubectl get pods-lapp=myapp,version=v1.25# 扩容RSkubectl scale rs myapp-rs-7d8f9c6a5b--replicas=5# 查看RS的YAML(看它记录的Pod模板)kubectl get rs myapp-rs-7d8f9c6a5b-oyaml|grep-A20'spec:'# 删除RS(会级联删除它管理的Pod)kubectl delete rs myapp-rs-7d8f9c6a5b# 删除RS但不删除Pod(让Pod变成"孤儿")kubectl delete rs myapp-rs-7d8f9c6a5b--cascade=orphan# 查看RS的事件日志kubectl describe rs myapp-rs-7d8f9c6a5b|grep-A10Events本篇小结
ReplicaSet是"沉默的大多数"——你不直接跟它打交道的概率远超你直接操作它的概率,但它时时刻刻在守护你的Pod:
- RC→RS进化:RS支持集合选择器(In/NotIn/Exists/DoesNotExist),是RC的完全替代品
- 三大职责:确保Pod数量、故障自动替换、弹性伸缩——核心就是一个"调谐循环"
- 两个选择器:
matchLabels做精准匹配,matchExpressions做模糊匹配,两者是AND关系 - 别手贱直接操作RS:RS没有滚动更新和回滚能力,直接改模板所有Pod会同时重建
下一篇咱们终于要聊Service了——Pod IP是临时的,每次重建都会变。那外部怎么稳定地访问Pod?kube-proxy怎么把流量引到正确的Pod上?这些就是Service的核心问题。
上一篇【第13篇】Deployment——无状态应用的“自动档“
下一篇【第15篇】Service——K8s的服务发现和负载均衡