在说剩余两种负载之前我们来说说K8s的持久化存储。在 Kubernetes 集群中,Pod 作为应用运行的载体具有临时性——Pod 重启、删除或调度到其他节点时,容器内的本地数据会彻底丢失。对于 MySQL、Redis 等需要持久化数据的应用,必须通过持久化存储将数据脱离 Pod 生命周期独立保存。本文将从核心概念入手,详解 EmptyDir、HostPath、NFS、PV/PVC 及 StorageClass 等主流存储方案的原理与实战配置。

一、K8s 持久化存储核心概念

在展开具体方案前,需要明确K8s存储体系的几个关键接口与组件,它们是实现持久化的基础:

  • CSI(Container Storage Interface):容器存储接口,标准化存储插件的接入方式(如 Ceph、GlusterFS 通过 CSI 与 K8s 集成);

  • CNI(Container Network Interface):容器网络接口,虽不直接关联存储,但负责 Pod 网络通信,确保存储访问的网络可达;

  • CRI(Container Runtime Interface):容器运行时接口,定义容器运行时(如 Docker、Containerd)与 K8s 的交互标准,存储卷挂载需依赖 CRI 协调;

  • Volume(存储卷):K8s 中抽象的存储单元,可挂载到 Pod 内的一个或多个容器,数据存储在 Volume 而非容器本地,实现数据与容器解耦。

二、主流持久化存储方案实战

K8s 支持多种 Volume 类型,不同类型适用于不同场景。以下按 “临时存储→节点级存储→共享存储→自动化存储” 的逻辑,逐一讲解核心方案。

1. EmptyDir:Pod 级临时存储

EmptyDir 是最简单的 Volume 类型,生命周期与 Pod 完全绑定——Pod 被调度到节点时自动创建,Pod 删除时数据永久删除,适用于临时数据存储(如容器间共享临时文件、应用缓存)。

1.1 核心特性

  • 无需提前创建存储目录,K8s 自动在节点的 /var/lib/kubelet/pods/<Pod-UID>/volumes/kubernetes.io~empty-dir/ 路径生成目录;

  • 支持同一 Pod 内多个容器共享数据;

  • 数据仅存于节点本地,Pod 调度到其他节点后,原数据无法访问。

1.2 实战配置

创建一个挂载 EmptyDir 的 Nginx Pod,将临时目录 /cache 挂载到容器内:

# emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-empty
spec:
  containers:
  - name: container-empty
    image: nginx:latest
    volumeMounts:  # 容器内挂载配置
    - mountPath: /cache  # 容器内挂载路径
      name: cache-volume  # 关联下面的 Volume 名称
  volumes:  # 定义 Volume
  - name: cache-volume  # Volume 名称(需与上面一致)
    emptyDir: {}  # 类型为 EmptyDir
1.3 验证
  1. 创建 Pod 并查看调度节点:
kubectl apply -f emptydir.yaml
kubectl get pods -o wide pod-empty  # 假设调度到节点 hd2

2.查看节点上的 EmptyDir 实际路径(需登录节点 hd2):

# 先获取 Pod 的 UID
kubectl get pods pod-empty -o yaml | grep uid
# 查看实际目录(替换 <Pod-UID> 为真实值)
ls /var/lib/kubelet/pods/<Pod-UID>/volumes/kubernetes.io~empty-dir/cache-volume/

2. HostPath:节点级持久存储

HostPath 直接挂载节点本地的目录或文件到 Pod,数据存储在节点磁盘上,Pod 删除后数据仍保留在节点中。适用于单节点场景(如测试环境、节点级日志存储),但不支持跨节点共享。

2.1 核心特性

  • 数据生命周期与节点绑定,Pod 重新调度到其他节点后无法访问原数据;
  • 支持预创建目录(Directory)、自动创建目录(DirectoryOrCreate)等类型;
  • 同一节点上的多个 Pod 可通过 HostPath 共享节点本地数据。

2.2 实战配置

创建一个挂载 HostPath 的 Pod,同时运行 Nginx 和 Tomcat 容器,共享节点的 /data1 目录:

# hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-hostpath
spec:
  containers:
  # Nginx 容器
  - name: test-nginx
    image: nginx:latest
    volumeMounts:
    - mountPath: /test-nginx  # Nginx 容器内挂载路径
      name: test-volume
  # Tomcat 容器
  - name: test-tomcat
    image: tomcat:8.5-jre8-alpine
    volumeMounts:
    - mountPath: /test-tomcat  # Tomcat 容器内挂载路径
      name: test-volume
  # 定义 HostPath Volume
  volumes:
  - name: test-volume
    hostPath:
      path: /data1  # 节点上的目录
      type: DirectoryOrCreate  # 节点无此目录时自动创建
2.3 验证
  1. 创建 Pod 并查看调度节点:
kubectl apply -f hostpath.yaml
kubectl get pods -o wide test-hostpath  # 假设调度到节点 hd2

2. 验证节点目录与容器共享:

# 登录节点 hd2,在 /data1 下创建目录 aa
ssh hd2 "mkdir /data1/aa"
# 进入 Nginx 容器,查看 /test-nginx 是否有 aa 目录
kubectl exec -it test-hostpath -c test-nginx -- ls /test-nginx  # 输出 aa
# 进入 Tomcat 容器,验证共享
kubectl exec -it test-hostpath -c test-tomcat -- ls /test-tomcat  # 输出 aa
2.4 缺点与解决

HostPath 存在单点故障:若 Pod 调度到其他节点,原节点的 /data1 数据无法访问。解决此问题需使用分布式共享存储(如 NFS、CephFS)。

3. NFS:跨节点共享存储

NFS(Network File System)是经典的网络共享文件系统,通过网络将服务器的目录共享给多个客户端,K8s 中的 Pod 可跨节点挂载 NFS 共享目录,实现数据跨节点共享。适用于中小规模集群的共享存储场景(如多 Pod 共享配置文件、数据库数据)。

3.1 部署 NFS 服务端

以 K8s 控制节点(IP:192.168.1.11)作为 NFS 服务端:

1.安装 NFS 组件:

yum install nfs-utils -y  # CentOS/RHEL
apt install nfs-kernel-server -y  # Ubuntu

2.创建共享目录并配置权限:

mkdir /data/volumes -p  # 创建共享目录
# 编辑 NFS 配置文件,允许 192.168.1.0/24 网段访问
cat >> /etc/exports << EOF
/data/volumes 192.168.1.0/24(rw,no_root_squash)
EOF
    • rw:读写权限;
    • no_root_squash:客户端以 root 身份访问时,保留 root 权限(避免权限问题)。

    3.启动 NFS 服务并生效配置:

    systemctl start nfs
    systemctl enable nfs
    exportfs -arv  # 重新加载配置

    3.2 配置 NFS 客户端(所有 K8s 节点)

    所有需要挂载 NFS 的节点需安装 NFS 客户端:

    yum install nfs-utils -y  # 所有节点执行

    3.3 实战:Pod 挂载 NFS

    创建一个 Nginx Pod,挂载 NFS 共享目录 /data/volumes 到容器的 /usr/share/nginx/html(Nginx 网页根目录):

    # nfs.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: test-nfs-volume
    spec:
      containers:
      - name: test-nfs
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nfs-volumes
          mountPath: /usr/share/nginx/html  # 挂载到 Nginx 网页目录
      volumes:
      - name: nfs-volumes
        nfs:  # 类型为 NFS
          server: 192.168.1.11  # NFS 服务端 IP
          path: /data/volumes    # NFS 共享目录

    3.4 验证

    1. 创建 Pod 并查看调度节点:
    kubectl apply -f nfs.yaml
    kubectl get pods -o wide test-nfs-volume  # 假设调度到节点 hd3

    2.在 NFS 服务端创建测试页面,验证 Pod 挂载:

    # NFS 服务端(192.168.1.11)创建 index.html
    echo "Hello NFS Storage" > /data/volumes/index.html
    # 访问 Pod 的 IP,验证页面内容
    kubectl get pods test-nfs-volume -o jsonpath='{.status.podIP}'  # 获取 Pod IP
    curl <Pod-IP>  # 输出 "Hello NFS Storage"

    3.5 缺点

    NFS 服务端存在单点故障:若 NFS 服务器宕机,所有挂载该 NFS 的 Pod 无法访问数据。生产环境需使用高可用 NFS 或分布式存储(如 CephFS、GlusterFS)。

    4. PV/PVC:存储资源的 “申请 - 分配” 模型

    PersistentVolume(PV)和 PersistentVolumeClaim(PVC)是 K8s 中解耦存储管理与应用使用的核心机制:

    • PV:由集群管理员创建的 “存储资源”,对应实际的存储设备(如 NFS 目录、Ceph 块存储),生命周期独立于 Pod;

    • PVC:由应用开发者创建的 “存储请求”,声明需要的存储大小、访问模式(如读写、只读),K8s 会自动匹配符合条件的 PV 并绑定。

    4.1 核心概念

    术语作用
    PV集群级存储资源,定义存储类型、大小、访问模式、回收策略等
    PVC命名空间级存储请求,声明需要的存储资源,与 PV 一对一绑定
    访问模式PV/PVC 支持的访问方式:RWO(单节点读写)、RWX(多节点读写)、ROX(多节点只读)
    回收策略PV 被释放后的处理方式:Retain(保留数据,手动清理)、Delete(删除 PV 及数据)

    4.2 实战:基于 NFS 的 PV/PVC 配置

    步骤 1:准备 NFS 共享目录(扩展)

    在 NFS 服务端(192.168.1.11)创建 10 个独立目录,用于创建 10 个 PV:

    mkdir /data/volume_test/v{1..10} -p
    # 更新 NFS 配置,共享这些目录
    cat >> /etc/exports << EOF
    /data/volume_test/v1 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v2 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v3 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v4 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v5 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v6 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v7 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v8 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v9 192.168.1.0/24(rw,no_root_squash)
    /data/volume_test/v10 192.168.1.0/24(rw,no_root_squash)
    EOF
    exportfs -arv  # 生效配置

    步骤 2:创建 PV

    定义 10 个 PV,分别对应 NFS 的 10 个目录,配置不同的大小和访问模式:

    # pv.yaml
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: v1  # PV 名称
    spec:
      capacity:
        storage: 1Gi  # PV 大小
      accessModes: ["ReadWriteOnce"]  # 单节点读写
      nfs:
        path: /data/volume_test/v1  # 关联 NFS 目录
        server: 192.168.1.11
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: v2
    spec:
      capacity:
        storage: 2Gi
      accessModes: ["ReadWriteMany"]  # 多节点读写
      nfs:
        path: /data/volume_test/v2
        server: 192.168.1.11
    # 省略 v3-v10 配置(类似 v1/v2,调整 name、storage、path)

    创建 PV 并查看状态:

    kubectl apply -f pv.yaml
    kubectl get pv  # 状态为 Available,表示可用

    步骤 3:创建 PVC

    创建一个 PVC,请求 2Gi 大小、支持多节点读写(RWX)的存储:

    # pvc.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: my-pvc  # PVC 名称
    spec:
      accessModes: ["ReadWriteMany"]  # 与 PV 的访问模式匹配
      resources:
        requests:
          storage: 2Gi  # 请求的存储大小

    创建 PVC 并查看绑定状态:

    kubectl apply -f pvc.yaml
    kubectl get pvc  # 状态为 Bound,表示已与 PV 绑定(此处绑定 v2)
    kubectl get pv v2  # 状态变为 Bound,关联 PVC default/my-pvc

    步骤 4:Pod 挂载 PVC

    创建 Nginx Pod,挂载 my-pvc 这个 PVC:

    # pod_pvc.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-pvc
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: nginx-html
          mountPath: /usr/share/nginx/html  # 挂载到 Nginx 网页目录
      volumes:
      - name: nginx-html
        persistentVolumeClaim:
          claimName: my-pvc  # 关联 PVC 名称

    验证数据持久化:

    kubectl apply -f pod_pvc.yaml
    # 在 NFS 服务端的 v2 目录创建测试页面
    echo "Hello PV/PVC" > /data/volume_test/v2/index.html
    # 访问 Pod,验证内容
    curl $(kubectl get pods pod-pvc -o jsonpath='{.status.podIP}')  # 输出 "Hello PV/PVC"

    4.3 回收策略验证(Retain)

    默认回收策略为 Retain,删除 PVC 后 PV 状态变为 Released,数据保留:

    kubectl delete pvc my-pvc
    kubectl get pv v2  # 状态为 Released
    # 若需重新使用,需手动删除 PV(数据仍在 NFS 目录)
    kubectl delete pv v2
    # 重新创建 PVC 会再次匹配可用 PV(或新建 PV)

    5. StorageClass:动态生成 PV 的自动化方案

    PV/PVC 模式需管理员手动创建大量 PV,运维成本高。StorageClass 作为 “PV 模板”,可动态生成 PV—— 当 PVC 请求存储时,K8s 调用 StorageClass 关联的 “存储供应者(Provisioner)” 自动创建 PV,实现存储管理自动化。

    5.1 核心组件

    • Provisioner:存储供应者,负责根据 StorageClass 配置创建实际存储(如 NFS Provisioner 自动创建 NFS 目录并生成 PV);

    • orageClass:定义 PV 的模板参数(如存储类型、回收策略、挂载选项),关联 Provisioner;

    • 动态供给:PVC 引用 StorageClass 后,K8s 自动触发 Provisioner 创建 PV 并完成绑定。

    5.2 实战:基于 NFS 的 StorageClass 配置

    以 NFS 为例,需先部署 NFS Provisioner(作为存储供应者),再创建 StorageClass,最终通过 PVC 动态生成 PV。

    步骤 1:部署 NFS Provisioner

    NFS Provisioner 是一个容器化工具,负责监听 PVC 请求,自动在 NFS 服务端创建目录并生成 PV。

    1. 创建 ServiceAccount(授权用)
      Provisioner 需要权限操作 PV/PVC 资源,需创建专用 ServiceAccount 并授权:

    # serviceaccount.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: nfs-provisioner  # 服务账号名称
    kubectl apply -f serviceaccount.yaml
    # 绑定集群管理员权限(简化测试,生产环境需最小权限)
    kubectl create clusterrolebinding nfs-provisioner-binding \
      --clusterrole=cluster-admin \
      --serviceaccount=default:nfs-provisioner

    2. 准备 NFS 共享目录
    在 NFS 服务端(192.168.1.11)创建 Provisioner 专用共享目录:

    mkdir /data/nfs_pro -p
    # 更新 NFS 配置,允许集群网段访问
    cat >> /etc/exports << EOF
    /data/nfs_pro 192.168.1.0/24(rw,no_root_squash)
    EOF
    exportfs -arv  # 生效配置

    3.部署 NFS Provisioner 容器
    使用现成的 NFS Provisioner 镜像(如 registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0):

    # nfs-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nfs-provisioner
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nfs-provisioner
      template:
        metadata:
          labels:
            app: nfs-provisioner
        spec:
          serviceAccount: nfs-provisioner  # 关联服务账号
          containers:
          - name: nfs-provisioner
            image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
            env:
            - name: PROVISIONER_NAME  # Provisioner 名称(后续 StorageClass 需引用)
              value: example.com/nfs
            - name: NFS_SERVER  # NFS 服务端 IP
              value: 192.168.1.11
            - name: NFS_PATH  # NFS 共享目录
              value: /data/nfs_pro
            volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes  # Provisioner 工作目录
          volumes:
          - name: nfs-client-root
            nfs:
              server: 192.168.1.11
              path: /data/nfs_pro
    kubectl apply -f nfs-deployment.yaml
    # 验证 Provisioner 运行状态(需为 Running)
    kubectl get pods -l app=nfs-provisioner
    步骤 2:创建 StorageClass

    定义 StorageClass,关联上述 Provisioner,指定默认回收策略:

    # nfs-storageclass.yaml
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: nfs-sc  # StorageClass 名称(后续 PVC 需引用)
    provisioner: example.com/nfs  # 必须与 Provisioner 的 PROVISIONER_NAME 一致
    reclaimPolicy: Delete  # 回收策略:删除 PVC 时自动删除 PV 及 NFS 目录
    allowVolumeExpansion: true  # 允许后续扩容 PVC
    kubectl apply -f nfs-storageclass.yaml
    # 验证 StorageClass(默认存储类可加 annotation: storageclass.kubernetes.io/is-default-class: "true")
    kubectl get sc  # 简写,全称 storageclass
    步骤 3:创建 PVC 并动态生成 PV

    创建 PVC 时引用 nfs-sc 这个 StorageClass,K8s 会自动触发 Provisioner 生成 PV:

    # pvc-dynamic.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: test-pvc-dynamic
    spec:
      accessModes: ["ReadWriteMany"]  # 多节点读写
      resources:
        requests:
          storage: 1Gi  # 请求 1Gi 存储
      storageClassName: nfs-sc  # 引用 StorageClass 名称
    kubectl apply -f pvc-dynamic.yaml
    # 验证:PVC 状态变为 Bound,且自动生成 PV
    kubectl get pvc test-pvc-dynamic  # STATUS: Bound
    kubectl get pv  # 新增一个 PV,名称格式为 pvc-<PVC-UID>,关联该 PVC
    步骤 4:Pod 挂载动态 PVC

    与挂载静态 PVC 一致,直接引用动态生成的 PVC 名称:

    # pod-dynamic.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-dynamic-storage
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: dynamic-volume
          mountPath: /usr/share/nginx/html
      volumes:
      - name: dynamic-volume
        persistentVolumeClaim:
          claimName: test-pvc-dynamic  # 引用动态 PVC
    kubectl apply -f pod-dynamic.yaml
    # 验证数据写入:在 NFS 自动创建的目录中写文件,Pod 内可访问
    echo "Dynamic PV from StorageClass" > /data/nfs_pro/default-test-pvc-dynamic-pvc-xxx/index.html
    curl $(kubectl get pods pod-dynamic-storage -o jsonpath='{.status.podIP}')  # 输出写入内容

    5.3 StorageClass 核心优势

    1. 自动化运维:无需手动创建 PV,PVC 提交后自动生成,降低运维成本;
    2. 标准化配置:通过 StorageClass 统一管理 PV 模板(如存储类型、权限),避免配置混乱;
    3. 支持扩容:开启 allowVolumeExpansion: true 后,可直接编辑 PVC 扩容存储(仅支持扩容,不支持缩容)。

    三、总结:存储方案选型建议

    存储方案适用场景优点缺点
    EmptyDir临时数据、容器间共享临时文件(如缓存)无需配置,随 Pod 自动创建数据不持久,Pod 删除后丢失
    HostPath单节点测试、节点级日志存储配置简单,数据在节点本地持久化不支持跨节点,存在单点故障
    NFS中小规模集群、多 Pod 共享数据(如配置文件)跨节点共享,部署简单服务端单点故障(需高可用 NFS 集群)
    PV/PVC生产环境固定存储需求(如数据库)解耦存储与应用,支持权限控制需手动创建 PV,运维成本高
    StorageClass生产环境大规模集群、动态存储需求自动化生成 PV,支持标准化配置、扩容需部署 Provisioner,初始配置稍复杂

    生产环境中,推荐优先使用 StorageClass + 分布式存储(如 CephFS、GlusterFS),兼顾自动化运维与高可用性;中小规模集群或测试场景,可使用 NFS 或 HostPath 简化配置。

    注:点赞+关注+收藏,下期我们接着讲剩余两种工作负载,不见不散。

    Logo

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

    更多推荐