k8s进阶-持久化基础

k8s存储管理-数据持久化及动态存储

1.Volumes介绍

官方介绍:https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/

Volumes是Kubernetes中一个对存储资源的抽象概念,属于pod级别的一个配置字段参数,本质上是一个可以被Pod中容器访问的目录。它解决了容器中文件系统的临时性问题,使数据可以在容器重启后依然保持。

Volumes在Pod中绑定多个,多种的数据类型,比如NFS、NAS、CEPH等,这些绑定的数据可以挂载到pod中的一个或多个容器中,从而实现容器的数据持久化和数据共享。

Volumes常用类型:

  1. EmptyDir:临时目录,当Pod从节点删除时,EmptyDir中的数据也会被删除,常用于临时数据存储,如缓存、中间计算结果、容器间数据共享等
  2. HostPath:节点数据共享,HostPath可以让容器直接访问节点上的文件或目录,常用于和节点共享数据
  3. ConfigMap & Secret:用于挂载ConfigMap和Secret到容器中
  4. Downward API:元数据挂载,主要用于容器访问Pod的一些元数据,比如标签、命名空间等
  5. NFS&NAS:网络文件系统,主要用于挂载远程存储到容器中,实现跨主机的数据共享和持久化
  6. PVC:PV请求,K8s中的一类资源,用于配置多种不同的存储后端

Volumes主要有以下几个用途:

  1. 数据持久化:保证容器重启后数据不丢失。
  2. 容器间数据共享:同一Pod中的多个容器可以访问同一个Volume。
  3. 扩展容器存储能力:可以将外部存储挂载到容器中。
  4. 配置注入:可以将配置文件以Volume的形式挂载到容器中。

1.1使用磁盘类型的EmptyDir实现数据共享

emptyDir是一种临时存储卷,具有以下特点:

  1. 当Pod被分配到某个节点上时创建,初始内容为空。
  2. 与Pod的生命周期绑定,当Pod从节点上移除时,emptyDir中的数据会被永久删除。
  3. 可以存储在节点的任何介质上,如磁盘、SSD或网络存储,也可以设置为存储在内存中。
  4. 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.log
emptydir #可以看到内容存在,实现了容器间的数据共享


/ # 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:  #描述pod的期望状态,定义pod的具体配置和行为
volumes: #定义pod中所有可用的卷
- name: emptydir #定义了一个名为emptydir的卷
emptyDir: #定义卷的类型为emptyDir。
medium: Memory #指定这个卷的介质类型为Memory,即内存。
sizeLimit: "1Gi" #指定这个卷的大小上限为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>

##注意:emptydir-5f76df9b7b-pkpjj是在 k8s-node02 点上,所以需要查看k8s-node02节点的/data路径
[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:  #描述pod的期望状态,定义pod的具体配置和行为
volumes: #定义pod中所有可用的卷
- name: hostpath-test #定义卷的名字是 hostpath-test
hostPath: #定义这个卷的类型是 hostPath
path: /data #指定这个卷的挂载路径,这里 /data 是宿主机的路径
type: DirectoryOrCreate #指定这个卷的类型是 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的生命周期:

  1. 准备(Provisioning)静态供应:管理员手动创建PV。动态供应:通过StorageClass动态创建PV。
  2. 绑定(Binding):用户创建PVC并指定需要的资源和访问模式。Kubernetes找到匹配的PV并将它们绑定。
  3. 使用(Using):Pod可以像使用普通Volume一样使用PVC。
  4. 释放(Releasing):当用户删除PVC时,PV会进入“已释放”状态,但仍保留数据。
  5. 回收(Reclaiming):PV的回收策略决定了如何处理已释放的PV:保留(Retain):数据保留,需手动清理。回收(Recycle):清除数据,PV可再次使用。删除(Delete):删除PV及其数据。

2.2PV回收策略:

在Kubernetes中,PersistentVolume(PV)的回收策略用于定义当与之绑定的PersistentVolumeClaim(PVC)被删除后,PV应该如何处理。Kubernetes支持三种主要的PV回收策略:

  1. Retain(保留):当PVC被删除时,PV不会被删除,而是被标记为“Released”已释放状态。此时,PV中的数据仍然存在,需要管理员手动清理或重新利用这些数据。这种策略适用于数据需要保留的场景.

  2. Recycle(回收):此策略会清除PV中的数据,例如删除所有文件,然后PV可以被重新绑定到新的PVC。这种策略比较简单且适用于某些特定场景,但在Kubernetes 1.10版本后已经被弃用,不再推荐使用.

  3. 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支持以下几种访问模式:

  1. ReadWriteOnce(RWO):PV可以被单个节点以读写方式挂载。这意味着只有一个节点可以同时以读写方式访问该存储卷。
  2. ReadOnlyMany(ROX):PV可以被多个节点以只读方式挂载。这种模式允许多个节点同时访问存储卷,但只能以只读方式访问。
  3. ReadWriteMany(RWX):PV可以被多个节点以读写方式挂载。这种模式允许多个节点同时以读写方式访问存储卷。
  4. 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]# cat pv-hostpath.yaml
kind: PersistentVolume # 定义资源的类型,这里是一个 PersistentVolume,持久卷为集群提供持久化存储,与 PersistentVolumeClaim 配合使用。
apiVersion: v1 # API版本:v1是PV的稳定版本
metadata: # 定义这个资源的元数据,通常包括 name、namespace、labels、annotations 等。
name: pv-hostpath # 指定PV的名称,集群内唯一标识符
labels: # 标签:用于分类和选择资源(可选但推荐)
type: local # 标记这是本地存储类型
spec: # 定义持久卷的详细规范和配置。
storageClassName: hostpath-sc # 定义存储类名称:用于PV和PVC的绑定匹配
volumeMode: Filesystem # 定义卷模式:文件系统类型,目前支持Filesystem(文件系统) 和 Block(块),其中Block类型需要后端存储支持,默认为文件系统
capacity: # 定义持久卷的存储容量配置
storage: 1Gi # 指定存储容量:该PV提供1GB的存储空间
accessModes: # 指定访问PV模式:定义如何访问该卷
- ReadWriteOnce # RWO模式:只能被单个节点以读写方式挂载,表示可以被多个节点以读写模式挂载。
persistentVolumeReclaimPolicy: Retain # 定义pv回收策略:PVC删除后PV的处理方式(Retain/Recycle/Delete)
hostPath: # 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]# cat pvc-nfs.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nfs-pvc-claim #定义pvc的名字,pod中会引用改名字
spec:
storageClassName: nfs-slow #指定存储类的名字,这里的名字必须要与pv中storageClassName名字一致
accessModes: #访问模式
- ReadWriteMany #pvc指定的访问模式必须要在pv中存在
resources:
requests:
storage: 1Gi #这里指定的大小要小于等于绑定的pv大小



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:

  • pvc没有创建成功

  • pvc的pod不在同一个namespace(命名空间)

3.动态存储

动态存储(Dynamic Provisioning)是 Kubernetes 中用于 自动创建持久化存储(PersistentVolume) 的机制。

在没有动态存储之前,你必须:

  1. 手动创建 PV(PersistentVolume)
  2. 编写 PVC(PersistentVolumeClaim)
  3. 确保两者参数匹配

这叫 静态存储(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. 下载安装文件:

    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
  2. 安装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.
  3. 检查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
# 可以参考模板中的配置,模板位置csi-driver-nfs-master/deploy/v4.12.1/storageclass.yaml
[root@k8s-master01 v4.12.1]# cat storageclass.yaml
apiVersion: storage.k8s.io/v1 # 资源 API 版本
kind: StorageClass # 资源类型:存储类
metadata: # 定义该资源的元数据
name: nfs-csi # 【重要】存储类的名称。创建 PVC 时,storageClassName 必须填这个名字
provisioner: nfs.csi.k8s.io # 【核心】指定驱动名称。必须与 kubectl get csidrivers 输出的名称完全一致
parameters: # 【参数】存储类特有的参数
# NFS 服务器地址。这里填写的是 K8s 内部 DNS 域名,说明你的 NFS Server 也是部署在 K8s 里的
# 如果 NFS 在集群外,这里通常填 IP,例如: 192.168.0.86
# server: nfs-server.default.svc.cluster.local
server: 192.168.0.86
# 指定nfs的共享目录 ,这里是 /data/nfs,动态创建的 PV 会在这个目录下生成子文件夹 (例如 /pvc-uuid-xxx)
share: /data/nfs
# 以下两个参数用于配置挂载的权限,可以根据实际情况进行调整
# 下面这几行被注释掉了,它们用于配置删除卷时的凭证(通常 NFS 不需要,企业级 NAS 可能需要)
# csi.storage.k8s.io/provisioner-secret is only needed for providing mountOptions in DeleteVolume
# csi.storage.k8s.io/provisioner-secret-name: "mount-options"
# csi.storage.k8s.io/provisioner-secret-namespace: "default"

reclaimPolicy: Delete # 【回收策略】PV 被删除后,NFS 上对应的数据和文件夹也会被物理删除,动态存储类默认使用此策略
volumeBindingMode: Immediate # 【绑定模式】创建 PVC 后立即创建 PV,不等待 Pod 调度
allowVolumeExpansion: true # 【允许扩容】允许用户通过编辑 PVC 的 storage 字段来申请更多空间
mountOptions: # 【挂载参数】Pod 挂载此卷时传递给 Linux mount 命令的参数
- 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]# cat pvc-nfs-csi-dynamic.yaml
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配置动态存储

DeploymentStatefulSet 在 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
# 1. 先创建共享的 PVC(动态 provisioning)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-cfs-pvc #这里指定pvc的名字,pod中会引用改名字
spec:
accessModes:
- ReadWriteMany # CubeFS 支持 RWX,多 Pod 同时读写
resources:
requests:
storage: 20Gi
storageClassName: cfs-sc #cfs-sc是cubefs的SC的名字,自动创建 PV并进行绑定

---
# 2. Deployment 使用这个共享 PVC
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 # 所有 Pod 挂载同一路径,指定容器内的挂载路径
name: shared-storage #这里引用的是volumes.name中定义的名字。
volumes:
- name: shared-storage #这里定义的是卷的名字,volumeMounts.name会应用该名字
persistentVolumeClaim:
claimName: shared-cfs-pvc # 所有 Pod 引用同一个 PVC,也就是引用事先创建的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 # 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 #指定存储的空间大小

总结对比表:

项目 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)。