StatefulSet使用思路梳理

2023-09-15
4分钟阅读时长

一般服务都是使用deployment部署,deploy的使用非常简单,无状态,随意调度。但是各种需要持久化数据的中间件都需要使用StatefulSet来部署,这个玩意儿实际上有点复杂。

最简单的方法:不要使用k8s来部署有状态应用

比如在部署kafka集群、MySQL集群时,你会发现直接用docker分别在选定节点部署,是最简单的。和传统的物理机部署并无什么区别,你要做的仅仅是将配置文件和宿主机目录挂载到容器里。

如果想要自动化,就写bash脚本,分别在各个主机运行即可,也并没有那么复杂。

容器内的服务访问容器外的服务也很简单:直接用网卡的内网ip就行。

不要太依赖分布式存储

第一次接触Ceph或者longhorn的时候,很容易会错误的认为分布式存储本身就可以解决高可用的问题。

比如sqlite,将它放在longhorn里,理论上pod在任意节点,都可以访问到这块pv. longhorn自带的多副本策略也可以保证数据不丢失。如果宿主机节点挂掉,k8s自动调度pod到其他结点,原来的pv也依然可用。

这一切很美好,但是有两个问题:

  • IO性能会变差
  • 数据可能损坏

第一点是显而易见的,毕竟分布式存储是在文件系统的基础上又加了一层,肯定会影响性能。

第二点就比较蛋疼了,分布式存储保证文件的高可用是通过类似Dropbox这种文件同步的思路来进行的,他并不理解文件的内容,所以文件有可能会损坏是很容易理解的。

众所周知,MySQL主从的备份思路是通过binlog同步来增量进行的。当master节点宕机时,可能有一部分变更没来得及同步到slave节点,这就会造成数据回档的效果。但是如果是使用longhorn做同步,可能整个database都无法打开,导致数据完全损坏,这种肯定是完全无法接受的。

另外,longhorn支持s3/minio/nfs二次备份,这个卷备份可以实现完全备份。当出现宕机等问题时,你可以向前回溯这个备份,总能找到可用的完整数据。当然,这个回档的时间可能有点长。

所以,对于MySQL这种数据库,还是使用应用层自带的副本方案更靠谱。分布式存储只能用来存放一些不太重要的东西:比如文件之类的。甚至于,这个需求也可以使用minio之类的中间件来解决。可能像日志之类的数据比较适合使用,搞一块RWM的PV,所有应用都往里面写日志,方便集中查看应用日志,而且日志丢了一般影响也不大。

当然,如果你是在云端,使用云厂商提供的分布式存储,一般并不需要关心这个问题,我们这里主要还是讨论边缘端这种不稳定的场景。

利用亲和性来部署StatefulSet

如果回到应用程序本身的副本方案——比如MySQL主从——那么就需要手动干涉k8s的调度。使其最终和你手动在各个节点部署的结果一致(也就是你自己用docker部署)。

首先,主从应当尽量在不同的宿主机上:如果你有两台以上宿主机的话。这个主要通过Pod反亲和性来实现:如果这个节点已经有匹配的Pod,则尽量不要还在这个节点上部署第二个sts.

其次,利用节点亲和性来选择宿主机:假设你手头的宿主机配置不太一样,那就在高配置的node上加上label,然后在sts的pod上加上节点亲和,选择匹配的标签。

利用这两个方法,你可以决定pod最终被调度到哪个节点上。当然至于哪个是主哪个是从,这个仍然是随机的。如果你想保证主在某个节点,从在某个节点,那只能写两个sts,直接指定nodeName。一个sts配置2个replia,是无法实现这种需求的。

使用LocalPV

不使用分布式存储,那就只能用LocalPV了。如果使用k3s,使用自带的local-path就行。

在sts里面指定VolumeClaimTemplate,k8s会延迟绑定PV直到Pod被调度。

对于LocalPV,k8s在调度时就会考虑到Pod和PV必须在同一个节点,这是因为StorageClass的配置里有volumeBindingMode: WaitForFirstConsumer,这是LocalPV的标记。在第一次创建sts时,PV还不存在,这个流程是反过来的:k8s先根据亲和性和资源需求来决定Pod在哪个节点,然后再根据PVC来动态创建PV,将PVC绑定PV,在同一个节点创建Pod然后完成挂载。

在第二次以及之后创建sts时,此时PVC/PV都已经存在,所以k8s在调度的时候就会自动将POD创建到对应的节点了。

如果是直接用k8s,那可能没有支持动态创建PV的StorageClass. 此时需要手动创建PV,其他的流程是一样的。当然最终节点只会被调度到有PV的节点:换句话说,你无须在POD中指定节点亲和性,而是在创建PV时指定节点亲和性。