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

What is the PersistentVolumeClaim policy for local PersistentVolume in Kubernetes?

场景 1:

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

当我使用 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.12 等上的 pv)

pv 和 pvc 之间唯一共同的配置是 storageClassName,所以我没有配置此行为。

问题: 那么,谁对这种魔法负责? Kubernetes 调度器? Kubernetes 配置器?


场景 2:

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

现在,我用 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 政策?我在哪里可以找到更多信息?


编辑:

yml 用于 StorageClass:

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

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

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

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

这在官方文档中都有详细的示例:https://kubernetes.io/docs/concepts/storage/volumes/#local

完成后,Persistent Volume Claims 可以从本地存储 class 请求存储,StatefulSets 可以有一个 volumeClaimTemplate 请求存储本地存储 class。


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

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

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

  • 如果 PV 是 selected,它会绑定到新创建的 PVC(并且从现在开始,只能与那个特定的 PV 一起使用,因为它现在是 Bound)

  • 由于 PV 的类型为 local,因此 PV 具有 nodeAffinity 所需的 select 节点。

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

这就是为什么每个 Pod 都被调度到 bounded persistent volume 的同一个节点上的原因。这意味着 Pod 仅限于该节点上的 运行。

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


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

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

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

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

  • 如果 HA 由应用程序本身直接处理。例如,具有 3 Pods 的 StatefulSet,如多主数据库(例如 Galera、Clickhouse、Percona)或 ElasticSearch 或 Kafka、Zookeeper 或类似的东西......所有这些都将尽可能地自行处理 HA只要有法定人数,就可以抵制其中一个节点出现故障。


更新

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

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

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

卷声明模板,作为一个 select 或字段,可用于根据标签限制 select 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

然后声明模板可以select 基于标签的一类卷。或者它可能不在乎所以它没有指定 selector 并且可以使用所有这些(前提是大小足以满足其索赔请求)

这可能很有用.. 但它不是 select 或限制哪些 PV 可以 selected 的唯一方法,因为当 PV 首次绑定时,如果存储 class是WaitForFirstConsumer,下面也适用:

Delaying volume binding ensures that the PersistentVolumeClaim binding decision will also be evaluated with any other node constraints the Pod may have, such as node resource requirements, node selectors, Pod affinity, and Pod anti-affinity.

这意味着如果 Pod 与其中一个节点有节点亲缘关系,它将 select 确定该节点上的 PV(如果使用的本地存储 class 是 WaitForFirstConsumer)


最后,引用官方文档中我认为可以回答您问题的内容:

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

A user creates, or in the case of dynamic provisioning, has already created, a PersistentVolumeClaim with a specific amount of storage requested and with certain access modes. A control loop in the master watches for new PVCs, finds a matching PV (if possible), and binds them together. If a PV was dynamically provisioned for a new PVC, the loop will always bind that PV to the PVC. Otherwise, the user will always get at least what they asked for, but the volume may be in excess of what was requested. Once bound, PersistentVolumeClaim binds are exclusive, regardless of how they were bound. A PVC to PV binding is a one-to-one mapping, using a ClaimRef which is a bi-directional binding between the PersistentVolume and the PersistentVolumeClaim.

Claims will remain unbound indefinitely if a matching volume does not exist. Claims will be bound as matching volumes become available. For example, a cluster provisioned with many 50Gi PVs would not match a PVC requesting 100Gi. The PVC can be bound when a 100Gi PV is added to the cluster.

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

Compared to hostPath volumes, local volumes are used in a durable and portable manner without manually scheduling pods to nodes. The system is aware of the volume's node constraints by looking at the node affinity on the PersistentVolume.

However, local volumes are subject to the availability of the underlying node and are not suitable for all applications. If a node becomes unhealthy, then the local volume becomes inaccessible by the pod. The pod using this volume is unable to run. Applications using local volumes must be able to tolerate this reduced availability, as well as potential data loss, depending on the durability characteristics of the underlying disk.