九、存储管理:数据持久化的艺术
1. 为什么Kubernetes需要存储管理
1.1 容器数据的”短命”问题
容器的本质特性是临时的,删除后数据全部丢失。这对于有状态应用(如数据库)是致命的。
演示问题:
# 启动MySQL,写入数据kubectl run mysql --image=mysql:8.0 --env="MYSQL_ROOT_PASSWORD=pass"kubectl exec mysql -- mysql -uroot -ppass -e "CREATE DATABASE testdb;"
# 删除Podkubectl delete pod mysql
# 重新创建,数据消失kubectl run mysql --image=mysql:8.0 --env="MYSQL_ROOT_PASSWORD=pass"kubectl exec mysql -- mysql -uroot -ppass -e "USE testdb;"# 报错:Database doesn't existKubernetes的存储挑战:
- Pod漂移:Pod可能在不同节点重启
- 多副本共享:多个Pod需访问同一数据
- 生命周期管理:存储生命周期独立于Pod
1.2 存储架构总览
Kubernetes通过四层架构解决存储问题:
四大组件:
| 组件 | 角色 | 说明 |
|---|---|---|
| Volume | 最基础 | Pod内定义,生命周期绑定Pod |
| PV | 存储资源 | 集群级,代表真实存储空间 |
| PVC | 存储申请 | 用户申请,自动匹配PV |
| StorageClass | 自动化 | 动态创建PV,存储分类 |
静态 vs 动态供给:
静态供给:管理员手动创建PV → 用户创建PVC → 自动绑定
动态供给:用户创建PVC(指定StorageClass)→ 自动创建PV → 自动绑定2. Volume:最基础的存储
2.1 emptyDir:临时共享存储
emptyDir 是最简单的Volume,Pod创建时自动创建,Pod删除时数据丢失。
使用场景:
- 容器间数据共享
- 临时缓存
- 计算中间结果
示例:
apiVersion: v1kind: Podmetadata: name: emptydir-demospec: containers: - name: writer image: busybox command: ['sh', '-c', 'echo "Hello" > /data/msg.txt; sleep 3600'] volumeMounts: - name: shared-data mountPath: /data
- name: reader image: busybox command: ['sh', '-c', 'while true; do cat /data/msg.txt 2>/dev/null; sleep 5; done'] volumeMounts: - name: shared-data mountPath: /data
volumes: - name: shared-data emptyDir: {} # 使用磁盘 # emptyDir: # medium: Memory # 使用内存(更快)2.2 hostPath:挂载宿主机目录
hostPath 将宿主机目录挂载到Pod。
⚠️ 注意:
- 不同节点路径可能不同
- 有安全风险
- 不推荐生产环境
示例:
apiVersion: v1kind: Podmetadata: name: hostpath-demospec: containers: - name: nginx image: nginx:1.20 volumeMounts: - name: host-data mountPath: /usr/share/nginx/html
volumes: - name: host-data hostPath: path: /data/nginx type: DirectoryOrCreatehostPath类型:
| type | 说明 |
|---|---|
| DirectoryOrCreate | 目录不存在则创建 |
| Directory | 必须存在的目录 |
| File | 必须存在的文件 |
3. PersistentVolume(PV):持久化存储资源
3.1 PV核心概念
PV 是集群级别的存储资源,由管理员创建或StorageClass动态创建。
特点:
- 独立于Pod生命周期
- 集群级资源(无namespace)
- 代表真实存储空间
PV生命周期:
Available(可用)→ Bound(已绑定)→ Released(已释放)→ Failed(失败)3.2 PV配置详解
apiVersion: v1kind: PersistentVolumemetadata: name: pv-nfs-001spec: capacity: storage: 10Gi
accessModes: - ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-storage
nfs: server: 192.168.100.14 path: /data/nfs/pv-0013.3 访问模式(AccessModes)
| 模式 | 缩写 | 说明 | 适用场景 |
|---|---|---|---|
| ReadWriteOnce | RWO | 单节点读写 | 数据库 |
| ReadOnlyMany | ROX | 多节点只读 | 静态资源 |
| ReadWriteMany | RWX | 多节点读写 | 共享文件系统 |
不同存储支持的模式:
| 存储类型 | RWO | ROX | RWX |
|---|---|---|---|
| hostPath | ✅ | ❌ | ❌ |
| NFS | ✅ | ✅ | ✅ |
| Ceph RBD | ✅ | ✅ | ❌ |
| 云盘 | ✅ | ❌ | ❌ |
3.4 回收策略(ReclaimPolicy)
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Retain | 保留数据,需手动清理 | 生产环境 |
| Delete | 自动删除PV和数据 | 动态供给 |
| Recycle | 删除数据(已废弃) | 不推荐 |
4. PersistentVolumeClaim(PVC):存储申请
4.1 PVC核心概念
PVC 是用户对存储的申请,类似于Pod对CPU的申请。
特点:
- 命名空间级资源
- 用户无需知道底层存储细节
- K8s自动匹配PV
4.2 PVC配置
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: mysql-pvc namespace: defaultspec: accessModes: - ReadWriteOnce
resources: requests: storage: 10Gi
storageClassName: nfs-storage4.3 PVC绑定规则
K8s如何选择PV?
匹配条件(全部满足):1. accessModes 匹配2. storage大小满足(PV >= PVC)3. storageClassName 匹配
优先级:1. 大小精确匹配2. 最小满足(PV略大于PVC)4.4 Pod使用PVC
apiVersion: v1kind: Podmetadata: name: mysql-podspec: containers: - name: mysql image: mysql:8.0 volumeMounts: - name: mysql-data mountPath: /var/lib/mysql
volumes: - name: mysql-data persistentVolumeClaim: claimName: mysql-pvc5. StorageClass:自动化存储供应
5.1 StorageClass是什么
StorageClass 是存储的”自动售货机”,用户申请存储时自动创建PV。
功能:
- 定义存储类型和参数
- 自动创建PV(动态供给)
- 不同性能等级(SSD/HDD)
5.2 StorageClass配置
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: nfs-storageprovisioner: nfs-provisionerparameters: archiveOnDelete: "false"reclaimPolicy: DeletevolumeBindingMode: Immediate关键字段:
| 字段 | 说明 |
|---|---|
| provisioner | 存储供应器(如nfs-provisioner) |
| parameters | 供应器特定参数 |
| reclaimPolicy | PV回收策略 |
| volumeBindingMode | 绑定模式(Immediate/WaitForFirstConsumer) |
5.3 设置默认StorageClass
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: nfs-storage annotations: storageclass.kubernetes.io/is-default-class: "true"provisioner: nfs-provisioner# 查看默认StorageClasskubectl get storageclass# NAME PROVISIONER AGE# nfs-storage (default) nfs-provisioner 1d6. 主流后端存储对比
后端存储是PV真正存储数据的地方,选择合适的存储方案至关重要。
6.1 存储方案对比
| 存储类型 | 性能 | 可靠性 | 复杂度 | 成本 | 访问模式 | 适用场景 |
|---|---|---|---|---|---|---|
| hostPath | ⭐⭐⭐ | ⭐ | ⭐ | 免费 | RWO | 开发测试 |
| NFS | ⭐⭐ | ⭐⭐ | ⭐⭐ | 低 | RWO/ROX/RWX | 开发环境、小规模生产 |
| Ceph/Rook | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 中 | RWO/ROX/RWX | 企业级生产环境 |
| 云盘(EBS) | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ | 高 | RWO | 云环境生产 |
| 对象存储(S3) | ⭐⭐ | ⭐⭐⭐ | ⭐ | 中 | ROX | 静态资源、备份 |
6.2 详细分析
1. hostPath(宿主机目录)
优点:✅ 简单直接,无需额外配置✅ 性能最好(本地磁盘)✅ 零成本
缺点:❌ 数据绑定节点,Pod漂移会丢失数据❌ 无法多节点共享❌ 安全风险高
适用场景:- 开发测试- 单节点集群- DaemonSet日志收集2. NFS(网络文件系统)
优点:✅ 支持RWX多节点读写✅ 配置简单✅ 成本低
缺点:❌ 性能一般(网络IO)❌ 单点故障风险(NFS Server挂了全挂)❌ 不适合高并发
适用场景:- 开发环境- 小规模生产(非核心业务)- 共享配置文件3. Ceph/Rook(分布式存储)
优点:✅ 高可用(数据多副本)✅ 可扩展(横向扩展)✅ 性能好✅ 支持RWX
缺点:❌ 部署复杂❌ 需要专业运维❌ 资源消耗大(至少3节点)
适用场景:- 企业级生产环境- 大规模集群- 对数据可靠性要求高4. 云盘(EBS/GCE Disk)
优点:✅ 高可用(云厂商保障)✅ 易用(云平台集成)✅ 性能可选(SSD/HDD)✅ 快照备份方便
缺点:❌ 只支持RWO❌ 成本较高❌ 绑定云厂商
适用场景:- 云环境生产- 数据库存储- 单实例应用5. 对象存储(S3/OSS)
优点:✅ 无限容量✅ 高可用✅ 成本低(按量付费)
缺点:❌ 不支持POSIX文件系统❌ 只适合读多写少场景
适用场景:- 静态资源(图片、视频)- 备份归档- 大数据存储6.3 选择建议
⚠️ 生产环境建议:
1. 能用云存储就用云存储(省心)2. 自建存储需要专业运维团队3. 数据库等核心应用使用高可用存储4. 定期备份,备份,备份!7. 实战1:使用hostPath实现持久化
7.1 实战目标
使用hostPath验证数据持久化,理解存储的基本原理。
场景: 创建Nginx Pod,使用hostPath存储网页,验证Pod删除后数据是否保留。
7.2 准备工作
# 在node1节点创建目录ssh root@192.168.100.21mkdir -p /data/nginx-htmlecho "<h1>Hello from hostPath - v1</h1>" > /data/nginx-html/index.html7.3 创建使用hostPath的Pod
hostpath-nginx.yaml:
apiVersion: v1kind: Podmetadata: name: nginx-hostpath labels: app: nginxspec: # 指定调度到node1(hostPath绑定节点) nodeSelector: kubernetes.io/hostname: k8s-node1
containers: - name: nginx image: nginx:1.20 ports: - containerPort: 80 volumeMounts: - name: html-data mountPath: /usr/share/nginx/html
volumes: - name: html-data hostPath: path: /data/nginx-html type: Directory---apiVersion: v1kind: Servicemetadata: name: nginx-hostpathspec: selector: app: nginx ports: - port: 80 nodePort: 30090 type: NodePortkubectl apply -f hostpath-nginx.yaml7.4 测试数据持久化
# 1. 访问Nginxcurl http://192.168.100.21:30090# 输出:<h1>Hello from hostPath - v1</h1>
# 2. 在容器内修改文件kubectl exec nginx-hostpath -- sh -c 'echo "<h1>Modified in container - v2</h1>" > /usr/share/nginx/html/index.html'
# 3. 再次访问,看到更新curl http://192.168.100.21:30090# 输出:<h1>Modified in container - v2</h1>
# 4. 删除Podkubectl delete pod nginx-hostpath
# 5. 重新创建Podkubectl apply -f hostpath-nginx.yaml
# 6. 数据还在!curl http://192.168.100.21:30090# 输出:<h1>Modified in container - v2</h1>
# 7. 在宿主机上验证ssh root@192.168.100.21 "cat /data/nginx-html/index.html"# 输出:<h1>Modified in container - v2</h1>⚠️ 测试Pod漂移问题:
# 删除nodeSelector,让Pod可以调度到任意节点kubectl delete -f hostpath-nginx.yaml
# 修改YAML,去掉nodeSelector# 重新创建
# 如果Pod调度到node2,会发现访问失败# 因为node2的/data/nginx-html目录不存在或为空结论:
- ✅ 数据持久化成功
- ❌ 但数据绑定节点,不适合生产环境
8. 实战2:使用NFS实现动态供给
8.1 实战目标
部署NFS服务器和NFS Provisioner,实现PVC自动创建PV的动态供给。
8.2 部署NFS服务器
在harbor机器(192.168.100.14)上安装NFS:
# 安装NFS服务yum install -y nfs-utils rpcbind
# 创建共享目录mkdir -p /data/nfs-storagechmod 777 /data/nfs-storage
# 配置NFS共享cat >> /etc/exports << EOF/data/nfs-storage *(rw,sync,no_root_squash,no_all_squash)EOF
# 启动NFS服务systemctl start rpcbindsystemctl start nfs-serversystemctl enable rpcbindsystemctl enable nfs-server
# 刷新NFS配置exportfs -r
# 查看共享目录showmount -e localhost# 输出:/data/nfs-storage *在所有K8s节点安装NFS客户端:
# 在master、node1、node2上执行yum install -y nfs-utils
# 测试挂载mount -t nfs 192.168.100.14:/data/nfs-storage /mntls /mntumount /mnt8.3 部署NFS Provisioner
创建nfs-provisioner.yaml:
# RBAC权限apiVersion: v1kind: ServiceAccountmetadata: name: nfs-provisioner namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: nfs-provisionerrules:- 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: ["create", "update", "patch"]---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: nfs-provisionerroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: nfs-provisionersubjects:- kind: ServiceAccount name: nfs-provisioner namespace: kube-system---# DeploymentapiVersion: apps/v1kind: Deploymentmetadata: name: nfs-provisioner namespace: kube-systemspec: replicas: 1 selector: matchLabels: app: nfs-provisioner template: metadata: labels: app: nfs-provisioner spec: serviceAccountName: nfs-provisioner containers: - name: nfs-provisioner image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner:latest volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: nfs-provisioner # Provisioner名称 - name: NFS_SERVER value: 192.168.100.14 # NFS服务器地址 - name: NFS_PATH value: /data/nfs-storage # NFS共享路径 volumes: - name: nfs-client-root nfs: server: 192.168.100.14 path: /data/nfs-storage---# StorageClassapiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: nfs-storage annotations: storageclass.kubernetes.io/is-default-class: "true" # 设为默认provisioner: nfs-provisionerparameters: archiveOnDelete: "false" # 删除PVC时不归档数据reclaimPolicy: DeletevolumeBindingMode: Immediatekubectl apply -f nfs-provisioner.yaml
# 查看Provisioner状态kubectl get pods -n kube-system -l app=nfs-provisioner
# 查看StorageClasskubectl get storageclass# NAME PROVISIONER RECLAIMPOLICY# nfs-storage (default) nfs-provisioner Delete8.4 测试动态供给
创建test-pvc.yaml:
apiVersion: v1kind: PersistentVolumeClaimmetadata: name: test-pvcspec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi storageClassName: nfs-storagekubectl apply -f test-pvc.yaml
# 查看PVC(自动绑定)kubectl get pvc test-pvc# NAME STATUS VOLUME CAPACITY# test-pvc Bound pvc-abc123-... 1Gi
# 查看自动创建的PVkubectl get pv# NAME CAPACITY ACCESS MODES STATUS# pvc-abc123-... 1Gi RWX Bound
# 在NFS服务器上查看自动创建的目录ssh root@192.168.100.14 "ls -l /data/nfs-storage/"# drwxrwxrwx 2 root root 6 Jan 15 10:30 default-test-pvc-pvc-abc123...创建Pod使用PVC:
apiVersion: v1kind: Podmetadata: name: test-nfs-podspec: containers: - name: app image: nginx:1.20 volumeMounts: - name: data mountPath: /usr/share/nginx/html volumes: - name: data persistentVolumeClaim: claimName: test-pvckubectl apply -f test-nfs-pod.yaml
# 写入测试数据kubectl exec test-nfs-pod -- sh -c 'echo "<h1>NFS Dynamic Provisioning Works!</h1>" > /usr/share/nginx/html/index.html'
# 在NFS服务器验证ssh root@192.168.100.14 "cat /data/nfs-storage/default-test-pvc-*/index.html"# 输出:<h1>NFS Dynamic Provisioning Works!</h1>
# 删除Pod,数据保留kubectl delete pod test-nfs-pod
# 重新创建,数据还在kubectl apply -f test-nfs-pod.yamlkubectl exec test-nfs-pod -- cat /usr/share/nginx/html/index.html8.5 测试回收策略
# 删除PVCkubectl delete pvc test-pvc
# PV自动删除(reclaimPolicy: Delete)kubectl get pv# 无输出(PV已删除)
# NFS服务器上的数据也被删除ssh root@192.168.100.14 "ls /data/nfs-storage/"# 空目录(数据已删除)9. 实战3:StatefulSet持久化MySQL
9.1 实战目标
使用StatefulSet部署MySQL集群,每个实例拥有独立的PVC,验证扩缩容时数据独立性。
9.2 创建StatefulSet MySQL
mysql-statefulset.yaml:
apiVersion: v1kind: Servicemetadata: name: mysql-headlessspec: clusterIP: None # Headless Service selector: app: mysql ports: - port: 3306---apiVersion: apps/v1kind: StatefulSetmetadata: name: mysqlspec: serviceName: mysql-headless replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD value: "MyPass123" volumeMounts: - name: data mountPath: /var/lib/mysql
# VolumeClaimTemplate(每个Pod独立PVC) volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] storageClassName: nfs-storage resources: requests: storage: 5Gikubectl apply -f mysql-statefulset.yaml9.3 验证独立存储
# 查看Podkubectl get pods -l app=mysql# NAME READY STATUS RESTARTS AGE# mysql-0 1/1 Running 0 1m# mysql-1 1/1 Running 0 50s# mysql-2 1/1 Running 0 40s
# 查看PVC(每个Pod独立PVC)kubectl get pvc# NAME STATUS VOLUME CAPACITY# data-mysql-0 Bound pvc-abc... 5Gi# data-mysql-1 Bound pvc-def... 5Gi# data-mysql-2 Bound pvc-ghi... 5Gi
# 在mysql-0中创建数据库kubectl exec mysql-0 -- mysql -uroot -pMyPass123 -e "CREATE DATABASE db0;"
# 在mysql-1中创建不同的数据库kubectl exec mysql-1 -- mysql -uroot -pMyPass123 -e "CREATE DATABASE db1;"
# 验证数据独立kubectl exec mysql-0 -- mysql -uroot -pMyPass123 -e "SHOW DATABASES;" | grep db# db0
kubectl exec mysql-1 -- mysql -uroot -pMyPass123 -e "SHOW DATABASES;" | grep db# db1
# 数据完全独立!9.4 测试扩缩容
# 缩容到1个副本kubectl scale statefulset mysql --replicas=1
# 查看Pod(mysql-1和mysql-2被删除)kubectl get pods -l app=mysql# NAME READY STATUS RESTARTS AGE# mysql-0 1/1 Running 0 5m
# PVC不会被删除(数据保护)kubectl get pvc# NAME STATUS VOLUME CAPACITY# data-mysql-0 Bound pvc-abc... 5Gi# data-mysql-1 Bound pvc-def... 5Gi <- 保留# data-mysql-2 Bound pvc-ghi... 5Gi <- 保留
# 扩容回3个副本kubectl scale statefulset mysql --replicas=3
# 新Pod自动绑定原来的PVC,数据恢复!kubectl exec mysql-1 -- mysql -uroot -pMyPass123 -e "SHOW DATABASES;" | grep db# db1 <- 数据还在!总结
本章学习了:
-
存储架构
- Volume/PV/PVC/StorageClass关系
- 静态供给 vs 动态供给
-
存储类型
- Volume:emptyDir、hostPath
- PV:访问模式、回收策略
- PVC:绑定规则
- StorageClass:自动化供给
-
后端存储选择
- hostPath:开发测试
- NFS:小规模生产
- Ceph:企业级
- 云盘:云环境
-
实战经验
- hostPath持久化(单节点)
- NFS动态供给(多节点共享)
- StatefulSet独立存储
生产建议:
⚠️ 存储是有状态应用的生命线
1. 能用云存储就用云存储(省心)2. 自建存储需要专业团队运维3. 生产环境必须: - 定期备份 - 测试恢复流程 - 监控存储容量和性能4. 核心数据使用高可用存储(Ceph/云盘)5. 非核心数据可用NFS风险提示:
⚠️ 自建存储意味着对数据负全责⚠️ 存储故障 = 数据丢失 = 业务灾难⚠️ 优先考虑云存储或托管存储服务第十章:k8s调度 1.介绍什么是k8s调度,为什么重要,介绍基本概念, 了解节点选择机制 实际适用场景与优先级关系 了解容忍与污点 污点的类型与效果 Pod 的容忍方式与配置项 了解调度约束与优先级
2.介绍各种调度的实现方法,策略
3.实战 实验一:使用 nodeAffinity 实现 Pod 定向调度 为节点打标签 部署 Pod 只调度到指定节点
实验二:使用 Taint 与 Toleration 实现节点隔离 给节点添加污点 创建能容忍污点的 Pod 测试结果