在Kubernetes中,因为deployment默认使用的是hostpath,当我们pod重启或删除pod后数据会丢失。这时候我们就需要一个持久化存储来解决这个问题。 本次介绍的是kubernetes pv与pvc,同时使用nfs作为后端存储进行演示。
在容器的生命周期里,位于磁盘上的文件,它的生命周期是很短暂的,docker里面如此,kubernetes里亦然。
数据很可能因为各种不可抗因素丢失,比如pod被迫下线时,它会根据rs控制器的数量定义,重新生成一个干净状态的新pod。
Volume的引入不但解决里数据稳定性的问题,也解决了同一个pod内,多个containers数据共享的需求;
和docker里不同的是:
1)kubernetes中内置封装了很多存储类型,pod也可以选择性的使用一个或多个。
2)当pod被删除时,Volume才可能会被清理,并且数据是否丢失和删除取决于Volume的具体类型和其回收策略
kubernetes内置封装了很多存储类型,大致可分为以下七大部分:
我已经整理好了官方配置文档,大家可以直接按需阅读,如何使用和配置。
存储类型 | 存储组件 | 官网文档 |
---|---|---|
云存储 | awsElasticBlockStore | awsElasticBlockStore |
云存储 | azureDisk | azureDisk |
云存储 | azureFile | azureFile |
云存储 | gcePersistentDisk | gcePersistentDisk |
云存储 | vsphereVolume | vsphereVolume |
分布式存储 | cephfs | cephfs |
分布式存储 | glusterfs | glusterfs |
分布式存储 | rbd | rbd |
网络存储 | nfs | nfs |
网络存储 | fc | fc |
网络存储 | iscsi | iscsi |
临时存储 | emptyDir | emptyDir |
本地存储 | hostPath | hostPath |
特殊存储 | configMap | configMap |
特殊存储 | downwardAPI | downwardAPI |
特殊存储 | secret | secret |
自定义存储 | csi | csi |
持久卷申请 | persistentVolumeClaim | persistentVolumeClaim |
1、资源供应 (Provisioning)
Kubernetes支持两种资源的供应模式:静态模式(Staic)和动态模式(Dynamic)。资源供应的结果就是创建好的PV。
静态模式:集群管理员手工创建许多PV,在定义PV时需要将后端存储的特性进行设置
动态模式:集群管理员无须手工创建PV,而是通过StorageClass的设置对后端存储进行描述,标记为某种 “类型(Class)”。此时要求PVC对存储的类型进行声明,系统将自动完成PV的创建及PVC的绑定。PVC可以声明Class为"",说明该PVC禁止使用动态模式
2、资源绑定 (Binding)
在用户定义好PVC后,系统将根据PVC对存储资源的请求 (存储空间和访问模式)在已存在的PV中选择一个满足PVC要求的PV,一旦找到,就将该PV与用户定义的PVC进行绑定,然后用户的应用就可以使用这个PVC了。如果系统中没有满足PVC要求的PV,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合要求的PV。PV一旦绑定在某个PVC上,就被这个PVC独占,不能再与其他PVC进行绑定了。在这种情况下,当PVC申请的存储空间比PV的少时,整个PV的空间都能够为PVC所用,可能会造成资源的浪费。如果资源供应使用的是动态模式,则系统在PVC找到合适的StorageClass后,将会自动创建PV并完成PVC的绑定
3、资源使用 (Using)
Pod 使用volume的定义,将PVC挂载到容器内的某个路径进行使用。volume的类型为persistentVoulumeClaim,在容器应用挂载了一个PVC后,就能被持续独占使用。不过,多个Pod可以挂载同一个PVC,应用程序需要考虑多个实例共同访问一块存储空间的问题
4、资源释放 (Releasing)
当用户对存储资源使用哪个完毕后,用户可以删除PVC,与该PVC绑定的PV将会被标记为已释放,但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还留在存储设备上,只有在清除之后该PV才能继续使用
当pod资源被删除时,其相关pv和数据如何操作?该删除还是保留呢?
kubernetes通过persistentVolumeReclaimPolicy字段进行设置:
Delete: 数据和pv都会删除
Recyle: (已废弃)
Retain: 数据和pv都不动
PV的申明类型可分为以下两种:
Static(静态):
管理员根据使用情况,人为预先进行配置
Dynamic(动态):
基于已创建的StorageClasses(简称SC)存储类,起到动态申请和创建的作用
API server需要增加一个参数配置:–enable-admission-plugins,具体类型参考:storage-classes
定义:
PVC:描述使用者(Pod)想要使用的持久化属性,比如存储大小、读写权限等
PV:描述一个具体的Volume属性,比如Volume的类型、挂载目录、远程存储服务器地址等
SC:运维人员根据pv特征,可能是性能、质量级别、备份策略等进行定义的抽象存储类,通过接收pvc请求,从而启到动态实例化pv的效果
你可能还有一点茫然,我来举个例子:
PVC:好比接口,使用者只需要知道这个接口如何使用即可,比如该传哪些参数,哪些是必传的等等,他并不需要了解接口是如何实现的
PV:就是这些接口的实现,内部是用nfs,还是ceph的存储系统等等
SC:则是这些接口根据一系列规则所进行的抽象类,通过接收pvc请求,从而启到动态实例化pv的效果
另外,还有2点是非常重要的:
1、pv没有namespace名称空间概念,而pvc有namespace名称空间的概念
2、pv和pvc一一对应绑定
#pv的API字段配置说明
[root@centos-1 mainfasts]# kubectl explain pods.spec.volumes
KIND: Pod
VERSION: v1
RESOURCE: volumes <[]Object>
#pvc的API字段配置说明
[root@centos-1 dingqishi]# kubectl explain pods.spec.volumes.persistentVolumeClaim
KIND: Pod
VERSION: v1
RESOURCE: persistentVolumeClaim <Object>
#sc的API字段配置说明
[root@k8s-etcd-mater01 fault-injection]# kubectl explain sc
KIND: StorageClass
VERSION: storage.k8s.io/v1
首先你要先准备好nfs的挂载配置,我这里的配置如下所示:
角色 | ip | 备注 |
---|---|---|
server | 192.168.0.11 | 共享目录:/home/nfstestdir |
client | 192.168.0.12 | 挂载点:/mnt |
1、首先编辑pv.yaml的配置文件,正确填写nfs服务端的配置信息,并使用apply -f命令生成
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 100Mi
accessModes:
- ReadWriteMany
nfs:
# FIXME: use the right IP
server: 192.168.0.11
path: "/home/nfstestdir"
注意:pv是没有名称空间概念的。
2、编辑pvc.yaml,需要指定刚才创建的pv的名字,并apply -f生成
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: pv-test
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 90Mi
3、此时你可以通过以下命令查看到pv和pvc的状态了,此时pv和pvc的绑定已经完成了
[root@k8s-etcd-mater01 aaron]# kubectl get pvc -n pv-test
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs-pv 100Mi RWX 17s
[root@k8s-etcd-mater01 aaron]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv 100Mi RWX Retain Bound pv-test/nfs-pvc 53s
4、接下来我们要部署一个pod,让他使用pvc资源
apiVersion: v1
kind: Pod
metadata:
name: nginx-volume-pvc
namespace: pv-test
spec:
containers:
- name: nginx-pvc
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: html-pvc #自定义名的引用
mountPath: /usr/share/nginx/html/
volumes: #这里是选择volume的类型
- name: html-pvc #自定义名
persistentVolumeClaim:
claimName: nfs-pvc #我们刚才定义的pvc名
注意:pvc是有名称空间的,需要使用pvc的话,需要和pvc在同一个名称空间里!
5、此时pvc的状态已经是Bound了,
[root@k8s-etcd-mater01 aaron]# kubectl get pod -n pv-test
NAME READY STATUS RESTARTS AGE
nginx-volume-pvc 1/1 Running 0 7m1s
[root@k8s-etcd-mater01 aaron]# kubectl get pvc -n pv-test
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs-pv 100Mi RWX 21m
[root@k8s-etcd-mater01 aaron]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv 100Mi RWX Retain Bound pv-test/nfs-pvc 22m
上面我们使用了基于nfs静态配置的方法,来给pod资源提供存储容量,这个方式的缺点显而易见。
需要我们作为管理员,预先配置好存储容量:要几个pv,并且容量多少!
这种协作方式一般不太可取,后面我将以NFS Provisioner为例,来讲述动态供给的例子。
1、NFS Provisioner介绍
NFS Provisioner是一个自动配置卷程序,它使用现有的和已配置的 NFS 服务器来支持通过持久卷声明动态配置Kubernetes持久卷。
你可以在Github-NFS Provisioner上找到他的项目。
2、NFS环境准备
这里我就沿用上面的环境了,不再另行配置
角色 | ip | 备注 |
---|---|---|
server | 192.168.0.11 | 共享目录:/home/nfstestdir |
client | 192.168.0.12 | 挂载点:/mnt |
3、部署方式
NFS Provisioner的部署方式可以分为两种:
helm和手工的方式。
helm install stable/nfs-client-provisioner --set nfs.server=nfs_serverip --set nfs.path=nfs_server_path
4、创建SA
首先我们需要创建一个serviceaccount,并使用RoleBinding绑定到leader-locking-nfs-client-provisioner上面。
kind: ServiceAccount
apiVersion: v1
metadata:
name: nfs-client-provisioner
---
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
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
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
kubectl apply -f rbac.yaml
5、创建nfs-client
将nfs配置成storageclass,安装对应的自动配置程序nfs-client,可以自动创建持久卷(pv)。
每当创建storageclass时,就会在kubernetes里面自动创建pv,nfs目录下自动创建文件夹,省去手动创建的繁琐。
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 192.168.0.114 #nfs服务器
- name: NFS_PATH
value: /home/nfstestdir #共享目录
volumes:
- name: nfs-client-root
nfs:
server: 192.168.0.114 #nfs服务器
path: /home/nfstestdir #共享目录
kubectl apply -f nfs-client-deployment.yaml
6、创建存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs #---动态卷分配者名称,必须和上面创建的"provisioner"变量中设置的Name一致
parameters:
archiveOnDelete: "true" #---设置为"false"时删除PVC不会保留数据,"true"则保留数据
设置默认存储类(可选):
metadata.annotations:
storageclass.kubernetes.io/is-default-class: "true" #---设置为默认的storageclass
7、创建PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" #---需要与上面创建的storageclass的名称一致
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
8、测试
此时,我们要创建一个测试用的pod,将nfs存储挂载至容器的mnt目录,并新建一个success的文件,观察动态供给是否正常工作。
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: busybox:1.24
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim #我们定义的pvc
具体创建流程如下所示:
pod->test-claim(pvc)->managed-nfs-storage(SC存储类)->provisioner: fuseim.pri/ifs(PV:nfs-client)
此时,我们查看相关pv,发现已经动态供给了。
[root@k8s-etcd-mater01 nfs]# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-50b542db-2ee3-11ea-a207-001c425c73bc 1Mi RWX Delete Bound default/test-claim managed-nfs-storage 41m
persistentvolume/pvc-9e0191c8-2ee0-11ea-82c1-001c42662fdd 2Gi RWO Delete Bound default/test-web-0 managed-nfs-storage 42m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/test-claim Bound pvc-50b542db-2ee3-11ea-a207-001c425c73bc 1Mi RWX managed-nfs-storage 41m
persistentvolumeclaim/test-web-0 Bound pvc-9e0191c8-2ee0-11ea-82c1-001c42662fdd 2Gi RWO managed-nfs-storage 61m
你也可以到nfs服务器端,查看success文件是否生成。
[root@k8s-node01 nfstestdir]# tree
.
├── 11
├── 12
├── 21313
├── 3
├── default-test-claim-pvc-50b542db-2ee3-11ea-a207-001c425c73bc
│ └── SUCCESS
└── default-test-web-0-pvc-9e0191c8-2ee0-11ea-82c1-001c42662fdd