在kubernetes中Pod的生命周期是很短暂的,会被频繁地销毁和创建。容器销毁时,保存在容器内部文件系统中的数据都会被清除。为了持久化保存容器的数据,需要使用 Kubernetes Volume数据卷,下面文章分别以本地卷,网络数据卷的方式来实践集群的持久化存储。
一、hostPath/emptyDir
hostPath:用于将目录从宿主机节点的文件系统挂载到pod中,属于单节点集群中的持久化存储,删除pod后,卷里面的文件会继续保持,在同一节点运行的Pod可以继续使用数据卷中的文件,但pod被重新调度到其他节点时,就无法访问到原数据。不适合作为存储数据库数据的目录。
emptyDir: 用于存储临时数据的简单空目录,生命周期是和pod捆绑的,随着pod创建而创建;删除而销毁,卷的内容将会丢失。emptyDir卷适用于同一个pod中运行的容器之间共享文件。
hostPath定义如下:
....
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
hostPath:
path: /opt/data
type: Directory
emptyDir定义如下:
....
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
二、PV/PVC
前面使用的 hostPath 和 emptyDir 类型的 Volume 并不具备持久化特征,它们既有可能被 kubelet 清理掉,也不能被“迁移”到其他节点上。所以,大多数情况下,持久化 Volume 的实现,往往依赖于一个远程存储服务,比如:远程文件存储(比如,NFS、GlusterFS)、远程块存储(比如,公有云提供的远程磁盘)等等。而 Kubernetes 需要做的工作,就是使用这些存储服务,来为容器准备一个持久化的宿主机目录,以供将来进行绑定挂载时使用。而所谓“持久化”,指的是容器在这个目录里写入的文件,都会保存在远程存储中,从而使得这个目录具备了“持久性”。为了屏蔽底层的技术实现细节,让用户更加方便的使用,Kubernetes 便引入了 PV 和 PVC 两个重要的资源对象来实现对存储的管理。
PV:持久化存储数据卷,全称为PersistentVolume,PV其实是对底层存储的一种抽象,通常是由集群的管理员进行创建和配置 ,底层存储可以是Ceph,GlusterFS,NFS,hostpath等,都是通过插件机制完成与共享存储的对接。
PVC: 持久化数据卷声明,全称为PersistentVolumeClaim,PVC 对象通常由开发人员创建,描述 Pod 所希望使用的持久化存储的属性。比如,Volume 存储的大小、可读写权限等等。PVC绑定PV,消耗的PV资源。
下面创建一个NFS类型的PV,首先节点部署NFS,共享/data/k8s目录
$ systemctl stop firewalld.service
$ yum -y install nfs-utils rpcbind
$ mkdir -p /data/k8s
$ chmod 755 /data/k8s
$ vim /etc/exports
/data/k8s *(rw,sync,no_root_squash)
$ systemctl start rpcbind.service
$ systemctl start nfs.service
$ mount 192.168.16.173:/data/k8s /data/k8s
创建nfspv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.16.173
path: "/data/k8s"
文件的内容定义如下:
- spec.storageClassName:定义了名称为 manual 的 StorageClass,该名称用来将 PVC请求绑定到该 PV。
- spec.Capacity:定义了当前PV的存储空间为storage=10G。
- spec.nfs: 定义了PV的类型为NFS,并指定了该卷位于节点上的 /data/k8s/目录。
- spec.accessModes:定义了当前的访问模式,可定义的模式如下:
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载。
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载;
- ReadWriteMany(ROX):只读权限,可以被多个节点挂载。
下图是一些常用的 Volume 插件支持的访问模式:
创建资源对象:
$ kubectl create -f nfspv.yaml
persistentvolume/nfs created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs 5Gi RWX Retain Available manual 4s
可以看到创建后的PV的Status为Available 可用状态,意味着当前PV还没有被PVC绑定使用。
PV生命周期的不同状态如下:
- Available(可用):表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定):表示 PVC 已经被 PVC 绑定
- Released(已释放):PVC 被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该 PV 的自动回收失败
另外还有一个RECLAIM POLICY字段输出内容为Retain,这个字段输出的其实是PV的回收策略,目前 PV 支持的策略有三种:
- Retain(保留):保留数据,需要管理员手工清理数据
- Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /thevoluem/*
- Delete(删除):与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS。
注意:目前只有 NFS 和 HostPath 两种类型支持回收策略。
现在我们创建一个PVC来绑定上面的PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 3Gi
创建PVC
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs 5Gi RWX manual 3m59s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs 5Gi RWX Retain Bound default/nfs-pvc manual 20m
可以看到PV和PVC的状态已经为Bound绑定状态,其中绑定需要检查的条件,包括两部分:
- 第一个条件,当然是 PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,权限等就必须满足 PVC 的要求。
- 而第二个条件,则是 PV 和 PVC 的 storageClassName 字段必须一样。
创建一个Pod,PVC使用方式和hostpath类似
apiVersion: v1
kind: Pod
metadata:
name: web-front
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
mountPath: "/usr/share/nginx/html"
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs-pvc
Pod将PVC挂载到容器的html目录,我们在PVC的目录下创建一个文件用来验证
$ echo "hello nginx" > /data/k8s/index.html
创建Pod,并验证是否将文件挂载至容器
$ kubectl create -f nfs-nginxpod.yaml
$ kubectl exec -it web-front -- /bin/bash
root@web-front:/# curl localhost
hello nginx
可以看到输出结果是我们前面写到PVC卷中的index.html 文件内容。
三、StorageClass
在上面PV对象创建的方式为Static Provisioning(静态资源调配),在大规模的生产环境里,面对集群中大量的PVC,需要提前手动创建好PV来与之绑定,这其实是一个非常麻烦的工作。还好kubernetes提供了Dynamic Provisioning(动态资源调配)的机制,即:StorageClass对象, 它的作用其实就是创建 PV 的模板。
StorageClass 对象会定义如下两个部分内容:
- PV 的属性。比如,存储类型、Volume 的大小等等。
- 创建这种 PV 需要用到的存储插件。比如,Ceph 等等。
Kubernetes 有了这样两个信息之后,就能够根据用户提交的 PVC,找到一个对应的StorageClass,然后Kubernetes 就会调用该 StorageClass 声明的存储插件,自动创建出需要的 PV。
现在我们创建一个NFS类型的StorageClass,首先需要创建nfs-client-provisioner(存储插件):nfs-client 的自动配置程序
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
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.16.173 # 修改成自己的 IP
- name: NFS_PATH
value: /data/k8s
volumes:
- name: nfs-client-root
nfs:
server: 192.168.16.173 # 修改成自己的 IP
path: /data/k8s
为nfs-client程序绑定相应的集群操作权限 :
apiVersion: v1
kind: ServiceAccount
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: ["list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
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
创建StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: es-data-db
provisioner: fuseim.pri/ifs
provisioner 字段的值是:fuseim.pri/ifs,这个是NFS提供的分配器,kubernetes也内置了一些存储的分配器:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/
创建资源对象
$ kubectl create -f nfs-client-Provisioner.yaml
$ kubectl create -f nfs-client-sa.yaml
$ kubectl create -f nfs-storageclass.yaml
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-data-db fuseim.pri/ifs Delete Immediate false 136m
StorageClass创建完成后,开发人员只需要在 PVC 里指定要使用的 StorageClass 名字即可,PV则会根据PVC的属性定义自动创建。
创建nfs-pvc02.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc02
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-data-db
resources:
requests:
storage: 1Gi
创建PVC并查看是否绑定相应的PV
$ kubectl create -f nfs-pvc02.yaml
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound nfs 5Gi RWX manual 7h49m
nfs-pvc02 Bound pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0 1Gi RWO nfs-data-db 7s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs 5Gi RWX Retain Bound default/nfs-pvc manual 8h
pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0 1Gi RWO Delete Bound default/nfs-pvc02 nfs-data-db 17s
$ ls /data/k8s
default-nfs-pvc02-pvc-9df576f0-b2d4-41cf-9d92-27958f68e7e0 index.html
可以看到创建完PVC后,StorageClass自动为PVC创建并绑定了对应的PV,而且PV的属性是和PVC相同的,在共享卷中也创建了相关的PV目录,这样我们创建Pod时只需要指定PVC的名字即可,不用再去手动的为PVC创建PV。
注意:Kubernetes 只会将StorageClass 定义相同的 PVC 和 PV 绑定起来
总结:
hostPath:属于单节点集群中的持久化存储,Pod需要绑定集群节点。删除pod后,卷里面的文件会继续保持,但pod被重新调度到其他节点时,就无法访问到原数据。不适合作为存储数据库数据的目录。
emptyDir: 用于存储临时数据的简单空目录,生命周期是和pod捆绑的,随着pod创建而创建;删除而销毁,卷的内容将会丢失。emptyDir卷适用于同一个pod中运行的容器之间共享文件。
PVC 描述的,是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。
PV 描述的,则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。
StorageClass 的作用,则是充当 PV 的模板。并且,只有同属于一个 StorageClass 的 PV 和 PVC,才可以绑定在一起。当然,StorageClass 的另一个重要作用,是指定 PV 的 Provisioner(存储插件)。这时候,如果你的存储插件支持 Dynamic Provisioning 的话,Kubernetes 就可以自动为你创建 PV 了。
参考资料:
深入剖析Kubernetes-张磊
关注公众号回复【k8s】获取视频教程及更多资料: