1. 为什么需要K8s持久化存储

在Kubernetes中运行有状态服务时,数据持久化是个绕不开的话题。想象一下这样的场景:你部署了一个MySQL数据库,运行几天后Pod因为节点故障被重新调度到其他节点,结果发现所有数据都丢失了——这就是典型的没有配置持久化存储的后果。

传统Volume确实能解决部分数据持久化问题,但它存在两个致命缺陷:一是生命周期与Pod绑定,Pod删除后数据就没了;二是管理耦合度高,开发人员需要了解底层存储细节。我曾经在一个电商项目中就踩过这个坑,当时为了快速上线直接用了emptyDir,结果促销活动时Pod崩溃导致订单数据全部丢失,教训相当深刻。

PV和PVC的引入完美解决了这些问题。PV(PersistentVolume)是集群中的一块存储空间,由管理员预先配置;PVC(PersistentVolumeClaim)则是用户对存储资源的申请。这种解耦设计让开发人员只需声明"我需要多少存储空间",而不必关心存储的具体实现方式。就像我们去酒店入住时,只需要说明需要大床房还是双床房,而不必了解房间的具体位置和构造。

2. 静态供给实战:NFS示例

2.1 环境准备

我们先从最经典的NFS方案开始。在我的实验环境中,使用1台CentOS 7作为NFS服务器(IP:192.168.1.100),两个工作节点已安装nfs-utils:

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

在NFS服务器上创建共享目录并配置权限:

mkdir /nfs_share
chmod 777 /nfs_share
echo "/nfs_share *(rw,no_root_squash,sync)" > /etc/exports
systemctl enable --now nfs-server

2.2 创建PV

接下来定义PV资源,这里有个小技巧:accessModes的选择很关键。ReadWriteOnce适合单Pod读写场景,ReadWriteMany则支持多Pod同时挂载。我们创建一个1Gi的PV:

# nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-demo
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  nfs:
    path: /nfs_share
    server: 192.168.1.100

应用配置后,可以用kubectl get pv查看状态,应该显示Available:

kubectl apply -f nfs-pv.yaml
kubectl get pv

2.3 创建PVC

PVC就像存储资源的"申请单",这里要注意storageClassName必须与PV匹配:

# nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-demo
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: manual

创建后检查绑定状态,正常情况PVC会Bound到我们创建的PV:

kubectl apply -f nfs-pvc.yaml
kubectl get pvc

2.4 在Pod中使用

现在可以在Deployment中引用这个PVC了。这里演示一个NGINX示例:

# nginx-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-with-pvc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: nfs-volume
      volumes:
      - name: nfs-volume
        persistentVolumeClaim:
          claimName: nfs-pvc-demo

创建Pod后,我们可以测试数据持久化:

kubectl exec -it nginx-with-pvc-xxx -- sh -c "echo 'Hello PV' > /usr/share/nginx/html/test.txt"
# 删除Pod后重新创建,检查文件是否仍然存在

3. 动态供给:StorageClass方案

3.1 静态供给的局限性

虽然静态供给能工作,但在实际生产环境中会暴露明显问题:每次新增存储都需要管理员手动创建PV。我曾管理过一个50节点的集群,每周要处理数十个存储申请,手动操作不仅效率低下还容易出错。

动态供给通过StorageClass实现了"按需分配"的自动化流程。当用户创建PVC时,系统会自动创建对应的PV。这就像云硬盘服务,用户只需要指定容量和类型,系统会自动在后端完成资源分配。

3.2 部署NFS Provisioner

由于Kubernetes原生不支持NFS动态供给,我们需要先部署nfs-subdir-external-provisioner(社区维护的方案):

helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
    --set nfs.server=192.168.1.100 \
    --set nfs.path=/nfs_share \
    --set storageClass.name=nfs-sc

这会自动创建StorageClass,可以通过以下命令验证:

kubectl get sc
# 应该能看到名为nfs-sc的StorageClass

3.3 动态PVC实战

现在我们可以创建直接使用StorageClass的PVC了:

# dynamic-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc-demo
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
  storageClassName: nfs-sc

创建这个PVC后,神奇的事情发生了——系统会自动创建对应的PV:

kubectl apply -f dynamic-pvc.yaml
kubectl get pvc  # 查看PVC状态
kubectl get pv   # 会自动出现一个2Gi的PV

3.4 StatefulSet中的应用

对于有状态服务,动态供给的优势更加明显。下面是一个MySQL StatefulSet示例:

# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "password"
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "nfs-sc"
      resources:
        requests:
          storage: 5Gi

StatefulSet会为每个Pod实例自动创建独立的PVC和PV,极大简化了有状态服务的部署流程。

4. 生产环境最佳实践

4.1 存储选型建议

根据多年经验,不同场景下的存储选择很有讲究:

  • 开发测试环境:NFS是最经济的选择,但要注意性能问题
  • 云环境:直接使用云厂商提供的StorageClass(如AWS EBS、Azure Disk)
  • 高性能需求:考虑Ceph RBD或Local PV方案
  • 多读场景:ReadWriteMany模式的存储如CephFS

4.2 性能调优技巧

在某个电商项目中,我们曾遇到NFS性能瓶颈,通过以下优化手段将IOPS提升了3倍:

  1. 调整NFS挂载参数,添加noatime,nodiratime选项
  2. 为关键业务PVC设置volumeMode: Block使用块设备
  3. 合理设置PV的回收策略,频繁读写的PVC建议用Retain

4.3 监控与运维

完善的监控是存储稳定的关键。建议:

  1. 使用Prometheus监控PV/PVC使用率
  2. 设置StorageClass的allowVolumeExpansion: true以便后期扩容
  3. 定期检查PV的VolumeHealth状态
# PVC扩容示例(需要StorageClass支持)
kubectl patch pvc dynamic-pvc-demo -p '{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'

4.4 常见故障排查

遇到PVC一直处于Pending状态时,可以按以下步骤排查:

  1. 检查StorageClass是否存在且配置正确
  2. 查看Provisioner Pod日志:kubectl logs -f nfs-provisioner-xxx
  3. 确认NFS服务器导出目录权限
  4. 检查kube-controller-manager日志中的存储相关错误

记得有一次我们的PVC无法绑定,最后发现是StorageClass的provisioner名称拼写错误,这种小细节往往最容易忽视。

Logo

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

更多推荐