【Docker-Day 30】解密 K8s 的“硬盘”:深入理解 PersistentVolume (PV) 与 PersistentVolumeClaim (PVC)
在 Kubernetes 的世界里,Pod 是短暂的、易逝的。当 Pod 因重启、扩缩容或节点故障而消亡时,其内部存储的数据也会随之灰飞烟灭。这对于需要持久化数据的有状态应用(如数据库、消息队列)是致命的。为了解决这一难题,Kubernetes 引入了一套强大的存储抽象机制:PersistentVolume (PV) 和 PersistentVolumeClaim (PVC)。本文将从“为什么需要
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)
文章目录
- Langchain系列文章目录
- Python系列文章目录
- PyTorch系列文章目录
- 机器学习系列文章目录
- 深度学习系列文章目录
- Java系列文章目录
- JavaScript系列文章目录
- Python系列文章目录
- Go语言系列文章目录
- Docker系列文章目录
- 摘要
- 一、引言:为什么 Pod 需要一块“外接硬盘”?
- 二、核心概念解析:PV 与 PVC 的“供需关系”
- 三、实战演练:一步步为 Pod 挂载持久化存储
- 四、总结
摘要
在 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
中还包含具体存储类型的定义,如hostPath
、nfs
、awsElasticBlockStore
、gcePersistentDisk
等,它指明了这块存储到底是什么。
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 绑定机制详解
- 用户创建 PVC:开发者创建一个 PVC 对象,声明对存储的需求。
- K8s 寻找 PV:
persistentvolume-controller
监听到新的 PVC 后,会遍历所有状态为Available
的 PV。 - 匹配条件:控制器会寻找一个满足以下所有条件的 PV:
storageClassName
必须匹配。accessModes
必须兼容(PV 支持的模式必须包含 PVC 请求的模式)。capacity
必须大于或等于 PVC 请求的storage
大小。
- 绑定成功:一旦找到合适的 PV,控制器就会将它们绑定。PV 和 PVC 的状态都会变为
Bound
。这个绑定是一对一的。一个 PV 一旦绑定到某个 PVC,就不能再被其他 PVC 绑定。 - 在 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
你应该能看到类似以下的输出,STATUS
为 Available
,表示它现在是一个可用的存储资源。
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 提供的 1Gi
,accessModes
和 storageClassName
也完全匹配,所以它们可以成功绑定。
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 中实现数据持久化的核心武器。最后,我们对关键知识点进行梳理总结:
- 核心问题与方案:Pod 的短暂性使其不适合有状态应用。K8s 通过 PV 和 PVC 机制,将存储的供给方(管理员)和消费方(开发者)解耦,优雅地解决了持久化存储问题。
- PersistentVolume (PV):是集群管理员提供的、独立于 Pod 生命周期的集群级存储资源。其关键属性包括容量 (
capacity
)、访问模式 (accessModes
) 和回收策略 (reclaimPolicy
)。 - PersistentVolumeClaim (PVC):是开发者在特定命名空间下发起的存储请求。它声明了所需存储的大小和访问模式,是 Pod 与 PV 之间的“中间人”。
- 绑定机制:K8s 的控制器会根据 PVC 的请求(
storageClassName
,accessModes
,resources.requests.storage
)自动寻找并绑定一个合适的、可用的 PV。 - Pod 中的使用:Pod 通过在
volumes
字段中引用 PVC 的名称 (claimName
),来声明使用这块持久化存储,并通过volumeMounts
将其挂载到容器的指定路径。 - 重要意义:PV 和 PVC 是在 Kubernetes 中部署数据库、消息队列、缓存等有状态服务的基石。掌握它们是从 K8s 入门走向精通的关键一步。在下一篇文章中,我们将探讨更高级的存储管理方式——StorageClass,它将实现 PV 的动态、自动化供给。
更多推荐
所有评论(0)