Kubernetes 笔记(13)— 有状态的应用 StatefulSet、实现 StatefulSet 的数据持久化

news/2024/12/5 5:20:51/

1. 什么是有状态的应用

有了持久化存储 PersistentVolume,应用就可以把一些运行时的关键数据落盘,相当于有了一份“保险”,如果 Pod 发生意外崩溃,也只不过像是按下了暂停键,等重启后挂载 Volume,再加载原数据就能够满血复活,恢复之前的“状态”继续运行。

所以从这个角度来说,理论上任何应用都是有状态的。

只是有的应用的状态信息不是很重要,即使不恢复状态也能够正常运行,这就是我们常说的“无状态应用”。“无状态应用”典型的例子就是 Nginx 这样的 Web 服务器,它只是处理 HTTP 请求,本身不生产数据(日志除外),不需要特意保存状态,无论以什么状态重启都能很好地对外提供服务。

还有一些应用,运行状态信息就很重要了,如果因为重启而丢失了状态是绝对无法接受的,这样的应用就是“有状态应用”。

“有状态应用”的例子也有很多,比如 RedisMySQL 这样的数据库,它们的“状态”就是在内存或者磁盘上产生的数据,是应用的核心价值所在,如果不能够把这些数据及时保存再恢复,那绝对会是灾难性的后果。

理解了这一点,我们结合目前学到的知识思考一下:Deployment 加上 PersistentVolume,在 Kubernetes 里是不是可以轻松管理有状态的应用了呢?

的确,用 Deployment 来保证高可用,用 PersistentVolume 来存储数据,确实可以部分达到管理“有状态应用”的目的。

但是 Kubernetes 的眼光则更加全面和长远,它认为“状态”不仅仅是数据持久化,在集群化、分布式的场景里,还有多实例的依赖关系、启动顺序和网络标识等问题需要解决,而这些问题恰恰是 Deployment 力所不及的。

因为只使用 Deployment,多个实例之间是无关的,启动的顺序不固定,Pod 的名字、IP 地址、域名也都是完全随机的,这正是“无状态应用”的特点。

但对于“有状态应用”,多个实例之间可能存在依赖关系,比如 master/slaveactive/passive,需要依次启动才能保证应用正常运行,外界的客户端也可能要使用固定的网络标识来访问实例,而且这些信息还必须要保证在 Pod 重启后不变。

所以,Kubernetes 就在 Deployment 的基础之上定义了一个新的 API 对象,名字也很好理解,就叫 StatefulSet,专门用来管理有状态的应用。

2. 使用 YAML 描述 StatefulSet

首先我们还是用命令 kubectl api-resources 来查看 StatefulSet 的基本信息,可以知道它的简称是 stsYAML 文件头信息是:


apiVersion: apps/v1
kind: StatefulSet
metadata:name: xxx-sts

DaemonSet 类似,StatefulSet 也可以看做是 Deployment 的一个特例,它也不能直接用 kubectl create 创建样板文件,但它的对象描述和 Deployment 差不多,你同样可以把 Deployment 适当修改一下,就变成了 StatefulSet 对象。

这里我给出了一个使用 RedisStatefulSet,你来看看它与 Deployment 有什么差异:

# redis-sts.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: redis-stsspec:serviceName: redis-svcreplicas: 2selector:matchLabels:app: redis-ststemplate:metadata:labels:app: redis-stsspec:containers:- image: redis:5-alpinename: redisports:- containerPort: 6379

我们会发现,YAML 文件里除了 kind 必须是 StatefulSet ,在 spec 里还多出了一个 serviceName 字段,其余的部分和 Deployment 是一模一样的,比如 replicasselectortemplate 等等。

这两个不同之处其实就是 StatefulSetDeployment 的关键区别。想要真正理解这一点,我们得结合 StatefulSetKubernetes 里的使用方法来分析。

3. 在 Kubernetes 里使用 StatefulSet

让我们用 kubectl apply 创建 StatefulSet 对象,用 kubectl get 先看看它是什么样的:


kubectl apply -f redis-sts.yml
kubectl get sts
kubectl get pod

sts

从截图里,你应该能够看到,StatefulSet 所管理的 Pod 不再是随机的名字了,而是有了顺序编号,从 0 开始分别被命名为 redis-sts-0redis-sts-1Kubernetes 也会按照这个顺序依次创建(0 号比 1 号的 AGE 要长一点),这就解决了“有状态应用”的第一个问题:启动顺序。

有了启动的先后顺序,应用该怎么知道自己的身份,进而确定互相之间的依赖关系呢?

Kubernetes 给出的方法是使用 hostname,也就是每个 Pod 里的主机名,让我们再用 kubectl exec 登录 Pod 内部看看:


kubectl exec -it redis-sts-0 -- sh

hostname

Pod 里查看环境变量 $HOSTNAME 或者是执行命令 hostname,都可以得到这个 Pod 的名字 redis-sts-0

有了这个唯一的名字,应用就可以自行决定依赖关系了,比如在这个 Redis 例子里,就可以让先启动的 0 号 Pod 是主实例,后启动的 1 号 Pod 是从实例。

解决了启动顺序和依赖关系,还剩下第三个问题:网络标识,这就需要用到 Service 对象。

不过这里又有一点奇怪的地方,我们不能用命令 kubectl expose 直接为 StatefulSet 生成 Service,只能手动编写 YAML

因为不能自动生成,你在写 Service 对象的时候要小心一些,metadata.name 必须和 StatefulSet 里的 serviceName 相同,selector 里的标签也必须和 StatefulSet 里的一致:

# redis-svc.yml
apiVersion: v1
kind: Service
metadata:name: redis-svcspec:selector:app: redis-stsports:- port: 6379protocol: TCPtargetPort: 6379

写好 Service 之后,还是用 kubectl apply 创建这个对象:

redis-svc

可以看到这个 Service 并没有什么特殊的地方,也是用标签选择器找到 StatefulSet 管理的两个 Pod,然后找到它们的 IP 地址。

不过,StatefulSet 的奥秘就在它的域名上。

Service 自己会有一个域名,格式是“对象名. 名字空间”,每个 Pod 也会有一个域名,形式是“IP 地址. 名字空间”。但因为 IP 地址不稳定,所以 Pod 的域名并不实用,一般我们会使用稳定的 Service 域名。

当我们把 Service 对象应用于 StatefulSet 的时候,情况就不一样了。

Service 发现这些 Pod 不是一般的应用,而是有状态应用,需要有稳定的网络标识,所以就会为 Pod 再多创建出一个新的域名,格式是 Pod 名. 服务名. 名字空间.svc.cluster.local 。当然,这个域名也可以简写成 Pod 名. 服务名

我们还是用 kubectl exec 进入 Pod 内部,用 ping 命令来验证一下:


kubectl exec -it redis-sts-0 -- sh

exec ping

显然,在 StatefulSet 里的这两个 Pod 都有了各自的域名,也就是稳定的网络标识。那么接下来,外部的客户端只要知道了 StatefulSet 对象,就可以用固定的编号去访问某个具体的实例了,虽然 PodIP 地址可能会变,但这个有编号的域名由 Service 对象维护,是稳定不变的。

到这里,通过 StatefulSetService 的联合使用,Kubernetes 就解决了“有状态应用”的依赖关系、启动顺序和网络标识这三个问题,剩下的多实例之间内部沟通协调等事情就需要应用自己去想办法处理了。

关于 Service,有一点值得再多提一下。

Service 原本的目的是负载均衡,应该由它在 Pod 前面来转发流量,但是对 StatefulSet 来说,这项功能反而是不必要的,因为 Pod 已经有了稳定的域名,外界访问服务就不应该再通过 Service 这一层了。所以,从安全和节约系统资源的角度考虑,我们可以在 Service 里添加一个字段 clusterIP: None ,告诉 Kubernetes 不必再为这个对象分配 IP 地址。

我画了一张图展示 StatefulSet 与 Service 对象的关系,你可以参考一下它们字段之间的互相引用:
image.png

4. 实现 StatefulSet 的数据持久化

现在 StatefulSet 已经有了固定的名字、启动顺序和网络标识,只要再给它加上数据持久化功能,我们就可以实现对“有状态应用”的管理了。

这里就能用到上一节课里学的 PersistentVolumeNFS 的知识,我们可以很容易地定义 StorageClass,然后编写 PVC,再给 Pod 挂载 Volume

不过,为了强调持久化存储与 StatefulSet 的一对一绑定关系,KubernetesStatefulSet 专门定义了一个字段 volumeClaimTemplates,直接把 PVC 定义嵌入 StatefulSetYAML 文件里。这样能保证创建 StatefulSet 的同时,就会为每个 Pod 自动创建 PVC,让 StatefulSet 的可用性更高。

volumeClaimTemplates 这个字段好像有点难以理解,你可以把它和 PodtemplateJobjobTemplate 对比起来学习,它其实也是一个“套娃”的对象组合结构,里面就是应用了 StorageClass 的普通 PVC 而已。

让我们把刚才的 Redis StatefulSet 对象稍微改造一下,加上持久化存储功能:

# redis-pv-sts.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: redis-pv-stsspec:serviceName: redis-pv-svcvolumeClaimTemplates:- metadata:name: redis-100m-pvcspec:storageClassName: nfs-clientaccessModes:- ReadWriteManyresources:requests:storage: 100Mireplicas: 2selector:matchLabels:app: redis-pv-ststemplate:metadata:labels:app: redis-pv-stsspec:containers:- image: redis:5-alpinename: redisports:- containerPort: 6379volumeMounts:- name: redis-100m-pvcmountPath: /data

首先 StatefulSet 对象的名字是 redis-pv-sts,表示它使用了 PV 存储。然后 volumeClaimTemplates 里定义了一个 PVC,名字是 redis-100m-pvc,申请了 100MB 的 NFS 存储。在 Pod 模板里用 volumeMounts 引用了这个 PVC,把网盘挂载到了 /data 目录,也就是 Redis 的数据目录。

下面的这张图就是这个 StatefulSet 对象完整的关系图:
image.png先执行上节里面的

kubectl apply -f rbac.yml
kubectl apply -f class.yml
kubectl apply -f deployment.yml

然后执行

kubectl apply -f redis-pv-svc.yml

最后使用 kubectl apply 创建这些对象,一个带持久化功能的“有状态应用”就算是运行起来了:


kubectl apply -f redis-pv-sts.yml

可以使用命令 kubectl get pvc 来查看 StatefulSet 关联的存储卷状态:

get_pvc

看这两个 PVC 的命名,不是随机的,是有规律的,用的是 PVC 名字加上 StatefulSet 的名字组合而成,所以即使 Pod 被销毁,因为它的名字不变,还能够找到这个 PVC,再次绑定使用之前存储的数据。

那我们就来实地验证一下吧,用 kubectl exec 运行 Redis 的客户端,在里面添加一些 KV 数据:


kubectl exec -it redis-pv-sts-0 -- redis-cli

set keys

这里我设置了两个值,分别是 a=111 和 b=222。

现在我们模拟意外事故,删除这个 Pod


kubectl delete pod redis-pv-sts-0

由于 StatefulSetDeployment 一样会监控 Pod 的实例,发现 Pod 数量少了就会很快创建出新的 Pod,并且名字、网络标识也都会和之前的 Pod 一模一样:

delete pod

Redis 里存储的数据怎么样了呢?是不是真的用到了持久化存储,也完全恢复了呢?

可以再用 Redis 客户端登录去检查一下:


kubectl exec -it redis-pv-sts-0 -- redis-cli

get keys

因为我们把 NFS 网络存储挂载到了 Pod/data 目录,Redis 就会定期把数据落盘保存,所以新创建的 Pod 再次挂载目录的时候会从备份文件里恢复数据,内存里的数据就恢复原状了。

5. 总结

这节我们学习了专门部署“有状态应用”的 API 对象 StatefulSet,它与 Deployment 非常相似,区别是由它管理的 Pod 会有固定的名字、启动顺序和网络标识,这些特性对于在集群里实施有主从、主备等关系的应用非常重要。

  1. StatefulSetYAML 描述和 Deployment 几乎完全相同,只是多了一个关键字段 serviceName
  2. 要为 StatefulSet 里的 Pod 生成稳定的域名,需要定义 Service 对象,它的名字必须和 StatefulSet 里的 serviceName 一致。
  3. 访问 StatefulSet 应该使用每个 Pod 的单独域名,形式是 Pod 名. 服务名 ,不应该使用 Service 的负载均衡功能。
  4. StatefulSet 里可以用字段 volumeClaimTemplates 直接定义 PVC,让 Pod 实现数据持久化存储。

若不使用 volumeClaimTemplates 内嵌定义 PVC ,那么可能的后果就是,多个副本挂载同一个网络存储设备,这可能会导致数据丢失。

Pod 负责服务,Job 负责调度,

Daemon/Deployment 负责无状态部署,StatefulSet 负责状态部署,

Service 负责四层访问(负载均衡、IP分配、域名访问),Ingress 负责应用层(7层)访问(路由规则),

PVC/PV 负责可靠性存储。


http://www.ppmy.cn/news/41844.html

相关文章

DJ编曲用什么软件,DJ编曲教需要哪些步骤

随着现在人们的生活水平不断提高,我们的精神生活也越来越丰富,对于现在的年轻人来说,DJ舞曲是一个较受欢迎的领域,有许多年轻人对DJ这个职业感兴趣,想要深入了解DJ编曲这份工作,那么今天我们就来说一说DJ编…

2.rabbitmq-linux安装

一.环境准备 1.RabbitMQ版本 和 Erlang 版本兼容性关系 https://www.rabbitmq.com/which-erlang.html 2.官方安装包下载地址 【erlang下载地址】:https://github.com/rabbitmq/erlang-rpm/releases/tag/v21.3.1【rabbitmq下载地址】:https://github.co…

派盘为您的个人数据安家

现如今,我们的生活中有着各种各样的数据。在工作中会有各种文件、邮件;在生活中则有照片和视频等。数据的来源多,时间点不一致且混乱。 数据是否能安全、稳定、长久的存储以及便捷高效的使用对我们来说相当重要。你是否经常出差需要带上电脑或者移动硬盘,想存网盘又怕丢失或…

sftp使用

Client端使用Server端的账户username,sftp登录Server,除了IP地址,也可以使用/etc/hosts定义的域名,注意,Client的默认路径:Shell中的当前路径,Server的默认路径:server账户家目录 ​…

华为OD机试-整理扑克牌-2022Q4 A卷-Py/Java/JS

给定一组数字,表示扑克牌的牌面数字,忽略扑克牌的花色,请按如下规则对这一组扑克牌进行整理: 步骤1、对扑克牌进行分组,形成组合牌,规则如下:当牌面数字相同张数大于等于4时,组合牌为“炸弹”:3张相同牌面数字 + 2张相同牌面数字,且3张牌与2张牌不相同时,组合牌为“葫…

C++多线程--线程安全的队列实现(基于锁)

0 引言 本文主要是根据C++ Concurrency in Action (豆瓣)第6章的基于锁的数据结构来讲解相应的队列实现。 本文会给出两种队列 基于STL的queue的线程安全队列基于链表的线程安全队列1 预备知识 如何实现一个线程安全的队列?需要遵循什么样的准则? 可参考: C++多线程--发…

采购招投标系统-高效管控招采流程-降低采购成本

项目说明 随着公司的快速发展,企业人员和经营规模不断壮大,公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境,最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范,以及…

让代码变得优雅简洁的神器:Java8 Stream流式编程

在实际项目当中,若能熟练使用Java8 的Stream流特性进行开发,就比较容易写出简洁优雅的代码。目前市面上很多开源框架,如Mybatis- Plus、kafka Streams以及Flink流处理等,都有一个相似的地方,即用到Stream流特性&#x…