k8s存储管理-数据持久化及动态存储 1.Volumes介绍 官方介绍:https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/
Volumes是Kubernetes中一个对存储资源的抽象概念,属于pod级别的一个配置字段参数,本质上是一个可以被Pod中容器访问的目录。它解决了容器中文件系统的临时性问题,使数据可以在容器重启后依然保持。
Volumes在Pod中绑定多个,多种的数据类型,比如NFS、NAS、CEPH等,这些绑定的数据可以挂载到pod中的一个或多个容器中,从而实现容器的数据持久化和数据共享。
Volumes常用类型:
EmptyDir:临时目录,当Pod从节点删除时,EmptyDir中的数据也会被删除,常用于临时数据存储,如缓存、中间计算结果、容器间数据共享等
HostPath:节点数据共享,HostPath可以让容器直接访问节点上的文件或目录,常用于和节点共享数据
ConfigMap & Secret:用于挂载ConfigMap和Secret到容器中
Downward API:元数据挂载,主要用于容器访问Pod的一些元数据,比如标签、命名空间等
NFS&NAS:网络文件系统,主要用于挂载远程存储到容器中,实现跨主机的数据共享和持久化
PVC:PV请求,K8s中的一类资源,用于配置多种不同的存储后端
Volumes主要有以下几个用途:
数据持久化:保证容器重启后数据不丢失。
容器间数据共享:同一Pod中的多个容器可以访问同一个Volume。
扩展容器存储能力:可以将外部存储挂载到容器中。
配置注入:可以将配置文件以Volume的形式挂载到容器中。
1.1使用磁盘类型的EmptyDir实现数据共享 emptyDir是一种临时存储卷,具有以下特点:
当Pod被分配到某个节点上时创建,初始内容为空。
与Pod的生命周期绑定,当Pod从节点上移除时,emptyDir中的数据会被永久删除。
可以存储在节点的任何介质上,如磁盘、SSD或网络存储,也可以设置为存储在内存中。
Pod中的所有容器都可以读写emptyDir卷中的相同文件。
案例:使用emptyDir实现同一个pod中多个容器数据共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 [root@k8s-master01 pra]# cat emptydir-deployment.yaml apiVersion: apps/v1 #指定这个资源使用的 Kubernetes API 版本,固定写法 kind: Deployment #指定资源类型是 Deployment。 metadata: # deployment的元数据 name: emptydir # 创建的deployment的名字是emptydir labels: #键值对标签,用于资源的分类和选择 app: emptydir #给这个deployment资源打上'app: emptydir'标签 spec: #描述 Deployment 的期望状态,定义 Deployment 的具体配置和行为。 replicas: 1 # 指定需要运行的 Pod 副本数量。 selector: #选择器,用于选择哪些pod属于这个deployment matchLabels: #匹配标签,选择带有特定标签的pod app: emptydir #指定带有' app: emptydir '标签的pod进行管理 template: # pod的模板,用于定义pod metadata: #pod的元数据 labels: #标签,配置pod的标签 app: emptydir #确保创建的 Pod 具有'app: emptydir' 标签。 spec: #描述pod的期望状态,定义pod的具体配置和行为 volumes: #定义pod中所有可用的卷 - name: emptydir #定义了一个名为emptydir的卷 emptyDir: {} #这个 emptyDir 卷在Pod启动时被创建,并且在Pod被删除时也会被删除。它本质上是一个临时存储,数据仅在Pod的生命周期内有效。 containers: #容器列表,复数,可配置多个容器 - name: nginx #容器的名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine #定义容器使用的镜像 volumeMounts: #定义容器中卷的挂载方式 - name: emptydir #指定挂载的卷名为 emptydir,也就是volumes中定义的卷名 mountPath: /opt #指定该卷挂载到容器内的 /opt 目录。 - name: redis image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/redis ports: - containerPort: 6379 volumeMounts: #定义容器中卷的挂载方式 - name: emptydir #指定挂载的卷名为 emptydir,也就是volumes中定义的卷名 mountPath: /emptydir #指定该卷挂载到容器内的 /emptydir 目录。 # 创建这个deployment [root@k8s-master01 16-pv-pvc]# kubectl create -f emptydir-deploy.yaml deployment.apps/emptydir created # 在nginx容器的/opt目录中创建一个名字是test.log文件,在容器redis的/data目录中验证是否存在这个test.log文件 [root@k8s-master01 16-pv-pvc]# kubectl get po NAME READY STATUS RESTARTS AGE emptydir-6df87b7c86-t9gs6 2/2 Running 0 6m33s # 进入第一个容器中创建文件 [root@k8s-master01 16-pv-pvc]# kubectl exec -ti emptydir-6df87b7c86-t9gs6 -c nginx -- sh / # ls -l /opt #/opt目录下没有文件 total 0 / # echo 'emptydir'>/opt/text.log #创建text.log文件 / # ls -l /opt/ total 4 -rw-r--r-- 1 root root 9 Dec 11 07:21 text.log # 进入reids容器的/data目录中验证是否有text.log [root@k8s-master01 16-pv-pvc]# kubectl exec -ti emptydir-6df87b7c86-t9gs6 -c redis -- sh # ls -l /emptydir/total 4 -rw-r--r-- 1 root root 9 Dec 11 07:21 text.log # cat /emptydir/text.logemptydir #可以看到内容存在,实现了容器间的数据共享 / # df -Th Filesystem Type Size Used Available Use% Mounted on overlay overlay 98.9G 6.9G 92.1G 7% / tmpfs tmpfs 64.0M 0 64.0M 0% /dev /dev/mapper/rl-root xfs 98.9G 6.9G 92.1G 7% /opt # 在Type类型中,tmpfs是基于内存的“临时文件系统”,数据主要存在内存;xfs 是持久化磁盘文件系统,数据写在磁盘或块设备上。
总结:用于多个容器之间的数据共享,同一个pod中a容器创建的数据在b容器中能获取到,与Pod的生命周期绑定,当Pod从节点上移除时,emptyDir中的数据会被永久删除。主要用于数据共享,而不是存储数据
1.2使用内存类型的EmptyDir实现数据共享 要使用内存类型的emptyDir ,需要设置medium字段为"Memory"即可:
1 2 3 4 5 volumes: - name: emptydir emptyDir: medium: Memory sizeLimit: 500Mi
修改1.1中的文件emptydir-deployment.yaml,增加medium: Memory 参数,并重新创建资源:
1 2 [root@k8s-master01 16-pv-pvc]# kubectl create -f emptydir-deploy.yaml deployment.apps/emptydir created
查看创建的资源:
1 2 3 4 5 6 7 8 [root@k8s-master01 16-pv-pvc]# kubectl exec -ti emptydir-6ddb864bc7-dwnxz -c nginx -- sh / # df -Th Filesystem Type Size Used Available Use% Mounted on overlay overlay 98.9G 6.9G 92.1G 7% / tmpfs tmpfs 64.0M 0 64.0M 0% /dev tmpfs tmpfs 7.4G 0 7.4G 0% /opt # 可以看到/opt目录的类型是tmpfs,该类型tmpfs是基于内存的“临时文件系统”,Size的大小是7.4G,这是该节点的内存大小
1.3限制EmptyDir大小 emptyDir 无论是“磁盘型”(默认)还是“内存型”(medium: Memory),都可以通过 sizeLimit 字段来限制大小。
不配置 medium 时,emptyDir 默认使用节点本地磁盘存储,当设置了medium: Memory表示使用内存作为存储。 通过 sizeLimit 控制这个卷最多能用多少空间(支持 Mi/Gi 等单位)。
配置案例如下:
1 2 3 4 5 6 spec: volumes: - name: emptydir emptyDir: medium: Memory sizeLimit: "1Gi"
更新deploy的配置:
1 2 [root@k8s-master01 16-pv-pvc]# kubectl replace -f emptydir-deploy.yaml deployment.apps/emptydir replaced
进入容器中查看/opt目录的大小:
1 2 3 4 5 6 7 8 9 [root@k8s-master01 16-pv-pvc]# kubectl exec -ti emptydir-694d578d49-l4x2j -- sh Defaulted container "nginx" out of: nginx, redis / # df -Th Filesystem Type Size Used Available Use% Mounted on overlay overlay 98.9G 15.1G 83.8G 15% / tmpfs tmpfs 64.0M 0 64.0M 0% /dev tmpfs tmpfs 1.0G 0 1.0G 0% /opt # 可以看到此时/opt的Size大小为1G
注意:
1 2 3 1.磁盘类型的emptyDir,限制大小后不会显示具体限制的大小 2.磁盘类型的超出最大限制时,Pod将会变成Completed状态,同时将会创建一个Pod 3.内存类型的emptyDir不会超出限制的大小,如不限制将会使用机器内存的最大值,或容器内存限制之和的最大值
1.4hostPath hostPath卷能将主机节点文件系统上的文件或目录挂载到Pod中。它允许Pod访问宿主机上的文件系统。并不推荐使用,因为创建的pod并不能保证创建在同一个节点上,当然也可以使用其他手段让pod创建在某个节点上
使用hostPath的示例:将宿主机上/data目录挂载到容器中的/liujunwei目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 [root@k8s-master01 pra]# vim hostPath-deploy.yaml apiVersion: apps/v1 #指定这个资源使用的 Kubernetes API 版本,固定写法 kind: Deployment #指定资源类型是 Deployment。 metadata: # deployment的元数据 name: hostpath # 创建的deployment的名字是emptydir labels: #键值对标签,用于资源的分类和选择 app: nginx #给这个deployment资源打上'app: nginx'标签 spec: #描述 Deployment 的期望状态,定义 Deployment 的具体配置和行为。 replicas: 2 # 指定需要运行的 Pod 副本数量。 selector: #选择器,用于选择哪些pod属于这个deployment matchLabels: #匹配标签,选择带有特定标签的pod app: nginx #指定带有' app: nginx '标签的pod进行管理 template: # pod的模板,用于定义pod metadata: #pod的元数据 labels: #标签,配置pod的标签 app: nginx #确保创建的 Pod 具有'app: nginx' 标签。 spec: #描述pod的期望状态,定义pod的具体配置和行为 volumes: #定义pod中所有可用的卷 - name: hostpath #定义卷的名字是 hostpath hostPath: #定义这个卷的类型是 hostPath path: /data #指定这个卷的挂载路径,这里 /data 是宿主机的路径 - name: emptydir #定义卷的名字是 emptydir emptyDir: {} #指定这个卷的类型是emptyDir,这个 emptyDir 卷在Pod启动时被创建,并且在Pod被删除时也会被删除。它本质上是一个临时存储,数据仅在Pod的生命周期内有效。 containers: #容器列表,复数,可配置多个容器 - name: nginx #容器的名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx #定义容器使用的镜像 volumeMounts: #定义容器中卷的挂载方式 - name: emptydir mountPath: /opt - name: redis image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/redis ports: - containerPort: 6379 volumeMounts: #定义容器中卷的挂载方式 - name: emptydir mountPath: /emptydir - name: hostpath #指定挂载的卷名为 hostpath,也就是volumes中定义的卷名 mountPath: /liujunwei #指定该卷挂载到容器内的 /liujunwei 目录。 # 创建这个deploy [root@k8s-master01 16-pv-pvc]# kubectl create -f hostPath-deploy.yaml deployment.apps/emptydir created # 查看创建的pod [root@k8s-master01 16-pv-pvc]# kubectl get po NAME READY STATUS RESTARTS AGE emptydir-5f76df9b7b-pkpjj 2/2 Running 0 4s emptydir-5f76df9b7b-sx9r8 2/2 Running 0 4s # 进入该pod中redis容器中查看是否存在/liujunwei目录,该pod中包含两个容器,-c redis表示进入名字是redis的容器中。 [root@k8s-master01 16-pv-pvc]# kubectl exec -ti emptydir-5f76df9b7b-pkpjj -c redis -- bash root@emptydir-5f76df9b7b-pkpjj:/data# ls /liujunwei/ -l total 0 # 创建文件hostpath.txt,并向文件中添加任意内容 root@emptydir-5f76df9b7b-pkpjj:/liujunwei# echo 'hello hostpath' >hostpath.txt root@emptydir-5f76df9b7b-pkpjj:/liujunwei# ls -l total 4 -rw-r--r-- 1 root root 15 Dec 11 12:17 hostpath.txt root@emptydir-5f76df9b7b-pkpjj:/liujunwei# cat hostpath.txt hello hostpath # 验证宿主机的/data目录中是否存在 hostpath.txt 文件 [root@k8s-master01 16-pv-pvc]# kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES busybox-6cc9f8bd56-27zw9 1/1 Running 331 (55m ago) 17d 172.16.135.169 k8s-node03 <none> <none> emptydir-5f76df9b7b-pkpjj 2/2 Running 0 4m21s 172.16.58.223 k8s-node02 <none> <none> emptydir-5f76df9b7b-sx9r8 2/2 Running 0 4m21s 172.16.85.254 k8s-node01 <none> <none> # [root@k8s-node02 data]# cd /data/ [root@k8s-node02 data]# ll total 4 -rw-r--r-- 1 root root 15 Dec 11 20:17 hostpath.txt [root@k8s-node02 data]# cat hostpath.txt #可以看到文件存在 hello hostpath #文件中的内容也是正确的 # 注意:该deployment有两个副本,分别是emptydir-5f76df9b7b-pkpjj和emptydir-5f76df9b7b-sx9r8,在hostPath类型中,emptydir-5f76df9b7b-pkpjj容器中的文件跟emptydir-5f76df9b7b-sx9r8是没有任何关系
hostPath卷有两个主要参数:
path: 指定宿主机上的文件或目录路径(必需)
type: 指定挂载类型(可选)
type支持的值包括:
DirectoryOrCreate: 如果路径不存在,则创建空目录
Directory: 必须存在的目录,是指每个运行pod的节点上都必须存在(宿主机)
FileOrCreate: 如果文件不存在,则创建空文件
File: 必须存在的文件,是指每个运行pod的节点上都必须存在(宿主机)
Socket: UNIX 套接字,如某个程序的socket文件,必须存在于给定路径中。
CharDevice: 字符设备,如串行端口、声卡、摄像头等,必须存在于给定路径中,且只 有Linux 支持。
BlockDevice: 块设备,如硬盘等,必须存在于给定路径中,且只有Linux支持。
配置案例:
1 2 3 4 5 6 spec: volumes: - name: hostpath-test hostPath: path: /data type: DirectoryOrCreate
1.5挂载NFS至容器 使用远程存储介质,可以实现跨主机容器之间的数据共享,比如使用 NFS、NAS、CEPH 等
1.5.1安装nfs服务 在使用nfs前需要先准备一台nfs服务器,这里用一台新机器作为nfs服务器,ip地址为192.168.0.106,主机名为:nfs-server。不占用k8s集群的五个节点资源。
在nfs-server机器上安装nfs服务端:
1 2 3 4 5 6 7 [root@nfs-server ~]# yum -y install rpcbind nfs-utils # CentOS、Rocky 系列 # yum install nfs-utils rpcbind -y # Ubuntu 系列 # apt install nfs-kernel-server -y
启动服务并开机自启
1 2 3 4 5 [root@nfs-server ~]# systemctl start nfs-server [root@nfs-server ~]# systemctl enable nfs-server Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service. [root@nfs-server ~]# systemctl enable rpcbind.service
创建共享目录:
1 2 mkdir -p /data/nfs/ chmod 755 -R /data/nfs/
编辑配置文件,配置谁能访问共享目录
1 2 3 4 5 6 7 8 9 10 11 12 13 vim /etc/exports # 在文件中假如下面配置 /data/nfs/ 192.168.0.0/24(rw,no_root_squash,no_all_squash,sync) # 这行代码的意思是把共享目录/data/share/共享给192.168.0.0/24网段,也是我k8s集群的网段,表示该网段的机器都能访问该目录。 # 后面括号里的内容是权限参数,其中: # rw 表示设置目录可读写。 # sync 表示数据会同步写入到内存和硬盘中,相反 rsync 表示数据会先暂存于内存中,而非直接写入到硬盘中。# no_root_squash NFS客户端连接服务端时如果使用的是root的话,那么对服务端分享的目录来说,也拥有root权限。 # no_all_squash 不论NFS客户端连接服务端时使用什么用户,对服务端分享的目录来说都不会拥有匿名用户权限。 # 如果有多个共享目录配置,则使用多行,一行一个配置。保存好配置文件后,需要执行exportfs -r命令使配置立即生效 [root@nfs-server ~]# exportfs -r
重启服务:
1 2 systemctl restart rpcbind systemctl restart nfs-server
重启后执行showmount 命令来查看服务端(本机)是否可连接:
1 2 3 [root@nfs-server ~]# showmount -e localhost Export list for localhost: /data/nfs 192.168.0.0/24
出现上面结果表明NFS服务端配置正常。
在需要挂载的机器节点上挂载测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 # 客服端挂载服务端的共享目录,前提客服端需要安装nfs客户端 # 这里在k8s-master01机器上挂载测试,所以在k8s-master01上安装客户端,建议所有k8s节点都安装。 # CentOS、Rocky系列 # yum install nfs-utils -y # Ubuntu系列 # apt install nfs-common -y # 查看nfs服务器可挂载的共享目录,nfs服务器ip是192.168.0.106 [root@k8s-master01 ~]# showmount -e 192.168.0.106 Export list for 192.168.0.106: /data/nfs 192.168.0.0/24 # 挂载测试 [root@k8s-master01 ~]# mount -t nfs 192.168.0.106:/data/nfs /mnt/nfs/ [root@k8s-master01 ~]# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 2.0G 0 2.0G 0% /dev tmpfs 2.0G 0 2.0G 0% /dev/shm tmpfs 2.0G 92M 1.9G 5% /run tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup /dev/mapper/centos_k8s--master01-root 17G 6.7G 11G 39% / /dev/sda1 1014M 170M 845M 17% /boot tmpfs 393M 0 393M 0% /run/user/0 192.168.0.106:/data/nfs 46G 2.7G 43G 6% /mnt/nfs #该目录是新挂载的 # 在/mnt/nfs目录创建文件,去nfs服务器(192.168.0.106)的/data/nfs目录查看是否存在 # 文件创建 [root@k8s-master01 ~]# cd /mnt/nfs/ [root@k8s-master01 nfs]# touch 123.txt # nfs服务器上验证文件是否存在 [root@nfs-server nfs]# pwd /data/nfs [root@nfs-server nfs]# ll 总用量 0 -rw-r--r-- 1 root root 0 8月 7 15:53 123.txt # 测试完成卸载/mnt/nfs目录 [root@k8s-master01 nfs]# umount /mnt/nfs # 上述测试完成表示nfs共享目录挂载成功!
1.5.2在pod中挂载nfs类型的volumes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 [root@k8s-master01 pra]# cat nfs-deploy.yaml apiVersion: apps/v1 #指定这个资源使用的 Kubernetes API 版本,固定写法 kind: Deployment #指定资源类型是 Deployment。 metadata: # deployment的元数据 name: nginx-deployment # 创建的deployment的名字 labels: #键值对标签,用于资源的分类和选择 app: nginx #给这个deployment资源打上'app: nginx'标签 spec: #描述 Deployment 的期望状态,定义 Deployment 的具体配置和行为。 replicas: 1 # 指定需要运行的 Pod 副本数量。 selector: #选择器,用于选择哪些pod属于这个deployment matchLabels: #匹配标签,选择带有特定标签的pod app: nginx #指定带有' app: nginx '标签的pod进行管理 template: # pod的模板,用于定义pod metadata: #pod的元数据 labels: #标签,配置pod的标签 app: nginx #确保创建的 Pod 具有'app: nginx' 标签。 spec: #描述pod的期望状态,定义pod的具体配置和行为 nodeSelector: #节点选择器,用于将pod部署到指定标签的节点上 valume: hostpath #节点上的标签 volumes: #定义pod中所有可用的卷 - name: my-hostpath #定义了一个名字是my-hostpath的卷,用于在volumeMounts中引用。 hostPath: #卷类型,hostPath表示允许pod访问宿主机上的文件或目录 path: /data/log.txt #定义要挂载的宿主机文件路径 type: File #定义挂载的是文件,表示该路径必须是一个文件。(也可以是目录类型Directory) - name: nfs-volume #定义了一个名字是nfs-volume的卷,用于在volumeMounts中引用。 nfs: server: 192.168.0.106 #nfs服务地址 path: /data/nfs/dp #nfs共享目录 containers: #容器列表,复数,可配置多个容器 - name: nginx #容器的名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine #定义容器使用的镜像 volumeMounts: #定义容器如何使用卷 - name: my-hostpath #引用卷的名称是my-hostPath,该字段必须与volumes.name字段相同 mountPath: /liujunwei/log.txt #定义容器内的挂载路径 - name: nfs-volume #引用卷的名称是nfs-volume ,该字段必须与volumes.name字段相同 mountPath: /junwei #指定容器中的目录,将/data/nfs/dp挂载到容器/junwei,也就是/junwei目录中的内容和/data/nfs/dp目录中的内容一样 # 创建资源 [root@k8s-master01 pra]# kubectl replace -f nginx-deployment.yaml deployment.apps/nginx-deployment replaced # 进入容器中验证目录是否挂载成功 [root@k8s-master01 pra]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-deployment-6df5d4d774-vgcrd 1/1 Running 0 6m [root@k8s-master01 pra]# kubectl exec -ti nginx-deployment-6df5d4d774-vgcrd -- sh / # df -h Filesystem Size Used Available Use% Mounted on overlay 17.0G 6.6G 10.4G 39% / tmpfs 64.0M 0 64.0M 0% /dev tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup 192.168.0.106:/data/nfs/dp 45.1G 2.6G 42.5G 6% /junwei /dev/mapper/centos_k8s--master01-root 17.0G 6.6G 10.4G 39% /liujunwei/acc.log /dev/mapper/centos_k8s--master01-root 17.0G 6.6G 10.4G 39% /etc/hosts /dev/mapper/centos_k8s--master01-root 17.0G 6.6G 10.4G 39% /dev/termination-log /dev/mapper/centos_k8s--master01-root 17.0G 6.6G 10.4G 39% /etc/hostname /dev/mapper/centos_k8s--master01-root 17.0G 6.6G 10.4G 39% /etc/resolv.conf shm 64.0M 0 64.0M 0% /dev/shm tmpfs 3.7G 12.0K 3.7G 0% /run/secrets/kubernetes.io/serviceaccount tmpfs 1.9G 0 1.9G 0% /proc/acpi tmpfs 64.0M 0 64.0M 0% /proc/kcore tmpfs 64.0M 0 64.0M 0% /proc/keys tmpfs 64.0M 0 64.0M 0% /proc/timer_list tmpfs 64.0M 0 64.0M 0% /proc/sched_debug tmpfs 1.9G 0 1.9G 0% /proc/scsi tmpfs 1.9G 0 1.9G 0% /sys/firmware / # cat /junwei/ 1.txt test/ / # cat /junwei/1.txt test # 在nfs服务器上查看文件 [root@nfs-server dp]# pwd /data/nfs/dp [root@nfs-server dp]# ll 总用量 4 -rw-r--r-- 1 root root 5 8月 7 17:00 1.txt drwxr-xr-x 2 root root 6 8月 7 17:00 test [root@nfs-server dp]# cat 1.txt test # 多副本的pod中也会存在该数据。
如果Pod一直处于创建中,可能是由于没有安装客户端工具导致的,可以使用describe查看 详情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # kubectl get po nfs-6db79bb58b-m4vvj 0/2 ContainerCreating 0 59s # kubectl describe po nfs-6db79bb58b-m4vvj Warning FailedMount 55s (x8 over 119s) kubelet MountVolume.SetUp failed for volume "nfs-volume" : mount failed: exit status 32 Mounting command: mount Mounting arguments: -t nfs 192.168.0.106:/data/nfs/testdata /var/lib/kubelet/pods/faae4701-1c1d-4a2d-a16e fa76c64a7ae7/volumes/kubernetes.io~nfs/nfs-volume Output: mount: /var/lib/kubelet/pods/faae4701-1c1d-4a2d-a16e fa76c64a7ae7/volumes/kubernetes.io~nfs/nfs-volume: bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program.
2.PV 和 PVC 使用抽象的PV和PVC替代原生的volume
2.1什么是pv和pvc 持久卷 PV (Persistent Volume) :是集群中的一块存储,可以由管理员事先制备, 或者使用存储类(Storage Class) 来动态制备,PV 卷的制备有两种方式:静态制备或动态制备。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。
**存储请求PVC (Persistent Volume Claim)**:表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存)。同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以挂载为 ReadWriteOnce、ReadOnlyMany、ReadWriteMany 或 ReadWriteOncePod, 请参阅访问模式 )。
PV和PVC的关系可以类比为“房东”和“租客”:
PV是房东 ,它提供了具体的存储资源。
PVC是租客 ,它向Kubernetes请求存储资源。
当PVC创建后,Kubernetes会根据PVC的需求去匹配一个合适的PV,并将它们绑定在一起。一旦绑定,PV就只能被这个PVC使用,无法再被其他PVC绑定。
PV和PVC的生命周期:
准备(Provisioning) :静态供应 :管理员手动创建PV。动态供应 :通过StorageClass动态创建PV。
绑定(Binding) :用户创建PVC并指定需要的资源和访问模式。Kubernetes找到匹配的PV并将它们绑定。
使用(Using) :Pod可以像使用普通Volume一样使用PVC。
释放(Releasing) :当用户删除PVC时,PV会进入“已释放”状态,但仍保留数据。
回收(Reclaiming) :PV的回收策略决定了如何处理已释放的PV:保留(Retain) :数据保留,需手动清理。回收(Recycle) :清除数据,PV可再次使用。删除(Delete) :删除PV及其数据。
2.2PV回收策略: 在Kubernetes中,PersistentVolume(PV)的回收策略用于定义当与之绑定的PersistentVolumeClaim(PVC)被删除后,PV应该如何处理。Kubernetes支持三种主要的PV回收策略:
Retain(保留) :当PVC被删除时,PV不会被删除,而是被标记为“Released”已释放状态。此时,PV中的数据仍然存在,需要管理员手动清理或重新利用这些数据。这种策略适用于数据需要保留的场景.
Recycle(回收) :此策略会清除PV中的数据,例如删除所有文件,然后PV可以被重新绑定到新的PVC。这种策略比较简单且适用于某些特定场景,但在Kubernetes 1.10版本后已经被弃用,不再推荐使用.
Delete(删除) :这是动态配置PV的默认策略。当PVC被删除时,PV和其存储资源将被自动删除。这种策略适用于不需要保留数据的场景.
如果更改回收策略,可以通过persistentVolumeReclaimPolicy: Recycle字段配置即可。
官方文档:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy
2.3PV的访问策略: 在Kubernetes中,Persistent Volume(PV)的访问策略(Access Modes)用于定义如何访问存储卷。PV支持以下几种访问模式:
ReadWriteOnce(RWO) :PV可以被单个节点以读写方式挂载。这意味着只有一个节点可以同时以读写方式访问该存储卷。
ReadOnlyMany(ROX) :PV可以被多个节点以只读方式挂载。这种模式允许多个节点同时访问存储卷,但只能以只读方式访问。
ReadWriteMany(RWX) :PV可以被多个节点以读写方式挂载。这种模式允许多个节点同时以读写方式访问存储卷。
ReadWriteOncePod(RWOP) :只能被单个pod以读写模式挂载目前仅支持在CSI且K8s在1.22+中使用
需要注意的是,虽然一些PV可能支持多种访问模式,但在实际挂载时只能选择一种模式进行使用。选择合适的访问模式取决于应用程序的需求和存储卷的特性。
官方文档:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
上述的访问策略实际上还要考虑存储是否支持这种访问策略。
2.4存储的分类 ➢ 文件存储:一些数据可能需要被多个节点使用,比如用户的头像、用户上传的文件等,实现方式:NFS、NAS、FTP、CephFS等。
➢ 块存储:一些数据只能被一个节点使用,或者是需要将一块裸盘整个挂载使用,比如数据库、Redis、rabbitmq、zookeeper、kafka等,实现方式:Ceph、GlusterFS、公有云。
➢ 对象存储:由程序代码直接实现的一种存储方式,云原生应用无状态化常用的实现方式,实现方式:一般是符合S3协议的云存储,比如AWS的S3存储、Minio、七牛云等。
2.5创建NFS或NAS类型的PV 生产环境不要使用NFS在k8s集群中做文件存储,nfs是单点的,如果出现故障会影响整个集群。生产环境可以用云平台的NAS服务
这里是测试使用,nfs使用1.5.1中搭建的nfs服务。这里五台k8s集群的节点都需要安装nfs客户端,因为pod会随机在节点上运行,所以每个节点都需要安装nfs客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # k8s集群中所有节点都安装 yum -y install nfs-utils # 在服务端新增一个共享目录 [root@nfs-server ~]# vim /etc/exports /data/nfs/ 192.168.0.0/24(rw,no_root_squash,no_all_squash,sync) # /data/k8s/目录为新增的目录 /data/k8s/ 192.168.0.0/24(rw,no_root_squash,no_all_squash,sync) # 重载 [root@nfs-server ~]# exportfs -r # 重启服务 [root@nfs-server ~]# systemctl restart nfs rpcbind # 测试挂载 # 192.168.0.106:/data/k8s是nfs服务器的ip,目录是配置的nfs共享目录 # /test-nfs/ 挂载目录,就是将192.168.0.106:/data/k8s挂载到本地的/test-nfs目录 [root@k8s-master01 pra]# mount -t nfs 192.168.0.106:/data/k8s /test-nfs/ # 查看挂载目录 [root@k8s-master01 pra]# df -h Filesystem Size Used Avail Use% Mounted on devtmpfs 2.0G 0 2.0G 0% /dev tmpfs 2.0G 0 2.0G 0% /dev/shm tmpfs 2.0G 173M 1.8G 9% /run tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup /dev/mapper/centos_k8s--master01-root 17G 6.7G 11G 40% / /dev/sda1 1014M 170M 845M 17% /boot tmpfs 393M 0 393M 0% /run/user/0 192.168.0.106:/data/k8s 46G 2.7G 43G 6% /test-nfs #挂载目录已经存在,证明可用
创建一个NFS类型的PV资源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 下面的 PV 定义了一个使用 NFS 存储的 10Gi 容量的持久化卷,可以被多个节点以读写模式挂载。当 PV 从 PVC 中释放后,数据会被保留。 [root@k8s-master01 pv]# cat pv-nfs.yaml apiVersion: v1 #指定了 Kubernetes API 的版本,这里使用的是 v1,表示最新稳定版本。 kind: PersistentVolume #定义了资源的类型,这里是一个 PersistentVolume。持久卷为集群提供持久化存储,与 PersistentVolumeClaim 配合使用。 metadata:#定义这个资源的元数据,通常包括 name、namespace、labels、annotations 等。 name: pv-nfs#指定了 PV 的名称,这里是 pv-nfs。在整个集群内必须是唯一的。 spec: #定义持久卷的详细规范和配置。 capacity: #定义持久卷的存储容量。 storage: 10Gi #定义了卷的大小,这里是 10Gi。 volumeMode: Filesystem #指定持久卷的卷模式,这里是 Filesystem,意味着它将作为文件系统被挂载。还有Block 模式,该参数是默认值,即使不指定也是该值。 accessModes:#定义 PV 的访问模式,这里是 ReadWriteMany,表示可以被多个节点以读写模式挂载。 - ReadWriteMany #表示可以被多个节点以读写模式挂载。 persistentVolumeReclaimPolicy: Retain #定义当 PV 从 PVC 中释放后的回收策略,这里是 Retain,表示当持久卷被释放后,仍然保留数据而不自动删除。 storageClassName: nfs-slow #指定存储类名称,pvc要绑定到pv上就是根据这个名称进行绑定。 nfs: #定义 NFS(网络文件系统)卷的相关配置。 server: 192.168.0.106 #指定 NFS 服务器的 IP 地址或主机名,这里是 192.168.0.106。 path: /data/k8s #指定 NFS 服务器上的共享目录路径,这里是 /data/k8s。 # 创建pv [root@k8s-master01 pv]# kubectl replace -f pv-nfs.yaml persistentvolume/pv-nfs replaced # 查看创建的pv # pv创建成功不代表存储就是可用的,要在pod中挂载才知道能不能用 [root@k8s-master01 pv]# kubectl get -f pv-nfs.yaml NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-nfs 10Gi RWX Retain Available nfs-slow 57m
pv的状态解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@k8s-master01 pv]# kubectl get -f pv-nfs.yaml NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-nfs 10Gi RWX Retain Available nfs-slow 57m ---下面是对每个字段的解释--- NAME: 这是持久卷的名称。你的 PV 名为 pv-nfs,这是在 YAML 文件的 metadata.name 字段中指定的。 CAPACITY: 这个字段显示持久卷的存储容量。这里是 10Gi,代表这个 PV 提供 10 Gibibytes 的存储空间。这个值对应于 YAML 文件中的 spec.capacity.storage。 ACCESS MODES: 显示持久卷的访问模式。RWX 表示 ReadWriteMany,即允许多个节点同时挂载并读写该卷。这个值对应于 YAML 文件中的 spec.accessModes。 RECLAIM POLICY: 指定 PV 的回收策略。Retain 表示在 PV 被释放后(即关联的 PersistentVolumeClaim 被删除后),PV 保持现状而不自动删除。这与 YAML 文件中的 spec.persistentVolumeReclaimPolicy 字段一致。 STATUS: 显示持久卷的当前状态。Available 表示这个 PV 目前未绑定任何 PersistentVolumeClaim,可以被新的 PVC 绑定。 CLAIM: 如果这个持久卷已经被某个 PersistentVolumeClaim 绑定,这里会显示该 PVC 的名称。在你的例子中,这个字段是空的,表示没有 PVC 绑定这个 PV。 STORAGECLASS: 这个字段显示了持久卷所属的存储类(StorageClass)。nfs-slow 是在 YAML 文件中 spec.storageClassName 字段中定义的存储类名称。存储类定义了动态供应存储卷的配置和策略。 REASON: 如果持久卷无法正常使用或者有问题时,这个字段会显示具体原因。在你的例子中,这个字段是空的,表示 PV 没有问题。 AGE: 显示这个 PV 已经存在的时间。57m 表示这个 PV 已经创建了 57 分钟。 ------------------------- pv其他状态:(STATUS字段) ➢ Available:可用,没有被PVC绑定的空闲资源。 ➢ Bound:已绑定,已经被PVC绑定。 ➢ Released:已释放,PVC被删除,但是资源还未被重新使用。 ➢ Failed:失败,自动回收失败。
2.6创建hostPath类型的PV 定义一个hostPath的PV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@k8s-master01 pv ] kind: PersistentVolume apiVersion: v1 metadata: name: pv-hostpath labels: type: local spec: storageClassName: hostpath-sc volumeMode: Filesystem capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: "/data/hostpath" type: DirectoryOrCreate
创建这个pv:
1 2 [root@k8s-master01 16-pv-pvc]# kubectl create -f pv-hostpath.yaml persistentvolume/pv-hostpath created
查看创建的pv(PV是没有命名空间隔离的):
字段解读:
1 2 3 4 5 6 7 8 9 10 NAME: PersistentVolume的名称,即 pv-hostpath,是该PV在集群中的唯一标识符 CAPACITY: PV的存储容量,显示为 1Gi,表示该持久卷提供1GB的存储空间 ACCESS MODES: 访问模式,显示为 RWO(ReadWriteOnce的缩写),表示该卷只能被单个节点以读写方式挂载 RECLAIM POLICY: 回收策略,显示为 Retain,表示当PVC被删除后,PV会被保留而不是自动删除 STATUS: PV的当前状态,显示为 Available,表示该PV处于可用状态,尚未被任何PVC绑定。当PV被PVC绑定后,状态会变为 Bound CLAIM: 显示绑定该PV的PVC信息,格式为 命名空间/PVC名称。您的输出中此字段为空,说明暂无PVC绑定该PV STORAGECLASS: 存储类名称,显示为 hostpath-sc,用于PV和PVC的匹配绑定 VOLUMEATTRIBUTESCLASS: 卷属性类,显示为 <unset>,表示未设置此高级特性 REASON: 显示PV状态的原因说明,通常在异常情况下显示 AGE: PV创建后的存在时间,显示为 10s,表示该PV创建了10秒
2.7创建CephRBD类型的PV CephRBD类型的VP配置案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 apiVersion: v1 kind: PersistentVolume metadata: name: ceph-rbd-pv spec: capacity: storage: 1Gi storageClassName: ceph-fast accessModes: - ReadWriteOnce rbd: monitors: - 192.168.1.123:6789 - 192.168.1.124:6789 - 192.168.1.125:6789 pool: rbd image: ceph-rbd-pv-test user: admin secretRef: name: ceph-secret fsType: ext4 readOnly: false
➢ monitors:Ceph的monitor节点的IP
➢ pool:所用Ceph Pool的名称,可以使用ceph osd pool ls查看
➢ image:Ceph块设备中的磁盘映像文件,可以使用rbd create POOL_NAME/IMAGE_NAME – size 1024 创建,使用rbd list POOL_NAME 查看
➢ user:Rados的用户名,默认是admin
➢ secretRef:用于验证Ceph身份的密钥
➢ fsType:文件类型,可以是ext4、XFS等
➢ readOnly:是否是只读挂载
2.8PVC绑定pv
2.8.1创建pvc绑定pv 注意:pv是没有命名空间隔离的,但是pvc具有命名空间隔离,pod和pvc要在同一个命名空间才能正常使用
这里的pvc跟2.5示例中创建的NFS类型的pv进行绑定
PV配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@k8s-master01 pv]# cat pv-nfs.yaml apiVersion: v1 #指定了 Kubernetes API 的版本,这里使用的是 v1,表示最新稳定版本。 kind: PersistentVolume #定义了资源的类型,这里是一个 PersistentVolume。持久卷为集群提供持久化存储,与 PersistentVolumeClaim 配合使用。 metadata: #定义这个资源的元数据,通常包括 name、namespace、labels、annotations 等。 name: pv-nfs #指定了 PV 的名称,这里是 pv-nfs。在整个集群内必须是唯一的。 spec: #定义持久卷的详细规范和配置。 capacity: #定义持久卷的存储容量。 storage: 10Gi #定义了卷的大小,这里是 10Gi。 volumeMode: Filesystem #指定持久卷的卷模式,这里是 Filesystem,意味着它将作为文件系统被挂载。还有Block 模式 accessModes: #定义 PV 的访问模式,这里是 ReadWriteMany,表示可以被多个节点以读写模式挂载。 - ReadWriteMany #表示可以被多个节点以读写模式挂载。 persistentVolumeReclaimPolicy: Retain #定义当 PV 从 PVC 中释放后的回收策略,这里是 Retain,表示当持久卷被释放后,仍然保留数据而不自动删除。 storageClassName: nfs-slow #指定存储类名称,这里使用 nfs-slow。存储类用来管理存储的动态供应和配置。 nfs: #定义 NFS(网络文件系统)卷的相关配置。 server: 192.168.0.106 #指定 NFS 服务器的 IP 地址或主机名,这里是 192.168.0.106。 path: /data/k8s #指定 NFS 服务器上的共享目录路径,这里是 /data/k8s。
PVC配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 [root@k8s-master01 pv ] kind: PersistentVolumeClaim apiVersion: v1 metadata: name: nfs-pvc-claim spec: storageClassName: nfs-slow accessModes: - ReadWriteMany resources: requests: storage: 1Gi pvc绑定pv总结: 1 . storageClassName 必须一致: PVC 要求:storageClassName: fast PV 必须写:storageClassName: fast 2. AccessModes 必须满足 PV 的 AccessMode 必须 包含 PVC 所需的 AccessMode。 PVC: accessModes: - ReadWriteOnce PV: accessModes: - ReadWriteOnce - ReadOnlyMany 3. PV 的容量必须 ≥ PVC 的请求容量 PV: capacity: storage: 10Gi PVC: resources: requests: storage: 5Gi
创建这个pvc,与pv进行绑定:
1 2 [root@k8s-master01 pv]# kubectl create -f pvc-nfs.yaml persistentvolumeclaim/nfs-pvc-claim created
查看pv和pvc的状态:
1 2 3 4 5 6 7 [root@k8s-master01 pv]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE hostpath-pv 10Gi RWO Retain Available hostpath 4d1h pv-nfs 10Gi RWX Retain Bound default/nfs-pvc-claim nfs-slow 162m [root@k8s-master01 pv]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE nfs-pvc-claim Bound pv-nfs 10Gi RWX nfs-slow 4m46s
2.8.2pod中使用pvc pod配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@k8s-master01 pv]# cat pvc-nfs-pod.yaml apiVersion: v1 kind: Pod metadata: name: pvc-nfs-pod #pod名称 spec: containers: - name: pvc-nfs-container #容器名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx #镜像地址 volumeMounts: #挂载卷 - name: nfs-pvc #卷的名称,这个名称必须和下面volumes中的name一致 mountPath: /usr/share/nginx/html #挂载路径 volumes: #指定卷的配置 - name: nfs-pvc #自定义卷名称,这里定义的名称会在volumeMounts.name中引用 persistentVolumeClaim: #持久卷声明 claimName: nfs-pvc-claim #指定使用名字是nfs-pvc-claim的pvc,这个名称是pvc的名字,也就是pvc中metadata.name字段的值
创建pod:
1 2 [root@k8s-master01 pv]# kubectl create -f pvc-nfs-pod.yaml pod/pvc-nfs-pod created
查看pod状态:
1 2 3 [root@k8s-master01 pv]# kubectl get po NAME READY STATUS RESTARTS AGE pvc-nfs-pod 1/1 Running 0 15s
进入pod中查看挂载目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@k8s-master01 pv]# kubectl exec -ti pvc-nfs-pod -- sh / # df -h Filesystem Size Used Available Use% Mounted on overlay 17.0G 5.5G 11.5G 32% / tmpfs 64.0M 0 64.0M 0% /dev tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup 192.168.0.106:/data/k8s 45.1G 2.6G 42.4G 6% /usr/share/nginx/html tmpfs 1.9G 0 1.9G 0% /proc/acpi tmpfs 64.0M 0 64.0M 0% /proc/kcore tmpfs 64.0M 0 64.0M 0% /proc/keys tmpfs 64.0M 0 64.0M 0% /proc/timer_list tmpfs 64.0M 0 64.0M 0% /proc/sched_debug tmpfs 1.9G 0 1.9G 0% /proc/scsi tmpfs 1.9G 0 1.9G 0% /sys/firmware # 通过df -h 可以看出192.168.0.106:/data/k8s目录已经挂载到容器的/usr/share/nginx/html目录
在nfs服务器上的共享目录中创建一个文件,在该pod中查看是否存在这个文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 在nfs服务器上的共享目录中创建文件 [root@nfs-server ~]# cd /data/k8s [root@nfs-server k8s]# echo "hello" >index.html [root@nfs-server k8s]# ll 总用量 4 -rw-r--r-- 1 root root 6 8月 20 18:16 index.html # 在pod上验证是否存在这个index.html [root@k8s-master01 pv]# kubectl exec -ti pvc-nfs-pod -- sh #进入pod / # ls -l /usr/share/nginx/html total 4 -rw-r--r-- 1 root root 6 Aug 20 10:16 index.html / # cat /usr/share/nginx/html/index.html hello / # curl localhost hello
2.8.3deployment中使用pvc deployment配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [root@k8s-master01 pv]# cat nfs-deploy.yaml apiVersion: apps/v1 #指定这个资源使用的 Kubernetes API 版本,固定写法 kind: Deployment #指定资源类型是 Deployment。 metadata: # deployment的元数据 name: nginx-deployment # 创建的deployment的名字 labels: #键值对标签,用于资源的分类和选择 app: nginx #给这个deployment资源打上'app: nginx'标签 spec: #描述 Deployment 的期望状态,定义 Deployment 的具体配置和行为。 replicas: 5 # 指定需要运行的 Pod 副本数量。 selector: #选择器,用于选择哪些pod属于这个deployment matchLabels: #匹配标签,选择带有特定标签的pod app: nginx #指定带有' app: nginx '标签的pod进行管理 template: # pod的模板,用于定义pod metadata: #pod的元数据 labels: #标签,配置pod的标签 app: nginx #确保创建的 Pod 具有'app: nginx' 标签。 spec: #描述pod的期望状态,定义pod的具体配置和行为 volumes: - name: nfs-pvc #定义卷的名称,用于在容器中引用,这个名称要和下面volumeMounts中的name一致 persistentVolumeClaim: #指定要使用的PVC claimName: nfs-pvc-claim #指定要使用的PVC的名称,这里指定的pvc名称是已经创建好的pvc的名字 containers: #容器列表,复数,可配置多个容器 - name: pvc-nfs-container #容器的名称 image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine #定义容器使用的镜像 volumeMounts: - mountPath: /usr/share/nginx/html #容器内的挂载路径 name: nfs-pvc #挂载的卷名称,与volumes中的name保持一致
创建找个5副本的deployment:
1 2 [root@k8s-master01 pv]# kubectl create -f nfs-deploy.yaml deployment.apps/nginx-deployment created
查看创建的pod:
1 2 3 4 5 6 7 [root@k8s-master01 pv]# kubectl get po NAME READY STATUS RESTARTS AGE nginx-deployment-9b55c9df6-4wc6v 1/1 Running 0 20h nginx-deployment-9b55c9df6-5bd2d 1/1 Running 0 20h nginx-deployment-9b55c9df6-c95lv 1/1 Running 0 20h nginx-deployment-9b55c9df6-kmvv9 1/1 Running 0 20h nginx-deployment-9b55c9df6-lc77f 1/1 Running 0 20h
验证挂载卷:在nfs服务器上的共享目录中(或者pod)创建一个文件,在该pod中(或者nfs的共享目录中)查看是否存在这个文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 进入nginx-deployment-9b55c9df6-c95lv 的pod 中(任意pod即可),容器内的挂载目录是/usr/share/nginx/html [root@k8s-master01 pv]# kubectl exec -ti nginx-deployment-9b55c9df6-c95lv -- sh / # cd /usr/share/nginx/html /usr/share/nginx/html # ls -l total 4 -rw-r--r-- 1 root root 6 Aug 20 10:16 index.html /usr/share/nginx/html # echo 'hello kubernetes' > index.html /usr/share/nginx/html # cat index.html hello kubernetes #在其他pod(和nfs共享目录中)内查看该文件内容是否一致 # 进入nginx-deployment-9b55c9df6-lc77f查看 [root@k8s-master01 pv]# kubectl exec -ti nginx-deployment-9b55c9df6-lc77f -- sh / # cat /usr/share/nginx/html/index.html hello kubernetes #(一致) # 查看nfs共享目录中的文件 [root@nfs-server k8s]# cat /data/k8s/index.html hello kubernetes #(一致)
2.8.4PVC创建和挂载失败原因 PVC一直处于Pending的原因:
pvc的空间申请大小大于pv的大小
pvc的storageClassName和pv的storageClassName不一致
pvc的accessModes和pv的accessModes不一致
挂载PVC的pod一直处于Pending:
3.动态存储 动态存储(Dynamic Provisioning)是 Kubernetes 中用于 自动创建持久化存储(PersistentVolume) 的机制。
在没有动态存储之前,你必须:
手动创建 PV(PersistentVolume)
编写 PVC(PersistentVolumeClaim)
确保两者参数匹配
这叫 静态存储(Static Provisioning)
而 动态存储 让 PVC 可以“自动生成” PV,不需要人工创建 PV。
PVC 提交后,Kubernetes 根据 PVC 的要求,自动创建 PV 并绑定 PVC。
创建 PV 的任务由 StorageClass 所指定的 Provisioner(供应器) 完成。
举个现实例子:
就像你去酒店前台说:
“我要一个 20 平米的房间,带窗。”
前台会自动给你分配一个符合条件的房间,不需要你自己提前预定具体的房间号。
PVC 就是客人 PV 就是房间 StorageClass 就是酒店的规则(房间类型)
动态存储工作原理:动态存储依赖StorageClass和CSI(ContainerStorage Interface)实现,当我们创建一个PVC时通过storageClassName指定动态存储的类,该类指向了不同的存储供应商,比如Ceph、NFS等,之后通过该类就可完成PV的自动创建。
3.1什么是CSI和StorageClass CSI(Container Storage Interface)是一个标准化的存储接口,用于在容器环境中集成外部存储系统。它提供了一种统一的方式来集成各种存储系统,无论是云提供商的存储服务,还是自建的存储集群,都可以通过CSI对接到容器平台中。 在同一个Kubernetes集群中,可以同时存在多个CSI对接不同的存储平台,之后可以通过StorageClass的provisioner字段声明该Class对接哪一种存储平台。
存储类—StorageClass和IngressClass类似,用于定义存储资源的类别,可以使用内置的存储插件或第三方的CSI驱动程序关联存储。 在同一个Kubernetes集群中,可以同时存在多个StorageClass对接不同或相同的CSI,用于实现更多的动态存储需求场景。
3.2实现NFS动态存储 在 K8s 中使用动态存储,需要准备哪些东西?
通用必备 5 样(任何动态存储都逃不掉)
序号
必备项
作用
1
后端存储系统
真正保存数据(NFS / 云盘 / Ceph)
2
CSI Driver / Provisioner
Kubernetes 创建、挂载存储的执行者
3
StorageClass
定义创建规则
4
PVC
应用声明存储需求
5
Pod / StatefulSet
消费 PVC
👉 动态存储 = 后端存储 + CSI + StorageClass + PVC
这里用的NFS存储服务是1.5.1中安装的NFS服务,接下来安装nfs的csi
3.2.1安装NFS CSI 动态存储驱动 用到的官方插件地址:https://github.com/kubernetes-csi/csi-driver-nfs
安装的版本要和自己的k8s版本对应,我集群版本1.31.4,我这里用v4.12.1的版本,使用kubectl方式安装。
选择【install via kubectl】— install CSI driver v4.12.1 version ,参考其安装步骤:
下面是详细的安装步骤:
下载安装文件:
1 2 3 4 5 6 7 8 # 如果网络正常可以直接clone 到本地 git clone https://github.com/kubernetes-csi/csi-driver-nfs.git # 另一种方法是下载压缩包上传到服务器并解压,步骤省略 [root@k8s-master01 16-pv-pvc]# ll total 19232 drwxr-xr-x 13 root root 4096 Dec 9 10:05 csi-driver-nfs-master -rw-r--r-- 1 root root 19660263 Dec 13 16:21 csi-driver-nfs-master.zip
安装CSI:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 进入安装的版本路径中 [root@k8s-master01 16-pv-pvc]# cd csi-driver-nfs-master/deploy/v4.12.1/ [root@k8s-master01 v4.12.1]# ll total 88 -rw-r--r-- 1 root root 52399 Dec 13 16:49 crd-csi-snapshot.yaml -rw-r--r-- 1 root root 6082 Dec 13 16:49 csi-nfs-controller.yaml -rw-r--r-- 1 root root 176 Dec 13 16:49 csi-nfs-driverinfo.yaml -rw-r--r-- 1 root root 4025 Dec 13 16:49 csi-nfs-node.yaml -rw-r--r-- 1 root root 2226 Dec 13 16:49 csi-snapshot-controller.yaml -rw-r--r-- 1 root root 2832 Dec 13 16:49 rbac-csi-nfs.yaml -rw-r--r-- 1 root root 2435 Dec 13 16:49 rbac-snapshot-controller.yaml -rw-r--r-- 1 root root 151 Dec 13 16:49 snapshotclass.yaml -rw-r--r-- 1 root root 512 Dec 13 16:49 storageclass.yaml # 安装前修改镜像的下载地址,默认使用的k8s官方地址,网络原因会导致无法下载,这里改成我自己的仓库地址 # 批量修改命令如下: sed -i 's#registry.k8s.io/sig-storage/#registry.cn-beijing.aliyuncs.com/k8s-liujunwei/#g' *.yaml
修改前的地址如下:
修改后的地址如下:
执行安装脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 # 执行脚本要在程序的根目录中执行,也就是进入解压后的目录csi-driver-nfs-master [root@k8s-master01 csi-driver-nfs-master]# ll total 152 drwxr-xr-x 2 root root 30 Dec 9 10:05 CHANGELOG drwxr-xr-x 20 root root 4096 Dec 9 10:05 charts lrwxrwxrwx 1 root root 29 Dec 13 16:31 cloudbuild.yaml -> release-tools/cloudbuild.yaml drwxr-xr-x 3 root root 23 Dec 9 10:05 cmd -rw-r--r-- 1 root root 148 Dec 9 10:05 code-of-conduct.md -rw-r--r-- 1 root root 1906 Dec 9 10:05 CONTRIBUTING.md drwxr-xr-x 19 root root 4096 Dec 9 10:05 deploy -rw-r--r-- 1 root root 866 Dec 9 10:05 Dockerfile drwxr-xr-x 2 root root 4096 Dec 9 10:05 docs -rw-r--r-- 1 root root 7452 Dec 9 10:05 go.mod -rw-r--r-- 1 root root 69012 Dec 9 10:05 go.sum drwxr-xr-x 3 root root 4096 Dec 9 10:05 hack -rw-r--r-- 1 root root 11357 Dec 9 10:05 LICENSE -rw-r--r-- 1 root root 5290 Dec 9 10:05 Makefile -rw-r--r-- 1 root root 171 Dec 9 10:05 OWNERS lrwxrwxrwx 1 root root 43 Dec 13 16:31 OWNERS_ALIASES -> release-tools/KUBERNETES_CSI_OWNERS_ALIASES drwxr-xr-x 3 root root 17 Dec 9 10:05 pkg -rw-r--r-- 1 root root 2836 Dec 9 10:05 README.md -rw-r--r-- 1 root root 529 Dec 9 10:05 RELEASE.md drwxr-xr-x 5 root root 4096 Dec 9 10:05 release-tools -rw-r--r-- 1 root root 594 Dec 9 10:05 SECURITY_CONTACTS -rw-r--r-- 1 root root 726 Dec 9 10:05 support.md drwxr-xr-x 6 root root 64 Dec 9 10:05 test drwxr-xr-x 13 root root 4096 Dec 9 10:05 vendor # 执行安装脚本,v4.12.1是你要安装的版本,local 固定写法 [root@k8s-master01 csi-driver-nfs-master]# ./deploy/install-driver.sh v4.12.1 local use local deploy Installing NFS CSI driver, version: v4.12.1 ... serviceaccount/csi-nfs-controller-sa created serviceaccount/csi-nfs-node-sa created clusterrole.rbac.authorization.k8s.io/nfs-external-provisioner-role created clusterrolebinding.rbac.authorization.k8s.io/nfs-csi-provisioner-binding created clusterrole.rbac.authorization.k8s.io/nfs-external-resizer-role created clusterrolebinding.rbac.authorization.k8s.io/nfs-csi-resizer-role created csidriver.storage.k8s.io/nfs.csi.k8s.io created deployment.apps/csi-nfs-controller created daemonset.apps/csi-nfs-node created NFS CSI driver installed successfully.
检查pod状态和生成的csidrivers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@k8s-master01 csi-driver-nfs-master]# kubectl get po -n kube-system |grep csi csi-nfs-controller-9c5d66c78-c6hrf 5/5 Running 1 (2m32s ago) 3m13s csi-nfs-node-4gfgq 3/3 Running 0 3m12s csi-nfs-node-56rvp 3/3 Running 0 3m12s csi-nfs-node-l4wkd 3/3 Running 0 3m12s csi-nfs-node-prwwk 3/3 Running 0 3m13s csi-nfs-node-trjmb 3/3 Running 0 3m12s csi-nfs-node-wv9pl 3/3 Running 0 3m12s [root@k8s-master01 csi-driver-nfs-master]# kubectl get csidrivers NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE nfs.csi.k8s.io false false false <unset> false Persistent 149m
3.2.2创建StorageClass 参考文档:https://github.com/kubernetes-csi/csi-driver-nfs/blob/master/deploy/example/README.md
StorageClass配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [root@k8s-master01 v4.12.1 ] apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: server: 192.168 .0 .86 share: /data/nfs reclaimPolicy: Delete volumeBindingMode: Immediate allowVolumeExpansion: true mountOptions: - nfsvers=4.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 # 关键参数详细解读与注意事项 1.provisioner: nfs.csi.k8s.io 作用:告诉 K8s,当有人要用这个 StorageClass 时,去呼叫哪个插件来干活。 注意:这个字符串是“写死”的,必须对应你安装的 CSI 驱动的名字,通过kubectl get csidrivers.storage.k8s.io可以查看都有哪些csidrivers。 2.parameters -> server 作用:指定 NFS 服务器的位置。 配置分析:nfs-server.default.svc.cluster.local 这是一个K8s Service 的 DNS 域名,这意味 NFS 服务器本身是作为一个 Pod 运行在当前 K8s 集群的 default 命名空间下的。 注意:如果你使用的是外部(独立物理机/NAS)的 NFS,请务必将其改为 IP 地址。 3.reclaimPolicy: Delete 作用:决定了“分手”后的处理方式。 Delete (默认):当用户删除 PVC (kubectl delete pvc ...),K8s 会通知 NFS 驱动去服务器上把对应的文件夹和数据删得干干净净。 Retain:PVC 删除后,NFS 上的数据保留。需要管理员手动去清理。 建议:测试环境用 Delete 省事;生产环境如果不放心数据丢失,建议改为 Retain。 4. volumeBindingMode: Immediate 作用:决定何时“分配”存储。 Immediate:只要 PVC 一创建,马上就去 NFS 上建文件夹、绑定 PV。 WaitForFirstConsumer:先观望。直到有一个 Pod 真正引用了这个 PVC,并且调度到了某个节点上,才开始创建存储。 场景:对于 NFS 这种网络存储,位置不敏感,用 Immediate 没问题。如果是本地磁盘(Local PV),必须用 WaitForFirstConsumer 以确保存储和 Pod 在同一台机器上。 5.allowVolumeExpansion: true 作用:允许动态扩容。 如何使用:比如你一开始申请了 10Gi,后来发现不够用。 你执行 kubectl edit pvc <pvc-name>。 把 storage: 10Gi 改成 storage: 20Gi。 K8s 会自动更新 PV 的容量记录。 注意:对于 NFS,这主要是逻辑上的配额更新,因为 NFS 本身就是共享目录,通常没有物理分区的硬性限制,但这个参数让 K8s 的管理更灵活。 6.mountOptions -> nfsvers=4.1 作用:性能与协议控制。 nfsvers=4.1:指定使用 NFS v4.1 协议。相比 v3,v4.1 性能更好,支持更好的文件锁机制,穿越防火墙也更容易(只用一个端口)。 排错:如果你的 NFS 服务器很老(只支持 v3),Pod 挂载时会报错 mount.nfs: Protocol not supported。此时需要把这里改成 nfsvers=3。
创建该storageclass:
1 2 [root@k8s-master01 16-pv-pvc]# kubectl create -f nfs-StorageClass.yaml storageclass.storage.k8s.io/nfs-csi created
查看创建的sc资源:
1 2 3 4 # sc是集群级别的资源,没有命名空间隔离 [root@k8s-master01 16-pv-pvc]# kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs-csi nfs.csi.k8s.io Delete Immediate true 35s
3.2.3挂载测试动态存储 该仓库中提供了pvc的模板,路径是csi-driver-nfs-master/deploy/example/pvc-nfs-csi-dynamic.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@k8s-master01 example ] apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-nfs-dynamic namespace: default spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: nfs-csi
创建该pvc资源:
1 2 [root@k8s-master01 example]# kubectl create -f pvc-nfs-csi-dynamic.yaml persistentvolumeclaim/pvc-nfs-dynamic created
查看pvc的状态和pv的状态:
此时会在NFS服务的目录下创建一个共享目录:
1 2 3 4 5 [root@k8s-node03 nfs]# pwd /data/nfs [root@k8s-node03 nfs]# ls -l total 0 drwxr-xr-x 2 root root 6 Dec 13 21:17 pvc-614b1333-ba6f-4ecd-ad7d-0f98f607367d
创建deployment资源进行挂载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 cat deployment.yaml kind: Deployment apiVersion: apps/v1 metadata: name: deploy-sc spec: replicas: 2 selector: matchLabels: app: deploy-sc template: metadata: labels: app: deploy-sc spec: containers: - name: deploy-sc image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx volumeMounts: - name: deploy-sc-pv-pvc mountPath: /data volumes: - name: deploy-sc-pv-pvc persistentVolumeClaim: claimName: pvc-nfs-dynamic
创建deployment:
1 2 [root@k8s-master01 16-pv-pvc]# kubectl create -f deployment.yaml deployment.apps/deploy-sc created
查看创建的pod:
1 2 3 4 [root@k8s-master01 16-pv-pvc]# kubectl get po NAME READY STATUS RESTARTS AGE deploy-sc-55d4c5b4f5-6zkk6 1/1 Running 0 29s deploy-sc-55d4c5b4f5-86rzw 1/1 Running 0 29s
进入pod中查看挂载情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 # 已经正常挂载192.168.0.86:/data/nfs/pvc-614b1333-ba6f-4ecd-ad7d-0f98f607367d 99G 9.9G 90G 10% /data [root@k8s-master01 16-pv-pvc]# kubectl exec -ti deploy-sc-55d4c5b4f5-6zkk6 -- bash root@deploy-sc-55d4c5b4f5-6zkk6:/# df -h Filesystem Size Used Avail Use% Mounted on overlay 99G 16G 84G 16% / tmpfs 64M 0 64M 0% /dev 192.168.0.86:/data/nfs/pvc-614b1333-ba6f-4ecd-ad7d-0f98f607367d 99G 9.9G 90G 10% /data /dev/mapper/rl-root 99G 16G 84G 16% /etc/hosts shm 64M 0 64M 0% /dev/shm tmpfs 7.4G 12K 7.4G 1% /run/secrets/kubernetes.io/serviceaccount tmpfs 3.8G 0 3.8G 0% /proc/acpi tmpfs 3.8G 0 3.8G 0% /proc/scsi tmpfs 3.8G 0 3.8G 0% /sys/firmware
3.2.4删除资源 1 2 3 4 5 6 7 8 9 10 11 12 13 # 删除deployment [root@k8s-master01 16-pv-pvc]# kubectl delete deployment.apps/deploy-sc deployment.apps "deploy-sc" deleted # 删除deployment并不会删除pvc和pv,接下来删除pvc [root@k8s-master01 16-pv-pvc]# kubectl delete pvc pvc-nfs-dynamic persistentvolumeclaim "pvc-nfs-dynamic" deleted # 删除了pvc后,绑定的pv也会被删除,因为该案例定义StorageClass时指定了reclaimPolicy: Delete 策略,在NFS 服务上对应的数据和文件夹也会被物理删除。 [root@k8s-node03 nfs]# ll /data/nfs/ # pvc-614b1333-ba6f-4ecd-ad7d-0f98f607367d已经不存在了 total 0 drwxr-xr-x 2 root root 21 Dec 12 22:01 pv
3.2.5StatefulSet配置动态存储 Deployment 和 StatefulSet 在 Kubernetes 中使用持久化存储的方式不一样 ,核心差异在于是否需要为每个 Pod 提供独立的持久卷 。
Deployment :适用于无状态应用,所有 Pod 通常共享同一个 PVC(PersistentVolumeClaim) ,从而共享同一个底层存储卷。
StatefulSet :适用于有状态应用,自动为每个 Pod 创建独立的 PVC 和独立的存储卷 ,确保数据隔离和稳定绑定。
这种差异在使用动态存储(Dynamic Provisioning) 时表现得尤其明显。下面结合 StorageClass cfs-sc(CubeFS CSI,默认 SC,支持动态 provisioning)进行案例讲解。
1 2 3 4 5 # 这里使用的是cubefs的动态存储cfs-sc进行演示。 [root@k8s-master01 ~]# kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE cfs-sc (default) csi.cubefs.com Delete Immediate true 2d4h nfs-csi nfs.csi.k8s.io Delete Immediate true 16d
Deployment 使用动态存储案例(共享存储,适合无状态应用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: shared-cfs-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 20Gi storageClassName: cfs-sc --- apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 4 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 volumeMounts: - mountPath: /usr/share/nginx/html name: shared-storage volumes: - name: shared-storage persistentVolumeClaim: claimName: shared-cfs-pvc
StatefulSet 使用存储(独立存储,适合有状态应用)
有状态应用(如 MySQL、Redis、ZooKeeper)每个 Pod 需要自己的独立数据。
StatefulSet 使用动态存储时,不需要提前手动创建任何 PVC 。而是直接在 StatefulSet 的 YAML 中通过volumeClaimTemplates 定义 PVC 的模板(包括 storageClassName、accessModes、resources.requests.storage 等)。
Kubernetes 会自动为每个 Pod 创建一个独立的 PVC
每个 PVC 都会触发动态存储 provision,创建独立的 PV。
假设需要搭建一个三节点的 RabbitMO 集群到 K8s中,并且需要实现数据的持久化,此时可以通过 StatefulSet 创建三个副本,并且通过 volumeClaimTemplates 绑定存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 # 下载部署文件 [root@k8s-master01 16-pv-pvc]# git clone https://gitee.com/dukuan/k8s.git Cloning into 'k8s'... remote: Enumerating objects: 1373, done. remote: Total 1373 (delta 0), reused 0 (delta 0), pack-reused 1373 (from 1) Receiving objects: 100% (1373/1373), 5.38 MiB | 4.24 MiB/s, done. Resolving deltas: 100% (627/627), done. # 进入目录中修改存储配置 [root@k8s-master01 16-pv-pvc]# cd k8s/k8s-rabbitmq-cluster/ [root@k8s-master01 k8s-rabbitmq-cluster]# vim rabbitmq-cluster-ss.yaml volumeMounts: - mountPath: /var/lib/rabbitmq # RabbitMQ 官方镜像默认的数据目录,千万不要改成别的路径,消息队列持久化文件所在位置 name: rabbitmq-storage # 必须和下面的 volumeClaimTemplates 里的 name 完全一致! readOnly: false # 默认就是false,可以不写。写true会直接导致RabbitMQ启动失败 volumeClaimTemplates: #关键!定义卷请求模板,自动生成独立 PVC - metadata: #定义pvc的元数据 name: rabbitmq-storage #生成的PVC名字:rabbitmq-storage-<pod名> 例如 rabbitmq-storage-rabbit-0 spec: accessModes: - ReadWriteOnce storageClassName: "cfs-sc" #这里定义的是cubefs的sc名字,根据自己的sc名字改 resources: requests: storage: 10Gi #指定存储的空间大小 # 创建资源 [root@k8s-master01 k8s-rabbitmq-cluster]# kubectl create ns public-service namespace/public-service created [root@k8s-master01 k8s-rabbitmq-cluster]# kubectl create -f . statefulset.apps/rmq-cluster created configmap/rmq-cluster-config created serviceaccount/rmq-cluster created role.rbac.authorization.k8s.io/rmq-cluster created rolebinding.rbac.authorization.k8s.io/rmq-cluster created secret/rmq-cluster-secret created service/rmq-cluster created service/rmq-cluster-balancer created # 查看pod部署状态 [root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get po -n public-service NAME READY STATUS RESTARTS AGE rmq-cluster-0 1/1 Running 0 3m57s rmq-cluster-1 1/1 Running 0 2m43s rmq-cluster-2 1/1 Running 0 81s # 查看pvc和pvc状态 [root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get pvc -n public-service NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE rabbitmq-storage-rmq-cluster-0 Bound pvc-6853fdb8-7f30-4f0f-9bd1-51532de2a0f2 10Gi RWO cfs-sc <unset> 6m22s rabbitmq-storage-rmq-cluster-1 Bound pvc-d2a6a0d9-a643-48f7-93be-36ac06745b23 10Gi RWO cfs-sc <unset> 5m8s rabbitmq-storage-rmq-cluster-2 Bound pvc-087ab0f6-fd1e-4cbf-88c4-fb738bd8315f 10Gi RWO cfs-sc <unset> 3m46s # pv [root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE nfs-pv 10Gi RWX Retain Bound default/nfs-pvc nfs-sc <unset> 18d pv-hostpath 1Gi RWO Retain Available hostpath-sc <unset> 18d pvc-087ab0f6-fd1e-4cbf-88c4-fb738bd8315f 10Gi RWO Delete Bound public-service/rabbitmq-storage-rmq-cluster-2 cfs-sc <unset> 77m pvc-375f416e-ccbf-43b8-8f90-e7e31a66a50a 10Gi RWX Delete Bound default/pvc-cubefs cfs-sc <unset> 2d4h pvc-6853fdb8-7f30-4f0f-9bd1-51532de2a0f2 10Gi RWO Delete Bound public-service/rabbitmq-storage-rmq-cluster-0 cfs-sc <unset> 79m pvc-c67787cf-3430-4551-8dbc-729c3c23b93d 50Gi RWO Delete Bound default/data-mysql-0 cfs-sc <unset> 45h pvc-d2a6a0d9-a643-48f7-93be-36ac06745b23 10Gi RWO Delete Bound public-service/rabbitmq-storage-rmq-cluster-1 cfs-sc <unset> 78m # 部署成功后访问rabbitmq控制台,通过NodePort的方式访问 [root@k8s-master01 k8s-rabbitmq-cluster]# kubectl get svc -n public-service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE rmq-cluster ClusterIP None <none> 5672/TCP 8m10s rmq-cluster-balancer NodePort 10.96.29.163 <none> 15672:30145/TCP,5672:30361/TCP 8m10s
控制台账号密码在rabbitmq-secret.yaml文件中定义的,可以根据自己的需求修改
这里不是演示如何部署rabbitmq,而是通过rabbitmq的案例来演示StatefulSet 如何配置动态存储,重点是下面的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 volumeMounts: - mountPath: /var/lib/rabbitmq name: rabbitmq-storage readOnly: false volumeClaimTemplates: - metadata: name: rabbitmq-storage spec: accessModes: - ReadWriteOnce storageClassName: "cfs-sc" resources: requests: storage: 10Gi
总结对比表:
项目
Deployment
StatefulSet
PVC 创建方式
手动预先创建(或外部创建)
自动通过 volumeClaimTemplates 生成
每个 Pod 的存储
共享同一个 PVC 和 PV
每个 Pod 独立 PVC 和 PV
动态存储行为
只 provision 一个 PV
为每个 Pod provision 一个独立 PV
适合场景
无状态、共享数据(如 Web、静态文件)
有状态、独立数据(如 DB、消息队列)
数据持久性保障
弱(Pod 互换可能混乱)
强(稳定绑定,顺序保证)
YAML 关键字段
volumes.persistentVolumeClaim
volumeClaimTemplates
建议:
无状态应用 → 用 Deployment + 预创建共享 PVC (RWX 模式更佳)。
有状态应用 → 必须用 StatefulSet + volumeClaimTemplates (通常 RWO)。