kubernetes in action 第二版----第八章翻译
8
在PersistentVolumes中持久化数据
本章涵盖
• 使用PersistentVolume对象来表示持久存储
- 使用PersistentVolumeClaim对象声明持久卷
- 动态分配持久卷
- 使用本地几点持久存储
前一章教你如何将网络存储卷挂载到pod中。然而,这种体验并不理想,因为你需要了解集群所处的环境,才能知道要向pod添加什么类型的卷。比如,如果你的集群运行在谷歌的基础设施上,那么你必须在你的pod清单中定义一个gcePersistentDisk卷。你不能在同一清单上运行应用程序在Amazon上运行因为在他们的环境中不支持GCE持久磁盘。要使清单与Amazon兼容,必须在部署pod之前修改清单中的卷定义。
你可能还记得在第一章中提到Kubernetes应该标准化云提供商之间的应用程序部署。在pod清单中使用专有存储卷类型违背了这个前提。
幸运的是,有一种更好的方法可以向你的pod添加持久存储。一种不需要在pod内使用特定的存储技术。本章将解释这种改进的方法。
8.1 将pod与底层存储技术分离
理想情况下,在Kubernetes上部署应用程序的开发人员不需要知道集群提供什么存储技术,就像他们不需要知道用于运行pod的物理服务器的特性一样。基础设施的细节应该由运行集群的人员来处理。
因此,当你将应用程序部署到Kubernetes时,通常不会像在前一章中那样直接引用pod清单中的外部存储。相反,你可以使用一种间接方法,下一节将对此进行解释。
前一章中的一个示例展示了如何在pod中使用NFS实现文件共享。pod清单中的卷定义包含NFS服务器的IP地址和该服务器导出的文件路径。这将pod定义绑定到特定的集群,并阻止在其他地方使用它。
如下图所示,如果要将这个pod部署到不同的集群,通常至少需要更改NFS服务器IP。这意味着pod定义不能跨集群移植。每次在新的Kubernetes集群中部署它时,都必须修改它。

图8.1 带有特定于基础设施的卷信息的pod清单不能移植到其他集群
8.1.1 引入持久卷和声明
为了使pod清单可以在不同的集群环境移植,有关实际存储卷的特定于环境的信息被移动到PersistentVolume对象中,如下图所示.一个PersistentVolumeClaim对象将pod连接到这个PersistentVolume对象。

图8.2 使用持久卷和持久卷声明将网络存储附加到pod上
接下来介绍这两个对象。
介绍持久卷
顾名思义,一个PersistentVolume对象表示用于持久化应用程序数据的存储卷。如上图所示,PersistentVolume对象存储有关底层存储的信息,并将这些信息与pod解耦。
当这些特定于基础设施的信息不在pod清单中时,可以使用相同的清单在不同的集群中部署pod。当然,每个集群现在都必须包含一个具有此信息的PersistentVolume对象。我同意这种方法似乎没有解决任何问题,因为我们只是将信息移动到不同的对象中,但是稍后你将看到这种新方法实现了以前不可能实现的事情。
介绍持久容量声明
pod不直接引用PersistentVolume对象。相反,它指向一个PersistentVolumeClaim对象,然后该对象指向PersistentVolume。
顾名思义,PersistentVolumeClaim对象表示用户对持久卷的声明。因为它的生命周期与pod无关,所以它允许将持久卷的所有权与pod解耦。在用户可以在他们的pod中使用持久卷之前,他们必须首先通过创建Persisten
-VolumeClaim对象来声明卷。在声明卷之后,用户就拥有了对卷的独占权,可以在自己的pod中使用卷。可以随时删除pod,并且不会失去持久卷的所有权。当不再需要卷时,用户通过删除PersistentVolumeClaim对象来释放它。
在pod中使用持久的卷声明
要在pod中使用持久卷,只需在其清单中引用卷绑定到持久卷声明的名称。
例如,如果你创建了一个持久卷声明,该卷绑定一个表示NFS共享文件的持久卷,你可以通过,那么你可以通过添加一个指向PersistentVolumeClaim对象接入将你的pod接入到NFS共享文件。pod清单中的卷定义只需要包含持久卷声明的名称,而不需要包含特定于基础设施的信息,例如NFS服务器的IP地址。
如下图所示,当这个pod被调度到工作节点时,Kubernetes找到与pod中引用的声明绑定的持久卷,并使用PersistentVolume对象中的信息将网络存储卷挂载到pod的容器中。

图8.3将持久卷安装到pod的容器中
在多个pod中使用声明
如果多个pod引用相同的持久卷声明,那么它们可以使用相同的存储卷,从而传递到相同的持久卷,如下图所示。

图8.4在多个pod中使用相同的持久卷声明
这些pod是否必须全部运行在相同的集群节点上,或者是否可以从不同的节点访问底层存储,这取决于提供该存储的技术。如果底层存储技术支持将存储并发地附加到多个节点,则可以由不同节点上的pod使用。如果没有,则必须首先将所有pod调度到附加存储卷的节点。
8.1.2 了解使用持久卷和声明的好处
一个必须使用两个额外对象才能让pod使用存储卷的系统比前一章中解释的简单方法要复杂得多,在前一章中,pod只是直接引用存储卷。为什么这种新方法更好?使用持久卷和声明的最大优点是,特定于基础设施的细节现在与pod所表示的应用程序解耦了。集群管理员比任何人都更了解数据中心,他们可以创建包含所有与基础设施相关的底层细节的PersistentVolume对象,而软件开发人员只专注于通过Pod和PersistentVolumeClaim对象描述应用程序及其需求。下图显示了这两个用户角色及其创建的对象是如何组合在一起的。

图8.5 持久化卷由集群管理员提供,并由pod通过持久化卷声明使用。
与开发人员向他们的pod添加特定于技术的卷不同,集群管理员设置底层存储,然后通过Kubernetes API创建一个PersistentVolume对象将其注入到Kubernetes。
当集群用户需要在其中一个pod中持久化存储时,他们首先创建一个PersistentVolumeClaim对象,在该对象中,他们可以通过名称引用特定的持久化卷,或者指定应用程序所需的最小卷大小和访问模式,然后让Kubernetes找到满足这些需求的持久化卷。在这两种情况下,持久性卷都被绑定到请求,并被授予独占访问权。然后可以在一个或多个pod内的卷定义中引用该权利要求。当pod运行时,在PersistentVolume对象中配置的存储卷被附加到工作节点并挂载到pod的容器中。重要的是要理解,应用程序开发人员可以为Pod和PersistentVolumeClaim对象创建清单,而无需了解应用程序将在其上运行的基础设施。类似地,集群管理员可以在不了解将要使用它们的应用程序的情况下,提前提供一组不同大小的存储卷。此外,正如本章后面讨论的那样,通过使用持久卷的动态供应,管理员根本不需要预先供应卷。如果集群中安装了自动化卷提供程序,则用户创建的每个PersistentVolumeClaim对象都会按需创建物理存储卷和PersistentVolume对象。
8.2 创建持久化卷和声明
现在你已经对持久卷和声明以及它们与pod的关系有了基本的了解,让我们回顾一下前一章中的测试pod。你可能还记得这个pod包含一个gcePersistentDisk卷。你将修改该pod的清单,使其使用GCE Persistent
磁盘通过一个PersistentVolume对象。
如前所述,通常有两种不同类型的Kubernetes用户参与持久化卷的配置和使用。在下面的练习中,你将首先扮演集群管理员的角色,并创建一些持久卷。其中之一将指向现有的GCE持续的磁盘。然后,你将扮演普通用户的角色,创建一个持久的卷声明,以获得该卷的所有权,并在quiz pod中使用它。
8.2.1创建PersistentVolume对象
假设你是集群管理员。开发团队要求你为他们的应用程序提供两个持久卷。一个将用于存储MongoDB在quiz pod中使用的数据文件,另一个将用于其他用途。如果你使用谷歌Kubernetes Engine来运行这些示例,你将创建指向GCE persistent Disks (GCE PD)的持久卷。对于quiz的数据文件,你可以使用在前一章中提供的GCE PD。
如果使用不同的云提供商,请查阅该提供商的文档,了解如何在其环境中创建物理卷。如果你使用Minikube、kind或任何其他类型的集群,你不需要创建卷,因为你将使用持久卷,该卷引用工作节点上的本地目录。
创建一个以GCE persistent Disk作为底层存储的持久卷
如果没有上一章中设置的quiz-data GCE Persistent Disk,请使用gcloud compute disks create quiz-data命令重新创建。创建磁盘之后,必须为PersistentVolume对象创建一个清单文件,如下面的清单所示。你可以在第08章/pv.quiz-data.gcepd.yaml中找到该文件。

清单8.1引用GCE持久磁盘的持久卷清单
PersistentVolume对象中的spec段指定卷的存储容量、它支持的访问模式、它使用的底层存储技术,以及使用底层存储所需的所有信息。对于GCE Persistent Disks,这包括Google Compute Engine中PD资源的名称、文件系统类型、卷中分区的名称等等。现在创建另一个名为other-data的GCE持久性磁盘和一个附带的PersistentVolume对象。根据清单8.1中的清单创建一个新文件,并进行必要的更改。你将在文件pv.other-data.gcepd.yaml中找到结果清单。
创建其他存储技术支持的持久卷
如果你的Kubernetes集群运行在不同的云提供商上,你应该能够很容易地改变持久卷清单,使用GCE持久磁盘以外的东西,就像你在前一章中直接引用pod清单中的卷时所做的那样。如果你使用Minikube或kind工具来配置你的集群,那么你可以创建一个使用工作节点上的本地目录持久的卷,而不是网络存储PersistentVolume清单。下一个清单显示了quiz_data持久卷的清单(pv.quiz_data.hostpath.yaml)。other-data持久卷的清单在pv.otherdata.hostpath.yaml中。
清单8.2使用本地目录的持久卷

你将注意到,在这个清单和前面的清单中显示的两个持久卷的不同之处仅在于指定使用哪种底层存储方法。hostpath支持的持久卷将数据存储在/var/ test-data目录下的工作节点文件系统。
注意 要列出可以在持久卷中使用的所有其他受支持的技术,请运行kubectl explain pv.spec。然后,你可以进一步深入查看每种技术的单独配置选项。例如,对于GCE Persistent Disks,执行kubectl explain pv.spec.gcePersistentDisk命令。
我不会详细介绍如何为每种可用的存储技术配置持久卷来烦你,但我确实需要解释必须在每个持久卷中设置的capacity和accessModes字段。
指定卷容量
卷的容量表示底层卷的大小。每个持久化卷必须指定其容量,以便Kubernetes可以在绑定它们之前确定特定的持久化卷是否能够满足持久化卷声明中指定的要求。
指定卷访问模式
每个持久卷必须指定它支持的accessModes列表。根据底层技术的不同,一个持久化卷可以由多个工作节点以读/写或只读模式同时挂载,也可以不挂载。Kubernetes检查持久卷的访问模式,以确定它是否满足声明的要求。
注意 访问模式决定一次可以挂载卷的节点数量,而不是pod的数量。即使卷只能附加到单个节点,如果它们都在单个节点上运行,它也可以挂载在许多pod中。
有三种访问方式。在下表中解释了它们以及kubectl显示的缩略形式。
表8.1 持久卷的访问模式
|
访问模式 |
缩写 |
描述 |
|
ReadWriteOnce |
RWO |
卷可以通过单个工作节点以读写方式挂载。当卷被挂载到节点时,其他节点无法挂载卷。 |
|
ReadOnlyMany |
ROX |
卷可以以只读方式同时挂载在多个工作节点上。 |
|
ReadWriteMany |
RWX |
卷可以同时以读写方式挂载到多个工作节点上。 |
注意 ReadOnlyOnce选项不存在。如果在不需要写入的pod中使用ReadWriteOnce卷,则可以以只读模式挂载该卷。
使用持久卷作为块设备
典型的应用程序使用带有格式化文件系统的持久卷。但是,也可以配置持久卷,以便应用程序可以直接访问底层块设备,而无需使用文件系统。这是在PersistentVolume对象上使用spec.volumeMode字段配置的。下表解释了该字段支持的值。
表8.2 配置持久卷的卷模式
|
卷模式 |
说明 |
|
Filesystem |
当持久卷挂载到容器中时,它被挂载到容器的文件树目录中。如果底层存储是一个未格式化的块设备,Kubernetes使用卷定义中指定的文件系统格式化设备(例如,在字段gcePersistentDisk.fsType),然后再挂载到容器中。这是默认的卷模式。 |
|
Block |
当pod以这种模式使用持久卷时,该卷可以作为原始块设备(没有文件系统)提供给容器中的应用程序。这允许应用程序在没有任何文件系统开销的情况下读取和写入数据。这种模式通常用于特殊类型的应用程序,例如数据库系统。 |
test-data和other-data持久卷的清单没有指定volumeMode字段,这意味着使用默认模式,即Filesystem.
创建和检查持久化卷
现在,你可以通过使用现在众所周知的kubectl apply命令将清单发布到Kubernetes API来创建PersistentVolume对象。然后使用kubectl get命令列出集群中的持久卷:

建议 使用pv作为PersistentVolume的简写。
STATUS列表示两个持久化卷都是可用的。这是预期的,因为它们还没有绑定到任何持久的卷声明,如空的claim列所示。同时显示卷容量和访问模式,它们以缩略形式显示,如表8.1所示。
kubectl get pv命令没有显示持久卷使用的底层存储技术,因为它不太重要。重要的是,每个持久卷代表集群中一定数量的可用存储空间,应用程序可以使用指定的模式访问这些存储空间。在每个持久卷中配置的技术和其他参数是部署应用程序的用户通常不感兴趣的实现细节。如果有人需要查看这些细节,他们可以使用kubectl describe或使用下面的命令打印PersistentVolume对象的完整定义:
![]()
8.2.2 声明持久卷
你的集群现在包含两个持久卷。在你可以在quiz pod中的quiz-data卷之前,你需要声明它。本节将解释如何做到这一点。
创建PersistentVolumeClaim对象
要声明一个持久卷,需要创建一个PersistentVolumeClaim对象,在该对象中指定持久卷必须满足的要求。这些包括卷的最小容量和所需的访问模式,这通常由将要使用卷的应用程序决定。出于这个原因,持久卷声明应该由应用程序的作者创建,而不是由集群管理员创建,所以现在摘掉管理员的帽子,戴上开发人员的帽子。
建议 作为应用程序开发人员,永远不应该在应用程序清单中包含持久的卷定义。应该包含持久卷声明,因为它们指定了应用程序的存储需求。
要创建PersistentVolumeClaim对象,请创建一个清单文件,其内容如下清单所示。你还可以在pvc.quizdata.static.yaml中找到该文件。
列表8.3 PersistentVolumeClaim对象清单

清单中定义的持久卷声明要求卷的大小至少为1GiB,并且可以以读/写模式挂载在单个节点上。字段storageClassName用于动态配置持久卷,你将在本章后面了解。如果你希望Kubernetes将预先配置的持久卷绑定到此声明,而不是配置新卷,则必须将该字段设置为空字符串。
在本练习中,你希望声明quiz-data持久卷,因此必须使用volumeName字段指出这一点。在你的集群中,存在两个匹配的持久卷。如果你不指定这个字段,Kubernetes可以将你的声明绑定到other-data持久卷。
如果集群管理员创建了一堆具有不可描述名称的持久卷,并且你不关心得到的是哪个卷那么可以跳过volumeName字段。在这种情况下,Kubernetes将随机选择一个容量和访问模式与声明匹配的持久卷。
注意 与持久卷一样,声明也可以指定所需的volumeMode。正如你在8.2.1节中所了解的,它可以是Filesystem或Block。如果未指定,则默认为Filesystem。当Kubernetes检查一个卷是否满足索赔时,还会考虑索赔和卷的volumeMode。
要创建PersistentVolumeClaim对象,使用kubectl apply应用它的清单文件。创建对象之后,Kubernetes很快将卷绑定到claim。如果claim请求按名称请求特定的持久卷,如果它也符合其他需求,则该卷将被绑定。你的claim要求1GiB的磁盘空间和ReadWriteOnce访问模式。你之前创建的quiz-data持久卷满足这两个需求,这允许将其绑定到claim对象。
列出持久卷声明
如果一切顺利,你的claim对象现在应该绑定到quiz_data持久卷。使用kubectl get命令查看情况是否如此:

建议 使用pvc作为持久性persistentvolumeclaim的简写。
kubectl命令的输出显示claim现在已绑定到持久卷。它还显示了该卷的容量和访问模式。尽管claim只请求1GiB,但它有10GiB的可用存储空间,因为这是卷的容量。类似地,虽然claim只请求读写一次访问模式,但它被绑定到一个同时支持读写一次(RWO)和读写一次(RWO)的卷上ReadOnlyMany (ROX)访问模式。
如果你把你的集群管理帽子重新戴上一会儿,列出你集群中的持久卷,你会看到它现在也显示为绑定:

任何集群管理员都可以看到每个持久卷绑定到哪个claim。在你的示例中,卷被绑定到claim default/quiz-data。
注意 你可能想知道claim名称中的default一词是什么意思。这是persistentvolumecclaim对象所在的名称空间。名称空间允许将对象组织成不相交的集合。你将在第10章中了解它们。
在新的pod实例中重用声明
当你通过持久卷claim删除使用持久卷的pod时,底层存储卷将与工作节点分离(假设它是该节点上唯一使用它的pod)。持久卷对象仍然绑定到claim。如果创建另一个引用此claim的pod,则这个新pod可以访问卷及其文件。
试着删除quiz pod并重新创建它。如果在这个新的pod实例中运行db.questions.find()查询,你将看到它返回与前一个相同的数据。如果持久卷使用网络附加存储(如GCE persistent Disks),那么pod将看到相同的数据,而不管它被调度到哪个节点。如果你使用的是一个预置的集群,并且不得不使用基于hostpath的持久卷,那么情况就不是这样了。要访问相同的数据,必须确保新pod实例被调度到原实例被调度到的节点,就像数据存储在节点的文件系统。
释放持久卷
当你不再计划部署将使用此claim的pod时,可以删除它。这将释放持久卷。你可能想知道是否可以重新创建claim并访问相同的卷和数据。让我们来看看。删除pod和claim,如下所示,看看会发生什么:

现在检查持久化卷的状态:

STATUS列显示卷为Released而不是Available,这与最初的情况一样。CLAIM列仍然显示之前绑定的quiz_data claim,即使该claim不再存在。你马上就会明白为什么。
绑定到已释放的持久卷
如果你再次创建claim会发生什么?持久卷是否还与claim绑定,以便可以在pod中重用?运行以下命令,看看情况是否如此。

claim未绑定到卷,其状态为Pending。当你之前创建claim时,它立即绑定到持久卷,那么为什么现在不这样做呢?这背后的原因是卷已经被使用,并且可能包含应该在另一个用户声明卷之前擦除的数据。这也是卷的状态为Released而不是Available的原因,为什么claim名称仍然显示在持久卷上,因为这有助于集群管理员了解数据是否可以安全删除。
使已释放的持久卷可供重用
要使卷再次可用,必须删除并重新创建PersistentVolume对象。但是这会导致存储在卷中的数据丢失吗?
想象一下,如果你不小心删除了pod和claim,并导致Kiada应用程序的服务中断。你需要尽快恢复服务,并确保所有数据完好无损。如果你认为删除PersistentVolume对象将删除数据,这听起来像是你应该做的最后一件事,但实际上是完全安全的。
对于预先配置的持久卷(比如手边的卷),删除对象相当于删除一个数据指针。PersistentVolume对象仅仅指向一个GCE持久化磁盘。它不存储数据。如果删除并重新创建该对象,最终会得到一个指向相同GCE PD的新指针,从而得到相同的数据。你将在下一个练习中确认这种情况。

注意 使持久卷再次可用的另一种方法是编辑PersistentVolume对象并从spec段删除claimRef。
持久化卷显示为“可用”。让我提醒你,你之前为该卷创建了claim。Kubernetes一直在等待一个卷绑定到claim。如你所料,你刚刚创建的卷将在几秒钟内绑定到此claim。再次列出卷确认:

输出显示持久卷再次绑定到请求。如果现在部署quiz pod并使用以下命令再次查询数据库,你将看到底层GCE Persistent Disk中的数据没有丢失:

配置持久卷回收策略
持久卷被释放时发生的事情由卷的回收策略决定。当你使用kubectl get pv命令列出持久卷时,你可能已经注意到quiz-data卷的策略是Retain。此策略使用该字段配置PersistentVolume对象中的.spec.persistentVolumeReclaimPolicy。
表8.3 该字段可以是下表中解释的三个值之一。
|
回收策略 |
描述 |
|
Retain |
当持久卷被释放时(当你删除绑定到它的claim时),Kubernetes保留该卷。集群管理员必须手动回收卷。这是手动创建持久卷的默认策略。 |
|
Delete |
PersistentVolume对象和底层存储在发布时被自动删除。这是动态配置的持久卷的默认策略,下一节将对此进行讨论。 |
|
Recycle |
此选项已弃用,不应使用,因为底层volume插件可能不支持此选项。此策略通常会导致删除卷上的所有文件,并使持久卷再次可用,而无需删除和重新创建它。 |
建议 你可以随时更改已存在的PersistentVolume的回收策略。如果它最初设置为Delete,但你不想在删除claim时丢失数据,那么在删除claim之前将卷的策略更改为Retain。
警告 如果一个持久化卷被释放,并且你随后将其回收策略从保留更改为删除,则PersistentVolume对象和底层存储将立即被删除。但是,如果手动删除对象,底层存储将保持不变。
删除绑定的持久卷
你已经完成了测试pod、quiz-data持久卷声明和quiz-data持久卷的操作,现在将它们删除。在这个过程中你会学到更多的东西。
你是否想知道,如果集群管理员在持久性卷正在使用时(当它绑定到一个声明时)删除它,会发生什么情况?让我们来看看。删除持久卷,如下所示:

这个命令告诉Kubernetes API删除PersistentVolume对象,然后等待Kubernetes控制器完成这个过程。但是,只有通过删除PersistentVolumeClaim对象从claim中释放持久卷,才会发生这种情况。
你可以按Control-C取消等待。然而,这并不能取消删除,因为它已经在进行中。你可以确认如下:

如你所见,持久卷的状态显示它正在被终止。但它仍然受制于持续的数量主张。为了完成卷的删除,需要删除claim。
删除一个pod正在使用的持久卷claim
quiz pod使用的claim现在还在用,不过我们还是把它删掉吧:

与kubectl delete pv命令一样,该命令也不会立即完成。与前面一样,该命令等待claim删除完成。你可以中断该命令的执行,但这不会取消删除,正如你可以通过以下命令看到的那样:

请求的删除被pod阻止。毫不奇怪,删除持久卷或持久卷声明不会立即对使用它的pod产生影响。在pod中运行的应用程序将继续不受影响地运行。Kubernetes绝不会仅仅因为集群管理员想要回它们的磁盘空间而杀死pod。
要允许终止持久卷请求并完成持久卷,请使用kubectl delete po quiz删除quiz pod。
删除底层存储
正如你在上一节中所了解的,如果你使用谷歌Kubernetes Engine来执行这些练习,那么删除持久卷并不会删除底层存储,例如quiz-data GCE persistent Disk如果你使用Minikube或kind,则在工作节点上的/var/ quiz_data目录。
你不再需要数据文件,可以安全地删除它们。如果你使用Minikube或其他类型,你不需要删除数据目录,因为它不需要花费任何费用。但是,GCE持久磁盘可以。删除命令如下:
![]()
你可能还记得,你还创建了另一个名为other-data的GCE Persistent Disk。先别删掉它。你将在下一节的练习中使用它。
8.2.4在多个pod中使用claim和卷
到目前为止,你每次只在一个 Pod 实例中使用了持久卷。你使用了所谓的 ReadWriteOnce(RWO)访问模式的持久卷,因为它附加到单个节点上,并允许读写操作。你可能还记得,还有另外两种模式,分别是 ReadWriteMany(RWX)和 ReadOnlyMany(ROX)。卷的访问模式表示它是否可以同时附加到一个或多个集群节点,以及它是否只能读取或也可以写入。ReadWriteOnce 模式并不意味着只有一个 Pod 可以使用它,而是指单个节点可以附加该卷。由于这一点常常让许多用户感到困惑,因此值得仔细研究一下。
将claim绑定到随机选择的持久卷
这个练习需要使用GKE集群。确保它至少有两个节点。首先,为前面创建的other-data持久卷创建一个持久卷声明。你可以在文件pvc.other-data.yaml中找到清单。如下面的清单所示。
列表 8.5请求ReadWriteOnce和ReadOnlyMany访问的持久卷声明

你会注意到,与上一部分不同,这个持久卷claim没有指定 volumeName。这意味着系统会在所有能够提供至少 1Gi 空间并支持 ReadWriteOnce 和 ReadOnlyMany 访问模式的卷中随机选择一个作为该声明的持久卷。你的集群目前应该仅包含 other-data 持久卷。由于它符合claim中的要求,这就是将与该claim绑定的卷。
在多个pod中使用ReadWriteOnce卷
绑定到声明的持久卷支持ReadWriteOnce和readonly多种访问模式。首先,你将在ReadWriteOnce模式下使用它,因为你将部署写入它的pod。你将从单个pod清单创建数据写入器pod的多个副本。清单显示在下面的清单中。你可以在pod.data.yaml中找到它。
列表 8.6将文件写入共享持久卷的pod

使用以下命令从清单中创建pod:

请注意,这次你没有使用 kubectl apply。因为 Pod 清单使用了generateName 字段,而不是指定 Pod 名称,所以 kubectl apply 将不起作用。你必须使用 kubectl create,这与 kubectl apply 类似,但仅用于创建对象而不是更新对象。重复执行该命令几次,以便创建的 writer Pod 数量是集群节点数量的两到三倍,从而确保每个节点至少调度到两个 Pod。通过使用 -o wide 选项列出 Pod 并检查 NODE 列,确认情况是否如预期。

注意 为了清晰起见,我缩短了节点名称。
如果你所有的 Pod 都位于同一个节点上,请再创建几个 Pod。然后查看这些 Pod 的状态(STATUS)。你会注意到,调度到第一个节点的所有 Pod 都运行正常,而其他节点上的 Pod 都卡在 ContainerCreating 状态。即使等待几分钟也不会有任何改变。这些 Pod 永远不会运行。如果你使用 kubectl describe 命令查看这些 Pod 相关的事件,你会看到它们无法运行,因为持久卷无法附加到 Pod 所在的节点上:

卷无法附加的原因是它已经以读写模式附加到第一个节点。该卷支持 ReadWriteOnce 和 ReadOnlyMany,但不支持 ReadWriteMany。这意味着只有一个节点可以以读写模式附加该卷。当第二个节点尝试执行相同操作时,操作会失败。第一个节点上的所有 Pod 都运行正常。请检查它们的日志以确认它们都能够向卷中写入文件。以下是其中一个 Pod 的日志:

你将发现第一个节点上的所有pod都成功地将它们的文件写入了卷。如果多个pod位于同一节点上,则不需要ReadWriteMany来写入卷。如前所述,读写Once中的单词“Once”指的是节点,而不是pod。
使用读写和只读pod的组合ReadWriteOnce和ReadOnlyMany
你现在将部署一组读取器 pod,与数据写入 pod 一起运行。它们将以只读模式使用持久卷。以下清单显示了这些数据读取器 pod 的 pod 清单。你可以在 pod.data-reader.yaml 中找到它。

使用 kubectl create 命令创建尽可能多的这些 reader pod,以确保每个节点至少运行两个实例。使用 kubectl get po -o wide 命令查看每个节点上有多少个 pod。像以前一样,你会注意到只有调度到第一个节点的 reader pod 正在运行。第二个节点上的 pod 与 writer pod 一样,仍处于 ContainerCreating 状态。以下是仅包含 reader pod 的列表(writer pod 仍然存在,但未显示):

这些pod以只读模式使用卷。claim(和volume)的访问模式都是ReadWriteOnce (RWO)和ReadOnlyMany (ROX),正如你可以通过运行kubectl get pvc看到的:

如果声明支持访问模式 ReadOnlyMany,为什么两个节点不能都挂载卷并运行读者 Pod?这是由写入者 Pod 引起的。第一个节点以读写模式挂载了持久卷。这会阻止其他节点即使以只读模式也无法挂载该卷。想知道如果删除所有写入者 Pod 会发生什么?这是否允许第二个节点以只读模式挂载卷并运行其 Pod?可以一个一个删除写入者 Pod,或者如果你使用的 shell 支持以下语法,可以使用以下命令一次性删除它们:
![]()
现在再次列出 pods。位于第二个节点上的 reader pods 的状态仍然是 ContainerCreating。即使你给它足够的时间,该节点上的 pods 也从未运行过。你能弄明白这是为什么吗?这是因为该卷仍然被第一个节点上的 reader pods 使用。该卷以读写模式挂载,因为这是你先前部署的 writer pods 所请求的模式。Kubernetes 无法在卷被 pods 使用时卸载该卷或更改其挂载模式。在下一节中,你将看到如果在没有先部署 writer 的情况下部署 reader pods,会发生什么。在继续之前,请按如下方式删除所有 pods:

给Kubernetes一些时间从节点上分离卷。然后做下一个练习。
在多个pod中使用ReadOnlyMany卷
通过重复kubectl Create -f pod.data-reader再次创建多个读取器pod。这一次,所有的pod都运行了,即使它们在不同的节点上:

所有这些 Pod 都在 persistentVolumeClaim 卷定义中指定了 readOnly: true 字段。这会导致运行第一个 Pod 的节点以只读模式挂载持久卷。第二个节点也会发生同样的情况。它们都可以挂载该卷,因为它们都是以只读模式挂载,并且该持久卷支持 ReadOnlyMany。ReadOnlyMany 访问模式无需进一步解释。如果没有 Pod 以读写模式挂载该卷,则任意数量的 Pod 都可以使用该卷,即使它们在许多不同的节点上。你能猜到如果现在部署一个写入 Pod 会发生什么吗?它能写入卷吗?创建该 Pod 并检查其状态,你会看到如下情况:

这个 Pod 显示为运行中。你感到惊讶吗?我确实感到惊讶。我原以为它会卡在 ContainerCreating 状态,因为节点无法以读写模式挂载卷,而卷已经以只读模式挂载。这是否意味着节点能够在不卸载卷的情况下,将挂载点从只读升级为读写模式?让我们检查一下 pod 的日志,以确认它能写入卷:

啊,这就是你的答案。Pod 无法写入卷,因为它是只读的。尽管卷没有以 Pod 请求的读写模式挂载,Pod 仍然被启动。这可能是一个 bug。如果你自己尝试时 Pod 无法运行,你就会知道这个 bug 在本书出版后已被修复。你现在可以删除所有 Pod、持久卷声明以及底层的 GCE 持久磁盘,因为你已经不再使用它们了。
在多个pod中使用readwritmany卷
GCE 持久磁盘不支持 ReadWriteMany 访问模式。然而,在其他云环境中可用的网络附加卷确实支持该模式。正如 ReadWriteMany 访问模式的名称所示,支持此模式的卷可以同时附加到多个集群节点,同时仍允许在卷上执行读写操作。由于此模式对可以以读写或只读模式使用持久卷的节点或 Pod 数量没有任何限制,因此不需要进一步解释。如果你仍然想尝试,我建议你像上一个练习那样部署写入器和读取器 Pod,但这次在持久卷和持久卷声明定义中都使用 ReadWriteMany 访问模式。
8.2.5 了解手动配置的持久卷的生命周期
图8.6静态配置的持久卷、声明和使用它们的pod的生命周期

在使用手动配置的持久卷时,底层存储卷的生命周期与 PersistentVolume 对象的生命周期无关。每次创建对象时,其初始状态为 Available。当 PersistentVolumeClaim 对象出现时,如果持久卷满足声明中规定的要求,则该卷会绑定到该声明。在声明绑定到卷之前,其状态为 Pending;一旦绑定后,卷和声明都会显示为 Bound。此时,一个或多个 Pod 可以通过引用该声明使用卷。当每个 Pod 运行时,底层卷会挂载到 Pod 的容器中。在所有 Pod 使用完该声明后,可以删除 PersistentVolumeClaim 对象。
当声明被删除时,卷的回收策略决定了 PersistentVolume 对象和底层卷将发生什么。如果策略为 Delete,则对象和底层卷都会被删除。如果策略为 Retain,PersistentVolume 对象和底层卷将被保留。对象的状态会变为 Released,并且在采取额外步骤使其再次可用之前,该对象不能被绑定。如果手动删除 PersistentVolume 对象,底层卷及其文件将保持完整。可以通过创建引用相同底层卷的新 PersistentVolume 对象再次访问它们。
注意 本节中描述的事件顺序适用于在创建声明之前已存在的静态配置卷。当持久卷按照下一节描述的方式动态配置时,情况则有所不同。请在下一节末寻找类似的图示。
8.3 动态分配持久卷
到目前为止,本章你已经了解到开发者如何声明预先配置的持久卷,作为其 Pod 持久存储数据的地方,而无需处理底层存储技术的细节。然而,集群管理员必须预先配置物理卷,并为每一个卷创建一个 PersistentVolume 对象。然后,每次卷被绑定和释放时,管理员都必须手动删除卷上的数据并重新创建对象。为了保持集群的顺利运行,管理员可能需要预先配置数十乃至数百个持久卷,并不断跟踪可用卷的数量,以确保集群永远不会用完。所有这些手工操作与 Kubernetes 的基本理念相矛盾,因为其理念是实现大规模集群管理的自动化。正如所料,确实存在一种更好的卷管理方式,即持久卷的动态配置。
通过动态配置,集群管理员不再需要提前(并手动)配置持久卷,而是部署一个持久卷供应器来自动化即时配置过程,如下图所示。
图8.7动态配置持久卷

与静态配置相反,声明和卷出现的顺序被反转。当用户创建一个持久卷声明时,动态配置程序会为该特定声明配置底层存储并创建 PersistentVolume 对象。然后这两个对象会被绑定。如果你的 Kubernetes 集群由云提供商管理,它可能已经配置了持久卷配置程序。如果你在本地运行 Kubernetes,则需要部署自定义配置程序,但这超出了本章的范围。使用 Minikube 或 kind 配置的集群通常也会开箱即用地带有一个配置程序。
8.3.1 引入 StorageClass 对象
在上一节中创建的持久卷声明(Persistent Volume Claim)定义了卷的最小大小和所需的访问模式,但其中还包含一个名为 storageClassName 的字段,这一点尚未讨论。一个 Kubernetes 集群可以运行多个持久卷(provisioner),而一个单独的 provisioner 可能支持多种不同类型的存储卷。在创建声明时,你可以使用 storageClassName 字段来指定所需的存储类。
列出存储类
集群中可用的存储类由 StorageClass API 对象表示。你可以使用 kubectl get sc 命令列出它们。在 GKE 集群中,结果如下:

注意 storageclass的简写是sc。
在提供类型的集群中,结果是类似的:

使用Minikube创建的集群也提供了一个同名的存储类:

在许多集群中,如这三个示例所示,通常只配置了一个名为 standard 的存储类。它也被标记为默认存储类,这意味着当持久卷声明(PersistentVolumeClaim)未指定存储类时,将使用此类来提供持久卷。
注意 请记住,省略 storageClassName 字段会使用默认的存储类,而显式设置该字段为 "" 会禁用动态配置,并导致选择现有的持久卷并将其绑定到声明上。
检查默认存储类
让我们通过检查标准存储类的 YAML 定义来了解 StorageClass 对象类型,可以使用 kubectl get 命令。在 GKE 中,你会看到以下定义:

提供类型的集群中的存储类定义没有太大区别。主要区别以粗体突出显示:

在Minikube创建的集群中,标准存储类如下所示:

注意 你会注意到 StorageClass 对象没有 spec 或 status 部分。这是因为该对象只包含静态信息。由于该对象的字段没有组织在这两个部分中,YAML 清单可能更难以阅读。这还因为 YAML 中的字段通常按字母顺序排序,这意味着某些字段可能出现在 apiVersion、kind 或 metadata 字段的上方。请注意不要忽略这些。
如果仔细查看存储类定义的顶部,就会发现它们都包含一个注释,将存储类标记为默认值。
注意 你将在第10章学习什么是对象注释。
正如 GKE 存储类定义中所指定的,当你创建一个引用 GKE 中标准类的持久卷声明(Persistent Volume Claim, PVC)时,会调用 provisioner kubernetes.io/gce-pd 来提供持久卷。在 kind 提供的集群中,provisioner 为 rancher.io/local-path,而在 Minikube 中则为 k8s.io/minikube-hostpath。GKE 的默认存储类还指定了一个提供给 provisioner 的参数。无论使用哪种 provisioner,卷的回收策略(reclaim policy)都会设置为存储类中指定的策略,在前述所有示例中都是 Delete。如你之前所学,这意味着当你通过删除声明释放卷时,卷也会被删除。存储类定义中的最后一个字段是 volumeBindingMode。GKE 和 Minikube 都使用 Immediate 绑定模式,而 kind 使用 WaitForFirstConsumer。你将在本章后面学习它们之间的区别。
StorageClass 对象还支持许多在上述列表中未显示的其他字段。你可以使用 kubectl explain 来查看它们是什么。在以下部分中,你将了解其中的一些。总之,StorageClass 对象表示可以动态配置的存储类。如下面的图所示,每个存储类指定在配置卷时应使用的配置器以及应传递给它的参数。用户决定他们的每个持久卷声明使用哪种存储类。
图8.8存储类、持久卷声明和动态卷提供程序之间的关系

8.3.2 使用默认存储类的动态配置
你之前为 quiz pod 使用了静态配置的持久卷。现在,你将使用动态配置来实现相同的结果,但所需的手动操作要少得多。最重要的是,无论你使用 GKE、Minikube、kind 还是任何其他工具运行集群,只要集群中存在默认存储类,都可以使用相同的 pod 清单。
使用动态配置创建claim
要使用上一节中的存储类动态提供持久卷,你可以创建一个 PersistentVolumeClaim 对象,并将 storageClassName 字段设置为 standard,或者干脆省略该字段。我们使用后者的方法,使清单尽可能简洁。你可以在 pvc.quiz-data-default.yaml 文件中找到该清单。其内容如下所示。
清单8.8使用默认存储类的最小PVC定义

这个 PersistentVolumeClaim 清单仅包含存储大小请求和所需的访问模式,但没有 storageClassName 字段,因此会使用默认存储类。创建声明后,通过 kubectl apply 创建后,可以使用 kubectl get 检查声明以查看它使用的是哪个存储类。如果你使用的是 GKE,你会看到如下内容:

正如预期的那样,并且如 STORAGECLASS 列所示,你刚创建的声明使用的是标准存储类。在 GKE 和 Minikube 中,持久卷会立即创建并绑定到声明上。然而,如果你在 kind 配置的集群中创建相同的声明,情况就不是这样了:

在一个采用 kind 提供的集群中,可能在其他集群中也是如此,你刚刚创建的持久卷声明并不会立即绑定,其状态显示为 Pending。在前面的章节中,你已经了解到,当没有持久卷与声明匹配时,这种情况就会发生,要么是因为不存在这样的卷,要么是因为它不可用于绑定。然而,你现在使用的是动态供给,此时卷应该在你创建声明后被创建,并且是专门为该声明创建的。难道你的声明处于 Pending 状态是因为集群需要更多时间来供给该卷吗?不,Pending 状态的原因并不是这个。你的声明会保持在 Pending 状态,直到你创建一个使用该声明的 Pod。稍后我会解释原因。现在,我们先创建 Pod。
在pod中使用持久卷声明
从你之前创建的 pod.quiz.pvc.yaml 文件中创建一个新的 Pod 清单文件。将 Pod 的名称更改为 quiz-default,并将 claimName 字段的值更改为 quiz-data-default。生成的清单文件可以在 pod.quiz-default.yaml 中找到。使用它来创建 Pod。如果你使用的是 kind 提供的集群,创建 Pod 后,持久卷声明的状态应在片刻内变为 Bound:

这意味着持久卷已创建。列出持久卷以确认(以下输出已重新格式化,以便更易于阅读):

如你所见,由于该卷是按需创建的,其属性与权利要求中指定的要求以及它所引用的存储类完全匹配。卷的容量为 1Gi,访问模式为 RWO。
了解动态配置的卷何时真正配置
为什么在 kind 提供的集群中,卷要在部署 Pod 之后才创建并绑定到声明?在之前使用手动预先提供的持久卷的示例中,卷在你创建声明时就已经绑定到声明了。这是静态和动态配置之间的区别吗?因为在 GKE 和 Minikube 中,卷都是动态配置的并且立即绑定到声明,所以很明显,仅仅是动态配置并不是导致这种行为的原因。系统之所以这样行为,是因为 kind 提供的集群中的存储类配置方式所致。你可能还记得,这个存储类是唯一一个将 volumeBindingMode 设置为 WaitForFirstConsumer 的存储类。这会导致系统在第一次 Pod(或声明的消费者)存在之前,保持等待状态才绑定声明。在此之前,持久卷也不会被配置。
某些类型的卷需要这种行为,因为系统需要知道 Pod 被调度到哪里,然后才能提供卷。这适用于创建节点本地卷的供应器,例如在使用 kind 工具创建的集群中可以找到的那种。你可能记得,存储类中引用的供应器名称中包含“local”一词(rancher.io/local-path)。Minikube 也提供本地卷(它使用的供应器名为 k8s.io/minikube-hostpath),但是由于集群中只有一个节点,因此无需等待 Pod 创建即可知道需要在哪个节点上创建持久卷。
注意 请参考所选提供程序的文档,以确定它是否需要将卷绑定模式设置为WaitForFirstConsumer。
WaitForFirstConsumer的替代方案是Immediate卷绑定模式。下表解释了这两种模式。
表8.4 支持的卷绑定模式
|
卷绑定模式 |
描述 |
|
Immediate |
在创建claim之后立即提供和绑定持久卷。 由于此时claim的使用者是未知的,因此此模式仅适用于可以从任何集群节点访问的卷 |
|
WaitForFirstConsumer |
当创建第一个使用此声明的 Pod 时,卷会被配置并绑定到该claim。此模式用于受拓扑约束的卷类型。 |
8.3.3 创建存储类并提供该类的卷
正如你在前面的章节中所看到的,大多数 Kubernetes 集群包含一个名为 standard 的单一存储类,但使用不同的供应器。一个完整的集群,例如你在 GKE 中找到的集群,当然可以提供不止一种类型的持久卷。那么,如何创建其他类型的卷呢?
检查 GKE 中的默认存储类
让我们更仔细地看看 GKE 中的默认存储类。我重新排列了字段,因为原来的按字母排序方式使 YAML 定义更难理解。存储类定义如下:

如果你创建一个引用此存储类的持久卷声明,将会调用 provisioner kubernetes.io/gce-pd 来创建卷。在此调用中,provisioner 会接收到存储类中定义的参数。在 GKE 的默认存储类情况下,参数 type: pdstandard 会传递给 provisioner。这告诉 provisioner 创建哪种类型的 GCE 持久磁盘。你可以创建其他存储类对象,并为 type 参数指定不同的值。接下来你将执行此操作。
注意 GCE 持久磁盘类型的可用性取决于你的集群部署的区域。要查看每个可用区域的类型列表,请运行 gcloud compute disk-types list。
创建一个新的存储类以在 GKE 中使用 SSD 持久磁盘
在大多数 GCE 区域中支持的磁盘类型之一是 pd-ssd 类型,它会提供一个网络附加的 SSD。让我们创建一个名为 fast 的存储类,并进行配置,使得当你在声明中请求此存储类时,供应者会创建 pd-ssd 类型的磁盘。存储类的清单显示在下一个示例文件中(文件 sc.fast.gcepd.yaml)。
列表 8.9自定义存储类定义

注意 如果你使用的是其他云提供商,请查看他们的文档以查找所需的配置器名称和参数。如果你使用的是 Minikube 或 kind,并且想运行此示例,请将配置器和参数设置为默认存储类中的相同值。在这个练习中,即使所提供的卷实际上没有使用 SSD 也无关紧要。
通过将此清单应用到你的集群来创建 StorageClass 对象,并列出可用的存储类以确认现在有不止一个可用。你现在可以在你的声明中使用这个存储类。让我们通过创建一个持久卷声明来结束动态配置部分,这将允许你的 Quiz Pod 使用 SSD 磁盘。
声明特定存储类的卷
下面的清单显示了更新后的test -data声明的YAML定义,它快速请求你刚刚创建的存储类,而不是使用默认类。你将在文件pvc.quiz-datafast.yaml中找到清单。

与其仅仅指定大小和访问模式并让系统使用默认存储类来配置持久卷,这个声明明确指定了必须使用 storage class fast 来创建卷。当你创建这个声明时,持久卷将由该存储类中引用的供应器使用指定的参数创建。你现在可以在 Quiz pod 的新实例中使用这个声明。应用文件 pod.quiz-fast.yaml。如果你在 GKE 上运行这个示例,pod 将使用 SSD 卷。
注意 如果持久卷声明引用了不存在的存储类,该声明将保持挂起状态,直到创建存储类。Kubernetes 会定期尝试绑定该声明,每次尝试时都会生成一个 ProvisioningFailed 事件。如果你对该声明执行 kubectl describe 命令,就可以看到该事件。
8.3.4 调整持久卷的大小
如果集群支持动态配置,集群用户可以根据声明和引用的存储类中指定的属性和大小,自行配置存储卷。如果用户之后需要为其卷使用不同的存储类,如你所料,他们必须创建一个引用其他存储类的新持久卷声明。Kubernetes 不支持在现有声明中更改存储类名称。如果尝试这样做,你会收到以下错误信息:
![]()
该错误表明,大部分claim的规格是不可变的。可变的部分是 spec.resources.requests,这里是你指明所需卷大小的地方。在之前的 MongoDB 示例中,你请求了 1GiB 的存储空间。现在假设数据库接近这个大小增长。是否可以在不重启 Pod 和应用程序的情况下调整卷的大小?让我们来看看。
在现有的持久卷声明中请求更大的卷
如果你使用动态配置,通常可以通过在关联的卷claim中请求更大的容量来更改持久卷的大小。在下一个练习中,你将通过修改 quiz-data-default 卷声明来增加卷的大小,该声明应该仍然存在于你的集群中。要修改该声明,可以直接编辑清单文件,或者先创建一个副本再进行编辑。将 spec.resources.requests.storage 字段设置为 10Gi,如下所示列表所示。你可以在本书的 GitHub 仓库中找到此清单文件(文件名为 pvc.quiz-data-default.10gib.pvc.yaml)。
清单8.11请求更大的卷

当你使用 kubectl apply命令应用此文件时,现有的 PersistentVolumeClaim 对象会被更新。使用 kubectl get pvc 命令查看卷的容量是否增加:

你可能还记得,当claim被列出时,“容量”列显示的是CAPACITY的大小,而不是claim中规定的大小要求。根据输出,这意味着卷册的大小没有改变。让我们来找出原因。
确定未调整卷大小的原因
要找出为什么无论你对claim作出何种更改,卷的大小都保持不变,首先你可以使用 kubectl describe 检查该claim。如果是这种情况,你已经掌握了在 Kubernetes 中调试对象的方法。你会发现,该claim的其中一个条件清楚地解释了为什么卷没有被调整大小:

要调整持久卷的大小,可能需要删除并重新创建使用声明的pod。这样做之后,claim和volume将显示新的大小:

允许和禁用存储类中的卷扩展
前面的示例显示,集群用户可以通过更改持久卷claim中的存储要求来增加绑定持久卷的大小。然而,这只有在供给器和存储类支持的情况下才可能。当集群管理员创建存储类时,他们可以使用 spec.allowVolumeExpansion 字段来指示此类卷是否可以调整大小。如果你尝试扩展不允许扩展的卷,API 服务器会立即拒绝对该声明的更新操作。
8.3.5了解动态配置的好处
本节关于动态配置的内容应该能让你相信,自动化持久卷的配置不仅对集群管理员有益,也对任何使用集群部署应用的人有益。通过设置动态卷配置器并配置具有不同性能或其他特性的多个存储类,管理员可以让集群用户能够按需配置任意数量的任意类型的持久卷。每个开发人员可以决定哪种存储类最适合他们创建的每个声明。
了解存储类如何允许claim可移植
关于存储类的另一个好处是,claim通过名称引用它们。如果存储类命名得当,例如 standard、fast 等,持久卷声明的清单在不同的集群之间是可移植的。
注意 请记住,持久卷claim通常是应用程序清单的一部分,并由应用程序开发人员编写。
如果你使用 GKE 运行了前面的示例,现在可以尝试在非 GKE 集群中部署相同的声明和 Pod 清单,例如使用 Minikube 或 kind 创建的集群。通过这种方式,你可以亲自看到这种可移植性。你唯一需要确保的是所有的集群都使用相同的存储类名称。
8.3.6 了解动态配置的持久卷的生命周期
为了总结本节关于动态配置的内容,让我们最后回顾一下底层存储卷(PersistentVolume 对象)、关联的 PersistentVolumeClaim 对象以及使用它们的 Pod 的生命周期,就像我们在上一节关于静态配置卷时所做的那样。
图8.9 动态配置的持久卷、claim和使用它们的pod的生命周期

与静态配置的持久卷不同,使用动态配置时的事件序列从创建 PersistentVolumeClaim 对象开始。一旦出现这样的对象,Kubernetes 就会指示存储类中配置的动态配置器为其配置卷,该存储类在此claim中被引用。配置器会创建底层存储(通常通过云提供商的 API)以及引用该底层卷的 PersistentVolume 对象。底层卷通常是异步配置的。当此过程完成时,PersistentVolume 对象的状态会变为可用(Available);此时,卷已绑定到claim。用户随后可以部署引用该claim的 Pod,以访问底层存储卷。当卷不再需要时,用户删除claim。这通常会触发删除 PersistentVolume 对象和底层存储卷。整个过程会针对用户创建的每个新claim重复进行。每个claim都会创建一个新的 PersistentVolume 对象,这意味着集群永远不会耗尽它们。显然,数据中心自身可能会耗尽可用的磁盘空间,但至少管理员不需要不断回收旧的 PersistentVolume 对象。
8.4 节点本地持久卷
在本章前面的节中,你已经使用了持久卷(Persistent Volumes)和卷声明(Claims)为你的 Pod 提供网络附加存储卷,但这种类型的存储对于某些应用来说速度太慢。要运行生产级数据库,你可能需要使用直接连接到数据库所在节点的 SSD。在前一章中,你学到了如果希望 Pod 访问主机的文件系统的一部分,可以在 Pod 中使用 hostPath 卷。现在你将学习如何在持久卷中实现同样的功能。你可能会想,为什么我要教你另一种实现同样功能的方法,但实际上这并不完全一样。你可能还记得,当你向 Pod 添加 hostPath 卷时,Pod 所看到的数据取决于 Pod 被调度到哪个节点。换句话说,如果 Pod 被删除并重新创建,它可能会调度到另一个节点,从而无法访问相同的数据。如果改为使用本地持久卷,这个问题就可以解决。Kubernetes 调度器会确保 Pod 始终调度到本地卷所挂载的节点。
注意 本地持久卷也比 hostPath 卷更安全。如前一章所述,你根本不希望普通用户使用 hostPath 卷。由于持久卷由集群管理员管理,普通用户无法使用它们访问主机节点上的任意路径。
8.4.1 创建本地持久卷
假设你是一名集群管理员,并且你刚刚将一块高速 SSD 直接连接到其中一个工作节点.由于这是集群中新的一类存储,因此创建一个表示它的新StorageClass 对象是合理的。
创建存储类以表示本地存储
创建一个新的存储类清单,如下面的列表所示。
列表8.12定义本地存储类

在我撰写本文时,本地附加的持久卷需要手动配置,因此你需要按照列表中的示例设置配置器。由于此存储类表示只能在物理连接的节点内访问的本地附加卷,因此 volumeBindingMode 设置为 WaitForFirstConsumer,以便在 Pod 被调度之前延迟claim的绑定。
将磁盘挂载到集群节点
我假设你正在使用 kind 工具创建的 Kubernetes 集群来运行这个练习。我们来模拟在名为 kind-worker 的节点上安装 SSD。运行以下命令,在该节点文件系统的 /mnt/ssd1 位置创建一个空目录:
$ docker exec kind-worker mkdir /mnt/ssd1
为新磁盘创建PersistentVolume对象
在将磁盘附加到某个节点后,你必须通过创建 PersistentVolume 对象告诉 Kubernetes 该节点现在提供本地持久卷。持久卷的清单如下所示。
列表8.13定义本地持久卷

因为这个持久卷表示附加到 kindworker 节点的本地磁盘,所以你给它一个能够传达这一信息的名字。它指的是你之前创建的本地存储类。与之前的持久卷不同,这个卷表示直接附加到节点的存储空间。因此,你需要指定它是一个本地卷。在本地卷配置中,你还需要指定 SSD 挂载的路径(/mnt/ssd1)。在清单的底部,你会发现几行指示卷的节点亲和性。卷的节点亲和性定义了哪些节点可以访问该卷。
注意 你将在后续章节中学习更多关于节点亲和性和选择器的内容。尽管看起来很复杂,列表中的节点亲和性定义只是简单地指定卷可以从主机名为 kind-worker 的节点访问。这显然只有一个节点。
好了,作为集群管理员,你现在已经完成了启用集群用户部署使用本地附加持久卷的应用程序所需的所有操作。现在是时候再次戴上你的应用程序开发者帽子了。
8.4.2 声明和使用本地持久卷
作为应用程序开发人员,现在可以部署pod及其关联的持久卷声明。
创建pod
pod定义如下面的列表所示。
列表8.14使用本地附加持久卷的Pod

对pod的清单应该不会感到意外。你已经知道这些了。
为本地卷创建持久卷声明
与pod一样,为本地持久卷创建声明与创建任何其他持久卷声明没有什么不同。清单显示在下面列表中。
列表8.15使用本地存储类的持久卷声明

这里也没有意外。现在开始创建这两个对象。
创建pod和claim
在你编写 Pod 和 PersistentVolumeClaim 清单后,你可以按任意顺序应用清单来创建这两个对象。如果你先创建 Pod,由于 Pod 需要 Claim 已经存在,它将保持在 Pending(等待)状态,直到你创建 Claim。 当 Pod 和 Claim 都创建完成后,将发生以下事件:1. Claim 与持久卷绑定。2. 调度器确定绑定到 Pod 使用的 Claim 的卷只能从 kind-worker 节点访问,因此它将 Pod 调度到该节点。3. Pod 的容器在该节点上启动,并在其中挂载卷。现在你可以再次使用 MongoDB shell 向其中添加文档。然后检查 kind-worker 节点上的 /mnt/ssd1 目录,查看文件是否已经存储在那里。
重新创建pod
如果你删除并重新创建 Pod,你会发现它总是被调度到 kind-worker 节点。如果在第一次部署 Pod 时有多个节点可以提供本地持久卷,情况也是如此。此时,调度器会选择其中一个节点来运行你的 MongoDB Pod。当 Pod 运行时,该声明会绑定到特定节点上的持久卷。如果你随后删除并重新创建该 Pod,它总是会被调度到同一个节点,因为该节点上有绑定到 Pod 中引用的声明的卷。
8.5 总结
本章解释了为你的应用程序添加持久存储的详细信息。你已经了解到:
• 关于存储卷的特定基础设施信息不属于 pod 清单。相反,它应该在 PersistentVolume 对象中指定。
• PersistentVolume 对象表示集群中可供应用程序使用的一部分磁盘空间。
• 在应用程序可以使用 PersistentVolume 之前,部署应用程序的用户必须通过创建 PersistentVolumeClaim 对象来声明该PersistentVolume。
• PersistentVolumeClaim 对象指定 PersistentVolume 必须满足的最小大小和其他要求。
• 在使用静态配置卷时,Kubernetes 会找到满足声明中要求的现有持久卷,并将其绑定到该声明。
• 当集群提供动态配置时,会为每个声明创建一个新的持久卷。卷的创建是基于声明中指定的要求。
• 集群管理员创建 StorageClass 对象,以指定用户在其声明中可以请求的存储类。
• 用户可以通过修改声明中请求的最小卷大小来更改其应用程序使用的持久卷的大小。
• 当应用程序需要访问直接附加到节点的磁盘时,会使用本地持久卷。这会影响 Pod 的调度,因为 Pod 必须调度到能够提供本地持久卷的节点之一。如果 Pod 随后被删除并重新创建,它将始终调度到相同的节点。
在下一章中,你将学习如何使用命令行参数、环境变量和文件将配置数据传递给应用程序。你将学习如何在 Pod 清单和其他 Kubernetes API 对象中直接指定这些数据。
更多推荐



所有评论(0)