在学习Kubernetes的控制器的时候,我们知道"Deployment控制器适用于无状态应用,StatefulSet控制器适用于有状态的应用",那么,什么是有状态,什么又是无状态?一起来看看他们的区别。
1.直击核心问题:"状态"到底指什么?
在K8s语境中,"状态"其实就两件事:
数据:应用重启之后,之前存的东西还在不在?
身份:应用重启之后,别人还能不能用同一个地址找到它?
如果一个应用对这两样都不在乎,那就是无状态;如果它在乎其中任何一样,那就是有状态。
举个例子:你去便利店买瓶水,收银员扫完码收了钱,这笔交易就结束了。下一位顾客来了,他不需要记得你买过什么,收银员也不需要在脑子里存着上一单的数据。这是无状态。但如果你办了张会员卡,余额存在系统里,下次来还能接着用,这个"余额"就是状态,系统必须把它记住。
对应到K8s里,一个Pod是"干完活就走"还是"得带着家当干活",直接决定了该用哪个控制器来管它。
2.无状态应用:Deployment轻松搞定
平时我们见到的大多数业务应用,比如用Spring Boot写的API服务、前端的Vue或React应用,本质上都是无状态的。它们收到请求,处理,返回结果,不往本地磁盘存什么重要东西,也不关心自己到底是集群里的第几号选手。
这类应用的核心特点 就是可替换。
-
Pod挂了?直接重建一个新的,哪怕IP变了、名字变了,完全没关系,新Pod和旧Pod等价,能照常干活。
-
流量大了?从3个副本直接扩到10个副本,新增的Pod马上就能加入工作。
-
版本升级?滚动更新走起,新的起来几个,老的停掉几个,整个过程不讲究顺序,并行操作就行。
在K8s里,负责管这类应用的控制器就是Deployment。Deployment的设计哲学很简单:你怎么定义,我就怎么执行。扩容、缩容、升级、回滚,统统几句话的事。它生成的Pod名字也是随机的,比如 order-api-6d8f9c7b-xt2k5,看名字就知道,这些Pod没什么特殊身份,谁跟谁都一样。
总之,无状态应用不存数据在本地,数据都在外面(Redis、MySQL、对象存储等)存着,所以Pod本身可以随意处置。
3.有状态应用:StatefulSet精心布置
跟无状态相比,有状态应用有额外的要求。像MySQL、Kafka、Zookeeper、Elasticsearch,它们在K8s里跑的时候,要遵循两个规则:
-
数据不能丢:数据库的Pod如果挂了,等它再起来,之前存的数据必须还在。这就需要给每个Pod挂上独立的PVC(持久化存储卷),人走数据不走。
-
身份要固定:比如MySQL主从集群,从库得一直能通过固定的名字找到主库。如果主库重启之后IP变了,从库就不认识了。所以每个Pod得有唯一且稳定的网络标识。
就像便利店收银员可以随便换人,谁站那儿都能干;快递柜就不一样了,每个格子有固定编号,你取件的时候得对着那个编号去开。而且格子里的包裹不能丢,那是人家的东西。这就是有状态。
K8s专门为这类应用定制了StatefulSet。跟 Deployment的"随便"不同,StatefulSet的规矩很细:
-
有序启停:创建时从0到N按顺序来,mysql-0 跑起来了,才启动 mysql-1。缩容时反过来,先删编号大的。不能乱插队。
-
固定名字和存储:mysql-0 永远叫 mysql-0,它用的PVC也永远跟着它。就算Pod被删了重建,新的mysql-0还是会挂上原来的盘,找回原来的数据。
-
稳定网络地址:配合 Headless Service(无头服务),每个Pod都有一个不会变的DNS域名,比如 mysql-0.database.svc.cluster.local。无论Pod怎么重启、IP怎么变,用这个域名永远能找到它。
一句话总结:StatefulSet里的每个Pod都是有"身份证"的,序号、存储、域名都是它的专属配置,不能搞混。
4. 他们不是对立关系,是黄金搭档
初学的时候,我把他们看作对立的关系,这个理解是错误的。在实际项目里,它们通常是配合使用的。
云原生架构有一个黄金原则:计算层尽量无状态,存储层做有状态**。**
什么意思呢?你把业务逻辑代码(计算层)做成无状态的,用Deployment部署,随便扩缩容,尽情享受K8s的弹性红利。但这些业务跑起来要存数据吧?那就把数据交给底层专业的中间件(存储层),用StatefulSet部署,让它们稳稳当当地保存。
举个例子,如果我要写一个订单服务(无状态,Deployment),它收到请求后处理业务逻辑,但最终的订单数据存在底层的MySQL数据库里(有状态,StatefulSet)。这样就会形成一个四层结构:无状态的前端页面、无状态的订单API服务、有状态的Redis缓存和有状态的MySQL数据库。每一层各司其职,各用各的控制器。
5.一张表格看懂区别
| 对比维度 | 无状态应用(Deployment) | 有状态应用(StatefulSet) |
|---|---|---|
| 数据存哪 | 不存本地,重启就丢 | 必须挂PVC,数据跟盘走 |
| Pod身份 | 完全等价,名字随机生成 | 有固定序号,比如mysql-0、mysql-1 |
| 扩缩容 | 随便扩、随便缩,不讲究顺序 | 必须有序,逐个启动或删除 |
| 升级策略 | 并行滚动更新 | 逆序滚动更新,从最大编号开始逐个升 |
| 网络标识 | 靠Service的虚拟IP,不固定 | 靠Headless Service,有稳定的DNS域名 |
| 对应控制器 | Deployment | StatefulSet |
| 典型应用 | Nginx、Spring Boot API、前端应用 | MySQL、Kafka、Zookeeper、Elasticsearch |
6.运维场景的差异
1.Pod挂了之后的恢复速度不一样。
Deployment管的Pod挂了,K8s几乎是立刻拉起一个新的,速度很快。StatefulSet的Pod挂了,重建的时候得等PVC重新挂载,这个过程会慢一些,这属于正常现象。
2. 删StatefulSet不会删PVC!
这是个特别容易忽略的点。当你执行 kubectl delete statefulset,Pod会被删掉,但那些PVC(硬盘)会留着。K8s这么设计是为了保护数据,防止误删。但如果你不手动清理,时间长了那些PVC会一直占着集群的存储资源。
3. 无状态≠没数据。
无状态应用不是说它不能处理数据,而是说数据不在Pod本地存。一个订单API处理订单数据,最后存到MySQL里,这个API本身依然是无状态的,因为数据不在它身上。数据在MySQL那里,MySQL才是有状态的。