近年来一直关注云计算领域的人,必定知道 Docker 和 Kubernetes 的崛起。如今,世界范围内的公有云巨头(谷歌、亚马逊、微软、华为云、阿里云等等)都在其传统的公共云服务之上提供托管的 Kubernetes 服务。Kubernetes 功能强大、扩展性高,在许多人看来,它正在成为云计算的终极解决方案。
但不得不说的是,尽管 Kubernetes 建立在谷歌在生产环境运行工作负载的超过 15 年的经验之上,它非常复杂,一些设计决策总是让用户难以理解。即使对于经验最丰富的工程师来说,Kubernetes 的学习曲线也很陡峭。
就以存储来举例。你知道 PV 和 PVC 的区别吗?storage class 和 provisioner 的关系是什么?VolumeClaimTemplates 是什么?什么时候该用 statefulset?
在本文中,我将尝试解释 Kubernetes 中的一些关键概念,以及我对它们的看法。我希望这也会帮助大家更多地了解 Kubernetes。使用 Kubernetes 时,有许多设计选择和警告让我意想不到。今天我将讲讲 PV、PVC、Storage Class 和 Provisioner。
Docker 中的 Volume(卷)
在深入了解 Kubernetes 之前,让我们先聊聊 Docker——毕竟 Kubernetes 是构建在 Docker 之上。
Docker 因其简单易用闻名,这也是 Docker 能如此受欢迎,并成为 Kubernetes 基础的原因。Docker 容器是无状态、快速的,它可以被破坏、重建,而不需要付出太多的代价。但是,就像是患了健忘症的人,想要记住有意义的事情是很困难的一样。无论是数据库、键值存储、还是一些原始数据,每一个都需要持久化存储。
在 Docker 中创建持久化存储非常简单。早期版本中,用户可以使用-v 来创建一个新的未定义大小的匿名空卷或者在主机上的目录中创建绑定挂载。那个时候,虽然可以很容易地通过挂载那些已经被存储供应商挂载在主机上的目录,但没有第三方接口帮助你直接挂载到 Docker 上。2015 年 8 月,Docker 发布了 v1.8 版本,正式引入了卷插件,允许第三方连接它们的存储解决方案。Docker 会调用已安装的卷插件来创建/删除/挂载/卸载/get/list 那些相关卷,而且每个卷都有一个名字,直到今天,卷插件的框架基本仍保持不变。
持久卷和持久卷声明
当你想弄清楚如何在 Kubernetes 中创建持久存储时,可能会遇到两个概念:持久卷(Persistent Volume,PV)和持久卷声明(Persistent Volume Claim,PVC)
它们是什么?它们中哪个更接近 Docker 中的卷?
实际上,它们都不像 Docker 中的卷。除了 PV 和 PVC 之外,Kubernetes 还有一个 Volume 的概念,但它与 Docker 中的概念不同,稍后我们会讨论它。
如果你了解一些关于 PV 和 PVC 信息,可能会意识到 PV 就是分配的存储,而 PVC 是使用该存储的请求。如果以前你有云计算或存储的经验,那么你可能会认为 PV 就是一个存储池,而 PVC 是一个从存储池中分割出来的卷。
不过这都不是 PV 和 PVC 真正的意义,在 Kubernetes 中,一个 PV 映射到一个 PVC,反之亦然,它是一对一的映射。
我已经多次给具有丰富存储和云计算经验的人解释过这些问题,他们几乎都是抓耳挠腮,不明白这是怎么回事。
而在我第一次遇到这两个概念的时候,我也没法理解。
我们在这里列出 PV 和 PVC 的定义
PersistentVolume(PV)是集群中由 管理员 配置的一块存储。它是集群中的资源,就和节点是集群资源一样。PV 是卷插件比如 Volumes,但是它的生命周期独立于使用 PV 的任何 pod 个体。该 API 对象捕获实现存储的详细信息,包括 NFS、iSCSI 或着是云服务商特定的存储系统。
PersistentVolumeClaim(PVC)是 用户 关于存储的请求。它类似于一个 pod,pod 消耗节点资源,而 PVC 消耗 PV 资源。Pods 可以请求特定级别的资源(CPU 和内容),而 Claim 可以请求特定的大小和访问模式(例如,可以一次读/写或者多次只读)。
这里需要留意的是“管理员”以及“用户”的区别。
简而言之,Kubernetes 将基本存储单元分为两个概念。PV 是一个存储器,应该由管理员预先分配,而 PVC 是用户对存储的请求。
也就是说,Kubernetes 希望管理员来实现分配各种大小的 PV。当用户创建 PVC 来请求存储时,Kubernetes 将尝试用该 PVC 和预先分配的 PV 匹配。如果可以找到匹配项,就将 PVC 绑定到 PV,用户就可以开始使用这片预分配的存储区。
这种方式和传统方法不同,传统方法中管理员并不负责分配每个存储空间。他们只需要授予用户访问某个存储池的权限,并且确定该用户的配额是多少,然后让用户从存储池中分割出所需的存储部分即可。
不过在 Kubernetes 的设计中,PV 已经从存储池中分割了出来,等待和 PVC 进行匹配,因此用户只能请求到预先分配的固定大小的存储空间。这就出现了两种情况:
如果用户只需要 1GiB 的卷,而可用的最小 PV 是 1TiB,那么用户就必须使用这个 1TiB 的卷。这样之后其他用户就没法使用到这个卷,而这些用户可能需求的容量超过了 1GiB。这不仅会造成存储空间的浪费,还会导致由于资源限制无法启动某些工作负载的情况,而其他的工作负载可能正占有了不需要的资源。
为了解决第一个问题,管理员要么需要不断地和用户保持通信,确定用户需要的存储大小/性能,要么就预测好需求,并相应地预先分配 PV。
这样一来就很难强制执行单独的分配(PV)和使用(PVC)。在实际使用中,我并没有看到大家讲 PV 和 PVC 作为他们的设计方式。很可能管理员很快就放弃了创建 PV 的权限并把它委托给用户执行。由于 PV 和 PVC 仍然是一对一的绑定,PVC 的存在就变得不那么必要了。
在我看来,至少可以说,使用 PV 和 PVC 的示例是不常见的。
Storage Class 和 Provisioner
可能因为 PV 和 PVC 使用起来太麻烦了,在 2017 年 3 月,随着 v1.6 版本的发布,Kubernetes 引入了动态纳管(dynamic provisioning)、Storage Class 和 Provisioner 的概念。动态纳管与传统存储方法类似。管理员可以使用 Storage Class 来描述他们提供的存储“class”。Storage Class 可以有不同的容量限制、不同的 IOPS 或其他 Provisioner 支持的参数。特定于存储供应商的 Provisioner 将与 Storage Class 一起使用,根据 Storage Class 对象中设置的参数自动分配 PV。此外,Provisioner 现在能够强制执行用户的报价(quotes)和权限要求。在这种设计中,管理员已经从预测和分配 PV 的繁琐中摆脱出来,这样的方式更有意义。
另外,你还可以使用 Storage Class 而无需在 Kubernetes 中创建 Storage Class 对象。由于 Storage Class 也是用于 PVC 和 PV(不必由 Provisioner 创建)的字段,因此你可以使用自定义的 Storage Class 名称手动创建 PV,然后创建一个请求相同 Storage Class 名称的 PVC。即使存储类 Storage Class 对象不存在,Kubernetes 也会将 PVC 绑定到具有相同存储类名称的 PV 上。
dynamic provisioning、Storage Class 以及 Provisioner 对我来说非常有意义,它解决了最初的 PV 和 PVC 设计中最大的可用性问题。但与此同时,这些新概念也加剧了 Kubernetes 存储的另一个问题,即处理持久存储的各种方式造成的混乱。在本系列文章的下一篇中,我们将分享 Kubernetes 中的卷与持久化存储的相关内容,敬请关注!
评论