动手动脑学Kubernetes系列教程之持久卷

查理谈科技 2024-05-11 02:37:36

在之前的文章中, 介绍了搭建好的#minikube#环境,如果你现在还没有一个可用的minikube环境, 那么可以去中直接下载;

在之前的文章中, 先后介绍了如何从源代码开始构建一个Node.js应用和Spring Boot 应用, 并且部署到Kubernetes 中(这个Kubernetes 环境主要是之前建好的#minikube#) , 现在我们开始进一步深入的学习Kubernetes, 用一个个可以实际运行的例子的形式, 深入理解#Kubernetes#的概念以及原理.

在#动手动脑学Kubernetes#系列教程中, 我们展示了Kubernetes的基本用法

在里, 学习了Pod的基本知识;

在里, 学习了标签(Label)的语法, 使用Label来选择和过滤Kubernetes 资源;

在里, 介绍了Deployment的使用, 介绍了Deployment与Replica Set、Pod的关系, 并展示了如何进行应用的版本回滚;

在里, 介绍了Service的使用,使用Replication Controller创建Pod, 并创建Service, 展示了从Service 调用应用的方法; 随后又展示了扩展 Pod的数量为2, 比较了Service和之前的不同, 基本展示了Cluster IP 类型的Service的基本用法.

在里, 介绍了Namespace的使用, 除了创建,列出系统的Namespace之外, 还说明Namespace 如何对资源进行隔离, 建立Development, Staging, Production等环境的方法.

在里, 介绍了Service Discovery的使用, 讲解了如何检查Kube-dns, 如何检查和使用Service的FQDN等知识, 对Kubernetes的DNS 系统有整体的理解.

在里, 介绍了Port Forwards, 端口转发的用法, 在本地程序开发的时候, 使用端口转发可以简化本地测试的工作, 同时介绍了其他几种本地端口转发的用法.

在里, 介绍了Probe(探针)的知识, 介绍了livenessProbe 和readinessProbe的用法,同时介绍了Pod 容器中的几个状态变化, 以及2个容器生命周期回调接口.

在里, 介绍了环境变量的用法, 使用环境变量可以把Pod 定义的信息传递给运行其中的镜像.

在里, 我们介绍了Volume, 卷的用法, 主要展示了emptyDir卷的使用, emptyDir卷的生命周期是和Pod 生命周期同步的.

在本篇中, 我们将学习Persistent Volumes, 也就是持久卷, 这个是卷知识的继续, 继续来学习吧!

永久卷(Persistent Volumes,PV)是群集范围内的资源,您可以使用它来存储数据,使其在Pod的生命周期之外, 仍可以持续存在, 这一点就跟上一篇讲的emptyDir卷不一样, emptyDir卷只能在Pod 生命周期里面存活 。 PV不是由工作节点上的本地连接存储支持的,而是由网络存储系统(例如EBS或NFS)或分布式文件系统(例如Ceph)支持的。

从镜像开始,创建PV卷

首先来创建一下这个PV卷.

Github地址:

https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/pv/pv.yaml

文件内容:

---apiVersion: v1kind: PersistentVolumemetadata: annotations: pv.kubernetes.io/bound-by-controller: "yes" finalizers: - kubernetes.io/pv-protection labels: volume: pv0001 name: pv0001 resourceVersion: "227035" selfLink: /api/v1/persistentvolumes/pv0001spec: accessModes: - ReadWriteOnce capacity: storage: 5Gi claimRef: apiVersion: v1 kind: PersistentVolumeClaim name: myclaim namespace: default resourceVersion: "227033" hostPath: path: /mnt/pv-data/pv0001 type: "" persistentVolumeReclaimPolicy: Recycle volumeMode: Filesystemstatus: phase: Bound

来创建这个PV:

$ kubectl apply -f https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/pv/pv.yamlpersistentvolume/pv0001 created$ kubectl get pv NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM             STORAGECLASS   REASON   AGEpv0001   5Gi        RWO            Recycle          Available   default/myclaim                           104s

创建之后, 通过kubectl get pv 可以查看到创建的Persistent Volume的信息, 注意pv 就是PersistentVolume的缩写.

创建PVC--Persistent Volume Claim

为了使用PV,首先需要使用永久卷声明(PersistentVolumeClaim,PVC)对其进行声明。 PVC向Kubernetes请求具有所需规格(大小,速度等)的PV,然后将其绑定到Pod,在Pod中可以将其作为卷安装。

因此,让我们创建一个这样的PVC,使用默认存储类向Kubernetes要求1 GB的存储空间:

Github地址:

https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/pv/pvc.yaml

文件内容:

apiVersion: v1kind: PersistentVolumeClaimmetadata: name: myclaimspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi

创建PVC:

$ kubectl apply -f https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/pv/pvc.yamlpersistentvolumeclaim/myclaim created[vagrant@control-plane ~]$ kubectl get pvc NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGEmyclaim   Bound    pv0001   5Gi        RWO            standard       3m5s[vagrant@control-plane ~]$ kubectl describe pvc myclaimName:          myclaimNamespace:     defaultStorageClass:  standardStatus:        BoundVolume:        pv0001Labels:        <none>Annotations:   pv.kubernetes.io/bind-completed: yes               pv.kubernetes.io/bound-by-controller: yesFinalizers:    [kubernetes.io/pvc-protection]Capacity:      5GiAccess Modes:  RWOVolumeMode:    FilesystemUsed By:       <none>Events:        <none>

可以看到, PVC 已经创建好了, 注意Persistent Volume Claim的缩写是PVC, 大小是5G.

创建Pod, 使用持久卷

下面我们创建Deployment, 然后指定Pod中使用卷 , 来使用这个PVC.

Github地址:

https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/pv/deploy.yaml

文件内容:

apiVersion: apps/v1kind: Deploymentmetadata: name: pv-deployspec: replicas: 1 selector: matchLabels: app: mypv template: metadata: labels: app: mypv spec: containers: - name: shell image: centos:7 command: - "bin/bash" - "-c" - "sleep 10000" volumeMounts: - name: mypd mountPath: "/tmp/persistent" volumes: - name: mypd persistentVolumeClaim: claimName: myclaim

创建:

$ kubectl apply -f https://raw.githubusercontent.com/hintcnuie/kbe/main/specs/pv/deploy.yamldeployment.apps/pv-deploy created[vagrant@control-plane ~]$ kubectl get deployNAME                   READY   UP-TO-DATE   AVAILABLE   AGEhello-springwebflux    1/1     1            1           13dhello-world            2/2     2            2           15dhttp-echo-deployment   1/1     1            1           14dpv-deploy              1/1     1            1           105s[vagrant@control-plane ~]$ kubectl describe pod pv-deployName:         pv-deploy-7d5f79cb7f-x4v8mNamespace:    defaultPriority:     0Node:         localhost.localdomain/10.0.2.15Start Time:   Tue, 16 Mar 2021 04:25:01 +0000Labels:       app=mypv              pod-template-hash=7d5f79cb7fAnnotations:  <none>Status:       RunningIP:           172.17.0.10IPs:  IP:           172.17.0.10Controlled By:  ReplicaSet/pv-deploy-7d5f79cb7fContainers:  shell:    Container ID:  docker://3bd5a2b560ee18ab6f54ae91764d030d9e3d554072f63ba658dde3a7c6fe15e9    Image:         centos:7    Image ID:      docker-pullable://centos@sha256:0f4ec88e21daf75124b8a9e5ca03c37a5e937e0e108a255d890492430789b60e    Port:          <none>    Host Port:     <none>    Command:      bin/bash      -c      sleep 10000    State:          Running      Started:      Tue, 16 Mar 2021 04:25:01 +0000    Ready:          True    Restart Count:  0    Environment:    <none>    Mounts:      /tmp/persistent from mypd (rw)      /var/run/secrets/kubernetes.io/serviceaccount from default-token-q77th (ro)Conditions:  Type              Status  Initialized       True   Ready             True   ContainersReady   True   PodScheduled      True Volumes:  mypd:    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)    ClaimName:  myclaim    ReadOnly:   false  default-token-q77th:    Type:        Secret (a volume populated by a Secret)    SecretName:  default-token-q77th    Optional:    falseQoS Class:       BestEffortNode-Selectors:  <none>Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300sEvents:  Type    Reason     Age   From               Message  ----    ------     ----  ----               -------  Normal  Scheduled  115s  default-scheduler  Successfully assigned default/pv-deploy-7d5f79cb7f-x4v8m to localhost.localdomain  Normal  Pulled     115s  kubelet            Container image "centos:7" already present on machine  Normal  Created    115s  kubelet            Created container shell  Normal  Started    115s  kubelet            Started container shell

在上面的命令中,我们通过Deployment 创建了一个Pod, Pod 中在指定了myclaim的PVC声明, 然后在容器中挂载到/tmp/persistent目录:

Mounts:      /tmp/persistent from mypd (rw)

Pod 已经和PVC, PV 关联起来了, 现在Pod, PV和PVC的关系是这样的:

Pod-PVC-Pod关系

PV 卷在创建之时, 同时声明了一个对PVC的引用, 也就是一个声明; 然后再创建一个PVC; Pod 在使用持久卷的时候, 并不是直接指定一个卷, 而是指向了这个卷的声明PVC, Pod和PV 之间通过PVC 做了一个隔离.

回想一下Linux中有关卷的概念:

1:物理卷(Physical Volume):通常一个分区或者一个硬盘就可以建立一个物理卷,物理卷的最小单位是PE,一般默认是4MB。

2:卷组(Volume Group):将多个物理卷组合到一起,成为一个卷组。

3:虚拟卷(Logical Volume)):其实就是在卷组的基础上再次划分,最小单位是LE,与PE一样,并且一一对应。逻辑卷跟物理卷没有本质区别,只是站在不同的层次来看罢了。

在这里, PVC 就类似Linux中的这个逻辑卷的概念, 可以把卷的使用和卷的创建分离.

向持久卷中写入数据

下面开始尝试着写一下数据了.

先看看Pod 的名字, 注意在这里po 就是资源Pod 的缩写:

$ kubectl get poNAME                                    READY   STATUS    RESTARTS   AGEpv-deploy-7d5f79cb7f-x4v8m              1/1     Running   0          38m

进入Pod, 写数据到/tmp/persistent目录:

$ kubectl exec pv-deploy-7d5f79cb7f-x4v8m -it  -- bash [root@pv-deploy-7d5f79cb7f-x4v8m /]# ls /tmp/persistent/[root@pv-deploy-7d5f79cb7f-x4v8m /]# echo 'test persistent volume data' > /tmp/persistent/data[root@pv-deploy-7d5f79cb7f-x4v8m /]# cat /tmp/persistent/datatest persistent volume data[root@pv-deploy-7d5f79cb7f-x4v8m /]# exit

可以看到, 我们在/tmp/persistent目录写入了一个data 文件.

删除Pod, PV(持久卷)的数据依然存在

前面我们说过, 持久卷和Pod 的关系是, 持久卷是超乎Pod 的生命周期的, 也就是说即使Pod 销毁了, 写在PV(持久卷)里面的数据依然存在,我们试一下.

先来删除刚才的Pod(名称为:pv-deploy-7d5f79cb7f-x4v8m):

$ kubectl delete pod pv-deploy-7d5f79cb7f-x4v8mpod "pv-deploy-7d5f79cb7f-x4v8m" deleted$ kubectl get poNAME                                    READY   STATUS    RESTARTS   AGEpv-deploy-7d5f79cb7f-gj6n6              1/1     Running   0          7m16s

可以看到, 在删除了之前的Pod 之后, Deployment 又创建了一个新的Pod, 新的名称是pv-deploy-7d5f79cb7f-gj6n6 .

到这个新的Pod 里面看看PV的数据还在不在:

$ kubectl exec pv-deploy-7d5f79cb7f-gj6n6 -it -- bash[root@pv-deploy-7d5f79cb7f-gj6n6 /]# ls /tmp/persistent/data[root@pv-deploy-7d5f79cb7f-gj6n6 /]# cat /tmp/persistent/data test persistent volume data[root@pv-deploy-7d5f79cb7f-gj6n6 /]# exitexit

可以看到, 文件/tmp/persistent/data仍然还在, 这就说明PV是独立于Pod 而存在的.

请注意,默认行为是,即使删除了Deployment,PVC(和PV)仍然存在。 这就是持久卷的存储保护功能, 也就是持久化的含义所在:避免数据丢失。

$ kubectl delete deploy/pv-deploydeployment.apps "pv-deploy" deleted[vagrant@control-plane ~]$ kubectl get pvcNAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGEmyclaim   Bound    pv0001   5Gi        RWO            standard       79m[vagrant@control-plane ~]$ kubectl get pvNAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGEpv0001   5Gi        RWO            Recycle          Bound    default/myclaim                           89m[vagrant@control-plane ~]$ 

一旦确定不再需要数据,就可以继续删除PVC,并最终销毁PV:

$ kubectl delete pvc/myclaimpersistentvolumeclaim "myclaim" deleted[vagrant@control-plane ~]$ kubectl get pvNAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM             STORAGECLASS   REASON   AGEpv0001   5Gi        RWO            Recycle          Released   default/myclaim                           92m[vagrant@control-plane ~]$ kubectl delete pv/pv0001persistentvolume "pv0001" deleted

可以看到删除PVC并不会影响PV, 只有真正的删除PV, 才可以销毁数据.

好了, 操作部分就到这里吧, 下面来学习关于持久卷的理论知识.

持久卷(PV)介绍

存储的管理是一个与计算实例的管理完全不同的问题。PersistentVolume 子系统为用户 和管理员提供了一组 API,将存储如何供应的细节从其如何被使用中抽象出来。 为了实现这点,Kubernetes引入了两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim。

持久卷(PersistentVolume,PV)是集群中的一块存储,可以由管理员创建和管理,或者使用存储类(Storage Class)来动态供应。 持久卷是集群资源,就像节点也是集群资源一样。

PV 持久卷和普通的 Volume 一样,也是使用 Volume(卷)插件来实现的,只是它们拥有独立于任何引用PV的Pod 的生命周期。 此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。

持久卷声明(PersistentVolumeClaim,PVC)表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载,参见访问模式)。

尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源,常见的情况是针对不同的 问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷。 集群管理员需要能够提供不同性质的 PersistentVolume,并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。 为了满足这类需求,就有了 存储类(StorageClass) 资源。

PV和PVC的生命周期

PV 卷是集群中的资源。PVC 申领是对这些资源的请求,也被用来执行对资源的申领检查。 PV 卷和 PVC 申领之间的互动遵循如下生命周期:

创建PV

PV 卷的创建有两种方式:静态创建或动态创建。

静态创建

集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息,并且对集群 用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。

动态创建

如果管理员所创建的所有静态 PV 卷都无法与用户的 PersistentVolumeClaim 匹配, 集群可以尝试为该 PVC 申领动态供应一个存储卷。 这一供应操作是基于 StorageClass 来实现的:PVC 申领必须请求某个 存储类,同时集群管理员必须 已经创建并配置了该类,这样动态供应卷的动作才会发生。 如果 PVC 申领指定存储类为 "",则相当于为自身禁止使用动态供应的卷。

为了基于存储类完成动态的存储供应,集群管理员需要在 API 服务器上启用 DefaultStorageClass 准入控制器。

绑定

用户创建一个带有特定存储容量和特定访问模式需求的 Persistent Volume Claim 对象; 在动态创建场景下,这个 PVC 对象可能已经创建完毕。 主控节点中的控制回路监测新的 PVC 对象,寻找与之匹配的 PV 卷(如果可能的话), 并将二者绑定到一起。 如果为了新的 PVC 申领动态供应了 PV 卷,则控制回路总是将该 PV 卷绑定到这一 PVC 申领。 否则,用户总是能够获得他们所请求的资源,只是所获得的 PV 卷可能会超出所请求的配置。 一旦绑定关系建立,则 PersistentVolumeClaim 绑定就是排他性的,无论该 PVC 申领是 如何与 PV 卷建立的绑定关系。 PVC 申领与 PV 卷之间的绑定是一种一对一的映射,实现上使用 ClaimRef 来记述 PV 卷 与 PVC 申领间的双向绑定关系。

如果找不到匹配的 PV 卷,PVC 申领会无限期地处于未绑定状态。 当与之匹配的 PV 卷可用时,PVC 申领会被绑定。 例如,即使某集群上供应了很多 50 Gi 大小的 PV 卷,也无法与请求 100 Gi 大小的存储的 PVC 匹配。当新的 100 Gi PV 卷被加入到集群时,该 PVC 才有可能被绑定。

使用

Pod 将 PVC 当做存储卷来使用。集群会检视 PVC 对象,找到所绑定的卷,并 为 Pod 挂载该卷。对于支持多种访问模式的卷,用户要在 Pod 中以卷的形式使用PVC时指定期望的访问模式。

一旦用户有了PVC对象并且该PVC已经被绑定,则所绑定的 PV 卷在用户仍然需要它期间 一直属于该用户。用户通过在 Pod 的 volumes 块中包含 persistentVolumeClaim 节区来调度 Pod,访问所申领的 PV 卷。

保护使用中的存储对象

保护使用中的存储对象(Storage Object in Use Protection)这一功能特性的目的 是确保仍被 Pod 使用的 PersistentVolumeClaim(PVC)对象及其所绑定的 PersistentVolume(PV)对象在系统中不会被删除,因为这样做可能会引起数据丢失。

说明: 当使用某 PVC 的 Pod 对象仍然存在时,认为该 PVC 仍被此 Pod 使用。

如果用户删除被某 Pod 使用的 PVC 对象,该 PVC 申领不会被立即移除。 PVC 对象的移除会被推迟,直至其不再被任何 Pod 使用。 此外,如果管理员删除已绑定到某 PVC 申领的 PV 卷,该 PV 卷也不会被立即移除。 PV 对象的移除也要推迟到该 PV 不再绑定到 PVC。

回收

当用户不再使用其存储卷时,他们可以从 API 中将 PVC 对象删除,从而允许 该资源被回收再利用。PersistentVolume 对象的回收策略告诉集群,当其被 从申领中释放时如何处理该数据卷。 目前,数据卷可以被 Retained(保留)、Recycled(回收)或 Deleted(删除)。

保留(Retain)

回收策略 Retain 使得用户可以手动回收资源。当 PersistentVolumeClaim 对象 被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然存在这前一申领人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:

删除 PersistentVolume 对象。与之相关的、位于外部基础设施中的存储资产 (例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在。根据情况,手动清除所关联的存储资产上的数据。手动删除所关联的存储资产;如果你希望重用该存储资产,可以基于存储资产的 定义创建新的 PersistentVolume 卷对象。删除(Delete)

对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 动态供应的卷会继承其 StorageClass 中设置的回收策略,该策略默认 为 Delete。 管理员需要根据用户的期望来配置 StorageClass;否则 PV 卷被创建之后必须要被 编辑或者修补。参阅更改 PV 卷的回收策略.

这一篇就写到这里吧.

回顾

这一篇中介绍了静态持久卷的使用, 首先创建了持久卷PV, 创建了持久卷的声明PVC, 然后是在Pod 指定对PVC的使用; 最后演示了如何在Pod向持久卷PV写入文件, 在理论部分, 介绍了PVC和PV的生命周期.

书中自有黄金屋

书中自有颜如玉

0 阅读:0

查理谈科技

简介:感谢大家的关注