Kubernetes存储

持久卷

Persistent Volume(PV)描述的是持久化存储数据卷。主要定义一个持久化存储在宿主机上的目录。
持久卷不属于任何命名空间,它跟节点一样是集群层面的资源。
通常情况下,会先在kubernetes集群里创建PV对象,比如定义一个NFS类型的PV:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv-nfs
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
storageClassName: manual
nfs:
server: 172.21.0.5
path: "/pk1yjy13/"

注意

  • RWOReadWriteOnce,允许单个节点挂载读写。
  • ROXReadOnlyMany,允许多个节点挂载只读。
  • RWXReadWriteMany,允许多个节点挂载读写。

PVC描述的是Pod期望的持久化存储属性。·比如,卷存储的大小、可读写权限等。

可以声明一个1GiB大小的PVC,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: manual
volumeName: nginx-pv-nfs

当创建好PVC,kubernetes就会找到合适的pv将与pvc绑定,要想PVC和PV绑定,需要满足以下两个条件:

PV和PVC的spec字段。PV的大小(storage)必须满足PVC所声明的要求。
PV和PVC的storageClassName字段必须一样。

通过kubectl get pvc可以列举pvc的状态

1
2
3
[root@VM-20-9-centos ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-pvc-nfs Bound nginx-pv-nfs 1Gi RWX manual 5d22h

通过列举的pv中可以看到,持久卷现在已经与声明绑定在一起了,显示绑定在default/nginx-pvc-nfs这个声明上,default表示声明所在的命名空间

1
2
3
[root@VM-20-9-centos ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nginx-pv-nfs 1Gi RWX Retain Bound default/nginx-pvc-nfs manual 5d23h

在pod中使用持久卷声明

在成功将PV和PVC进行绑定之后,Pod就能够像使用常规类型的Volume一样,在Pod的spec中声明使用这个PVC了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
......
spec:
containers:
- name: hexo-blog
image: nginx:1.20.1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
volumeMounts:
- name: nginx-volume
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-volume
persistentVolumeClaim:
claimName: nginx-pvc
......

在spec.volumes中声明要使用的PVC名字。Pod创建之后,kubelet就会把该PVC对应的PV,挂载在这个Pod容器内的目录上。

在未创建PV时,pod启动会报错,因为pvc无法找到合适的pv进行绑定,所以pod中的容器想要使用的卷也不存在。

保护使用中的存储

这一功能特性确保仍被 Pod 使用的 PersistentVolumeClaim(PVC) 对象及其所绑定的 PersistentVolume(PV)对象在系统中不会被删除并导致数据丢失。

如果用户删除被某Pod使用的PVC对象,该PVC声明不会被立即移除。PVC 对象会在不被任何Pod使用时移除。 如果删除已绑定到某 PVC的PV卷,该PV卷也不会被立即移除。PV对象会在不被任何PVC绑定时被移除。

可以看到当PV和PVC的状态为 Terminating 且其 Finalizers 列表中包含 kubernetes.io/pvc-protection 时,PV和PVC对象是处于被保护状态的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@VM-20-9-centos nginx]# kubectl describe pv prometheus-pv
Name: prometheus-pv
Labels: <none>
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass: manual
Status: Terminating (lasts 26d)
Claim: monitor/prometheus-pvc
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 5Gi
Node Affinity: <none>
Message:
Source:
Type: HostPath (bare host directory volume)
Path: /home/prometheus
HostPathType:
Events: <none>

回收持久卷

持久卷可以被 Retained(保留)、Recycled(回收)或 Deleted(删除)

保留

通过设置策略 Retain可以手动回收资源。当持久卷声明对象被删除时,持久卷仍然存在,对应的持久卷状态为released

让持久卷与持久卷声明的绑定释放后,持久卷仍然能够保留,如果想手动回收持久卷,需要删除并重新创建持久卷资源。

删除

Delete 回收策略的卷插件,会将持久卷对象随着持久卷声明的删除而从 Kubernetes 中移除。

回收

recyle可以删除持久卷的内容并使持久卷可以再次被声明使用,通过这种方式,持久卷可以被不同的持久卷声明和pod反复使用

警告

回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。

PV的动态卷配置

正如上面所说,当一个pod需要一个持久卷时,如未创建合适的pv与pvc进行绑定,pod就无法正常启动,kubernetes提供了一种可以自动创建PV的机制:Dynamic provisioning。而前面人工管理PV的方式叫作Static provisioning。

Dynamic provisioning的核心在于一个StorageClass的API对象。StorageClass的作用就是创建PV的模板。

StorageClass对象会定义如下两个内容:

PV的属性,如存储类型、卷的大小等
创建PV需要用到的插件,如ceph、nfs等

通过以上信息,kubernetes就能根据提交的pvc定义的StorageClass,然后通过StorageClass来创建一个合适的pv模板

通过StorageClass资源定义可用存储类型

创建ServiceAccount

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
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default # 替换成你要部署的 Namespace
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io

安装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
44
45
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
nodeName: vm-20-9-centos
tolerations:
- key: node-role.kubernetes.io/master
operator: Equal
value: "true"
containers:
- name: nfs-client-provisioner
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.21.0.5 # nfs服务器地址
- name: NFS_PATH
value: /pk1yjy13/ # NFS服务器端共享路径
volumes:
- name: nfs-client-root
nfs:
server: 172.21.0.5 # nfs服务器地址
path: /pk1yjy13/ # NFS服务器端共享路径

创建SotageClass

创建一个 StoageClass,声明 NFS 动态卷提供者名称为 nfs-storage-1。

1
2
3
4
5
6
7
8
9
10
11
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage-1
provisioner: k8s/nfs-subdir-external-provisioner
parameters:
# 设置为"false"时删除PVC不会保留数据,"true"则保留数据
archiveOnDelete: "false"
mountOptions:
# 指定NFS版本
- nfsvers=3

当持久卷声明中请求此StorageClass时,StorageClass使用声明的插件来来提供持久卷

创建请求特定存储类的PVC定义

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nfs-storage-pvc-1
spec:
  storageClassName: nfs-storage-1    #请求上面创建的StoageClass
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi

除了指定卷的大小和访问模式,还指定了要使用的存储类别。当PVC创建时,持久卷会由StorageClass(nfs-storage-1)资源中引用的provisioner创建

注意

  • RWO即ReadWriteOnce,允许单个节点挂载读写。
  • ROX即ReadOnlyMany,允许多个节点挂载只读。
  • RWX即ReadWriteMany,允许多个节点挂载读写。

通过命令查看pvc的状态,可以看到是bound状态

1
2
3
4
5
6
7
[root@VM-20-9-centos storageclass]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-storage-pvc-1 Bound pvc-450c1793-4831-48c5-a804-ae26eae3f63d 10Mi RWO nfs-storage-1 3d7h

[root@VM-20-9-centos storageclass]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-450c1793-4831-48c5-a804-ae26eae3f63d 10Mi RWO Delete Bound default/nfs-storage-pvc-1 nfs-storage-1 3d6h

可以看到动态配置自动创建的持久卷容量和访问模式是之前PVC中所要求的。回收策略是Delete,意味着PVC被删除时,持久卷也会被删除