Kubernetes之存储卷

一、存储卷的概念和类型

为了保证数据的持久性,必须保证数据在外部存储在docker容器中,为了实现数据的持久性存储,在宿主机和容器内做映射,可以保证在容器的生命周期结束,数据依旧可以实现持久性存储。但是在k8s中,由于pod分布在各个不同的节点之上,并不能实现不同节点之间持久性数据的共享,并且,在节点故障时,可能会导致数据的永久性丢失。为此,k8s就引入了外部存储卷的功能。

k8s的存储卷类型:

1
2
3
4
5
6
7
8
9
10
[root@k8s-master ~]# kubectl explain pod.spec.volumes #查看k8s支持的存储类型
KIND: Pod
VERSION: v1

常用分类:
emptyDir(临时目录):Pod删除,数据也会被清除,这种存储成为emptyDir,用于数据的临时存储。
hostPath (宿主机目录映射):
本地的SAN (iSCSI,FC)、NAS(nfs,cifs,http)存储
分布式存储(glusterfs,rbd,cephfs)
云存储(EBS,Azure Disk)

k8s要使用存储卷,需要2步:

1、在pod定义volume,并指明关联到哪个存储设备
2、在容器使用volume mount进行挂载

二、emptyDir存储卷演示

一个emptyDir 第一次创建是在一个pod被指定到具体node的时候,并且会一直存在在pod的生命周期当中,正如它的名字一样,它初始化是一个空的目录,pod中的容器都可以读写这个目录,这个目录可以被挂在到各个容器相同或者不相同的的路径下。当一个pod因为任何原因被移除的时候,这些数据会被永久删除。注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除pod.

emptyDir 磁盘的作用:

(1)普通空间,基于磁盘的数据存储
(2)作为从崩溃中恢复的备份点
(3)存储那些那些需要长久保存的数据,例web服务中的数据
默认的,emptyDir 磁盘会存储在主机所使用的媒介上,可能是SSD,或者网络硬盘,这主要取决于你的环境。当然,我们也可以将emptyDir.medium的值设置为Memory来告诉Kubernetes 来挂在一个基于内存的目录tmpfs,因为tmpfs速度会比硬盘块度了,但是,当主机重启的时候所有的数据都会丢失。

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
[root@k8s-master ~]# kubectl explain pods.spec.volumes.emptyDir  #查看emptyDir存储定义
[root@k8s-master ~]# kubectl explain pods.spec.containers.volumeMounts #查看容器挂载方式
[root@k8s-master ~]# vim pod-vol-demo.yaml #创建emptyDir的清单
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: default
labels:
app: myapp
tier: frontend
annotations:
magedu.com/create-by:"cluster admin"
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts: #在容器内定义挂载存储名称和挂载路径
- name: html
mountPath: /usr/share/nginx/html/
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /data/ #在容器内定义挂载存储名称和挂载路径
command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
volumes: #定义存储卷
- name: html #定义存储卷名称
emptyDir: {} #定义存储卷类型
1
2
[root@k8s-master volumes]# kubectl apply -f pod-vol-demo.yaml 
pod/pod-vol-demo created
1
2
3
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-vol-demo 2/2 Running 0 16s 10.244.2.34 k8s-node02
1
2
3
4
5
6
7
8
9
10
11
12
13
14
在上面,我们定义了2个容器,其中一个容器是输入日期到index.html中,然后验证访问nginx的html是否可以获取日期。以验证两个容器之间挂载的emptyDir实现共享。如下访问验证:
[root@k8s-master volumes]# curl 10.244.2.34 #访问验证
Tue Oct 9 03:56:53 UTC 2018
Tue Oct 9 03:56:55 UTC 2018
Tue Oct 9 03:56:57 UTC 2018
Tue Oct 9 03:56:59 UTC 2018
Tue Oct 9 03:57:01 UTC 2018
Tue Oct 9 03:57:03 UTC 2018
Tue Oct 9 03:57:05 UTC 2018
Tue Oct 9 03:57:07 UTC 2018
Tue Oct 9 03:57:09 UTC 2018
Tue Oct 9 03:57:11 UTC 2018
Tue Oct 9 03:57:13 UTC 2018
Tue Oct 9 03:57:15 UTC 2018

三、hostPath存储卷演示

hostPath宿主机路径,就是把pod所在的宿主机之上的脱离pod中的容器名称空间的之外的宿主机的文件系统的某一目录和pod建立关联关系,在pod删除时,存储数据不会丢失。(hostPath可以实现持久存储,但是在node节点故障时,也会导致数据的丢失)

1、查看hostPath存储类型定义

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
[root@k8s-master volumes]# kubectl explain pods.spec.volumes.hostPath  
KIND: Pod
VERSION: v1

RESOURCE: hostPath <Object>

DESCRIPTION:
HostPath represents a pre-existing file or directory on the host machine
that is directly exposed to the container. This is generally used for
system agents or other privileged things that are allowed to see the host
machine. Most containers will NOT need this. More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath

Represents a host path mapped into a pod. Host path volumes do not support
ownership management or SELinux relabeling.

FIELDS:
path <string> -required- #指定宿主机的路径
Path of the directory on the host. If the path is a symlink, it will follow
the link to the real path. More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath

type <string>
Type for HostPath Volume Defaults to "" More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath

type
DirectoryOrCreate 宿主机上不存在创建此目录
Directory 必须存在挂载目录
FileOrCreate 宿主机上不存在挂载文件就创建
File 必须存在文件

2、清单定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@k8s-master volumes]# vim pod-hostpath-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-hostpath
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
hostPath:
path: /data/pod/volume1
type: DirectoryOrCreate

3、在node节点上创建挂载目录

1
2
3
4
5
6
7
8
[root@k8s-node01 ~]# mkdir -p /data/pod/volume1
[root@k8s-node01 ~]# vim /data/pod/volume1/index.html
node01.magedu.com
[root@k8s-node02 ~]# mkdir -p /data/pod/volume1
[root@k8s-node02 ~]# vim /data/pod/volume1/index.html
node02.magedu.com
[root@k8s-master volumes]# kubectl apply -f pod-hostpath-vol.yaml
pod/pod-vol-hostpath created

4、访问测试

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
......
pod-vol-hostpath 1/1 Running 0 37s 10.244.2.35 k8s-node02
......
[root@k8s-master volumes]# curl 10.244.2.35
node02.magedu.com
[root@k8s-master volumes]# kubectl delete -f pod-hostpath-vol.yaml #删除pod,再重建,验证是否依旧可以访问原来的内容
[root@k8s-master volumes]# kubectl apply -f pod-hostpath-vol.yaml
pod/pod-vol-hostpath created
[root@k8s-master volumes]# curl 10.244.2.37
node02.magedu.com

四、subPath使用演示

1、subPath的使用场景

1
2
1个pod中可以拉起多个容器,有时候希望将不同容器的路径挂载在存储卷volume的子路径,这个时候需要用到subpath
volume支持将configMap/Secret以文件形式挂载在容器中,但是会覆盖掉挂载路径下原有的文件,如何支持选定configMap/Secret的每个key-value挂载在容器中,且不会覆盖掉原目录下的文件,这个时候也可以用到subpath

2、1个pod中多个容器时使用subPath

有时,在单个 Pod 中多个container使用同一个volume。 volumeMounts.subPath 属性可用于指定所引用的卷内的子路径,而不是其根路径。

下面例子展示了如何配置某包含 LAMP 堆栈(Linux Apache MySQL PHP)的 Pod 使用同一共享卷。 此示例中的 subPath 配置不建议在生产环境中使用。 PHP 应用的代码和相关数据映射到卷的 html 文件夹,MySQL 数据库存储在卷的 mysql 文件夹中:

例如pv路径为 /data/pod/volume5 那么他们存储路径为 /data/pod/volume5/html /data/pod/volume5/mysql 目录下

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
apiVersion: v1
kind: Pod
metadata:
name: my-lamp-site
spec:
containers:
- name: mysql
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpasswd"
volumeMounts:
- mountPath: /var/lib/mysql
name: site-data
subPath: mysql
- name: php
image: php:7.0-apache
volumeMounts:
- mountPath: /var/www/html
name: site-data
subPath: html
volumes:
- name: site-data
persistentVolumeClaim:
claimName: my-lamp-site-data

3、在config/secret中使用subPath

有些时候我们希望将 config/secret 作为文件挂载到容器中的某个目录下而不覆盖挂载目录下的文件。

1
2
3
4
5
6
7
8
9
10
11
12
[root@devops010015001030 ~]# kubectl describe secret test-secret
Name: test-secret
Namespace: default
Labels: <none>
Annotations: <none>

Type: kubernetes.io/tls

Data
====
tls.crt: 4422 bytes # subPath中用到的名字
tls.key: 3243 bytes # subPath中用到的名字
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: Pod
metadata:
name: secret-pod
spec:
containers:
- name: secret
image: zhangguanzhang/centos
command:
- sleep
- "36000"
volumeMounts:
- name: secrets
mountPath: /root/yedong.key # 挂载到pod中的路径
subPath: tls.key # secret中的名字
- name: secrets
mountPath: /root/yedong.crt # 挂载到pod中的路径
subPath: tls.crt # secret中的文件名字
volumes:
- name: secrets
secret:
secretName: test-secret

五、nfs共享存储卷演示

nfs使的我们可以挂在已经存在的共享到的我们的Pod中,和emptyDir不同的是,emptyDir会被删除当我们的Pod被删除的时候,但是nfs不会被删除,仅仅是解除挂在状态而已,这就意味着NFS能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递.并且,nfs可以同时被多个pod挂在并进行读写

注意:必须先保证NFS服务器正常运行在我们进行挂在nfs的时候

1、在stor01节点上安装nfs,并配置nfs服务

1
2
3
4
5
6
7
8
[root@stor01 ~]# yum install -y nfs-utils  ----------->192.168.56.14
[root@stor01 ~]# mkdir /data/volumes -pv
[root@stor01 ~]# vim /etc/exports
/data/volumes 192.168.56.0/24(rw,no_root_squash)
[root@stor01 ~]# systemctl start nfs
[root@stor01 ~]# showmount -e
Export list for stor01:
/data/volumes 192.168.56.0/24

2、在node01和node02节点上安装nfs-utils,并测试挂载

1
2
3
4
5
6
7
[root@k8s-node01 ~]# yum install -y nfs-utils
[root@k8s-node02 ~]# yum install -y nfs-utils
[root@k8s-node02 ~]# mount -t nfs stor01:/data/volumes /mnt
[root@k8s-node02 ~]# mount
......
stor01:/data/volumes on /mnt type nfs4 (rw,relatime,vers=4.1,rsize=131072,wsize=131072,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.56.13,local_lock=none,addr=192.168.56.14)
[root@k8s-node02 ~]# umount /mnt/

3、创建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
[root@k8s-master volumes]# cp pod-hostpath-vol.yaml pod-nfs-vol.yaml
[root@k8s-master volumes]# vim pod-nfs-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-nfs
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
nfs:
path: /data/volumes
server: stor01 #必须要先做名称解析或者直接使用IP地址
[root@k8s-master volumes]# kubectl apply -f pod-nfs-vol.yaml
pod/pod-vol-nfs created
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-vol-nfs 1/1 Running 0 21s 10.244.2.38 k8s-node02

4、在nfs服务器上创建index.html

1
2
3
4
5
6
7
8
[root@stor01 ~]# cd /data/volumes
[root@stor01 volumes ~]# vim index.html
<h1> nfs stor01</h1>
[root@k8s-master volumes]# curl 10.244.2.38
<h1> nfs stor01</h1>
[root@k8s-master volumes]# kubectl delete -f pod-nfs-vol.yaml #删除nfs相关pod,再重新创建,可以得到数据的持久化存储
pod "pod-vol-nfs" deleted
[root@k8s-master volumes]# kubectl apply -f pod-nfs-vol.yaml

六、PVC和PV的概念

我们有通过 hostPath 或者 emptyDir 的方式来持久化我们的数据,但是显然我们还需要更加可靠的存储来保存应用的持久化数据,这样容器在重建后,依然可以使用之前的数据。首先kubernetes的各个Node节点能管理这些存储,但是各种存储参数也需要专业的存储工程师才能了解,由此我们的kubernetes管理变的更加复杂,为了屏蔽底层的技术实现细节,让用户更加方便的使用, Kubernetes 便引入了 PV 和 PVC 两个重要的资源对象来实现对存储的管理。

概念

PV 的全称是:PersistentVolume(持久化卷),PV又分为静态PV和动态PV。是对底层的共享存储的一种抽象,PV 由管理员进行创建和配置,它和具体的底层的共享存储技术的实现方式有关,比如 Ceph、GlusterFS、NFS 等,都是通过插件机制完成与共享存储的对接。

volumes-1

PVC 的全称是:PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明,PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个 PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。
有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的空间从哪里分配,如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理,只有管理员才应该关心创建 PersistentVolume 的细节信息。

动态PV

虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是常见的需求是,用户需要根据不同的需求去创建PV,用于不同的场景。而此时需要集群管理员提供不同需求的PV,而不仅仅是PV的大小和访问模式,但又不需要用户了解这些卷的实现细节。 对于这样的需求,此时可以采用StorageClass资源。

PV是集群中的资源,PVC是对这些资源的请求,同时也是这些资源的“提取证”。PV和PVC的交互遵循以下生命周期:
Provisioning(配置)—> Binding(绑定)—>Using(使用)—> Releasing(释放) —> Recycling(回收)

Provisioning

这里有两种PV的提供方式:静态或者动态

  • 静态–>直接固定存储空间:
    集群管理员创建一些 PV。它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费。
  • 动态–>通过存储类进行动态创建存储空间:
    当管理员创建的静态 PV 都不匹配用户的 PVC 时,集群可能会尝试动态地为 PVC 配置卷。此配置基于StorageClasses:PVC 必须请求存储类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。

Binding(绑定)

用户创建一个PVC(或者之前就已经就为动态供给创建了),指定要求存储的大小和访问模式。master中有一个控制回路用于监控新的PVC,查找匹配的PV(如果有),并把PVC和PV绑定在一起。如果一个PV曾经动态供给到了一个新的PVC,那么这个回路会一直绑定这个PV和PVC。另外,用户总是至少能得到它们所要求的存储,但是volume可能超过它们的请求。一旦绑定了,PVC绑定就是专属的,无论它们的绑定模式是什么。
如果没找到匹配的PV,那么PVC会无限期得处于unbound未绑定状态,一旦PV可用了,PVC就会又变成绑定状态。比如,如果一个供给了很多50G的PV集群,不会匹配要求100G的PVC。直到100G的PV添加到该集群时,PVC才会被绑定。注意,PV和PVC是一一对应的,一个PV只能被一个PVC绑定。

Using(使用)

当系统为用户创建的PVC绑定PV后,表明用户成功申请了存储资源。用户在pod中定义PVC类型的volume,当创建POD实例时系统将与PVC绑定的PV挂载到POD实例。如果PV支持多种访问模式(accessModes),用户需要pod的PVC volume中指定期望的类型。注意,pod与PVC必需位于相同namespace之下。

Releasing(释放)

当用户使用PV完毕后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就被认为是已经是“released”了,但还不能再给另外一个PVC使用。前一个PVC的数据还存在于该PV中,必须根据策略来处理掉。

Reclaiming(回收)

PV的回收策略告诉集群,在PV被释放之后集群应该如何处理该PV。

  • Retain
    保护被PVC释放的PV及其上数据,并将PV状态改成”released”,不将被其它PVC绑定。集群管理员手动通过如下步骤释放存储资源:
    • 手动删除PV,但与其相关的后端存储资源如(AWS EBS, GCE PD, Azure Disk, or Cinder volume)仍然存在。
    • 手动清空后端存储volume上的数据。
    • 手动删除后端存储volume,或者重复使用后端volume,为其创建新的PV。
  • Delete
    删除被PVC释放的PV及其后端存储volume。对于动态PV其”reclaim policy”继承自其”storage class”,默认是Delete。集群管理员负责将”storage class”的”reclaim policy”设置成用户期望的形式,否则需要用户手动为创建后的动态PV编辑”reclaim policy”。
  • Recycle 
    如果PV卷支持再利用,再利用会在PV卷上执行一个基础的擦除操作(rm -rf /thevolume/*),使得它可以再次被其他PVC声明利用。(Recycle回收政策已弃用)

七、NFS使用PV和PVC

实验图如下:

volumes-2

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
[root@k8s-master ~]# kubectl explain pv    #查看pv的定义方式
FIELDS:
apiVersion
kind
metadata
spec
[root@k8s-master ~]# kubectl explain pv.spec #查看pv定义的规格
spec:
nfs(定义存储类型)
path(定义挂载卷路径)
server(定义服务器名称)
accessModes(定义访问模型,有以下三种访问模型,以列表的方式存在,也就是说可以定义多个访问模式)
ReadWriteOnce(RWO) 单节点读写
ReadOnlyMany(ROX) 多节点只读
ReadWriteMany(RWX) 多节点读写
capacity(定义PV空间的大小)
storage(指定大小)

[root@k8s-master volumes]# kubectl explain pvc #查看PVC的定义方式
KIND: PersistentVolumeClaim
VERSION: v1
FIELDS:
apiVersion <string>
kind <string>
metadata <Object>
spec <Object>
[root@k8s-master volumes]# kubectl explain pvc.spec
spec:
accessModes(定义访问模式,必须是PV的访问模式的子集)
resources(定义申请资源的大小)
requests:
storage:

1、配置nfs存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@stor01 volumes]# mkdir v{1,2,3,4,5}

[root@stor01 volumes]# vim /etc/exports
/data/volumes/v1 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.56.0/24(rw,no_root_squash)

[root@stor01 volumes]# exportfs -arv
exporting 192.168.56.0/24:/data/volumes/v5
exporting 192.168.56.0/24:/data/volumes/v4
exporting 192.168.56.0/24:/data/volumes/v3
exporting 192.168.56.0/24:/data/volumes/v2
exporting 192.168.56.0/24:/data/volumes/v1

[root@stor01 volumes]# showmount -e
Export list for stor01:
/data/volumes/v5 192.168.56.0/24
/data/volumes/v4 192.168.56.0/24
/data/volumes/v3 192.168.56.0/24
/data/volumes/v2 192.168.56.0/24
/data/volumes/v1 192.168.56.0/24

2、定义PV(非常注意:在创建PV时一定不要指定namespace空间PV)

这里定义5个PV,并且定义挂载的路径以及访问模式,还有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
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
[root@k8s-master volumes]# kubectl explain pv

[root@k8s-master volumes]# kubectl explain pv.spec.nfs

[root@k8s-master volumes]# vim pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: stor01
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 5Gi

[root@k8s-master volumes]# kubectl apply -f pv-demo.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
persistentvolume/pv005 created

[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 7s
pv002 2Gi RWO Retain Available 7s
pv003 2Gi RWO,RWX Retain Available 7s
pv004 4Gi RWO,RWX Retain Available 7s
pv005 5Gi RWO,RWX Retain Available 7s

Capacity(存储能力)

一般来说,一个 PV 对象都要指定一个存储能力,通过 PV 的 capacity属性来设置的,目前只支持存储空间的设置,就是我们这里的 storage=1Gi,不过未来可能会加入 IOPS、吞吐量等指标的配置。

AccessModes(访问模式)

AccessModes 是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

  • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
  • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
  • ReadWriteMany(RWX):读写权限,可以被多个节点挂载

下图是一些常用的 Volume 插件支持的访问模式:

volumes-3

persistentVolumeReclaimPolicy(回收策略)

我这里指定的 PV 的回收策略为 Recycle,目前 PV 支持的策略有三种:
Retain(保留)- 保留数据,需要管理员手工清理数据
Recycle(回收)- 清除 PV 中的数据,效果相当于执行 rm -rf /thevoluem/*
Delete(删除)- 关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除。
不过需要注意的是,目前只有 NFS 和 HostPath 两种类型支持回收策略。当然一般来说还是设置为 Retain 这种策略保险一点。

状态

一个 PV 的生命周期中,可能会处于4中不同的阶段:
Available(可用):表示可用状态,还未被任何 PVC 绑定
Bound(已绑定):表示 PVC 已经被 PVC 绑定
Released(已释放):PVC 被删除,但是资源还未被集群重新声明
Failed(失败): 表示该 PV 的自动回收失败

3、定义PVC(非常注意:在创建PVC时PVC的namespace空间一定和Pod在同一namespace空间中)

这里定义了pvc的访问模式为多路读写,该访问模式必须在前面pv定义的访问模式之中。定义PVC申请的大小为2Gi,此时PVC会自动去匹配多路读写且大小为2Gi的PV,匹配成功获取PVC的状态即为Bound

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
[root@k8s-master volumes ~]# vim pod-vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-pvc
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc

[root@k8s-master volumes]# kubectl apply -f pod-vol-pvc.yaml
persistentvolumeclaim/mypvc created
pod/pod-vol-pvc created

[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 19m
pv002 2Gi RWO Retain Available 19m
pv003 2Gi RWO,RWX Retain Bound default/mypvc 19m
pv004 4Gi RWO,RWX Retain Available 19m
pv005 5Gi RWO,RWX Retain Available 19m

[root@k8s-master volumes]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pv003 2Gi RWO,RWX 22s

4、测试访问

在存储服务器上创建index.html,并写入数据,通过访问Pod进行查看,可以获取到相应的页面。

1
2
3
4
5
6
7
8
9
[root@stor01 volumes]# cd v3/

[root@stor01 v3]# echo "welcome to use pv3" > index.html

[root@k8s-master volumes]# kubectl get pods -o wide
pod-vol-pvc 1/1 Running 0 3m 10.244.2.39 k8s-node02

[root@k8s-master volumes]# curl 10.244.2.39
welcome to use pv3

八、StorageClass

在pv和pvc使用过程中存在的问题,在pvc申请存储空间时,未必就有现成的pv符合pvc申请的需求,上面nfs在做pvc可以成功的因素是因为我们做了指定的需求处理。那么当PVC申请的存储空间不一定有满足PVC要求的PV时,又该如何处理呢???为此,Kubernetes为管理员提供了描述存储”class(类)”的方法(StorageClass)。举个例子,在存储系统中划分一个1TB的存储空间提供给Kubernetes使用,当用户使用PVC申请需要一个10G的PV时,会立即通过restful发送请求,从而让存储空间创建一个10G的image,之后在我们的集群中定义成10G的PV供给给当前的PVC作为挂载使用。在此之前我们的存储系统必须支持restful接口,比如ceph分布式存储,而glusterfs则需要借助第三方接口完成这样的请求。如图:

volumes-4

StorageClass 中包含 provisioner、parameters 和 reclaimPolicy 字段,当 class 需要动态分配 PersistentVolume 时会使用到。

1、提供者(Provisioner)

描述存储资源的提供者,也可以看做后端存储驱动。目前 Kubernetes 支持的 Provisioner 都以 “kubernetes.io/” 为开头,用户也可以使用自定义的后端存储提供者。为了符合 StorageClass 的用法,自定义 Provisioner 需要符合存储卷的开发规范。

2、参数(Parameters)

后端存储资源提供者的参数设置,不同的 Provisioner 包括不同的参数设置。某些参数可以不显示设定,Provisioner 将使用其默认值。

接下来通过几种常见的 Provisioner 对 StorageClass 的定义进行详细说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master ~]# kubectl explain storageclass  #storageclass也是k8s上的资源
KIND: StorageClass
VERSION: storage.k8s.io/v1
FIELDS:
allowVolumeExpansion <boolean>
allowedTopologies <[]Object>
apiVersion <string>
kind <string>
metadata <Object>
mountOptions <[]string> #挂载选项
parameters <map[string]string> #参数,取决于分配器,可以接受不同的参数。 例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。当参数被省略时,会使用默认值。
provisioner <string> -required- #存储分配器,用来决定使用哪个卷插件分配 PV。该字段必须指定。
reclaimPolicy <string> #回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy ,它将默认为 Delete。
volumeBindingMode <string> #卷的绑定模式

Glusterfs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: slow #定义一个名字,PVC storageClassName时就要用到这个名字
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://127.0.0.1:8081"
clusterid: "630372ccdc720a92c681fb928f27b53f"
restauthenabled: "true"
restuser: "admin"
secretNamespace: "default"
secretName: "heketi-secret"
gidMin: "40000"
gidMax: "50000"
volumetype: "replicate:3"

resturl:分配 gluster 卷的需求的 Gluster REST 服务/Heketi 服务 url。 通用格式应该是 IPaddress:Port,这是 GlusterFS 动态分配器的必需参数。 如果 Heketi 服务在 openshift/kubernetes 中安装并暴露为可路由服务,则可以使用类似于 http://heketi-storage-project.cloudapps.mystorage.com 的格式,其中 fqdn 是可解析的 heketi 服务 url。

restauthenabled:Gluster REST 服务身份验证布尔值,用于启用对 REST 服务器的身份验证。 如果此值为 ‘true’,则必须填写 restuser 和 restuserkey 或 secretNamespace+ secretName。 此选项已弃用,当在指定 restuser,restuserkey,secretName 或 secretNamespace 时,身份验证被启用。

restuser:在 Gluster 可信池中有权创建卷的 Gluster REST服务/Heketi 用户。

restuserkey:Gluster REST 服务/Heketi 用户的密码将被用于对 REST 服务器进行身份验证。此参数已弃用,取而代之的是 secretNamespace + secretName。

secretNamespace,secretName:Secret 实例的标识,包含与 Gluster REST 服务交互时使用的用户密码。 这些参数是可选的,secretNamespace 和 secretName 都省略是使用空密码。提供的密码必须有 “kubernetes.io/glusterfs” type,例如以这种方式创建:
kubectl create secret generic heketi-secret
–type=”kubernetes.io/glusterfs” –from-literal=key=’opensesame’
–namespace=default

secret 都例子可以在 glusterfs-provisioning-secret.yaml 中找到。

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
name: heketi-secret
namespace: default
data:
# base64 encoded password. E.g.: echo -n "mypassword" | base64
key: bXlwYXNzd29yZA==
type: kubernetes.io/glusterfs

clusterid:630372ccdc720a92c681fb928f27b53f 是集群的 ID,当分配卷时,Heketi 将会使用这个文件。 它也可以是一个 clusterid 列表,例如: “8452344e2becec931ece4e33c4674e4e,42982310de6c63381718ccfa6d8cf397”。这个是可选参数。

gidMin,gidMax:storage class GID 范围的最小值和最大值。在此范围(gidMin-gidMax)内的唯一值(GID)将用于动态分配卷。 这些是可选的值。如果不指定,卷将被分配一个 2000-2147483647 之间的值,这是 gidMin 和 gidMax 的默认值。

volumetype:卷的类型及其参数可以用这个可选值进行配置。如果未声明卷类型,则由分配器决定卷的类型。
例如:
Replica volume: volumetype: replicate:3 其中 ‘3’ 是 replica 数量
Disperse/EC volume: volumetype: disperse:4:2 其中 ‘4’ 是数据,’2’ 是冗余数量.
Distribute volume: volumetype: none

当动态分配 persistent volume 时,Gluster 插件自动创建一个端点和一个 以 gluster-dynamic- 命名的 headless 服务。当 persistent volume claim 删除时,动态端点和服务是自动删除的。

pvc storageClassName

声明可以通过使用属性 storageClassName 指定 StorageClass 的名称来请求特定的类。只有所请求的类与 PVC 具有相同 storageClassName 的 PV 才能绑定到 PVC。

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
storageClassName: slow

例如:PVC指定了storageClassName=slow。那么Kubernetes会在集群中寻找是否存在metadata.name=slow的StorageClass,如果存在,此StorageClass会自动为此PVC创建一个accessModes = ReadWriteOnce,并且大小为8GB的PV。

DefaultStorageClass

前面我们说到,PVC和PV的绑定是通过StorageClassName进行的。然而如果定义PVC时没有指定StorageClassName呢?这取决与admission插件是否开启了DefaultDefaultStorageClass功能:

  • 如果DefaultDefaultStorageClass功能开启,那么此PVC的StorageClassName就会被指定为DefaultStorageClass。DefaultStorageClass从何处而来呢?原来在定义StorageClass时,可以在Annotation中添加一个键值对:storageclass.kubernetes.io/is-default-class: true,那么此StorageClass就变成默认的StorageClass了。
  • 如果DefaultDefaultStorageClass功能没有开启,那么没有指定StorageClassName的PVC只能被绑定到同样没有指定StorageClassName的PV。没有指定storageClassName 的 PVC 的处理方式与 storageClassName 设置为 “” 的 PVC 的处理方式相同。

设置默认的(Default)StorageClass

要在系统中设置一个默认的 StorageClass,首先需要启用名为 “DefaultStorageClass” 的 admission controller。即在 kube-apiserver 的命令行使用 –enable-admission-plugins=DefaultStorageClass 或 –disable-admission-plugins 指定需要打开或者关闭的 Admission Controller。

然后,在 StorageClass 的定义中设置一个 annotation:

1
2
3
4
5
6
7
8
9
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gold
annotations:
storageclass.beta.kubernetes.io/is-default-class="true"
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd

通过 kuberctl 命令创建成功后,查看 StorageClass 列表,可以看到名为 gold 的 StorageClass 被标记为 “default”:

1
2
3
$ kubectl get sc
NAME TYPE
gold (default) kubetnetes.io/gce-pd

九、动态PV扩容

  • v1.11之前需要在feature gate中开启ExpandPersistentVolumes,还建议启用PersistentVolumeClaimResize准入控制器。默认情况下,该准入控制器防止调整所有PersistentVolumeClaim的大小(防止在底层存储不支持扩容的情况下对 PVC 进行扩容),除非PersistentVolumeClaimStorageClass通过将allowVolumeExpansion设置为true显式地启用大小调整。一旦管理员确定底层提供程序支持卷扩展,管理员就可以通过在自己的StorageClass对象中将allowVolumeExpansion字段设置为true来开启该特性。

  • 调整使用中的持久卷大小,v1.15之前需要在feature gate中开启ExpandInUsePersistentVolumes,在开启这种功能的情况下,你不需要删除和重新创建使用现有PVC的Pod或Deployment。只要扩展了文件系统,任何正在使用的PVC都会自动对Pod可用。

以下持久化卷支持扩展持久化卷声明:

  • AWS-EBS
  • GCE-PD
  • Azure Disk
  • Azure File
  • Glusterfs
  • Cinder
  • Portworx
  • Ceph RBD
1
2
3
4
5
6
7
8
9
10
11
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gluster-vol-default
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://192.168.10.100:8080"
restuser: ""
secretNamespace: ""
secretName: ""
allowVolumeExpansion: true

要为PVC请求更大的卷,请编辑PVC对象并指定更大的空间。这将触发与其绑定的PV扩容。不是创建新的PV,而是在原有基础上resize,原有数据不会丢失。

扩容操作

修改PVC的size使用kubectl edit pvc xxx,直接修改PVC的容量即可。

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.beta.kubernetes.io/storage-provisioner: ceph.com/rbd
name: resize
spec:
resources:
requests:
storage: 5Gi
storageClassName: sata

之后PVC会进入FileSystemResizePending状态,等待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
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.beta.kubernetes.io/storage-provisioner: ceph.com/rbd
name: resize
spec:
resources:
requests:
storage: 6Gi
storageClassName: sata
status:
accessModes:
- ReadWriteOnce
capacity:
storage: 5Gi
conditions:
- lastProbeTime: null
lastTransitionTime: 2019-04-14T03:51:36Z
message: Waiting for user to (re-)start a pod to finish file system resize of
volume on node.
status: "True"
type: FileSystemResizePending
phase: Bound

重启挂载该PVC的Pod之后,controller manager 会调用 resize命令,更新image的大小,并重新挂载卷到Pod上去。


Kubernetes之存储卷
https://system51.github.io/2019/08/26/Kubernetes-StorageVolume/
作者
Mr.Ye
发布于
2019年8月26日
许可协议