Kubernetes 存储 PV 与 PVC 动态供应

举报
鱼弦 发表于 2025/05/06 09:54:22 2025/05/06
【摘要】 Kubernetes 存储 PV 与 PVC 动态供应介绍 (Introduction)容器技术(如 Docker)的兴起极大地简化了应用的打包和部署,但容器本身是无状态且生命周期短暂的。当容器重启、重建或被调度到其他节点时,其内部的数据会丢失。这对于需要持久化存储的应用(如数据库、消息队列、文件存储)是不可接受的。Kubernetes 提供了 PersistentVolume (PV) 和...

Kubernetes 存储 PV 与 PVC 动态供应

介绍 (Introduction)

容器技术(如 Docker)的兴起极大地简化了应用的打包和部署,但容器本身是无状态且生命周期短暂的。当容器重启、重建或被调度到其他节点时,其内部的数据会丢失。这对于需要持久化存储的应用(如数据库、消息队列、文件存储)是不可接受的。Kubernetes 提供了 PersistentVolume (PV)PersistentVolumeClaim (PVC) 这两个 API 对象来解决容器存储的持久化问题,将存储的生命周期与 Pod 的生命周期解耦。

PV 表示集群中的一块独立存储资源,它可以是网络存储(如 NFS, iSCSI, 公有云块存储等)或本地存储,由集群管理员或通过自动化方式提供。
PVC 是用户(开发者)对存储的请求。Pod 通过引用 PVC 来使用存储,而无需关心底层存储的具体实现细节。

最初,PV 的提供是静态的,即集群管理员需要提前手动创建一批 PV 供用户声明。然而,这种方式效率低下,难以应对用户的动态存储需求。为了解决这个问题,Kubernetes 引入了 动态存储供应 (Dynamic Provisioning) 机制。通过动态供应,当用户创建 PVC 时,Kubernetes 会自动为该 PVC 创建一个匹配的 PV,并将其绑定,从而实现存储资源的按需、自动化分配。

引言 (Foreword/Motivation)

在 Kubernetes 集群中运行有状态应用时,存储的管理是一个核心且复杂的任务。传统的静态 PV 供应模式存在诸多不便:

  • 管理负担重: 集群管理员需要预测用户的存储需求,提前创建各种大小、访问模式的 PV。
  • 资源浪费: 提前创建的 PV 可能长时间不被使用,造成资源闲置。
  • 难以匹配: 用户请求的 PVC 可能找不到完全匹配的静态 PV,导致 PVC 处于 Pending 状态。
  • 沟通成本: 用户需要了解可用的 PV 类型,并与管理员协调存储需求。

动态存储供应彻底改变了这一模式。通过引入 StorageClass 资源对象,集群管理员可以定义不同类型的存储(例如:高性能 SSD 块存储、低成本 HDD 存储、共享文件存储等),并指定一个 供应器 (Provisioner)。用户只需在 PVC 中指定所需的 StorageClass 名称、大小和访问模式,Kubernetes 就会自动调用 StorageClass 定义的供应器,在底层存储系统上创建新的存储卷,并自动生成对应的 PV 并与 PVC 绑定。这实现了存储的“自助服务”,极大地提高了开发和运维效率。

技术背景 (Technical Background)

动态存储供应依赖于 Kubernetes 控制平面中的特定控制器以及与外部存储系统的集成:

  1. Kubernetes 控制平面:
    • API Server: 接收用户提交的 PVC、StorageClass 等资源对象,并存储到 etcd 中。
    • Controller Manager: 包含多个控制器,其中 PersistentVolume Controller 负责管理 PV 和 PVC 的生命周期,包括静态绑定和触发动态供应。
    • Scheduler: 当 Pod 申请使用 PVC 时,Scheduler 会确保 Pod 被调度到能够访问该 PV 的节点上(对于 RWO 模式尤其重要)。
  2. StorageClass: 一个集群级别的资源,定义了动态供应的“模板”。它包含:
    • provisioner: 指定哪个外部供应器负责创建该类型的存储卷。
    • parameters: 传递给供应器的参数,用于定制创建的存储卷(如磁盘类型、IOPS、区域等)。
    • reclaimPolicy: 定义当绑定的 PVC 被删除时,PV 的回收策略(Retain, Delete)。
    • mountOptions: 挂载卷时使用的选项。
    • volumeBindingMode: 定义卷绑定和调度的时机(ImmediateWaitForFirstConsumer)。
  3. 外部供应器 (External Provisioner): 一个运行在集群中(通常是 Pod)的程序,实现了特定的存储接口。它监听 Kubernetes API Server,接收来自 PersistentVolume Controller 的动态供应请求,并与底层的存储系统(如 AWS EBS API, Ceph API, NFS 服务器等)交互来创建和删除存储卷。
  4. CSI (Container Storage Interface): 容器存储接口是 Kubernetes 用于将任意块和文件存储系统暴露给容器化工作负载的标准接口。CSI 驱动程序(实现了 CSI 规范的外部供应器)是实现动态存储供应的主流方式,它使得存储厂商能够独立于 Kubernetes 发布其存储插件。Kubernetes 通过 CSI 代理与 CSI 驱动程序通信。

应用使用场景 (Application Scenarios)

动态存储供应非常适合以下应用场景:

  1. 部署数据库: 为 MySQL, PostgreSQL, MongoDB, Elasticsearch 等数据库的每个实例或副本动态创建独立的持久化存储卷(通常使用 ReadWriteOnce 访问模式)。这是 StatefulSet 的典型用法。
  2. 消息队列: 为 Kafka, RabbitMQ 等消息队列的持久化存储动态创建卷。
  3. 分布式文件系统: 为 GlusterFS, CephFS 等分布式文件系统提供存储卷(可能使用 ReadWriteMany 访问模式)。
  4. 有状态微服务: 为需要保持状态的微服务(如缓存服务、配置中心、日志聚合器)动态分配存储。
  5. CI/CD 构建工具: 为 Jenkins, GitLab CI 等构建工具提供持久化工作空间或存储构建产物。
  6. 内容管理系统 (CMS): 为存储用户上传文件、媒体资源等的 CMS 应用提供存储。

不同场景下详细代码实现 (Detailed Code Examples for Different Scenarios)

以下是使用 YAML 定义 StorageClass 和 PVC 的示例。

场景 1: 定义一个基本的 StorageClass 和 PVC

这通常是云环境中 StorageClass 的简化形式,它依赖于云提供商预装的 CSI 驱动。

# StorageClass 定义 (storageclass.yaml)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-storage # StorageClass 的名称,用户将在 PVC 中引用它
provisioner: kubernetes.io/aws-ebs # 指定供应器,这里是 AWS EBS 的内置供应器 (较老),或使用 csi.aws.amazon.com/ebs (CSI)
parameters: # 传递给供应器的参数
  type: gp2 # EBS 卷类型
reclaimPolicy: Retain # PVC 删除后 PV 保留
allowVolumeExpansion: true # 允许卷在线扩容 (如果供应器支持)
volumeBindingMode: WaitForFirstConsumer # 直到 Pod 首次使用时才绑定和创建卷,有助于更好的调度
# PVC 定义 (pvc.yaml)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-pvc # PVC 的名称
spec:
  storageClassName: fast-storage # 引用上面定义的 StorageClass
  accessModes:
    - ReadWriteOnce # 访问模式:读写一次,只能被一个节点挂载
  resources:
    requests:
      storage: 5Gi # 请求 5Gi 的存储空间

说明:

  • StorageClass: 定义了一个名为 fast-storage 的存储类型。它使用 AWS EBS 供应器创建 gp2 类型的卷。reclaimPolicy: Retain 意味着当 PVC 被删除时,底层 EBS 卷和对应的 PV 会被保留,数据不会丢失,但需要手动清理。volumeBindingMode: WaitForFirstConsumer 是推荐的方式,它会等待第一个 Pod 尝试使用 PVC 时才进行卷的创建和绑定,这有助于调度器选择一个能够访问该卷的节点,避免因区域限制导致的调度失败。
  • PVC: 定义了一个名为 my-app-pvc 的存储请求。它请求使用 fast-storage 类型的存储,访问模式为 ReadWriteOnce,大小为 5Gi。

场景 2: 使用特定云提供商参数的 StorageClass (CSI Driver)

更现代的方式是使用 CSI 驱动,参数通常更丰富。

# StorageClass 定义 (aws-ebs-sc.yaml) - 使用 CSI
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: aws-ebs-csi-gp3 # 使用更具体的名称
provisioner: ebs.csi.aws.com # AWS EBS CSI 驱动的供应器名称
parameters:
  type: gp3 # 更现代的 EBS 卷类型
  fstype: ext4 # 文件系统类型
  # iopsPerGB: "300" # gp3 卷通常不需要指定,但其他类型可能需要
  # encrypted: "true" # 启用加密
reclaimPolicy: Delete # PVC 删除后自动删除 PV 和底层卷
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
# PVC 定义 (pvc-gp3.yaml) - 引用 CSI StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-gp3-pvc
spec:
  storageClassName: aws-ebs-csi-gp3 # 引用 CSI StorageClass
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi # 请求 10Gi

说明: 这个例子使用了 AWS EBS CSI 驱动的供应器名称 (ebs.csi.aws.com),并指定了 gp3 卷类型。reclaimPolicy: Delete 是更常用的策略,它会在 PVC 删除时自动清理底层资源,避免手动管理。

场景 3: 在 Pod 中使用动态供应的 PVC

一个 Pod 如何挂载上面创建的 PVC。

# Pod 定义 (app-pod-with-pvc.yaml)
apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
spec:
  containers:
  - name: my-container
    image: nginx:latest
    ports:
    - containerPort: 80
    volumeMounts:
    - name: my-persistent-storage # 卷挂载点名称
      mountPath: /usr/share/nginx/html # 在容器内的挂载路径
  volumes: # 定义 Pod 使用的卷
  - name: my-persistent-storage # 卷名称,与 volumeMounts 中的名称匹配
    persistentVolumeClaim: # 引用一个 PVC
      claimName: my-app-pvc # PVC 的名称 (引用场景 1 或 2 中创建的 PVC)
      # readOnly: true # 可选:如果只想读,可以设置为 true

说明: Pod 通过 volumes 字段引用了名为 my-app-pvc 的 PVC。然后在 containersvolumeMounts 字段中,将这个卷挂载到容器内部的 /usr/share/nginx/html 路径。

在 StatefulSet 中使用动态供应的 PVC

StatefulSet 通常用于需要稳定网络标识符和持久化存储的应用程序(如数据库集群)。StatefulSet 使用 volumeClaimTemplates 来为每个 Pod 副本动态创建独立的 PVC。

# StatefulSet 定义 (mysql-statefulset.yaml) - 概念性示例
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: "mysql" # Headless Service 名称,用于网络标识
  replicas: 3 # 3 个副本
  selector:
    matchLabels:
      app: mysql
  template: # Pod 模板
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage # 挂载点名称
          mountPath: /var/lib/mysql # MySQL 数据目录
        env: # MySQL 配置环境变量,包括密码等
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret # 引用 Secret
              key: root_password
  volumeClaimTemplates: # PVC 模板
  - metadata:
      name: mysql-persistent-storage # 模板名称,与 Pod 模板中的 volumeMounts 名称匹配
    spec:
      storageClassName: aws-ebs-csi-gp3 # 引用 StorageClass
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi # 每个副本请求 20Gi 存储

说明: StatefulSet 的 volumeClaimTemplates 会为 StatefulSet 的每个 Pod 副本(如 mysql-0, mysql-1, mysql-2)自动创建一个独立的 PVC,名称格式为 <volumeMounts 名称>-<StatefulSet 名称>-<Pod 序号> (例如:mysql-persistent-storage-mysql-0)。每个 PVC 会根据 storageClassName 动态供应一个 PV。这确保了每个数据库副本都有自己的独立持久化存储卷。

原理解释 (Principle Explanation)

动态存储供应的核心流程如下:

  1. 用户创建 PVC: 用户(或 StatefulSet 控制器)创建一个 PVC 对象,在 spec.storageClassName 字段中指定了所需的 StorageClass 名称,并在 spec.resources.requests.storage 中指定了所需的大小,以及 spec.accessModes
  2. PersistentVolume Controller 监听: Kubernetes 控制平面中的 PersistentVolume Controller 持续监听 API Server,查找处于 Unbound (未绑定) 状态且指定了 storageClassName 的 PVC。
  3. 匹配 StorageClass: Controller 找到匹配的 StorageClass 对象。
  4. 确定供应器: 从 StorageClass 对象中获取 provisioner 字段的值,该值标识了负责供应此 StorageClass 定义的存储类型的外部供应器。
  5. 调用外部供应器: PersistentVolume Controller 通过 CSI 接口(或 FlexVolume,如果使用较旧的技术)向对应的外部供应器发送一个 Provision (供应) 请求,请求创建满足 PVC 要求的存储卷,并传递 StorageClass 中定义的 parameters
  6. 外部存储系统交互: 外部供应器接收到请求后,使用其实现的逻辑和凭据,通过调用底层存储系统(如 AWS EBS API, GCE PD API, vSphere API, Ceph API 等)的 API,实际创建一块物理或逻辑存储卷。
  7. 创建 PV 对象: 底层存储卷创建成功后,外部供应器会在 Kubernetes API Server 中创建一个对应的 PV 对象。这个 PV 对象包含了底层存储卷的连接信息(如卷 ID, 访问协议等),并设置其状态为 Available (可用)。它还会复制 PVC 请求的大小、访问模式等信息,并将 storageClassName 设置为与 PVC 匹配的值。
  8. 绑定 PVC 和 PV: PersistentVolume Controller 发现新创建的 PV 与等待中的 PVC 匹配(storageClassName, accessModes, resources.requests.storage 在合理范围内匹配),则将两者进行绑定。绑定后,PVC 的状态变为 Bound,PV 的状态变为 Bound,并且两者会相互引用对方。
  9. Pod 挂载 PVC: 此时,引用了该 PVC 的 Pod 可以被调度到节点上,并且 Kubelet 会负责将已绑定的 PV 实际挂载到 Pod 所在的节点上,然后将卷暴露给 Pod 中的容器使用。

删除流程 (如果 Reclaim Policy 是 Delete):

  1. 用户删除 PVC: 用户删除 PVC 对象。
  2. PersistentVolume Controller 监听: Controller 发现 PVC 被删除。
  3. 执行回收策略: Controller 根据绑定的 PV 的 reclaimPolicy 决定如何处理底层存储卷。如果 reclaimPolicyDelete,Controller 会再次调用外部供应器,发送一个 Delete (删除) 请求,并传递 PV 中记录的底层存储卷信息。
  4. 外部供应器删除卷: 外部供应器接收到请求后,调用底层存储系统的 API 删除对应的物理或逻辑存储卷。
  5. 删除 PV 对象: 底层存储卷删除成功后,外部供应器或 Controller 会删除 Kubernetes API Server 中对应的 PV 对象。

核心特性 (Core Features)

动态存储供应提供了以下核心特性:

  1. 自动化: 自动化完成存储卷的创建和绑定过程,无需管理员手动介入。
  2. 抽象: 将存储的具体实现细节对用户隐藏,用户只需关注所需的存储类型、大小和访问模式。
  3. 按需供应: 存储卷在用户请求时才被创建,避免资源浪费。
  4. 灵活性: 通过 StorageClass 的 parameters 字段,可以灵活定制不同类型的存储卷。
  5. 解耦: 存储生命周期与 Pod 解耦,数据可在 Pod 重启、重建、迁移后依然可用。
  6. 多种存储支持: 通过 CSI 驱动机制,可以轻松集成各种云存储、本地存储和分布式存储系统。

原理流程图以及原理解释 (Principle Flowchart and Explanation)

(此处无法直接生成图形,用文字描述流程图)

图示:K8s 动态存储供应流程

+-----------------+       +---------------------+       +--------------------------+
|   用户/StatefulSet | ----> |     K8s API Server    | ----> | etcd (持久化存储 PVC/SC) |
|    (创建 PVC)    |       |                       |       +--------------------------+
+-----------------+       +---------------------+
                                    ^       |
                                    |       | watches
                                    |       |
+-----------------------+     +------------------------+
| 外部供应器 (External  | <---| PersistentVolume       |
| Provisioner / CSI Driver)|   | Controller (in Ctrl Mgr)|
| (创建/删除底层卷,创建 PV)|--> |                        |
+-----------------------+     +------------------------+
                                    ^       |
                                    |       | finds matching SC
                                    |       |
+---------------------+       +---------------------+
| 底层存储系统 (AWS EBS,| <---|    StorageClass     |
| Ceph, NFS, etc.)    |       |     (已由管理员创建)    |
+---------------------+       +---------------------+

详细流程解释 (与上面文字描述一致,此处为图示对应):

  1. 用户创建 PVC: 用户提交 PVC YAML 到 K8s API Server。API Server 将其保存到 etcd。
  2. PersistentVolume Controller 监测: PersistentVolume Controller 持续监测 API Server,发现新的、未绑定 (Unbound)、指定了 storageClassName 的 PVC。
  3. 查找 StorageClass: Controller 根据 PVC 中指定的 storageClassName 找到对应的 StorageClass 对象。
  4. 确定供应器: Controller 从 StorageClass 中读取 provisioner 名称。
  5. 调用外部供应器: Controller 根据 provisioner 名称定位到集群中运行的外部供应器 Pod (通过 CSI 注册机制),并向其发送一个 CreateVolume 的请求,附带 PVC 的要求 (大小、访问模式) 和 StorageClass 的参数。
  6. 外部供应器创建底层卷: 外部供应器接收请求,通过调用底层存储系统的 API,在实际存储设备上创建一个新的卷。
  7. 外部供应器创建 PV: 底层卷创建成功后,外部供应器向 K8s API Server 发送请求,创建一个新的 PV 对象,其中包含底层卷的标识、容量、访问模式等信息,并关联到对应的 StorageClass。
  8. Controller 绑定 PV 与 PVC: PersistentVolume Controller 监测到新的 PV,发现它与之前等待的 PVC 匹配,于是将该 PV 与 PVC 绑定。此时 PVC 和 PV 的状态都变为 Bound
  9. Pod 挂载 PVC: 引用该 PVC 的 Pod 现在可以被调度到合适的节点上,并在 Pod 启动时由 Kubelet 负责将已绑定的 PV 挂载到容器中。

环境准备 (Environment Setup)

要实践 Kubernetes 动态存储供应,你需要:

  1. Kubernetes 集群:
    • 一个正在运行的 Kubernetes 集群。这可以是:
      • 本地环境: Minikube (需启用对应的存储插件), Kind。
      • 云环境: 各大云服务提供商的 Kubernetes 服务,如 AWS EKS, Google GKE, Azure AKS, 阿里云 ACK 等。这些服务通常预配置了默认的 StorageClass 和 CSI 驱动。
      • 自建集群: 你需要手动部署和配置 StorageClass 以及对应的外部供应器(CSI 驱动)。
  2. kubectl 命令行工具: 安装并配置好 kubectl,使其能够连接到你的 Kubernetes 集群。
  3. YAML 编辑器: 用于编写和修改 Kubernetes 资源定义文件 (YAML)。
  4. 了解底层存储系统: 如果你使用的是自建集群,你需要了解你选择的底层存储系统(如 NFS, iSCSI, Ceph, OpenEBS 等)以及如何为其部署 CSI 驱动。

代码示例实现 (Code Sample Implementation)

上面“不同场景下详细代码实现”部分已经提供了 StorageClass, PVC, Pod 和 StatefulSet 的 YAML 文件示例。你可以将它们保存为 .yaml 文件并在你的集群上应用。

例如,保存场景 1 的 StorageClass 和 PVC 到 fast-storage.yamlmy-app-pvc.yaml

fast-storage.yaml:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-storage
provisioner: kubernetes.io/aws-ebs # 替换为你集群实际可用的供应器,例如:ebs.csi.aws.com
parameters:
  type: gp2 # 如果使用 AWS EBS,这是 gp2 类型参数
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer

my-app-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-pvc
spec:
  storageClassName: fast-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

执行步骤:

  1. 确保你的 kubectl 已正确配置连接到你的 Kubernetes 集群。
  2. 应用 StorageClass 定义(如果你的集群没有名为 fast-storage 且使用对应供应器的 StorageClass):
    kubectl apply -f fast-storage.yaml
    
  3. 应用 PVC 定义:
    kubectl apply -f my-app-pvc.yaml
    
  4. (可选)应用 Pod 定义来使用这个 PVC:保存场景 3 的 Pod 定义到 app-pod-with-pvc.yaml
    kubectl apply -f app-pod-with-pvc.yaml
    

运行结果 (Execution Results)

应用 YAML 文件后,你可以使用 kubectl get 命令查看资源状态:

  1. 查看 StorageClass:

    kubectl get sc
    

    应该能看到 fast-storage 这个 StorageClass。

  2. 查看 PVC 状态:

    kubectl get pvc my-app-pvc
    

    刚开始创建时,状态可能是 Pending

    NAME           STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    my-app-pvc     Pending                                      fast-storage   5s
    

    如果你的 StorageClass 使用 volumeBindingMode: WaitForFirstConsumer,它会保持 Pending 直到 Pod 尝试使用它。如果使用 Immediate 或 Pod 已经创建,并且供应器正常工作,状态会很快变为 Bound

    NAME           STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    my-app-pvc     Bound     pvc-a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8   5Gi        RWO            fast-storage   25s
    

    可以看到,一个自动生成的 VOLUME (PV) 名称已经绑定到了 PVC 上。

  3. 查看自动生成的 PV 状态:

    kubectl get pv <VOLUME 名称> # 将 <VOLUME 名称> 替换为上面输出中的名称
    

    例如:

    kubectl get pv pvc-a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8
    

    输出将显示该 PV 的详细信息,包括容量、访问模式、回收策略、引用的 StorageClass 和绑定的 PVC。

  4. 查看 Pod 状态(如果创建了 Pod):

    kubectl get pod my-app-pod
    

    如果 PVC 已经 Bound,Pod 应该能够正常启动并运行:

    NAME         READY   STATUS    RESTARTS   AGE
    my-app-pod   1/1     Running   0          1m
    

测试步骤以及详细代码 (Testing Steps and Detailed Code)

除了上面的状态检查,还可以进行更深入的测试:

  1. 验证数据持久性:

    • 在挂载了 PVC 的 Pod 中写入数据。

    • 删除 Pod。

    • 创建一个新的 Pod(或者如果是 Deployment/StatefulSet,K8s 会自动重建 Pod),引用同一个 PVC。

    • 在新 Pod 中读取之前写入的数据,验证数据是否保留。

    • 测试数据持久性示例 (使用 Pod 的 exec 命令):
      假设你已经创建了 my-app-pod 并且它正在运行。

      • 进入 Pod 执行命令:
        kubectl exec my-app-pod -- sh -c 'echo "Hello Persistent World" > /usr/share/nginx/html/test.txt'
        
        这会在挂载的卷中创建一个 test.txt 文件。
      • 删除 Pod:
        kubectl delete pod my-app-pod
        
      • 再次应用 Pod 定义(让 K8s 创建一个新的同名 Pod):
        kubectl apply -f app-pod-with-pvc.yaml
        
      • 等待新 Pod 变为 Running 状态。
      • 进入新 Pod 读取文件内容:
        kubectl exec my-app-pod -- cat /usr/share/nginx/html/test.txt
        
        如果输出是 Hello Persistent World,则数据持久性测试成功。
  2. 测试回收策略:

    • 确保你的 StorageClass 的 reclaimPolicyDeleteRetain

    • 删除引用该 PVC 的 Pod。

    • 删除 PVC。

    • 检查对应的 PV 是否被删除(如果策略是 Delete),或者是否变为 Released 状态(如果策略是 Retain)。

    • 测试删除示例:
      假设 my-app-pvc 已经 Bound 并且 PV 存在。

      • (如果 Pod 存在,先删除 Pod):kubectl delete pod my-app-pod
      • 删除 PVC:kubectl delete pvc my-app-pvc
      • 查看 PV 状态:kubectl get pv <VOLUME 名称>
      • 如果 reclaimPolicy: Delete,PV 应该会很快消失 (Error from server (NotFound): persistentvolumes ... not found)。
      • 如果 reclaimPolicy: Retain,PV 状态将变为 Released,底层卷依然存在,需要手动删除 PV 和底层卷。
  3. 测试并发访问模式:

    • 尝试创建多个 Pod,都引用同一个 PVC。
    • 如果 PVC 的 accessModesReadWriteOnce (RWO),只有一个 Pod 能够成功挂载,其他 Pod 将停留在 ContainerCreating 或 Pending 状态,并显示卷被占用的事件。
    • 如果 accessModes 包含 ReadWriteMany (RWX) 并且你的供应器支持,多个 Pod 应该都能成功挂载。

部署场景 (Deployment Scenarios)

动态存储供应是部署有状态应用到 Kubernetes 的标准方式:

  1. StatefulSet 部署数据库集群: 使用 StatefulSet 的 volumeClaimTemplates 为每个数据库副本自动创建独立的 PV,确保数据持久化和副本的稳定性。
  2. 部署微服务应用: 为需要持久化存储的微服务(例如,存储用户配置、会话数据)创建 Deployment 并挂载动态供应的 PVC。
  3. CI/CD 流水线: 在 Jenkins 等 CI/CD 工具的 Agent Pod 中挂载动态供应的 PVC,用于存储构建缓存、依赖库或构建产物,加速后续构建过程。
  4. 日志和监控系统: 为 Elasticsearch, Prometheus 等需要大量存储的系统动态分配和管理存储卷。

疑难解答 (Troubleshooting)

动态存储供应中最常见的问题是 PVC 长时间处于 Pending 状态。排查步骤:

  1. 检查 PVC 事件: 使用 kubectl describe pvc <pvc-name> 查看 PVC 的详细信息和 Events 字段。Events 通常会显示为什么 PVC 无法绑定,常见的错误包括:

    • waiting for first consumer to be created before binding:这是正常的,如果 volumeBindingModeWaitForFirstConsumer,需要先创建 Pod。
    • no matching StorageClass:PVC 中指定的 storageClassName 不存在。
    • no provisioner for storage class ...:指定的 StorageClass 存在,但集群中没有部署或运行对应的外部供应器。
    • Failed to provision volume ...:供应器收到了请求但创建卷失败,可能是底层存储系统配置问题、权限问题、容量不足等。
    • invalid access mode:请求的访问模式不被 StorageClass 或供应器支持。
    • volume capacity ... less than requested capacity ...:请求的容量小于 PV 实际提供的容量(通常不是问题,只要大于等于请求即可),但如果错误消息不同,可能指示容量匹配问题。
  2. 检查 StorageClass: 使用 kubectl get sc <storageclass-name> 查看 StorageClass 是否存在,并使用 kubectl describe sc <storageclass-name> 查看其详细配置,特别是 provisioner 名称和 parameters 是否正确。

  3. 检查外部供应器 (CSI Driver):

    • 找到对应 StorageClass 的 provisioner 名称。
    • 查看该供应器的 Pod 是否正在运行:kubectl get pods -n <csi-driver-namespace> (CSI 驱动通常部署在特定的命名空间)。
    • 查看供应器 Pod 的日志:kubectl logs <provisioner-pod-name> -n <csi-driver-namespace>。日志中通常会有创建卷失败的详细原因。
    • 确认 CSI 驱动已正确安装和配置。
  4. 检查底层存储系统: 如果供应器日志指示底层存储系统错误,登录到存储系统管理界面或查看其日志,排查容量、权限、网络连接等问题。

  5. 检查节点调度: 如果 PVC 绑定模式是 WaitForFirstConsumer,Pod 的调度可能会影响 PVC 绑定。检查 Pod 的 Events (kubectl describe pod <pod-name>),看是否有调度失败的原因,这可能是因为没有节点满足 Pod 的资源请求或节点亲和性/反亲和性规则,或者该节点无法访问特定的存储区域。

  6. PV 回收问题: 如果 PVC 删除后 PV 变为 Released 状态而不是 Deleted,说明 reclaimPolicyRetain。你需要手动删除 PV (kubectl delete pv <pv-name>) 和底层存储卷,否则会造成资源浪费。

未来展望 (Future Outlook)

Kubernetes 存储的未来发展将主要集中在 CSI 接口的深化和存储功能的增强:

  1. CSI 功能扩展: CSI 将支持更多高级存储特性,如卷快照 (Snapshotting)、卷克隆 (Cloning)、在线扩容 (Volume Expansion - 已经比较成熟)、卷迁移 (Volume Migration)。
  2. 数据保护和管理: 出现更多与 Kubernetes 集成的备份、恢复、灾难恢复解决方案,利用卷快照和克隆等 CSI 能力。
  3. 存储感知调度: Scheduler 将更深入地考虑存储拓扑(如卷所在的可用区、节点与存储的网络延迟)来进行更优的 Pod 调度。
  4. 无服务器存储 (Serverless Storage): 抽象掉更多底层存储细节,提供更接近无服务器体验的存储服务。
  5. 持久内存 (Persistent Memory): 将持久内存作为一种新的存储层级集成到 Kubernetes 中。

技术趋势与挑战 (Technology Trends and Challenges)

技术趋势:

  • CSI 标准化: 越来越多的存储厂商提供 CSI 驱动,成为 Kubernetes 存储集成的标准。
  • 声明式数据管理: 趋势是将存储的备份、恢复、迁移等数据管理操作也声明式地定义在 Kubernetes API 中。
  • 多云与混合云存储: 如何在不同云环境或数据中心之间管理和访问存储。
  • 软件定义存储 (SDS): 通过软件实现存储的高级功能(如副本、纠删码、快照),与 Kubernetes 集成更紧密。

挑战:

  • 性能与延迟: 如何在容器化环境中保证存储的低延迟和高吞吐量,尤其对于高性能应用。
  • 复杂性: 集成和管理多样化的底层存储系统以及 CSI 驱动仍然具有挑战性。
  • 数据安全: 如何在 Kubernetes 环境中保护持久化数据的安全,包括加密、访问控制等。
  • 故障处理: 如何优雅地处理底层存储系统或供应器本身的故障,保证应用的高可用性。
  • 状态应用测试: 测试依赖持久化存储的复杂有状态应用比无状态应用困难得多。
  • 成本优化: 如何有效地管理动态供应的存储成本,避免不必要的资源开销。

总结 (Conclusion)

Kubernetes 的 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 机制成功地将存储的生命周期与 Pod 解耦,为容器化应用提供了持久化存储能力。动态存储供应作为其核心特性,通过 StorageClass 和外部供应器(特别是基于 CSI 的驱动)的协作,实现了存储资源的按需、自动化分配。这极大地简化了集群管理员的存储管理工作,并赋予了用户自助获取存储的能力,是 Kubernetes 普及和StatefulSet 广泛应用的关键基石。理解动态供应的原理和流程,掌握 StorageClass、PVC 和对应供应器的配置与排查方法,对于在 Kubernetes 上成功部署和管理有状态应用至关重要。尽管面临性能、安全和管理复杂性等挑战,动态存储供应及其相关技术(如 CSI)的发展将持续推动云原生环境中存储管理的自动化和智能化。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。