前言
上一篇整理 Pod 时,我留下了一个问题:Pod 本身是短暂的,被删除后不会自己回来,那长期运行的服务到底靠什么稳定住?
刚开始看 Kubernetes 时,我总觉得 Deployment、ReplicaSet、Pod 这三层有点绕。明明最终跑起来的是 Pod,为什么中间还要多一个 ReplicaSet?既然有 ReplicaSet 能保证副本数,Deployment 又多做了什么?
后来我换了一个角度理解:
1 | Pod 负责运行应用实例 |
如果把 Pod 看成一个个正在工作的同学,ReplicaSet 像点名表,少了人就补上;Deployment 则像一次任务安排,不只关心有几个人,还关心大家用的是哪一版资料、要不要逐步换成新版。
这篇就接着 Pod 往上看,梳理 Deployment 和 ReplicaSet 是怎么配合的。
直接创建 Pod 会遇到什么问题
先从最简单的情况开始。假设我直接创建了一个 Pod:
1 | apiVersion: v1 |
它确实可以表达“我要运行一个容器”。但如果这个 Pod 被删掉了,事情就比较尴尬:
1 | 直接创建 Pod |
这就像只安排了一个人值班,却没有点名机制。人一走,表上也没人发现。
长期运行的服务通常需要的是:
- 希望一直有固定数量的实例;
- 某个实例没了可以自动补;
- 发布新版本时不要一次性全部替换;
- 新版本有问题时可以回到旧版本;
- 扩容和缩容不要靠手动创建或删除 Pod。
这些需求就不是单独一个 Pod 能处理的了。
Deployment、ReplicaSet、Pod 的关系
Deployment、ReplicaSet、Pod 这三层关系可以先看成这样:
1 | Deployment: demo-api |
这里有三个层次:
Deployment:声明应用应该是什么版本、几个副本、如何更新;ReplicaSet:确保符合条件的 Pod 数量满足期望;Pod:真正承载容器运行。
我现在更愿意把它理解成分工合作,而不是重复包装:
1 | Deployment 管版本 |
Deployment 创建 ReplicaSet,ReplicaSet 再创建 Pod。平时我们主要写 Deployment,ReplicaSet 往往由 Deployment 自动维护。
一个最小 Deployment 示例
下面是一个常见的 Deployment:
1 | apiVersion: apps/v1 |
这段 YAML 看起来比 Pod 多了几层,但拆开后还算清楚。
1 | metadata.name |
template 这部分很关键。它不是已经存在的 Pod,而是一份 Pod 模板。ReplicaSet 发现副本不够时,就根据这份模板创建新的 Pod。
1 | Deployment |
这里还有一个容易踩坑的点:selector.matchLabels 要能匹配 template.metadata.labels。
1 | selector: |
如果 Deployment 说“我管理 app=demo-api 的 Pod”,但模板里创建出来的 Pod 却没有这个标签,那就像点名表和座位号对不上,控制器很难正确管理这些 Pod。
ReplicaSet 如何保持副本数量
ReplicaSet 最核心的工作是维护副本数。
假设期望是 3 个副本:
1 | 期望状态:3 个 Pod |
如果某个 Pod 被删除:
1 | 期望状态:3 个 Pod |
如果实际 Pod 太多:
1 | 期望状态:3 个 Pod |
这就是控制循环的味道:不是执行一次命令就结束,而是不断观察实际状态,再尝试把它拉回期望状态。
1 | ReplicaSet 控制循环 |
所以如果我手动删除 Deployment 管理的某个 Pod,通常很快会看到新的 Pod 被创建出来。不是原来的 Pod 复活了,而是 ReplicaSet 又补了一个新 Pod。
1 | 删除旧 Pod |
新 Pod 的名字、UID、IP 都可能变化,但它会继续使用同一份 Pod 模板。
扩容和缩容其实是改期望数量
理解 ReplicaSet 后,扩缩容就更容易看了。
比如我把 replicas 从 3 改成 5:
1 | spec: |
控制器看到期望数量变成 5,而当前只有 3 个,就会再创建 2 个 Pod。
1 | 原来: |
如果把 replicas 从 5 改成 2,它会删除多出来的 3 个 Pod:
1 | replicas: 5 -> 2 |
常见命令大概是这样:
1 | kubectl scale deployment demo-api --replicas=5 |
不过我更倾向先理解它背后的含义:扩缩容不是直接命令某几个 Pod 生死,而是改变 Deployment 里的期望副本数,剩下的交给控制器对账。
Deployment 比 ReplicaSet 多做了什么
看到这里,ReplicaSet 已经能维护副本数量了。那为什么日常还是推荐用 Deployment,而不是直接写 ReplicaSet?
关键差别在“版本更新”。
ReplicaSet 更关心一组 Pod 数量是否正确,但 Deployment 还会管理 Pod 模板的变化。例如镜像从 1.0.0 改成 1.1.0:
1 | containers: |
这个变化不是简单地把旧 Pod 原地改一下。Pod 的很多字段不能随意原地修改,Kubernetes 更常见的做法是创建新 Pod,逐步替换旧 Pod。
1 | 旧版本 ReplicaSet |
Deployment 会创建新的 ReplicaSet,让新 ReplicaSet 逐步扩容,同时让旧 ReplicaSet 逐步缩容。
1 | Deployment |
这就是 Deployment 比 ReplicaSet 更适合日常部署的原因:它不只是保证“有几个”,还负责“怎么从旧版本走到新版本”。
滚动发布:逐步换人,而不是一口气清空
Deployment 默认常用的是滚动发布思路。它不会先把所有旧 Pod 删除,再创建所有新 Pod,而是分批替换。
假设现在有 3 个副本,从 1.0.0 更新到 1.1.0:
1 | 开始 |
实际节奏会受到发布策略影响。常见配置是:
1 | strategy: |
我把这两个字段这样理解:
maxSurge:更新过程中最多可以额外多几个 Pod;maxUnavailable:更新过程中最多允许几个 Pod 暂时不可用。
如果 replicas: 3、maxSurge: 1,更新时最多可以短暂出现 4 个 Pod。这样新 Pod 可以先起来,旧 Pod 再慢慢退场。
1 | 最多额外多 1 个 |
如果 maxUnavailable: 1,表示更新过程中最多允许 1 个副本暂时不可用。它限制的是发布期间服务能力下降的幅度。
1 | 最多不可用 1 个 |
这两个参数共同决定了发布时“稳一点”还是“快一点”。更保守会降低风险,但更新时间更长;更激进会更快,但对服务可用性要求更高。
什么变化会触发新版本
不是 Deployment 的任何字段变化都会触发新 ReplicaSet。真正会触发 rollout 的,是 Pod 模板变化,也就是 .spec.template 里的内容变化。
例如这些通常会触发新版本:
- 修改容器镜像;
- 修改容器环境变量;
- 修改容器端口;
- 修改 Pod 模板里的标签或注解;
- 修改探针、资源限制、挂载配置等 Pod 运行描述。
而单纯修改 replicas 只是扩缩容,一般不会生成新的应用版本。
1 | 修改 replicas |
这一点挺重要。否则很容易把“扩容”和“发布新版本”混在一起。
回滚:旧 ReplicaSet 不一定马上消失
Deployment 更新完成后,旧 ReplicaSet 通常不会立刻完全删除,而是保留为历史版本的一部分。这样新版本出问题时,可以回滚到之前的模板。
常见命令是:
1 | kubectl rollout history deployment/demo-api |
可以把这个过程想成保留了一些旧的任务安排:
1 | Deployment: demo-api |
不过回滚不是万能撤销。它主要帮我们恢复 Deployment 的 Pod 模板,不会自动恢复数据库数据,也不会撤销应用自己对外部系统造成的影响。
所以我现在理解回滚时,会把它限定在“工作负载模板层面”:镜像、环境变量、探针、资源配置这类东西可以跟着模板回退,但业务数据问题仍然要靠应用和数据层自己的方案。
查看 Deployment 时该看哪些对象
如果真的在集群里排查一个 Deployment,我会按这条链路看:
1 | kubectl get deployment demo-api |
它们分别对应不同层次:
1 | Deployment |
如果 Deployment 显示副本不够,直接去看 Pod 可能只能看到结果。沿着 Deployment -> ReplicaSet -> Pod 往下看,更容易知道是发布卡住、调度失败、镜像拉取失败,还是应用启动后一直没有 Ready。
1 | Deployment 没完成 |
后面整理健康检查和排障时,可以继续沿着这条链路展开。
删除 Pod、ReplicaSet、Deployment 的区别
这三个对象有父子关系,所以删除时的结果也不同。
删除 Pod
如果这个 Pod 由 ReplicaSet 管理,ReplicaSet 会发现少了一个,然后创建新的 Pod。
1 | 删 Pod |
这常用于让某个异常 Pod 重新创建,但它不是发布新版本。
删除 ReplicaSet
如果 ReplicaSet 由 Deployment 管理,Deployment 会发现自己管理的副本结构不符合预期,可能继续创建或调整 ReplicaSet。
实际操作中,我一般不会手动删 Deployment 管理的 ReplicaSet,因为它是 Deployment 的内部执行层。
删除 Deployment
删除 Deployment 通常会连带删除它管理的 ReplicaSet 和 Pod。
1 | 删 Deployment |
这才更像是“这个应用不再需要运行了”。
哪些场景适合用 Deployment
Deployment 最适合长期运行的无状态服务,例如:
- Web API;
- 前端静态服务;
- 后台常驻 worker;
- 可以横向复制的业务服务。
这些服务的共同点是:每个副本大体等价,任意一个 Pod 被替换后,不应该依赖它原来的本地状态才能继续提供服务。
1 | 无状态副本 |
不太适合直接用 Deployment 表达的场景:
- 需要稳定网络身份和稳定存储的有状态服务,通常看 StatefulSet;
- 一次性任务,通常看 Job;
- 定时任务,通常看 CronJob;
- 每个节点都要跑一份的守护进程,通常看 DaemonSet。
比如数据库当然也能放进容器里,但它通常关心固定身份、数据卷绑定、主从关系和启动顺序。单纯用 Deployment 管起来,很容易把“能跑起来”和“适合长期维护”混为一谈。
我现在如何理解这一层
整理到这里,我对 Deployment 的理解从“一个复杂 YAML”变成了“长期维护一组 Pod 的声明”。
1 | 我希望 demo-api: |
如果只看运行时,最后看到的是一个个 Pod;但如果看管理层,Deployment 才是日常部署无状态应用时更合适的入口。
1 | 日常写 YAML |
这几层不是为了把简单事情复杂化,而是把职责拆开:Pod 专心描述运行单元,ReplicaSet 专心维护数量,Deployment 专心处理版本和发布。
小结
这一篇整理下来,我对 Deployment 和 ReplicaSet 的理解可以概括成几句话:
- Pod 是实际运行单元,但它本身是短暂的;
- ReplicaSet 负责让匹配标签的 Pod 数量符合期望;
- Deployment 通常负责创建和管理 ReplicaSet;
- 修改
replicas是扩缩容,修改.spec.template才会触发新版本发布; - 滚动发布会逐步创建新 Pod、缩减旧 Pod;
- 回滚主要回退的是 Deployment 的 Pod 模板,不等于恢复业务数据;
- 长期运行的无状态应用通常优先用 Deployment,而不是直接创建 Pod 或 ReplicaSet。
下一篇准备继续往 Service 看。Deployment 能让一组 Pod 稳定运行,但 Pod 的 IP 会变化,副本数量也会变化。接下来要解决的问题是:其他服务如何稳定访问这一组不断变化的 Pod。
参考资料
- Deployments:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- ReplicaSet:https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
- kubectl rollout:https://kubernetes.io/docs/reference/kubectl/generated/kubectl_rollout/
- Workload Resources:https://kubernetes.io/docs/concepts/workloads/controllers/