Volume - 数据持久化

Volume - 数据持久化

为什么需要 Volume

容器中的文件系统是临时的:

  • 容器重启:文件丢失
  • Pod 删除:数据丢失
  • 多容器共享:无法共享数据

Volume(存储卷) 解决了这些问题,提供持久化存储。

Volume 类型

Kubernetes 支持多种 Volume 类型:

临时存储

  • emptyDir:Pod 生命周期内的临时存储
  • hostPath:挂载节点文件系统

持久存储

  • PersistentVolume:持久卷
  • PersistentVolumeClaim:持久卷声明

网络存储

  • nfs:NFS 网络文件系统
  • cephfs:Ceph 文件系统
  • glusterfs:GlusterFS

云存储

  • awsElasticBlockStore:AWS EBS
  • gcePersistentDisk:GCP 持久磁盘
  • azureDisk:Azure 磁盘

配置存储

  • configMap:ConfigMap 作为文件
  • secret:Secret 作为文件

emptyDir - 临时存储

emptyDir 在 Pod 创建时创建,Pod 删除时清空。

基本用法

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: writer
    image: busybox
    command: ['sh', '-c', 'echo "Hello" > /data/message.txt; sleep 3600']
    volumeMounts:
    - name: shared-data
      mountPath: /data
  - name: reader
    image: busybox
    command: ['sh', '-c', 'while true; do cat /data/message.txt; sleep 5; done']
    volumeMounts:
    - name: shared-data
      mountPath: /data
  volumes:
  - name: shared-data
    emptyDir: {}

使用内存作为存储

volumes:
- name: cache-volume
  emptyDir:
    medium: Memory      # 使用内存(tmpfs)
    sizeLimit: 1Gi      # 限制大小

使用场景

  • 缓存数据
  • 临时文件
  • 容器间数据共享
  • 数据处理管道

hostPath - 节点存储

hostPath 挂载节点上的文件或目录到 Pod。

apiVersion: v1
kind: Pod
metadata:
  name: test-hostpath
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: host-volume
      mountPath: /usr/share/nginx/html
  volumes:
  - name: host-volume
    hostPath:
      path: /data/web      # 节点路径
      type: DirectoryOrCreate

hostPath 类型

  • DirectoryOrCreate:目录不存在则创建
  • Directory:必须存在的目录
  • FileOrCreate:文件不存在则创建
  • File:必须存在的文件
  • Socket:UNIX socket
  • CharDevice:字符设备
  • BlockDevice:块设备

⚠️ 注意事项

  • 仅用于单节点测试
  • 不适合生产环境(Pod 可能调度到其他节点)
  • 存在安全风险

PersistentVolume (PV)

PV 是集群级别的存储资源,由管理员创建。

创建 PV

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-example
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data

访问模式

  • ReadWriteOnce (RWO):单节点读写
  • ReadOnlyMany (ROX):多节点只读
  • ReadWriteMany (RWX):多节点读写

回收策略

  • Retain:保留数据,需手动清理
  • Delete:自动删除
  • Recycle:清空数据后重用(已废弃)

PersistentVolumeClaim (PVC)

PVC 是用户对存储的请求,类似 Pod 消费节点资源。

创建 PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: manual

在 Pod 中使用

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-pvc
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: persistent-storage
      mountPath: /usr/share/nginx/html
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: pvc-example

PV 和 PVC 的关系

┌─────────────────┐       ┌─────────────────┐
│ PersistentVolume│◄──────│PersistentVolume │
│   (管理员创建)   │  绑定  │     Claim       │
│   (集群资源)     │       │   (用户请求)     │
└─────────────────┘       └─────────────────┘
                                   ▲
                                   │ 引用
                                   │
                          ┌────────┴────────┐
                          │      Pod        │
                          └─────────────────┘

绑定过程

  1. 用户创建 PVC,指定存储需求
  2. Kubernetes 查找匹配的 PV
  3. 将 PV 绑定到 PVC
  4. Pod 通过 PVC 使用存储

StorageClass - 动态供应

StorageClass 提供动态创建 PV 的能力。

创建 StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true

使用 StorageClass

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dynamic-pvc
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: fast-storage
  resources:
    requests:
      storage: 10Gi

创建 PVC 后,StorageClass 会自动创建 PV。

默认 StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/gce-pd

查看默认 StorageClass:

kubectl get storageclass

# 输出
NAME                 PROVISIONER            RECLAIMPOLICY   AGE
standard (default)   kubernetes.io/gce-pd   Delete          10d
fast-storage         kubernetes.io/aws-ebs  Delete          5d

实战示例

MySQL 持久化存储

# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: standard

---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "password"
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc

---
# Service
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306

WordPress + MySQL

# MySQL PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

---
# WordPress PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wordpress-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

---
# MySQL Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "rootpassword"
        - name: MYSQL_DATABASE
          value: "wordpress"
        - name: MYSQL_USER
          value: "wordpress"
        - name: MYSQL_PASSWORD
          value: "wordpresspassword"
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc

---
# WordPress Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  replicas: 2
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - name: wordpress
        image: wordpress:latest
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql
        - name: WORDPRESS_DB_USER
          value: wordpress
        - name: WORDPRESS_DB_PASSWORD
          value: wordpresspassword
        - name: WORDPRESS_DB_NAME
          value: wordpress
        ports:
        - containerPort: 80
        volumeMounts:
        - name: wordpress-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-storage
        persistentVolumeClaim:
          claimName: wordpress-pvc

扩容 PVC

如果 StorageClass 支持扩容(allowVolumeExpansion: true):

# 编辑 PVC
kubectl edit pvc mysql-pvc

# 修改 storage 大小
spec:
  resources:
    requests:
      storage: 30Gi  # 从 20Gi 扩容到 30Gi

或使用 patch:

kubectl patch pvc mysql-pvc -p '{"spec":{"resources":{"requests":{"storage":"30Gi"}}}}'

常用命令

# PersistentVolume
kubectl get pv
kubectl describe pv <name>
kubectl delete pv <name>

# PersistentVolumeClaim
kubectl get pvc
kubectl describe pvc <name>
kubectl delete pvc <name>

# StorageClass
kubectl get storageclass
kubectl describe storageclass <name>

# 查看 PVC 绑定状态
kubectl get pvc -w

# 查看 Pod 使用的 Volume
kubectl describe pod <name> | grep -A5 Volumes

Volume 状态

PV 状态

  • Available:可用,未绑定
  • Bound:已绑定到 PVC
  • Released:PVC 已删除,但资源未回收
  • Failed:自动回收失败

PVC 状态

  • Pending:等待绑定 PV
  • Bound:已绑定到 PV
  • Lost:关联的 PV 不存在

最佳实践

1. 生产环境使用动态供应

# 推荐:使用 StorageClass
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-pvc
spec:
  storageClassName: fast-ssd
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

2. 选择合适的访问模式

  • 数据库:ReadWriteOnce
  • 共享文件:ReadWriteMany
  • 只读数据:ReadOnlyMany

3. 设置合适的回收策略

# 生产环境
persistentVolumeReclaimPolicy: Retain

# 测试环境
persistentVolumeReclaimPolicy: Delete

4. 为 StatefulSet 使用 volumeClaimTemplate

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

5. 监控存储使用

# 查看 PVC 使用情况
kubectl get pvc -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.resources.requests.storage,USED:.status.capacity.storage

# 查看 Pod 磁盘使用
kubectl exec <pod-name> -- df -h

6. 备份重要数据

即使使用持久化存储,也要定期备份:

# 使用 kubectl cp 备份
kubectl cp <namespace>/<pod-name>:/data ./backup

# 使用专业备份工具
# - Velero
# - Kasten K10

小结

Volume 是 Kubernetes 实现数据持久化的核心机制:

临时存储

  • emptyDir:Pod 生命周期内有效
  • hostPath:挂载节点路径(测试用)

持久存储

  • PV:集群级存储资源
  • PVC:用户存储请求
  • StorageClass:动态供应

关键概念

  • PV/PVC 解耦存储和使用
  • StorageClass 实现动态供应
  • 选择合适的访问模式和回收策略
  • 生产环境使用云存储或网络存储

下一章我们将进入进阶篇,学习 StatefulSet 管理有状态应用。