k8s之PV&PVC

网友投稿 236 2022-10-27

k8s之PV&PVC

一、概念

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

但是通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。

二、pv的使用

1、创建NFS

我们这里为了演示方便,决定使用相对简单的 NFS 这种存储资源,接下来我们在节点192.168.140.50上来安装 NFS 服务,数据目录:/data/k8s/

关闭防火墙

$ systemctl stop firewalld.service $ systemctl disable firewalld.service

安装配置 nfs

yum -y install nfs-utils

共享目录设置权限:

chmod 755 /data/k8s/

配置 nfs,nfs 的默认配置文件在 /etc/exports 文件下,在该文件中添加下面的配置信息:

$ vi /etc/exports /data/k8s *(rw,sync,no_root_squash)

配置说明:

/data/k8s:是共享的数据目录 *:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名 rw:读写的权限 sync:表示文件同时写入硬盘和内存 no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份

2、创建PV

PV 作为存储资源,主要包括存储能力、访问模式、存储类型、回收策略等关键信息,下面我们来新建一个 PV 对象,使用 nfs 类型的后端存储,1G 的存储空间,访问模式为 ReadWriteOnce,回收策略为 Recyle,对应的 YAML 文件如下:(pv1-demo.yaml)

apiVersion: v1 kind: PersistentVolume metadata: name: pv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle nfs: path: /data/k8s server: 10.151.30.57

Kubernetes 支持的 PV 类型有很多,比如常见的 Ceph、GlusterFs、NFS,甚至 HostPath也可以,不过 HostPath 我们之前也说过仅仅可以用于单机测试,更多的支持类型可以前往 Kubernetes PV 官方文档进行查看,因为每种存储类型都有各自的特点,所以我们在使用的时候可以去查看相应的文档来设置对应的参数。

然后同样的,直接使用 kubectl 创建即可:

$ kubectl create -f pv1-demo.yaml persistentvolume "pv1" created $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Available 12s

我们可以看到 pv1 已经创建成功了,状态是 Available,表示 pv1 就绪,可以被 PVC 申请。我们来分别对上面的属性进行一些解读。

Capacity(存储能力)

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

AccessModes(访问模式)

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

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

persistentVolumeReclaimPolicy(回收策略)

我这里指定的 PV 的回收策略为 Recycle,目前 PV 支持的策略有三种:

Retain(保留)- 保留数据,需要管理员手工清理数据Recycle(回收)- 清除 PV 中的数据,效果相当于执行 rm -rf /thevoluem/*Delete(删除)- 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS。不过需要注意的是,目前只有 NFS 和 HostPath 两种类型支持回收策略。当然一般来说还是设置为 Retain 这种策略保险一点。

状态

一个 PV 的生命周期中,可能会处于4中不同的阶段:

3、创建PVC

在使用 PVC 之前,我们还得把其他节点上的 nfs 客户端给安装上,否则可能会导致 PV 挂载不上的问题

kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-nfs spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi

kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-nfs 1Gi RWO Recycle Available 19m

我们可以看到当前 pv-nfs 是在 Available 的一个状态,所以这个时候我们的 PVC 可以和这个 PV 进行绑定:

$ kubectl create -f pvc-nfs.yaml persistentvolumeclaim "pvc-nfs" created $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc-nfs Bound pv-nfs 1Gi RWO 12s

我们可以看到 pvc-nfs 创建成功了,状态是 Bound 状态了,这个时候我们再看下 PV 的状态呢:

$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-nfs 1Gi RWO Recycle Bound default/pvc-nfs 23m

4、使用 PVC

上面我们已经知道怎么创建 PV 和 PVC 了,现在我们就来使用下我们的 PVC,这里我们同样使用之前的 nginx 的镜像来测试下:(nfs-pvc-deploy.yaml)

apiVersion: apps/v1 kind: Deployment metadata: name: nfs-pvc spec: replicas: 3 selector: matchLabels: app: nfs-pvc template: metadata: labels: app: nfs-pvc spec: containers: - name: nginx image: nginx:1.7.9 imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumes: - name: www persistentVolumeClaim: claimName: pvc2-nfs --- apiVersion: v1 kind: Service metadata: name: nfs-pvc labels: app: nfs-pvc spec: type: NodePort ports: - port: 80 targetPort: web selector: app: nfs-pvc

我们这里使用 nginx 镜像,将容器的 /usr/share/nginx/html 目录通过 volume 挂载到名为 pvc2-nfs 的 PVC 上面,然后创建一个 NodePort 类型的 Service 来暴露服务:

$ kubectl create -f nfs-pvc-deploy.yaml deployment.extensions "nfs-pvc" created service "nfs-pvc" created $ kubectl get pods kubectl get pods NAME READY STATUS RESTARTS AGE ... nfs-pvc-57c9945bd9-5r4r6 1/1 Running 0 19s nfs-pvc-57c9945bd9-gz6p9 1/1 Running 0 19s nfs-pvc-57c9945bd9-x6mvc 1/1 Running 0 19s ... $ kubectl get svc kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... nfs-pvc NodePort 10.98.246.155 80:30769/TCP 1m

然后我们就可以通过任意节点的 IP:30769 端口来访问我们这里的 Nginx 服务了,但是这个时候我们来访问会出现403,这是为什么?我们再去看看 nfs 共享数据目录下面有没有数据呢?

$ ls /data/k8s

我们发现并没有任何数据,这是因为我们把容器目录/user/share/nginx/html和挂载到了pvc2-nfs这个 PVC 上面,这个 PVC 就是对应着我们上面的 nfs 的共享数据目录的,该目录下面还没有任何数据,所以我们访问就出现了403,现在我们在/data/k8s这个目录下面新建一个 index.html 的文件:

$ echo "Hello Kubernetes~" >> /data/k8s/index.html $ ls /data/k8s/ index.html

我们可以看到共享数据目录中已经有一个 index.html 的文件了,由于我们挂载了 pvc2-nfs 到上面的 nginx 容器中去,是不是这个时候容器目录/user/share/nginx/html下面也有index.html这个文件了啊?所以这个时候我们再来访问下服务,任一节点IP:30769:

5、subPath

现在是不是正常了啊,但是我们可以看到我们容器中的数据是直接放到共享数据目录根目录下面的,如果以后我们又有一个新的 nginx 容器也做了数据目录的挂载,是不是就会有冲突了啊,所以这个时候就不太好区分了,这个时候我们可以在 Pod 中使用一个新的属性:subPath,该属性可以来解决这个问题,我们只需要更改上面的 Pod 的 YAML 文件即可:

...volumeMounts:

name: nginxpvc-testmountPath: /usr/share/nginx/html...

更改完 YAML 文件后,我们重新更新即可:

$ kubectl apply -f nfs-pvc-deploy.yaml Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply deployment.extensions "nfs-pvc" configured Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply service "nfs-pvc" configured

更新完后,我们再去看看 nfs 的数据共享目录:

$ ls /data/k8s/ index.html nginxpvc-test $ ls /data/k8s/nginxpvc-test/

三、StorageClass

前面的 PV 都是静态的,什么意思?就是我要使用的一个 PVC 的话就必须手动去创建一个 PV,我们也说过这种方式在很大程度上并不能满足我们的需求,比如我们有一个应用需要对存储的并发度要求比较高,而另外一个应用对读写速度又要求比较高,特别是对于 StatefulSet 类型的应用简单的来使用静态的 PV 就很不合适了,这种情况下我们就需要用到动态 PV,也就是 StorageClass。

1、创建

要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner,这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。

自动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中 而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上。

当然在部署nfs-client之前,我们需要先成功安装上 nfs 服务器,服务地址是10.151.30.57,共享数据目录是/data/k8s/,然后接下来我们部署 nfs-client 即可,我们也可以直接参考 nfs-client 的文档:Deployment,将里面的对应的参数替换成我们自己的 nfs 配置(nfs-client.yaml)

kind: Deployment apiVersion: apps/v1 metadata: name: 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: 10.151.30.57 - name: NFS_PATH value: /data/k8s volumes: - name: nfs-client-root nfs: server: 10.151.30.57 path: /data/k8s

第二步:将环境变量 NFS_SERVER 和 NFS_PATH 替换,当然也包括下面的 nfs 配置,我们可以看到我们这里使用了一个名为 nfs-client-provisioner 的serviceAccount,所以我们也需要创建一个 sa,然后绑定上对应的权限:(nfs-client-sa.yaml)

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

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: course-nfs-storage provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'

现在我们来创建这些资源对象吧:

$ kubectl create -f nfs-client.yaml $ kubectl create -f nfs-client-sa.yaml $ kubectl create -f nfs-client-class.yaml

创建完成后查看下资源状态:

$ kubectl get pods NAME READY STATUS RESTARTS AGE ... nfs-client-provisioner-7648b664bc-7f9pk 1/1 Running 0 7h ... $ kubectl get storageclass NAME PROVISIONER AGE course-nfs-storage fuseim.pri/ifs 11s

测试新建PV

上面把StorageClass资源对象创建成功了,接下来我们来通过一个示例测试下动态 PV,首先创建一个 PVC 对象:(test-pvc.yaml)

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc annotations: volume.beta.kubernetes.io/storage-class: "course-nfs-storage" spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi

$ kubectl create -f test-pvc.yaml persistentvolumeclaim "test-pvc" created $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE ... test-pvc Bound pvc-73b5ffd2-8b4b-11e8-b585-525400db4df7 1Mi RWX course-nfs-storage 2m ...

我们可以看到一个名为 test-pvc 的 PVC 对象创建成功了,状态已经是 Bound 了,是不是也产生了一个对应的 VOLUME 对象,最重要的一栏是 STORAGECLASS,现在是不是也有值了,就是我们刚刚创建的 StorageClass 对象 course-nfs-storage。

然后查看下 PV 对象呢:

$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE ... pvc-73b5ffd2-8b4b-11e8-b585-525400db4df7 1Mi RWX Delete Bound default/test-pvc course-nfs-storage 8m ...

可以看到是不是自动生成了一个关联的 PV 对象,访问模式是 RWX,回收策略是 Delete,这个 PV 对象并不是我们手动创建的吧,这是通过我们上面的 StorageClass 对象自动创建的。这就是 StorageClass 的创建方法。

2、测试

apiVersion: apps/v1 kind: StatefulSet metadata: name: nfs-web spec: serviceName: "nginx" replicas: 3 selector: matchLabels: app: nfs-web template: metadata: labels: app: nfs-web spec: terminationGracePeriodSeconds: 10 containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www annotations: volume.beta.kubernetes.io/storage-class: course-nfs-storage spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi

实际上 volumeClaimTemplates 下面就是一个 PVC 对象的模板,就类似于我们这里 StatefulSet 下面的 template,实际上就是一个 Pod 的模板,我们不单独创建成 PVC 对象,而用这种模板就可以动态的去创建了对象了,这种方式在 StatefulSet 类型的服务下面使用得非常多。

直接创建上面的对象:

$ kubectl create -f test-statefulset-nfs.yaml statefulset.apps "nfs-web" created $ kubectl get pods NAME READY STATUS RESTARTS AGE ... nfs-web-0 1/1 Running 0 1m nfs-web-1 1/1 Running 0 1m nfs-web-2 1/1 Running 0 33s ...

创建完成后可以看到上面的3个 Pod 已经运行成功,然后查看下 PVC 对象:

$ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE ... Bound pvc-cc36b3ce-8b50-11e8-b585-525400db4df7 1Gi RWO course-nfs-storage 2m Bound pvc-d38285f9-8b50-11e8-b585-525400db4df7 1Gi RWO course-nfs-storage 2m Bound pvc-e348250b-8b50-11e8-b585-525400db4df7 1Gi RWO course-nfs-storage 1m ...

我们可以看到是不是也生成了3个 PVC 对象,名称由模板名称 name 加上 Pod 的名称组合而成,这3个 PVC 对象也都是 绑定状态了,很显然我们查看 PV 也可以看到对应的3个 PV 对象:

$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE ... 1d pvc-cc36b3ce-8b50-11e8-b585-525400db4df7 1Gi RWO Delete Bound default/ course-nfs-storage 4m pvc-d38285f9-8b50-11e8-b585-525400db4df7 1Gi RWO Delete Bound default/ course-nfs-storage 4m pvc-e348250b-8b50-11e8-b585-525400db4df7 1Gi RWO Delete Bound default/ course-nfs-storage 4m ...

查看 nfs 服务器上面的共享数据目录:

$ ls /data/k8s/ ... default-www-nfs-web-0-pvc-cc36b3ce-8b50-11e8-b585-525400db4df7 default-www-nfs-web-1-pvc-d38285f9-8b50-11e8-b585-525400db4df7 default-www-nfs-web-2-pvc-e348250b-8b50-11e8-b585-525400db4df7

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring使用ThreadPoolTaskExecutor自定义线程池及异步调用方式
下一篇:苹果MacBook为什么使用USB-C接口?
相关文章

 发表评论

暂时没有评论,来抢沙发吧~