第10章:K8s 数据持久化
我们已经知道,Pod 的生命周期是短暂的。它们可能因为节点故障、版本更新、或者弹性伸缩而被销毁和重建。如果我们将数据(比如数据库文件)直接存储在 Pod 的文件系统中,那么当 Pod 消失时,数据也会随之而去。
这对于无状态应用 (Stateless Application) (如我们的 my-app
Web 服务器)来说没有问题,但对于有状态应用 (Stateful Application)(如数据库、消息队列等)来说是致命的。
为了在 K8s 中管理有状态应用,我们需要一种独立于 Pod生命周期的、可靠的数据持久化机制。K8s 通过一套设计精巧的 API 资源来解决这个问题:PersistentVolume (PV) 和 PersistentVolumeClaim (PVC)。
10.1 PersistentVolume (PV) & PersistentVolumeClaim (PVC)
K8s 的存储设计,巧妙地将"存储资源的管理 "和"存储资源的使用"这两个角色进行了解耦。
-
PersistentVolume (PV) : 持久卷
- 角色 :集群管理员 (Administrator)
- 是什么 :PV 是集群中的一块网络存储。它可以是云服务商提供的磁盘(如 AWS EBS, GCP Persistent Disk),也可以是本地的存储设备(如 NFS)。
- 类比 :你可以把 PV 想象成是管理员在公司仓库里,提前准备好的一批"移动硬盘"。这些硬盘大小不一,性能各异。
-
PersistentVolumeClaim (PVC) : 持久卷声明
- 角色 :开发者 (Developer) / 应用
- 是什么 :PVC 是用户对存储资源的一次"申请"。它描述了"我需要多大的硬盘(比如 5GB)"、"我需要什么样的读写性能(比如支持多节点读写)"等需求。
- 类比 :PVC 就像是你(开发者)填写的一张"硬盘申领单"。
工作流程 (PV 和 PVC 的绑定过程):
- 管理员创建一批 PV,放入集群的"资源池"中。
- 开发者为自己的应用创建一个 PVC,声明需要一块 5GB 的硬盘。
- K8s 的控制平面会像一个仓库管理员,自动在资源池里寻找能够满足这个 PVC 要求的、尚未被使用的 PV。
- 如果找到了一个合适的 PV(比如一块 10GB 的硬盘),K8s 就会将这个 PVC 和 PV 绑定 (Bind) 在一起。
- 一旦绑定,这个 PV 就被这个 PVC "独占"了,其他 PVC 不能再使用它。
- 最后,开发者可以将这个 PVC 像普通 Volume 一样,挂载到自己的 Pod 中使用。
这个设计的最大好处是抽象 。作为开发者,你只需要关心如何申请 (PVC),而完全不需要关心底层存储到底是什么、由谁提供。这些复杂的细节都由集群管理员通过 PV 来处理。
10.2 StorageClass:动态供给存储
手动地预先创建一堆 PV 让管理员很累。如果每次开发者提交一个 PVC,K8s 都能自动地 根据 PVC 的要求,去调用云平台的 API,动态地创建一个 PV 并与之绑定,那该多好?
StorageClass 就是实现这个"动态供给 (Dynamic Provisioning)"的魔法师。
- 是什么 :StorageClass 定义了"如何创建 PV"的模板 。它描述了要使用哪种存储插件(比如
aws-ebs
),以及创建时需要哪些参数(比如磁盘类型是gp2
还是io1
)。 - 工作方式 :
- 管理员在集群中创建一个或多个 StorageClass。
- 开发者在创建 PVC 时,可以指定要使用哪个 StorageClass。
- K8s 看到这个 PVC 后,就会找到对应的 StorageClass,并根据它的定义,自动地去创建所需的 PV,然后将 PV 和 PVC 绑定。
在大多数现代 K8s 集群(包括 Minikube 和各大云厂商的 K8s 服务)中,都会有一个默认的 StorageClass 。这意味着,你甚至不需要手动创建 PV 和 StorageClass,你只需要创建 PVC,K8s 就会为你搞定剩下的一切!
10.3 [实战] 为我们的数据库 Pod 挂载持久化存储
让我们来为我们的 postgres
数据库实现数据持久化。我们将不再使用 Docker 的数据卷,而改用 K8s 的标准方式。
第一步:检查 StorageClass
在我们的 Minikube 集群中,已经有一个默认的 StorageClass 了。我们可以查看一下:
bash
kubectl get sc
# sc 是 storageclass 的缩写
你会看到一个名为 standard
(或类似名字) 的 StorageClass。
第二步:创建 PVC (持久卷声明)
创建一个 database-pvc.yaml
文件,来申请一块存储空间。
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
# 指定存储类别,我们可以省略,因为它会使用默认的
# storageClassName: standard
# 访问模式,定义了 PV 该如何被节点挂载
# ReadWriteOnce (RWO): 卷可以被单个节点以读写方式挂载 (最常用)
# ReadOnlyMany (ROX): 卷可以被多个节点以只读方式挂载
# ReadWriteMany (RWX): 卷可以被多个节点以读写方式挂载
accessModes:
- ReadWriteOnce
# 资源需求
resources:
# 声明需要 1Gi 的存储空间
requests:
storage: 1Gi
应用它:kubectl apply -f database-pvc.yaml
查看 PVC 和自动创建的 PV:
bash
kubectl get pvc postgres-pvc
kubectl get pv
你会看到 PVC 的状态是 Bound
,并且 K8s 已经自动为我们创建了一个对应的 PV。
第三步:创建 StatefulSet (有状态应用部署)
对于有状态应用,我们通常不使用 Deployment,而是使用一个更适合的控制器:StatefulSet 。StatefulSet 提供了 Deployment 的所有功能,此外还为 Pod 提供了稳定的、唯一的网络标识符 和稳定的、持久的存储。
创建一个 database-statefulset.yaml
文件:
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-db
spec:
serviceName: "postgres"
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: mysecretpassword
# 将 PVC 挂载到容器的
# PostgreSQL 数据目录 /var/lib/postgresql/data
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
# 卷声明模板,StatefulSet 会根据这个模板
# 为每个 Pod 副本创建一个对应的 PVC
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
注意 :上面的 YAML 结合了 PVC 和 StatefulSet,是更高级的用法。你也可以先创建独立的 PVC,然后在 StatefulSet 的 volumes
里直接引用它。
第四步:创建 Service
为了让我们的 Web 应用能访问到数据库,还需要为它创建一个 Service。创建一个 database-service.yaml
。
yaml
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
ports:
- port: 5432
selector:
app: postgres
第五步:部署并验证
bash
kubectl apply -f database-statefulset.yaml
kubectl apply -f database-service.yaml
现在,你可以 exec
进入 Pod,创建一些数据。然后,尝试删除这个 Pod: kubectl delete pod postgres-db-0
StatefulSet 会立刻重建一个新的 postgres-db-0
Pod。当新 Pod 启动后,它会自动重新挂载之前由 PVC 提供的同一块持久化存储。你再次 exec
进去,会发现数据完好无损!
10.4 本章小结
你已经攻克了在 K8s 中运行有状态应用的最后一道难关。
- 本章回顾 :
- 我们理解了 PV (管理员的硬盘) 和 PVC (开发者的申请单) 之间解耦的设计哲学。
- 我们了解了 StorageClass 是实现动态存储供给的关键。
- 我们学会了如何为有状态应用(如数据库)创建 PVC 来申请持久化存储。
- 我们认识了专门用于部署有状态应用的 StatefulSet 控制器。
- 我们通过实战,成功地为一个
postgres
数据库 Pod 挂载了持久化存储,并验证了数据的持久性。
至此,K8s 篇的核心概念已经全部介绍完毕。你已经具备了在 K8s 上部署一个相对完整的、包含无状态和有状态服务的应用所需的所有理论知识。
在最后一章,我们将迎来我们的大结局------将所有学过的知识融会贯通,把我们的主线项目完整地部署到 Kubernetes 上!