K8s 数据存储全解析:从 EmptyDir 到 PV/PVC

前言

在 Kubernetes 中,Pod 是调度和运行的最小单元,但 Pod 本身具有" ephemeral"(短暂)的特性------它们可以被频繁地创建、销毁和重新调度。当容器崩溃或 Pod 被删除时,容器内部文件系统中的数据也会随之丢失。为了解决这一困境,Kubernetes 引入了 Volume(数据卷) 抽象。Volume 的生命周期独立于容器,即使容器被重建,Volume 中的数据也会保留,并可以被同一 Pod 中的其他容器共享。Kubernetes 支持多种类型的 Volume,包括 emptyDirhostPath、NFS、Ceph、云存储等。本文将系统介绍几种常用的存储机制,并通过实际示例演示如何在不同场景下为 Pod 配置持久化存储,最后深入讲解 PV(PersistentVolume)和 PVC(PersistentVolumeClaim)的使用方法及回收策略。


一、K8s 中可用的数据卷概览

Kubernetes Volume 本质上就是一个目录,它与 Pod 绑定,Pod 中的所有容器都可以挂载并访问该目录。Volume 的生命周期与 Pod 相同,但内容是否持久取决于 Volume 的类型。

Kubernetes 支持以下主流 Volume 类型:

  • emptyDir:临时目录,Pod 删除时数据清除。

  • hostPath:宿主机节点上的目录,Pod 删除后数据保留,但节点故障则数据不可用。

  • NFS:网络文件系统,支持多节点同时挂载,数据持久化。

  • PVC/PV:通过 PersistentVolume 和 PersistentVolumeClaim 实现存储资源与存储消费的解耦。

  • 其他:Ceph、GlusterFS、AWS EBS、GCE PD、Azure Disk 等。


二、emptyDir 类型存储

emptyDir 是最基础的 Volume 类型。当 Pod 被分配到节点时,会在该节点上创建一个空目录,Pod 中的所有容器都可以挂载该目录。注意emptyDir 的生命周期与 Pod 完全一致------Pod 从节点删除时,该目录及其内容会被永久清除;但如果只是容器被销毁而 Pod 还在,则 Volume 不受影响。

示例:同一个 Pod 内两个容器共享 Volume

下面的 YAML 文件描述了一个 Pod,其中包含两个容器 pods1pods2,它们共享一个名为 share-vm 的 emptyDir Volume:

复制代码
# pods.yml
apiVersion: v1
kind: Pod
metadata:
  name: share-pods
spec:
  containers:
  - name: pods1
    image: busybox
    volumeMounts:
    - name: share-vm
      mountPath: /pods1
    args:
    - /bin/sh
    - -c
    - echo "hello k8s pods" > /pods1/hello ; sleep 3000
  - name: pods2
    image: busybox
    volumeMounts:
    - name: share-vm
      mountPath: /pods2
    args:
    - /bin/sh
    - -c
    - cat /pods2/hello ; sleep 3000
  volumes:
  - name: share-vm
    emptyDir: {}

执行命令创建 Pod:

复制代码
[root@master data]# kubectl apply -f pods.yml

查看 pods2 容器的日志,确认它能读取到 pods1 写入的数据:

复制代码
[root@master data]# kubectl logs share-pods pods2
hello k8s pods

通过 docker inspect 可以在宿主机上看到两个容器挂载的正是同一个临时目录(路径类似 /var/lib/kubelet/pods/<pod-id>/volumes/kubernetes.io~empty-dir/share-vm)。

emptyDir 特别适合 Pod 内容器之间需要临时共享数据的场景,无需额外配置,但数据不持久化。


三、hostPath 存储类型

hostPath Volume 将宿主机节点文件系统中的已有目录挂载到 Pod 的容器中。一些系统级 Pod(如 kube-apiserver)就会使用 hostPath 挂载节点上的证书目录:

复制代码
[root@master data]# kubectl edit --namespace=kube-system pod kube-apiserver-master

可以看到类似下面的挂载配置:

复制代码
volumes:
- hostPath:
    path: /etc/ssl/certs
    type: DirectoryOrCreate
  name: ca-certs
- hostPath:
    path: /etc/pki
    type: DirectoryOrCreate
  name: etc-pki
- hostPath:
    path: /etc/kubernetes/pki
    type: DirectoryOrCreate
  name: k8s-certs

hostPath 的特点是:即使 Pod 被删除,宿主机上的目录依然保留,持久性比 emp``tyDir 强。但缺点也很明显------它增加了 Pod 与节点的耦合性(Pod 必须调度到存在特定目录的节点上),且节点故障时数据无法自动迁移。

示例:将宿主机 /tmp 目录挂载到 nginx 容器

复制代码
# hostpath.yml
apiVersion: v1
kind: Pod
metadata:
  name: myhostpath
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: hostpath
      mountPath: /usr/share/nginx/html
  volumes:
  - name: hostpath
    hostPath:
      path: /tmp
      type: Directory

创建 Pod,找到其运行的节点(假设为 node1):

复制代码
[root@master data]# kubectl apply -f hostpath.yml
[root@master data]# kubectl get pod -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP            NODE
myhostpath   1/1     Running   0          7m    10.244.1.95   node1

在 node1 上创建测试文件:

复制代码
[root@node1 data]# echo "test hostpath" > /tmp/index.html

通过 Pod IP 访问 nginx:

复制代码
[root@master data]# curl 10.244.1.95
test hostpath

四、使用 NFS 作为数据存储

NFS(Network File System)是一种成熟的网络文件共享协议,支持多个节点同时挂载,数据持久化且独立于节点。要在 Kubernetes 中使用 NFS,需要先搭建一台 NFS 服务器,并在所有节点上安装 nfs-utils 客户端。

步骤 1:搭建 NFS 服务器(IP 示例:172.16.213.230)

复制代码
# 在 NFS 服务器上
[root@nfsserver ~]# yum install nfs-utils -y
[root@nfsserver ~]# mkdir -p /nfs
[root@nfsserver ~]# chown -R nfsnobody.nfsnobody /nfs
[root@nfsserver ~]# echo "/nfs *(rw,sync,no_root_squash)" > /etc/exports
[root@nfsserver ~]# systemctl start nfs

步骤 2:在每个 K8s 节点安装客户端

复制代码
[root@node1 ~]# yum install nfs-utils -y

步骤 3:在 Pod 中挂载 NFS

复制代码
# nfs-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: nfsdata
      mountPath: /usr/share/nginx/html
  volumes:
  - name: nfsdata
    nfs:
      path: /nfs
      server: 172.16.213.230

创建 Pod 并测试:

复制代码
[root@master data]# kubectl apply -f nfs-pod.yml
[root@nfsserver ~]# echo "nfs test" > /nfs/index.html
[root@master data]# curl $(kubectl get pod nfs-pod -o jsonpath='{.status.podIP}')
nfs test

五、使用 NFS PV 存储数据

5.1 PV 和 PVC 概念

  • PersistentVolume(PV):由管理员预先创建的存储资源,是集群中的一块存储"存货",独立于任何 Pod。PV 具有持久性,生命周期与集群相当。

  • PersistentVolumeClaim(PVC):由用户(开发者)创建的存储请求,指明所需存储的大小和访问模式。Kubernetes 会根据 PVC 的要求自动匹配并绑定合适的 PV。

通过 PV/PVC,用户只需要关心"我需要多大的存储",而不用关心底层是 NFS、Ceph 还是云硬盘;管理员则负责管理真实的存储后端。

5.2 创建 PV

编写 nfs-pv1.yml,定义一个容量为 1Gi、访问模式为 ReadWriteMany 的 PV,存储后端为 NFS:

复制代码
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs1
  nfs:
    path: /nfs/pv1
    server: 172.16.213.230

关键字段说明

  • accessModes:支持 ReadWriteOnce(单节点读写)、ReadOnlyMany(多节点只读)、ReadWriteMany(多节点读写)。

  • persistentVolumeReclaimPolicy:PV 回收策略,可选 Retain(保留数据,需管理员手动处理)、Recycle(自动清理数据,即 rm -rf)、Delete(删除云存储资源)。

  • storageClassName:存储类名称,用于 PVC 匹配 PV。

  • nfs:指定 NFS 服务器地址和共享路径。

创建 PV:

复制代码
[root@master data]# kubectl apply -f nfs-pv1.yml

5.3 创建 PVC

编写 nfs-pvc1.yml,申请 1Gi 存储空间,并使用相同的 storageClassName

复制代码
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc1
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nfs1
  resources:
    requests:
      storage: 1Gi
  volumeName: mypv1   # 可选,直接指定绑定的 PV

创建 PVC 并查看绑定状态:

复制代码
[root@master data]# kubectl apply -f nfs-pvc1.yml
[root@master data]# kubectl get pvc
NAME     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc1   Bound    mypv1    1Gi        RWX            nfs1           10s
[root@master data]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM           STORAGECLASS   AGE
mypv1   1Gi        RWX            Recycle          Bound    default/mypvc1  nfs1           30s

5.4 在 Pod 中使用 PVC

创建一个 Deployment,通过 persistentVolumeClaim 引用 mypvc1

复制代码
# deploy-pod2-nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginxserver-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginxserver
  template:
    metadata:
      labels:
        app: nginxserver
    spec:
      containers:
      - name: nginx-pod
        image: nginx
        volumeMounts:
        - name: mynfsdata
          mountPath: /usr/share/nginx/html
      volumes:
      - name: mynfsdata
        persistentVolumeClaim:
          claimName: mypvc1

为方便外部访问,创建一个 NodePort Service:

复制代码
# nfs-service-nginx.yml
apiVersion: v1
kind: Service
metadata:
  name: service-nginx
spec:
  type: NodePort
  selector:
    app: nginxserver
  ports:
  - protocol: TCP
    nodePort: 31000
    port: 80
    targetPort: 80

应用并测试:

复制代码
[root@master data]# kubectl apply -f deploy-pod2-nginx.yml
[root@master data]# kubectl apply -f nfs-service-nginx.yml
[root@master data]# kubectl get svc
NAME            TYPE        CLUSTER-IP     PORT(S)         AGE
service-nginx   NodePort    10.106.0.151   80:31000/TCP    55m

现在,可以通过 http://<任意节点IP>:31000 访问 nginx。将文件放入 NFS 服务器的 /nfs/pv1/index.html,即可看到内容。

5.5 PV 回收策略实战

Recycle 策略示例

当 PVC 被删除时,Kubernetes 会启动一个 recycler Pod 清理 PV 中的数据,然后将 PV 状态重置为 Available

复制代码
[root@master data]# kubectl delete pvc mypvc1
[root@master data]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   AGE
mypv1   1Gi        RWX            Recycle          Available           nfs1           4h

注意:Recycle 策略已被标记为废弃,推荐使用动态 PV 供给或 Retain

Retain 策略示例

如果希望删除 PVC 后保留数据,可以将 PV 的回收策略设置为 Retain(创建 PV 时指定或通过 patch 修改):

复制代码
persistentVolumeReclaimPolicy: Retain

删除 PVC 后,PV 状态变为 Released,但数据依然保留在 NFS 服务器上。此时该 PV 不能被新的 PVC 绑定,需要管理员手动删除并重新创建 PV(删除 PV 对象不会删除后端数据):

复制代码
[root@master data]# kubectl delete pv mypv2
[root@master data]# kubectl apply -f nfs-pv2.yml   # 重新创建

新创建的 PV 状态为 Available,可供后续 PVC 使用。


结尾

Kubernetes 的数据存储机制为容器化应用提供了灵活、持久的存储方案。从基础的 emptyDirhostPath,到网络存储 NFS,再到解耦存储供给与消费的 PV/PVC,K8s 覆盖了从开发测试到生产环境的多种存储需求。本文通过丰富的示例,演示了每种存储类型的使用场景和配置方法,并深入讲解了 PV 的回收策略(Recycle 与 Retain)对数据生命周期的影响。

在实际生产环境中,建议:

  • 对于临时、非关键的共享数据,使用 emptyDir

  • 仅当需要访问节点特定文件(如证书、设备文件)时使用 hostPath

  • 对于多节点共享的持久化数据,优先使用 NFS、Ceph 或云存储,并结合 PV/PVC 进行资源管理。

  • 合理设置回收策略,避免误删重要数据;对于生产数据,推荐使用 Retain 策略。

理解并熟练运用这些存储机制,将使你在 Kubernetes 中构建有状态应用时更加得心应手。

相关推荐
创世宇图2 小时前
【Python工程化实战】Kubernetes 中 Python 应用的优雅启停与健康检查:零停机滚动更新实战
python·云原生·kubernetes·优雅停机
江畔柳前堤4 小时前
第16章:docker企业级实战综合项目
运维·git·安全·docker·容器·eureka
zh73145 小时前
docker日志监控dozzle,高性能,性能消耗小
运维·docker·容器
weixin_471383035 小时前
Docker - 05 - Railway 部署
运维·docker·容器
江畔柳前堤6 小时前
第15章:docker故障排查与面试题
大数据·运维·git·elasticsearch·docker·容器·eureka
江畔柳前堤6 小时前
第07章:Docker 网络模型
运维·网络·git·elasticsearch·docker·容器·架构
江畔柳前堤6 小时前
第17章:Docker 大厂面试题精选(腾讯/阿里/字节/美团)
运维·网络·spring cloud·docker·容器·eureka
江畔柳前堤11 小时前
第14章:Docker 与 CI/CD
运维·ci/cd·docker·容器·eureka·github
阿里云云原生2 天前
研发视角的新突破:当 AI Coding 工具集成全域运维诊断,排查线上故障只需 3 分钟
云原生