前言

在容器化技术飞速发展的今天,Kubernetes 已成为编排容器集群的事实标准。然而,容器天生的 “短暂性” 特性给数据持久化带来了巨大挑战 —— 容器重启后内部数据丢失、跨容器数据共享困难等问题,成为阻碍企业级应用大规模容器化部署的关键瓶颈。如何为容器提供稳定、可靠且灵活的存储支持,实现数据的安全持久化与高效共享,成为每一位 Kubernetes 运维与开发人员必须攻克的核心课题。
本文围绕 Kubernetes 容器存储展开,从基础问题出发,逐步深入讲解各类存储方案的原理、配置与实践。首先剖析容器存储的短暂性痛点,引出 Kubernetes Volume 抽象的核心作用;随后详细介绍 emptyDir、hostPath、NFS 等基础存储卷的特性与使用方法;再深入讲解 PV(Persistent Volume)与 PVC(Persistent Volume Claim)的持久化机制,厘清二者的生命周期与匹配规则;通过 NFS + PV + PVC 的实战案例,帮助读者掌握静态存储配置流程;最后介绍 StorageClass 动态存储方案,对比各类存储方案的优缺点并给出选型建议,为不同规模、不同场景下的容器存储需求提供完整的解决方案参考。


1. 容器存储的核心挑战与解决方案

1.1 容器存储的短暂性问题

容器的文件系统存在天然局限性:

  • 容器崩溃重启后,内部数据会完全丢失
  • 同一 Pod 内的多个容器无法直接实现文件共享

1.2 Kubernetes Volume 抽象

Kubernetes 通过 Volume 抽象 解决上述问题:

  • 引入 Pause 容器作为 Pod 内所有容器的共享存储载体
  • 实现跨容器的文件共享与数据持久化
  • 支持多种存储后端,适应不同场景需求

2. 基础存储卷类型

2.1 emptyDir 存储卷

2.1.1 核心特点
  • 随 Pod 调度到节点时自动创建
  • 与 Pod 生命周期绑定,Pod 删除后数据随之销毁
  • 仅适合临时缓存或容器间短期数据共享
2.1.2 配置示例
mkdir /opt/volumes
cd /opt/volumes

vim pod-emptydir.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-emptydir
  namespace: default
  labels:
    app: myapp
    tier: frontend
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    # 定义容器挂载内容
    volumeMounts:
    # 使用的存储卷名称,需与下方volumes字段name值匹配
    - name: html
      # 挂载至容器中哪个目录
      mountPath: /usr/share/nginx/html/
  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      # 在容器内定义挂载存储名称和挂载路径
      mountPath: /data/
    command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
  # 定义存储卷
  volumes:
  # 定义存储卷名称  
  - name: html
    # 定义存储卷类型
    emptyDir: {}
2.1.3 验证方法
kubectl apply -f pod-emptydir.yaml

# 查看Pod状态
kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
pod-emptydir   2/2     Running   0          36s   10.244.2.19   node02   <none>           <none>

# 验证容器间共享效果
curl 10.244.2.19
Thu May 27 18:17:11 UTC 2021
Thu May 27 18:17:13 UTC 2021
...

2.2 hostPath 存储卷

2.2.1 核心特点
  • 将节点(宿主机)上的目录直接挂载到容器
  • 可实现数据持久化(不受 Pod 生命周期影响)
  • 存在节点亲和性,节点故障会导致数据无法访问
2.2.2 配置示例
# 1. 在节点上创建挂载目录(所有可能调度的节点都需要)
# 在 node01 节点
mkdir -p /data/pod/volume1
echo 'node01.benet.com' > /data/pod/volume1/index.html

# 在 node02 节点
mkdir -p /data/pod/volume1
echo 'node02.benet.com' > /data/pod/volume1/index.html

# 2. 创建 Pod 资源
vim pod-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-hostpath
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    # 定义容器挂载内容
    volumeMounts:
    # 使用的存储卷名称,需与下方volumes字段name值匹配
    - name: html
      # 挂载至容器中哪个目录
      mountPath: /usr/share/nginx/html
      # 读写挂载方式,默认为读写模式false
      readOnly: false
  # volumes字段定义了paues容器关联的存储卷
  volumes:
    # 存储卷名称
    - name: html
      # 路径,为宿主机存储路径
      hostPath:
        # 在宿主机上目录的路径
        path: /data/pod/volume1
        # 定义类型,表示如果宿主机没有此目录则自动创建
        type: DirectoryOrCreate
2.2.3 验证方法
kubectl apply -f pod-hostpath.yaml

# 查看Pod调度节点
kubectl get pods -o wide

# 访问测试
curl 10.244.2.35  # 输出对应节点的内容(node01或node02.benet.com)

# 验证持久化效果
kubectl delete -f pod-hostpath.yaml  
kubectl apply -f pod-hostpath.yaml 
# 重新访问仍能获取相同内容

注意:hostPath 不适合生产环境的分布式部署,因为它会导致 Pod 与特定节点强绑定,影响调度灵活性。

2.3 NFS 网络共享存储卷

2.3.1 核心特点
  • 基于网络的共享存储,支持多节点访问
  • 数据集中存放于 NFS 服务端,实现真正的跨节点共享
  • 支持 RWX(多路读写)访问模式,适合多 Pod 协作场景
2.3.2 配置示例
2.3.2.1 NFS 服务端配置(stor01节点)
# 安装NFS组件
rpm -q rpcbind nfs-utils
yum install -y nfs-utils rpcbind

# 创建共享目录
mkdir /data/volumes -p
chmod 777 /data/volumes

# 配置共享策略
vim /etc/exports
/data/volumes 192.168.10.0/24(rw,no_root_squash)

# 启动服务
systemctl start rpcbind
systemctl start nfs
systemctl enable rpcbind
systemctl enable nfs

# 验证配置
showmount -e
# 输出应显示:Export list for stor01:/data/volumes 192.168.10.0/24
2.3.2.2 创建使用NFS的Pod
vim pod-nfs-vol.yaml
kind: Pod
apiVersion: v1
metadata:
  name: pod-vol-nfs
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      nfs:
        path: /data/volumes
        server: stor01   
2.3.3 验证方法
kubectl apply -f pod-nfs-vol.yaml

# 在NFS服务器创建测试文件
echo "<h1> nfs stor01</h1>" >/data/volumes/index.html

# 访问测试
kubectl get pods -o wide  # 获取Pod IP
curl 10.244.2.38  # 应输出<h1> nfs stor01</h1>

# 验证持久化
kubectl delete -f pod-nfs-vol.yaml  
kubectl apply -f pod-nfs-vol.yaml 
# 重新访问仍能获取相同内容

在这里插入图片描述

3. PV 与 PVC 持久化机制

3.1 核心概念

  • PV(Persistent Volume):持久化存储卷,由运维人员定义,描述底层存储资源
  • PVC(Persistent Volume Claim):持久化存储请求,由开发者创建,描述所需存储特性
  • StorageClass:存储类,用于自动创建 PV 的模板,简化存储管理
概念作用维护者
PV定义底层存储资源运维管理员
PVC用户提出存储需求应用开发者
StorageClass自动创建 PV 模板运维工程师

3.2 生命周期管理

PV和PVC的交互遵循以下生命周期:

Provisioning(配置)→ Binding(绑定)→ Using(使用)→ Releasing(释放)→ Recycling(回收)
  1. Provisioning:PV的创建,支持静态创建和动态创建(通过StorageClass)
  2. Binding:系统将合适的PV分配给PVC
  3. Using:Pod通过PVC使用存储卷,系统会阻止删除正在使用的PVC
  4. Releasing:Pod删除PVC释放存储卷
  5. Recycling:根据回收策略处理PV资源

3.3 PV的状态流转

  • Available:可用状态,未被任何PVC绑定
  • Bound:已绑定到某个PVC
  • Released:PVC已删除,但资源尚未被集群回收
  • Failed:自动回收失败

3.4 回收策略

  • Retain:保留数据,需手动清理(默认策略)
    • PV被标记为Released状态,数据保留
    • 需要手动删除PV并清理存储资源
  • Delete:自动删除存储资源
    • 仅支持AWS EBS、GCE PD等云存储
  • Recycle:清空数据后重新可用
    • 仅支持NFS和HostPath
    • 相当于执行rm -rf /thevolume/*
    • 注意:Kubernetes 1.14+已弃用此策略

3.5 PV与PVC定义规范

3.5.1 PV定义示例
# 查看PV定义规范
kubectl explain pv
kubectl explain pv.spec

# 示例PV定义
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001  # PV名称
spec:
  nfs:  # 存储类型
    path: /data/volumes/v1  # 挂载路径
    server: stor01  # NFS服务器地址
  accessModes:  # 访问模式
    - ReadWriteOnce  # RWO:仅单Pod读写
    - ReadWriteMany  # RWX:多Pod读写
  capacity:  # 存储容量
    storage: 1Gi
  persistentVolumeReclaimPolicy: Retain  # 回收策略
  storageClassName: ""  # 存储类名称(为空表示不关联任何存储类)
3.5.2 PVC定义示例
# 查看PVC定义规范
kubectl explain pvc
kubectl explain pvc.spec

# 示例PVC定义
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc  # PVC名称
  namespace: default
spec:
  accessModes:  # 访问模式(必须是PV支持的子集)
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi  # 申请的存储容量
  storageClassName: ""  # 需与目标PV的storageClassName匹配

匹配规则:PVC与PV需在accessModes、storageClassName和存储容量上匹配才能成功绑定

4. NFS + PV + PVC 实战

4.1 准备工作

# 在NFS服务器上创建目录
mkdir /data/volumes/v{1,2,3,4,5}

# 配置NFS共享
vim /etc/exports
/data/volumes/v1 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.10.0/24(rw,no_root_squash)

# 生效配置
exportfs -arv

# 验证
showmount -e

在这里插入图片描述

4.2 创建PV

vim pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: stor01
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv004
  labels:
    name: pv004
spec:
  nfs:
    path: /data/volumes/v4
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv005
  labels:
    name: pv005
spec:
  nfs:
    path: /data/volumes/v5
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 5Gi
# 创建PV
kubectl apply -f pv-demo.yaml

# 查看PV状态
kubectl get pv
# 所有PV应处于Available状态

在这里插入图片描述

4.3 创建PVC和使用它的Pod

vim pod-vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-pvc
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      persistentVolumeClaim:
        claimName: mypvc
# 创建资源
kubectl apply -f pod-vol-pvc.yaml

# 查看绑定状态
kubectl get pv
# pv003应处于Bound状态,CLAIM列显示default/mypvc

kubectl get pvc
# mypvc应处于Bound状态,VOLUME列显示pv003

在这里插入图片描述
在这里插入图片描述

4.4 验证效果

# 在NFS服务器对应目录创建测试文件
cd /data/volumes/v3/
echo "welcome to use pv3,hhhhhhh" > index.html

# 访问Pod验证
kubectl get pods -o wide  # 获取Pod IP
curl 10.244.1.27  # 应输出welcome to use pv3,hhhhhhh

在这里插入图片描述

5. StorageClass + NFS 动态存储

在这里插入图片描述

5.1 动态存储的优势

  • 自动创建PV,无需手动预配置
  • 按需分配存储资源,提高资源利用率
  • 简化存储管理流程,降低运维成本

5.2 实现原理

通过nfs-client-provisioner组件实现:

  • 监听PVC创建事件
  • 根据StorageClass配置自动创建PV
  • 在NFS服务器上创建对应目录
  • 建立PV与PVC的绑定关系

5.3 部署步骤

5.3.1 准备NFS服务
# 在stor01节点
mkdir /opt/k8s
chmod 777 /opt/k8s/

vim /etc/exports
/opt/k8s 192.168.10.0/24(rw,no_root_squash,sync)

systemctl restart nfs
5.3.2 创建RBAC权限配置

因为要使用外部插件所以必须给进行授权,然后给权限绑定角色

vim nfs-client-rbac.yaml
# 创建ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
# 创建集群角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-clusterrole
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
# 集群角色绑定
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: nfs-client-provisioner-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: nfs-client-provisioner
  namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-clusterrole
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f nfs-client-rbac.yaml

在这里插入图片描述

5.3.3 部署NFS Provisioner
# 注意:K8s 1.20+版本需要添加以下配置
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --feature-gates=RemoveSelfLink=false  # 添加此行
    - --advertise-address=192.168.10.120
    ...
    
# 重启API Server
kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
kubectl delete pods kube-apiserver -n kube-system 

# 创建Provisioner部署
vim nfs-client-provisioner.yaml        
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-storage  # 需与StorageClass中的provisioner一致
            - name: NFS_SERVER
              value: stor01      # NFS服务器地址
            - name: NFS_PATH
              value: /opt/k8s    # NFS共享目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: stor01
            path: /opt/k8s
kubectl apply -f nfs-client-provisioner.yaml 

# 验证部署
kubectl get pod
# 应看到nfs-client-provisioner-xxx处于Running状态

在这里插入图片描述

5.3.4 创建StorageClass
vim nfs-client-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client-storageclass
provisioner: nfs-storage  # 与Provisioner中的PROVISIONER_NAME一致
parameters:
  archiveOnDelete: "false"  # 删除PVC时是否存档数据
kubectl apply -f nfs-client-storageclass.yaml

# 查看存储类
kubectl get storageclass

在这里插入图片描述

5.3.5 测试动态存储
vim test-pvc-pod.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: nfs-client-storageclass  # 关联存储类
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: test-storageclass-pod
spec:
  containers:
  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh", "-c", "sleep 3600"]
    volumeMounts:
    - name: nfs-pvc
      mountPath: /mnt
  restartPolicy: Never
  volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-nfs-pvc  # 与PVC名称一致
kubectl apply -f test-pvc-pod.yaml

在这里插入图片描述

5.4 验证动态存储功能

# 1. 查看PVC状态
kubectl get pvc
# test-nfs-pvc应处于Bound状态

# 2. 查看自动创建的PV
kubectl get pv
# 应有一个与PVC对应的PV处于Bound状态

# 3. 查看NFS服务器上自动创建的目录
ls /opt/k8s/
# 应看到类似default-test-nfs-pvc-<随机ID>的目录

# 4. 验证数据写入
kubectl exec -it test-storageclass-pod sh
/ # cd /mnt
/mnt # echo 'this is test file' > test.txt

# 5. 在NFS服务器验证
cat /opt/k8s/default-test-nfs-pvc-*/test.txt
# 应输出this is test file

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

6. 存储方案对比与选型建议

存储类型生命周期共享性持久性典型场景优缺点
emptyDirPod生命周期同Pod内临时缓存、容器间通信优点:简单易用;缺点:生命周期短
hostPath节点生命周期单节点节点级日志存储、临时数据优点:实现简单;缺点:节点亲和性强、不适合分布式
NFS独立服务多节点✅✅集群共享存储、中小规模部署优点:易维护、成本低;缺点:性能一般
PV + PVC集群级别按需分配✅✅✅生产环境、规范存储管理优点:解耦存储与应用;缺点:需手动管理PV
StorageClass自动管理动态分配✅✅✅✅大规模部署、云原生应用优点:自动化程度高;缺点:初期配置复杂

6.1 选型建议

  1. 开发测试环境:可选用emptyDir或hostPath
  2. 小规模生产环境:NFS + PV + PVC组合
  3. 大规模生产环境:StorageClass + 企业级存储(如Ceph、GlusterFS)
  4. 有状态应用:推荐使用PVC + StatefulSet组合,确保存储稳定性

6.2 注意事项

  1. 避免在生产环境使用hostPath,会导致Pod调度受限
  2. NFS性能有限,高IO场景建议使用专业存储方案
  3. 合理设置PV回收策略,避免数据意外丢失
  4. 生产环境建议为StorageClass配置默认存储类,简化PVC创建

通过合理选择和配置Kubernetes存储方案,可以有效解决容器化应用的数据持久化问题,为应用提供可靠、灵活的存储支持。


总结

本文系统梳理了 Kubernetes 容器存储的核心技术与实践方案,从问题本质到解决方案,从基础配置到高级实战,构建了一套完整的容器存储知识体系。首先,针对容器存储的短暂性问题,Kubernetes 通过 Volume 抽象实现了数据持久化与跨容器共享,而 emptyDir、hostPath、NFS 三类基础存储卷,分别适用于临时缓存、节点级存储、跨节点共享等不同基础场景,各有其适用边界与局限性。
在此基础上,PV 与 PVC 机制实现了存储资源与应用需求的解耦 —— 运维人员定义 PV 描述底层存储资源,开发人员通过 PVC 提出存储需求,二者通过访问模式、存储容量、存储类等维度实现自动匹配,规范了存储管理流程。而 StorageClass 动态存储方案则进一步提升了存储管理的自动化水平,通过 nfs-client-provisioner 组件实现 PV 的自动创建、目录生成与绑定,大幅降低了大规模集群下的存储运维成本。
在实际选型中,需结合业务场景与规模综合考量:开发测试环境可选择简单的 emptyDir 或 hostPath;小规模生产环境推荐 NFS + PV + PVC 组合,平衡易用性与稳定性;大规模生产环境则建议采用 StorageClass 结合企业级存储(如 Ceph、GlusterFS),满足高可用与高 IO 需求。同时,需警惕 hostPath 的节点亲和性风险、NFS 的性能瓶颈,合理设置 PV 回收策略,确保数据安全。
总之,Kubernetes 容器存储方案没有 “最优解”,只有 “最适解”。掌握各类存储方案的特性与适用场景,根据业务需求灵活选择与配置,才能为容器化应用构建稳定、高效且可靠的数据存储底座,支撑企业级应用的持续稳定运行。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐