深入浅出聊聊Kubernetes存储(一):详解Kubernetes存储关键概念
近年来一直关注云计算领域的人,必定知道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中的卷与持久化存储的相关内容,敬请关注!