i007.cc

i007.cc

优先队列-降维打击

05.价值资料

讲讲 StatefulSet vs Deployment

这两个都是管理一组 Pod 副本的 Controller,但解决的是完全不同性质的问题:Deployment 假设每个副本都是无差别、可互换的;StatefulSet 假设每个副本都有独一无二的身份,不能被随便替换。

最直观的区别:Pod 命名

你的 fleet-api 就是典型的 Deployment,之前你贴过的 Pod 名字长这样:

fleet-api-6958d5465d-nmg4g
fleet-api-6958d5465d-shntt

 

6958d5465d 是 ReplicaSet 的哈希,nmg4g/shntt 是随机后缀——这两个 Pod 之间没有任何区别,谁死了 Deployment 就随便造一个新的随机名字补上去,调用方压根不关心自己连的是哪一个副本。

换成 StatefulSet 管理同样两个副本,名字会是:

mysql-0
mysql-1

 

固定的序号,不是随机哈希。mysql-0 这个身份是持久的——它重启、被重新调度到别的节点,名字还是 mysql-0,不会变成别的东西。

网络身份:要不要能单独找到某一个副本

Deployment 前面配一个普通 Service,你只能说”随便转发给这几个副本里的一个”(就是你现在 fleet-api 这个 Service 干的事)。

StatefulSet 通常搭配一个 Headless ServiceclusterIP: None),这样每个 Pod 会额外获得一个可以单独寻址的 DNS 名字:

mysql-0.mysql.default.svc.cluster.local
mysql-1.mysql.default.svc.cluster.local

 

这在数据库集群里是刚需——比如”写请求必须发给 mysql-0(主库),读请求可以发给 mysql-1/2(从库)”,这种”必须精确指定某一个成员”的场景,普通 Service 的负载均衡语义完全做不到。

存储:磁盘跟着身份走,不是跟着 Pod 走

这是最关键的差异。StatefulSet 用 volumeClaimTemplates,会给每个序号单独创建一个 PVC(data-mysql-0data-mysql-1……),并且这个绑定关系是长期固定的:mysql-0 不管重启多少次、被调度到哪个节点,重新起来后永远拿回它自己那块盘,数据不丢。

普通 Deployment 没有这个机制——如果你给 Deployment 挂载持久卷,通常所有副本要么共享同一个 PVC(会有并发写冲突问题),要么干脆就不该用 Deployment 装有状态应用,这正是 StatefulSet 存在的意义。

创建/更新顺序:StatefulSet 讲究先后顺序,Deployment 不讲究

Deployment 滚动更新时,多个新 Pod 可以并行起来,谁先 Ready 无所谓。StatefulSet 默认严格按序号顺序操作:扩容时 0 号必须 Running+Ready 之后才会创建 1 号;缩容/更新时反过来,从最大序号开始处理。这对”集群成员需要按顺序加入”的场景(比如数据库主从初始化、需要先选出 leader 再让 follower 加入)很重要。

一句话版本 + 例子

Deployment StatefulSet
副本关系 完全对等、可互换 每个副本有独立身份
Pod 命名 随机哈希后缀 固定序号(-0, -1, -2)
存储 通常无状态或共享 每个副本独立 PVC,重启后原样带回
单独寻址 不支持 支持(Headless Service + DNS)
创建/更新顺序 无序、可并行 严格按序号
典型场景 Web/API 服务(你的 fleet-api) MySQL/Postgres 集群、Kafka、ZooKeeper、etcd

顺带一提——我们之前聊了很多的 etcd,如果你要在 K8s 上跑一个独立的 etcd 集群(而不是像你 kind 集群里这种 kubeadm 管理的静态 Pod),几乎总是用 StatefulSet 部署,原因就是上面这几条全都用得上:每个 etcd 成员需要固定的身份用于 Raft 里的 peer 寻址(etcd-0etcd-1etcd-2 各自持久化的 WAL/snapshot 数据不能跟别的成员搞混),这也是为什么”有状态”和”StatefulSet”这两个词是绑在一起的。

发表回复