Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

Go语言系列文章目录

Docker系列文章目录

01-【Docker-Day 1】告别部署噩梦:为什么说 Docker 是每个开发者的必备技能?
02-【Docker-Day 2】从零开始:手把手教你在 Windows、macOS 和 Linux 上安装 Docker
03-【Docker-Day 3】深入浅出:彻底搞懂 Docker 的三大核心基石——镜像、容器与仓库
04-【Docker-Day 4】从创建到删除:一文精通 Docker 容器核心操作命令
05-【Docker-Day 5】玩转 Docker 镜像:search, pull, tag, rmi 四大金刚命令详解
06-【Docker-Day 6】从零到一:精通 Dockerfile 核心指令 (FROM, WORKDIR, COPY, RUN)
07-【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
08-【Docker-Day 8】高手进阶:构建更小、更快、更安全的 Docker 镜像
09-【Docker-Day 9】实战终极指南:手把手教你将 Node.js 应用容器化
10-【Docker-Day 10】容器的“持久化”记忆:深入解析 Docker 数据卷 (Volume)
11-【Docker-Day 11】Docker 绑定挂载 (Bind Mount) 实战:本地代码如何与容器实时同步?
12-【Docker-Day 12】揭秘容器网络:深入理解 Docker Bridge 模式与端口映射
13-【Docker-Day 13】超越默认Bridge:精通Docker Host、None与自定义网络模式
14-【Docker-Day 14】Docker Compose深度解析
15-【Docker-Day 15】一键部署 WordPress!Docker Compose 实战终极指南
16-【Docker-Day 16】告别单机时代:为什么 Docker Compose 不够用,而你需要 Kubernetes?
17-【Docker-Day 17】K8s 架构全解析:深入理解 Kubernetes 的大脑 (Master) 与四肢 (Node)
18-【Docker-Day 18】告别选择困难症:一文掌握 Minikube、kind、k3d,轻松搭建你的第一个 K8s 集群
19-【Docker-Day 19】万物皆 YAML:掌握 Kubernetes 声明式 API 的艺术
20-【Docker-Day 20】揭秘 Kubernetes 的原子单位:深入理解 Pod
21-【Docker-Day 21】Pod的守护神:ReplicaSet与ReplicationController,轻松实现应用高可用
22-【K8s-Day 22】深入解析 Kubernetes Deployment:现代应用部署的基石与滚动更新的艺术
23-【K8s-Day 23】从 Pod 的“失联”到 Service 的“牵线”:深入理解 ClusterIP 核心原理
24-【Docker-Day 24】K8s网络解密:深入NodePort与LoadBalancer,让你的应用走出集群
25-【Docker-Day 25】深入理解 Kubernetes Namespace:实现多租户与环境隔离的利器
26-【Docker-Day 26】K8s实战演练:从零开始部署一个完整的前后端分离Web应用
27-【K8s-Day 27】应用的“体检医生”:深入解析 Kubernetes 健康检查探针 (Probe)
28-【Docker-Day 28】K8s 核心配置管理:解密 ConfigMap,告别硬编码!
29-【Docker-Day 29】K8s 安全第一课:揭秘敏感信息管理器 Secret
30-【Docker-Day 30】解密 K8s 的“硬盘”:深入理解 PersistentVolume (PV) 与 PersistentVolumeClaim (PVC)


文章目录


摘要

在 Kubernetes 的世界里,Pod 是短暂的、易逝的。当 Pod 因重启、扩缩容或节点故障而消亡时,其内部存储的数据也会随之灰飞烟灭。这对于需要持久化数据的有状态应用(如数据库、消息队列)是致命的。为了解决这一难题,Kubernetes 引入了一套强大的存储抽象机制:PersistentVolume (PV) 和 PersistentVolumeClaim (PVC)。本文将从“为什么需要持久化存储”出发,深入剖析 PV 和 PVC 的核心概念、工作原理及生命周期,并通过一个完整的实战演练,手把手教你如何为 Pod 配置一块永不丢失的“硬盘”,为在 K8s 中部署有状态应用打下坚实的基础。

一、引言:为什么 Pod 需要一块“外接硬盘”?

在深入技术细节之前,我们必须先理解问题的根源:为什么 Kubernetes 中默认的存储方式无法满足所有应用的需求?

1.1 Pod 的“健忘症”:短暂的生命周期

我们在之前的文章中已经知道,Pod 是 K8s 中最小的部署单元。然而,Pod 的生命周期是短暂的 (ephemeral)。这意味着:

  • 重启即丢失:当 Pod 内的某个容器崩溃并重启时,所有未持久化的改动都会丢失。kubelet 会用一个全新的、干净的文件系统来替换它。
  • Pod 重建即丢失:当一个 Pod 被删除(例如,通过 Deployment 进行滚动更新,或者 Pod 被调度到另一个节点),它的整个存储空间都会被清理。新的 Pod 会拥有一个全新的、空的存储。

这种“健忘”的特性对于无状态应用(如 Web 前端、无状态 API 服务)来说非常完美,它们不依赖本地存储。但对于有状态应用(如 MySQL 数据库、Redis 缓存、Elasticsearch 集群),数据就是生命,这种短暂性是不可接受的。

1.2 传统方案的局限性

有人可能会想,我可以直接在 Pod 的 YAML 文件里定义一个卷,并将其挂载到宿主机(Node)的某个特定目录上(例如 hostPath)。这种方式虽然能实现数据持久化,但存在严重问题:

  • 强耦合:Pod 与特定的宿主机节点紧密绑定。如果该节点发生故障,Pod 无法被 K8s 调度到其他健康节点上,因为它依赖于故障节点上的特定目录。
  • 管理复杂:开发人员需要关心底层存储的实现细节,比如哪个节点上有可用空间、目录权限等问题,这违背了 K8s 屏蔽底层基础设施复杂性的初衷。
  • 扩展性差:当集群规模扩大,手动管理成百上千个 Pod 与宿主机目录的对应关系将是一场噩梦。

1.3 K8s 的答案:存储与计算解耦

为了优雅地解决这个问题,Kubernetes 设计了 PersistentVolume (PV)PersistentVolumeClaim (PVC) 两个核心资源对象。其核心思想是将存储的“供给”(Provisioning)与存储的“消费”(Consumption)彻底分离

  • 管理员 (Administrator):负责提供和管理底层的存储资源(如云硬盘、NFS、Ceph 等),并将其注册到 K8s 集群中,形成一个“存储资源池”,这些资源就是 PV
  • 开发者 (Developer):不需要关心底层存储的具体实现。当应用需要存储时,只需提交一个“存储申请单”,即 PVC,声明自己需要多大的空间、需要什么样的访问模式。

Kubernetes 会自动在“存储资源池”(PVs) 中寻找满足申请单 (PVC) 要求的资源,并将它们绑定 (Bind) 在一起。之后,开发者就可以像使用普通 Volume 一样,在 Pod 中引用这个 PVC,从而获得持久化的存储能力。

这种模式极大地简化了存储管理,实现了存储资源与计算资源的解耦,让开发者可以专注于应用逻辑,而管理员则可以专注于基础设施。

二、核心概念解析:PV 与 PVC 的“供需关系”

让我们把 PV 和 PVC 的关系想象成一个图书馆的借书流程:

  • PersistentVolume (PV):就像是图书馆里的藏书。每本书(PV)都有自己的属性,比如大小(页数)、类型(小说/技术)、是否可外借等。这些书由图书管理员(集群管理员)采购并上架。
  • PersistentVolumeClaim (PVC):就像是读者的借书卡/借阅申请。读者(开发者)在申请单(PVC)上写明:“我需要一本至少500页的技术类书籍,并且我需要能带回家读(读写)”。
  • 绑定过程:图书管理员(K8s)会根据你的申请单(PVC),在藏书(PVs)中找到一本符合条件的书,然后把它借给你,并在书和借书卡上都盖上“已借出”的章。

下面,我们来详细解析这两个核心资源。

2.1 什么是 PersistentVolume (PV)? - 存储管理员的角色

PersistentVolume (PV) 是由集群管理员手动配置,或由 StorageClass 动态创建的一块网络存储。它是一个集群级别的资源 (Cluster-scoped),不属于任何特定的 Namespace,其生命周期独立于任何使用它的 Pod。

2.1.1 PV 的定义与特性

一个基本的 PV YAML 定义如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-manual-pv
spec:
  capacity:
    storage: 5Gi # 存储容量为 5 GiB
  volumeMode: Filesystem # 卷模式,可以是 Filesystem 或 Block
  accessModes:
    - ReadWriteOnce # 访问模式
  persistentVolumeReclaimPolicy: Retain # 回收策略
  storageClassName: manual # 存储类名,用于与 PVC 匹配
  hostPath: # 实际的存储后端类型
    path: "/mnt/data/pv1" # 宿主机上的路径
2.1.2 PV 的核心参数解析
  • capacity.storage: 定义了该 PV 的存储容量。
  • volumeMode: 指定卷是作为文件系统(Filesystem,默认)还是裸块设备(Block)使用。
  • accessModes: 定义了卷的挂载方式,这非常重要,因为它决定了 PV 能如何被 Pod 访问。
  • persistentVolumeReclaimPolicy: 定义了当 PVC 被释放后(即用户删除了 PVC),这个 PV 何去何从。
  • storageClassName: PV 所属的存储类别。同类的 PV 可以被特定 storageClassName 的 PVC 请求。名称为 manual 表示这个 PV 只能被明确请求 manual 这个 storageClassName 的 PVC 绑定。
  • 存储后端: spec 中还包含具体存储类型的定义,如 hostPathnfsawsElasticBlockStoregcePersistentDisk 等,它指明了这块存储到底是什么。
2.1.3 表格:AccessModes 详解
访问模式 (Access Mode) 描述 常用场景
ReadWriteOnce (RWO) 卷可以被单个节点以读写模式挂载。 大多数块存储(如云硬盘)只支持此模式。
ReadOnlyMany (ROX) 卷可以被多个节点以只读模式挂载。 共享配置数据、静态资源等。
ReadWriteMany (RWX) 卷可以被多个节点以读写模式挂载。 网络文件系统(NFS)、分布式文件系统(CephFS)等。
ReadWriteOncePod (RWOP) 卷可以被单个 Pod 以读写模式挂载。(Kubernetes 1.22+) 需要 Pod 级别数据隔离的有状态服务。
2.1.4 表格:Reclaim Policies 详解
回收策略 (Reclaim Policy) 描述 适用场景
Retain (保留) 当 PVC 被删除后,PV 保持不变,其状态变为 Released。数据仍然存在于底层存储中,需要管理员手动清理和回收该 PV。 生产环境,保护重要数据,防止误删除。
Delete (删除) 当 PVC 被删除后,K8s 会自动删除 PV 以及其关联的后端存储(如 AWS EBS 卷)。 开发、测试环境,或数据可再生的情况。
Recycle (回收) (已废弃) 当 PVC 被删除后,K8s 会对卷执行基本清理(如 rm -rf /thevolume/*),然后使其可用于新的 PVC。强烈不推荐使用,已被动态供给取代。 -

2.2 什么是 PersistentVolumeClaim (PVC)? - 开发者的角色

PersistentVolumeClaim (PVC) 是用户对存储资源发出的请求。它是一个命名空间级别的资源 (Namespace-scoped),因此必须在与 Pod 相同的 Namespace 中创建。

2.2.1 PVC 的定义与特性

一个基本的 PVC YAML 定义如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce # 请求的访问模式,必须是 PV 支持的子集
  resources:
    requests:
      storage: 2Gi # 请求的存储大小
  storageClassName: manual # 请求绑定到名为 'manual' 的 StorageClass 的 PV
2.2.2 PVC 的核心参数解析
  • accessModes: 用户期望的访问模式。K8s 只会将此 PVC 绑定到支持该模式的 PV 上。
  • resources.requests.storage: 用户期望的最小存储容量。K8s 会寻找一个容量大于或等于该值的 PV。
  • storageClassName: 指定希望使用的存储类别。如果省略,可能会使用集群中标记为 “default” 的 StorageClass 进行动态供给(我们将在下一篇文章中详细介绍)。

2.3 PV 与 PVC 的“联姻”:绑定过程

PV 和 PVC 的交互是 Kubernetes 存储管理的核心。这个过程由 persistentvolume-controller 负责。

2.3.1 绑定机制详解
  1. 用户创建 PVC:开发者创建一个 PVC 对象,声明对存储的需求。
  2. K8s 寻找 PVpersistentvolume-controller 监听到新的 PVC 后,会遍历所有状态为 Available 的 PV。
  3. 匹配条件:控制器会寻找一个满足以下所有条件的 PV:
    • storageClassName 必须匹配。
    • accessModes 必须兼容(PV 支持的模式必须包含 PVC 请求的模式)。
    • capacity 必须大于或等于 PVC 请求的 storage 大小。
  4. 绑定成功:一旦找到合适的 PV,控制器就会将它们绑定。PV 和 PVC 的状态都会变为 Bound。这个绑定是一对一的。一个 PV 一旦绑定到某个 PVC,就不能再被其他 PVC 绑定。
  5. 在 Pod 中使用:开发者在 Pod 定义中引用该 PVC,K8s 会在调度 Pod 时,将绑定的 PV 挂载到指定的节点和 Pod 内部路径。
2.3.2 Mermaid 图:PV/PVC 的生命周期状态

下面这张图清晰地展示了 PV 和 PVC 在其生命周期中的状态流转。

graph TD
    subgraph 集群管理员 (Admin)
        A[创建底层存储<br/>(e.g., NFS, Cloud Disk)] --> B{创建 PV<br/>(YAML 文件)};
    end

    subgraph 开发者 (Developer)
        C{创建 PVC<br/>(YAML 文件)} --> D[在 Pod 中<br/>引用 PVC];
    end

    subgraph K8s 控制平面 (Control Plane)
        B -- 注册 --> E[PV 状态: Available];
        C -- 请求 --> F[寻找匹配的 PV];
        E -- 匹配成功 --> G[PV/PVC 状态: Bound];
        G -- Pod 使用 --> H[Volume 挂载到 Node, 再到 Pod];
    end
    
    subgraph 资源释放
        I[删除 Pod] --> J[Volume 从 Pod 卸载];
        K[删除 PVC] --> L{PV 根据回收策略<br/>(Reclaim Policy)处理};
        L -- Retain --> M[PV 状态: Released<br/>(数据保留, 需手动清理)];
        L -- Delete --> N[PV 和后端存储<br/>被删除];
    end
    
    H --> I;
    J --> K;

三、实战演练:一步步为 Pod 挂载持久化存储

理论说完了,让我们动手实践一下。我们将使用 hostPath 类型的 PV,因为它最简单,不需要配置外部存储,非常适合在 Minikube 或 Kind 等本地环境中学习。

警告: hostPath 类型的 PV 将数据存储在单个节点的文件系统上。这在单节点测试环境中很方便,但不适用于生产环境,因为它将 Pod 限制在特定节点上,并且如果节点故障,数据可能丢失。

3.1 准备工作:创建宿主机目录 (hostPath)

首先,我们需要在 K8s 集群的某个节点上创建一个目录,作为我们 PV 的后端存储。如果你使用的是 Minikube,可以 SSH 登录到 Minikube 虚拟机中。

# 如果你使用 Minikube
minikube ssh

# 在 Minikube 节点内创建目录
sudo mkdir -p /mnt/data/pv1
sudo chmod 777 /mnt/data/pv1 # 赋予权限,以便 Pod 可以写入
echo "Hello from HostPath PV!" | sudo tee /mnt/data/pv1/index.html
exit

# 如果你使用 Kind 或其他环境,请在对应的工作节点上执行类似操作

3.2 第一步:创建 PersistentVolume (PV)

3.2.1 编写 pv-definition.yaml

创建一个名为 pv-definition.yaml 的文件,内容如下:

# pv-definition.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-hostpath-pv
spec:
  capacity:
    storage: 1Gi # 定义容量为 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce # 支持单个节点读写
  persistentVolumeReclaimPolicy: Retain # 数据保留策略
  storageClassName: manual # 定义存储类为 'manual'
  hostPath:
    path: "/mnt/data/pv1" # 指向我们刚刚在节点上创建的目录
3.2.2 应用并验证 PV

在终端中应用这个 YAML 文件:

kubectl apply -f pv-definition.yaml

查看 PV 的状态:

kubectl get pv

你应该能看到类似以下的输出,STATUSAvailable,表示它现在是一个可用的存储资源。

NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
my-hostpath-pv    1Gi        RWO            Retain           Available           manual                  10s

3.3 第二步:创建 PersistentVolumeClaim (PVC)

3.3.1 编写 pvc-definition.yaml

现在,我们以开发者的身份来申请存储。创建一个名为 pvc-definition.yaml 的文件:

# pvc-definition.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-pvc
spec:
  accessModes:
    - ReadWriteOnce # 请求的访问模式
  resources:
    requests:
      storage: 500Mi # 请求 500Mi 的存储空间
  storageClassName: manual # 指定要使用 'manual' 类的 PV

注意:我们请求的 500Mi 小于 PV 提供的 1GiaccessModesstorageClassName 也完全匹配,所以它们可以成功绑定。

3.3.2 应用并验证 PVC 与 PV 的绑定

应用 PVC 定义:

kubectl apply -f pvc-definition.yaml

现在,再次查看 PV 和 PVC 的状态:

# 查看 PVC
kubectl get pvc

# 查看 PV
kubectl get pv

你会看到神奇的变化:

PVC 输出:

NAME         STATUS   VOLUME           CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-app-pvc   Bound    my-hostpath-pv   1Gi        RWO            manual         15s

STATUS 变为 Bound,并且它已经绑定到了 my-hostpath-pv

PV 输出:

NAME              CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
my-hostpath-pv    1Gi        RWO            Retain           Bound    default/my-app-pvc   manual                  1m

PV 的 STATUS 也变为 Bound,并且 CLAIM 字段显示了它被哪个 Namespace 下的哪个 PVC 所绑定 (default/my-app-pvc)。

3.4 第三步:在 Pod 中使用 PVC

存储已经准备就绪,现在我们可以在 Pod 中消费它了。

3.4.1 编写 pod-with-pvc.yaml

创建一个名为 pod-with-pvc.yaml 的文件。我们将创建一个 Nginx Pod,并将我们的持久化卷挂载到 Nginx 的 Web 根目录 /usr/share/nginx/html

# pod-with-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-persistent-pod
spec:
  containers:
    - name: nginx-container
      image: nginx
      ports:
        - containerPort: 80
      volumeMounts:
        - name: my-storage # 引用下面定义的 volume
          mountPath: /usr/share/nginx/html # 挂载到容器内的这个路径
  volumes:
    - name: my-storage # 定义一个 volume
      persistentVolumeClaim:
        claimName: my-app-pvc # 声明使用这个 PVC
3.4.2 部署 Pod 并验证挂载

部署 Pod:

kubectl apply -f pod-with-pvc.yaml

等待 Pod 启动后,我们可以进入容器内部,看看挂载点的内容:

# 确认 Pod 正在运行
kubectl get pod my-persistent-pod

# 进入 Pod
kubectl exec -it my-persistent-pod -- bash

# 查看挂载目录的内容
ls /usr/share/nginx/html

你会看到 index.html 文件,这是我们最开始在宿主机 /mnt/data/pv1 目录下创建的文件!这证明挂载成功了。

3.5 第四步:验证数据持久性

现在,我们来验证最关键的一点:数据持久性。

3.5.1 在 Pod 中写入数据

在 Pod 的 shell 中,我们创建一个新文件:

# (在 Pod 内部)
echo "Data persists across Pod restarts!" > /usr/share/nginx/html/test.txt
exit
3.5.2 删除并重建 Pod

现在,在你的本地终端,删除这个 Pod:

kubectl delete pod my-persistent-pod

确认 Pod 已被删除。此时,PV 和 PVC 依然存在并且处于 Bound 状态。现在,我们重新创建同一个 Pod:

kubectl apply -f pod-with-pvc.yaml
3.5.3 验证数据是否依然存在

等待新的 Pod 启动后,再次进入容器:

kubectl exec -it my-persistent-pod -- bash

# 再次查看挂载目录
ls /usr/share/nginx/html

你会惊喜地发现,不仅 index.html 还在,我们刚刚创建的 test.txt 文件也依然存在!

index.html  test.txt

这完美地证明了,通过 PV 和 PVC 机制,我们的数据成功地在 Pod 的生命周期之外得以持久化

四、总结

通过本文的学习和实践,我们掌握了 Kubernetes 中实现数据持久化的核心武器。最后,我们对关键知识点进行梳理总结:

  1. 核心问题与方案:Pod 的短暂性使其不适合有状态应用。K8s 通过 PV 和 PVC 机制,将存储的供给方(管理员)和消费方(开发者)解耦,优雅地解决了持久化存储问题。
  2. PersistentVolume (PV):是集群管理员提供的、独立于 Pod 生命周期的集群级存储资源。其关键属性包括容量 (capacity)、访问模式 (accessModes) 和回收策略 (reclaimPolicy)。
  3. PersistentVolumeClaim (PVC):是开发者在特定命名空间下发起的存储请求。它声明了所需存储的大小和访问模式,是 Pod 与 PV 之间的“中间人”。
  4. 绑定机制:K8s 的控制器会根据 PVC 的请求(storageClassName, accessModes, resources.requests.storage)自动寻找并绑定一个合适的、可用的 PV。
  5. Pod 中的使用:Pod 通过在 volumes 字段中引用 PVC 的名称 (claimName),来声明使用这块持久化存储,并通过 volumeMounts 将其挂载到容器的指定路径。
  6. 重要意义:PV 和 PVC 是在 Kubernetes 中部署数据库、消息队列、缓存等有状态服务的基石。掌握它们是从 K8s 入门走向精通的关键一步。在下一篇文章中,我们将探讨更高级的存储管理方式——StorageClass,它将实现 PV 的动态、自动化供给。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐