第10章:K8s 数据持久化

第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 的绑定过程):

  1. 管理员创建一批 PV,放入集群的"资源池"中。
  2. 开发者为自己的应用创建一个 PVC,声明需要一块 5GB 的硬盘。
  3. K8s 的控制平面会像一个仓库管理员,自动在资源池里寻找能够满足这个 PVC 要求的、尚未被使用的 PV。
  4. 如果找到了一个合适的 PV(比如一块 10GB 的硬盘),K8s 就会将这个 PVC 和 PV 绑定 (Bind) 在一起。
  5. 一旦绑定,这个 PV 就被这个 PVC "独占"了,其他 PVC 不能再使用它。
  6. 最后,开发者可以将这个 PVC 像普通 Volume 一样,挂载到自己的 Pod 中使用。

这个设计的最大好处是抽象 。作为开发者,你只需要关心如何申请 (PVC),而完全不需要关心底层存储到底是什么、由谁提供。这些复杂的细节都由集群管理员通过 PV 来处理。

10.2 StorageClass:动态供给存储

手动地预先创建一堆 PV 让管理员很累。如果每次开发者提交一个 PVC,K8s 都能自动地 根据 PVC 的要求,去调用云平台的 API,动态地创建一个 PV 并与之绑定,那该多好?

StorageClass 就是实现这个"动态供给 (Dynamic Provisioning)"的魔法师。

  • 是什么 :StorageClass 定义了"如何创建 PV"的模板 。它描述了要使用哪种存储插件(比如 aws-ebs),以及创建时需要哪些参数(比如磁盘类型是 gp2 还是 io1)。
  • 工作方式
    1. 管理员在集群中创建一个或多个 StorageClass。
    2. 开发者在创建 PVC 时,可以指定要使用哪个 StorageClass。
    3. 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 上!

相关推荐
richxu202510013 小时前
Java开发环境搭建之 9.使用Docker Compose 安装部署RabbitMQ
java·docker·java-rabbitmq
小闫BI设源码3 小时前
istio集群服务治理
云原生·负载均衡·istio·service服务·ingress路由·网络插件·端口暴露
Achou.Wang3 小时前
Kubernetes 的本质:一个以 API 为中心的“元操作系统”
java·容器·kubernetes
小闫BI设源码3 小时前
istio 部署
云原生·istio·集群架构·api server·kubelet组件·控制器管理器·etcd存储
2501_920047034 小时前
k8s-Service服务
云原生·容器·kubernetes
rggrgerj5 小时前
前后端部署实战:Vue3+Vite+PNPM + NestJS + Docker + Nginx + 云效
nginx·docker·容器
Vahala0623-孔勇6 小时前
服务发现与注册中心设计:从Eureka到Nacos的CAP权衡——AP与CP的边界,藏在服务列表的一致性里
云原生·eureka·服务发现
ayaya_mana6 小时前
Docker常见问题与解决
运维·docker·容器
江湖有缘7 小时前
【Docker项目实战】使用Docker部署Hasty Paste粘贴应用程序
docker·容器·eureka