Kubernetes 中本地 PersistentVolume 的 PersistentVolumeClaim 政策是什么?

时间:2021-06-30 08:30:46

标签: kubernetes persistent-volumes

场景 1:

我配置了 3 个本地持久卷,每个 pv 安装在不同的节点上:

  • 10.30.18.10
  • 10.30.18.11
  • 10.30.18.12

当我使用 3 个副本启动我的应用程序时:

kind: StatefulSet
metadata:
  name: my-db
spec:
  replicas: 3
...
...
  volumeClaimTemplates:
  - metadata:
      name: my-local-vol
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-local-sc"
      resources:
        requests:
          storage: 10Gi

然后我注意到 pods 和 pvs 在同一台主机上:

  • 具有 ip 10.30.18.10 的 pod1 已声明安装在 10.30.18.10 上的 pv
  • 具有 ip 10.30.18.11 的 pod2 已声明安装在 10.30.18.11 上的 pv
  • 具有 ip 10.30.18.12 的 pod3 已声明安装在 10.30.18.12 上的 pv

(没有发生的是:带有 ip 10.30.18.10 的 pod1 已经声明了挂载在不同节点 10.30.18.12 等上的 pv)

pv 和 pvc 之间唯一常见的配置是 storageClassName,所以我没有配置这个行为。

问题: 那么,谁对这种魔法负责? Kubernetes 调度程序? Kubernetes 供应商?


场景 2:

我配置了 3 个本地持久卷:

  • pv1 的容量为 10Gi
  • pv2 的容量为 100Gi
  • pv3 的容量为 100Gi

现在,我用 1 个副本启动我的应用

kind: StatefulSet
metadata:
  name: my-db
spec:
  replicas: 1
...
...
  volumeClaimTemplates:
  - metadata:
      name: my-local-vol
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-local-sc"
      resources:
        requests:
          storage: 10Gi

我想确保这个 StatefulSet 总是声明 pv1 (10Gi),即使它在不同的节点上,并且不要声明 pv2 (100Gi) 和 pv3 (100Gi)

问题:

这会自动发生吗?

我如何确保所需的行为?我应该使用单独的 storageClassName 来确保这一点吗?

什么是 PersistentVolumeClaim 政策?在哪里可以找到更多信息?


编辑:

用于 StorageClass 的 yml:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: my-local-pv
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

1 个答案:

答案 0 :(得分:2)

对于本地持久卷,这是预期的行为。让我试着解释一下使用本地存储时会发生什么。

集群上本地存储的通常设置如下:

  • 本地存储类,配置为 WaitForFirstConsumer
  • 一系列本地持久卷,链接到本地​​存储类

这一切都在官方文档中有详细记录:https://kubernetes.io/docs/concepts/storage/volumes/#local

完成此操作后,Persistent Volume Claims 可以从本地存储类请求存储,而 StatefulSets 可以有一个 volumeClaimTemplate 来请求本地存储类的存储。


让我以您的 StatefulSet 为例,它有 3 个副本,每个副本都需要带有 volumeClaimTemplate 的本地存储。

  • 首次创建 Pod 时,它们会请求存储所需的 storageClass。例如您的 my-local-sc

  • 由于此存储类是手动创建的,并且支持动态配置新 PV(例如 Ceph 或类似),因此会检查 PV 是否附加到存储类是 available 被绑定。

  • 如果选择了一个 PV,它将绑定到新创建的 PVC(从现在开始,只能与该特定 PV 一起使用,因为它现在是 Bound

  • 由于 PV 的类型为 local,因此 PV 需要一个 nodeAffinity 来选择节点。

  • 强制现在绑定到该 PV 的 Pod 仅在该特定节点上进行调度

这就是为什么每个 Pod 都被调度在有界持久卷的同一节点上的原因。这意味着 Pod 只能在该节点上运行。

您可以通过排空/封锁其中一个节点,然后尝试重新启动绑定到该特定节点上可用 PV 的 Pod 来轻松测试这一点。您应该看到的是 Pod 将启动,因为 PV 受限于其 nodeAffinity 并且节点不可用。


一旦 StatefulSet 的每个 Pod 都绑定到一个 PV,该 Pod 将仅被调度到特定节点上。 Pod 不会更改它们正在使用的 PV,除非删除 PVC(这将强制 Pod再次请求一个新的 PV 绑定)

由于本地存储是手动处理的,被绑定的 PV 和相关的 PVC 从集群中删除,进入 Released 状态并且不能再被声明,它们必须由某人处理.. 可能删除它们并然后在同一位置重新创建新的(可能还会清理文件系统,具体取决于情况)

这意味着可以只使用本地存储:

  • 如果 HA 不是问题.. 例如,我不在乎我的应用程序是否被一个不工作的节点阻止

  • 如果 HA 由应用本身直接处理。例如,具有 3 个 Pod 的 StatefulSet,如多主数据库(例如 Galera、Clickhouse、Percona)或 ElasticSearch 或 Kafka、Zookeeper 或类似的东西……所有这些都将自行处理 HA,因为它们可以抵抗它们中的一个只要有法定人数,节点就会关闭。


更新

关于您问题的情景 2。假设您有多个可用 PV 和一个 Pod,它启动并希望绑定到其中一个。这是正常行为,控制平面会自行选择其中一个 PV(如果它们与 Claim 中的请求匹配)

有一种预先绑定 PV 和 PVC 的特定方法,以便它们始终绑定在一起。这在文档中被描述为“保留 PV”:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reserving-a-persistentvolume

但问题是这不能应用于 olume 声明模板,因为它需要使用特殊属性手动创建声明。

卷声明模板,作为一个选择器字段,可用于限制基于标签的 PV 的选择。它可以在 API 规范 ( https://v1-18.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#persistentvolumeclaimspec-v1-core )

中看到

当你创建一个 PV 时,你可以用你想要的东西来标记它......例如,你可以像下面这样标记它:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-small-pv
  labels:
    size-category: small
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-big-pv
  labels:
    size-category: big
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node-2

然后声明模板可以根据标签选择卷的类别。或者它可能不在乎所以它不指定 selector 并且可以使用所有这些(前提是大小足以满足其声明请求)

这可能很有用..但它不是选择或限制可以选择哪些 PV 的唯一方法,因为当第一次绑定 PV 时,如果存储类是 WaitForFirstConsumer,则还应用以下内容:

<块引用>

延迟卷绑定确保 PersistentVolumeClaim 绑定 决策也将与任何其他节点约束一起评估 Pod 可能有,比如节点资源需求、节点选择器、Pod 亲和性和 Pod 反亲和性。

这意味着如果 Pod 与其中一个节点具有节点亲和性,它将肯定会在该节点上选择一个 PV(如果使用的本地存储类是 WaitForFirstConsumer)


最后,让我引用官方文档,我认为他们可以回答您的问题:

来自https://kubernetes.io/docs/concepts/storage/persistent-volumes/

<块引用>

用户创建,或者在动态配置的情况下,已经 已创建,具有特定存储量的 PersistentVolumeClaim 请求并具有某些访问模式。主站中的控制回路 监视新的 PVC,找到匹配的 PV(如果可能),并绑定 他们在一起。如果为新的 PVC 动态配置了 PV,则 循环将始终将该 PV 绑定到 PVC。否则,用户将 总是至少得到他们要求的东西,但数量可能在 超出了要求。一旦绑定,PersistentVolumeClaim 绑定 是排他性的,无论它们是如何绑定的。 PVC 到 PV 绑定 是一个一对一的映射,使用一个双向的 ClaimRef PersistentVolume 和 PersistentVolumeClaim 之间的绑定。

如果没有匹配的数量,索赔将无限期地保持未绑定状态 存在。当匹配数量可用时,索赔将受到约束。为了 例如,配置有许多 50Gi PV 的集群将不匹配 PVC 要求 100Gi。添加 100Gi PV 时可以绑定 PVC 集群。

来自https://kubernetes.io/docs/concepts/storage/volumes/#local

<块引用>

与 hostPath 卷相比,本地卷用于持久且 可移植的方式,无需手动将 pod 调度到节点。系统 通过查看节点了解卷的节点约束 PersistentVolume 上的关联。

但是,本地卷取决于可用的 底层节点,并不适合所有应用程序。如果一个节点 变得不健康,然后本地卷变得无法访问 荚。使用此卷的 pod 无法运行。应用程序使用 本地卷必须能够容忍这种降低的可用性,因为 以及潜在的数据丢失,取决于耐用性 底层磁盘的特性。