3725 字
19 分钟
06.Kubernetes 学习笔记:存储管理与数据持久化

九、存储管理:数据持久化的艺术#

1. 为什么Kubernetes需要存储管理#

1.1 容器数据的”短命”问题#

容器的本质特性是临时的,删除后数据全部丢失。这对于有状态应用(如数据库)是致命的。

演示问题:

Terminal window
# 启动MySQL,写入数据
kubectl run mysql --image=mysql:8.0 --env="MYSQL_ROOT_PASSWORD=pass"
kubectl exec mysql -- mysql -uroot -ppass -e "CREATE DATABASE testdb;"
# 删除Pod
kubectl 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 exist

Kubernetes的存储挑战:

  1. Pod漂移:Pod可能在不同节点重启
  2. 多副本共享:多个Pod需访问同一数据
  3. 生命周期管理:存储生命周期独立于Pod

1.2 存储架构总览#

Kubernetes通过四层架构解决存储问题:

graph TB subgraph "用户层" Pod["Pod<br/>使用存储"] end subgraph "申请层" PVC["PVC<br/>存储申请"] end subgraph "资源层" PV["PV<br/>存储资源"] SC["StorageClass<br/>自动创建PV"] end subgraph "实现层" Backend["后端存储<br/>NFS/Ceph/云盘"] end Pod -->|volumes| PVC PVC -.绑定.-> PV SC -.动态创建.-> PV PV -->|挂载| Backend

四大组件:

组件角色说明
Volume最基础Pod内定义,生命周期绑定Pod
PV存储资源集群级,代表真实存储空间
PVC存储申请用户申请,自动匹配PV
StorageClass自动化动态创建PV,存储分类

静态 vs 动态供给:

静态供给:
管理员手动创建PV → 用户创建PVC → 自动绑定
动态供给:
用户创建PVC(指定StorageClass)→ 自动创建PV → 自动绑定

2. Volume:最基础的存储#

2.1 emptyDir:临时共享存储#

emptyDir 是最简单的Volume,Pod创建时自动创建,Pod删除时数据丢失。

使用场景:

  • 容器间数据共享
  • 临时缓存
  • 计算中间结果

示例:

apiVersion: v1
kind: Pod
metadata:
name: emptydir-demo
spec:
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: v1
kind: Pod
metadata:
name: hostpath-demo
spec:
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: DirectoryOrCreate

hostPath类型:

type说明
DirectoryOrCreate目录不存在则创建
Directory必须存在的目录
File必须存在的文件

3. PersistentVolume(PV):持久化存储资源#

3.1 PV核心概念#

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

特点:

  • 独立于Pod生命周期
  • 集群级资源(无namespace)
  • 代表真实存储空间

PV生命周期:

Available(可用)→ Bound(已绑定)→ Released(已释放)→ Failed(失败)

3.2 PV配置详解#

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-001
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs-storage
nfs:
server: 192.168.100.14
path: /data/nfs/pv-001

3.3 访问模式(AccessModes)#

模式缩写说明适用场景
ReadWriteOnceRWO单节点读写数据库
ReadOnlyManyROX多节点只读静态资源
ReadWriteManyRWX多节点读写共享文件系统

不同存储支持的模式:

存储类型RWOROXRWX
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: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: nfs-storage

4.3 PVC绑定规则#

K8s如何选择PV?

匹配条件(全部满足):
1. accessModes 匹配
2. storage大小满足(PV >= PVC)
3. storageClassName 匹配
优先级:
1. 大小精确匹配
2. 最小满足(PV略大于PVC)

4.4 Pod使用PVC#

apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:8.0
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc

5. StorageClass:自动化存储供应#

5.1 StorageClass是什么#

StorageClass 是存储的”自动售货机”,用户申请存储时自动创建PV。

功能:

  • 定义存储类型和参数
  • 自动创建PV(动态供给)
  • 不同性能等级(SSD/HDD)

5.2 StorageClass配置#

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: nfs-provisioner
parameters:
archiveOnDelete: "false"
reclaimPolicy: Delete
volumeBindingMode: Immediate

关键字段:

字段说明
provisioner存储供应器(如nfs-provisioner)
parameters供应器特定参数
reclaimPolicyPV回收策略
volumeBindingMode绑定模式(Immediate/WaitForFirstConsumer)

5.3 设置默认StorageClass#

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: nfs-provisioner
Terminal window
# 查看默认StorageClass
kubectl get storageclass
# NAME PROVISIONER AGE
# nfs-storage (default) nfs-provisioner 1d

6. 主流后端存储对比#

后端存储是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 选择建议#

graph TD Start[选择存储方案] --> Q1{生产环境?} Q1 -->|否| Dev[开发测试环境] Q1 -->|是| Q2{云环境?} Dev --> hostPath[hostPath<br/>快速简单] Dev --> NFS1[NFS<br/>共享文件] Q2 -->|是| Cloud[云盘<br/>EBS/GCE Disk] Q2 -->|否| Q3{需要RWX?} Q3 -->|是| Q4{规模大?} Q3 -->|否| Local[本地盘<br/>或云盘RWO] Q4 -->|是| Ceph[Ceph/Rook<br/>企业级] Q4 -->|否| NFS2[NFS<br/>小规模]

⚠️ 生产环境建议:

1. 能用云存储就用云存储(省心)
2. 自建存储需要专业运维团队
3. 数据库等核心应用使用高可用存储
4. 定期备份,备份,备份!

7. 实战1:使用hostPath实现持久化#

7.1 实战目标#

使用hostPath验证数据持久化,理解存储的基本原理。

场景: 创建Nginx Pod,使用hostPath存储网页,验证Pod删除后数据是否保留。

7.2 准备工作#

Terminal window
# 在node1节点创建目录
ssh root@192.168.100.21
mkdir -p /data/nginx-html
echo "<h1>Hello from hostPath - v1</h1>" > /data/nginx-html/index.html

7.3 创建使用hostPath的Pod#

hostpath-nginx.yaml:

apiVersion: v1
kind: Pod
metadata:
name: nginx-hostpath
labels:
app: nginx
spec:
# 指定调度到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: v1
kind: Service
metadata:
name: nginx-hostpath
spec:
selector:
app: nginx
ports:
- port: 80
nodePort: 30090
type: NodePort
Terminal window
kubectl apply -f hostpath-nginx.yaml

7.4 测试数据持久化#

Terminal window
# 1. 访问Nginx
curl 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. 删除Pod
kubectl delete pod nginx-hostpath
# 5. 重新创建Pod
kubectl 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漂移问题:

Terminal window
# 删除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:

Terminal window
# 安装NFS服务
yum install -y nfs-utils rpcbind
# 创建共享目录
mkdir -p /data/nfs-storage
chmod 777 /data/nfs-storage
# 配置NFS共享
cat >> /etc/exports << EOF
/data/nfs-storage *(rw,sync,no_root_squash,no_all_squash)
EOF
# 启动NFS服务
systemctl start rpcbind
systemctl start nfs-server
systemctl enable rpcbind
systemctl enable nfs-server
# 刷新NFS配置
exportfs -r
# 查看共享目录
showmount -e localhost
# 输出:/data/nfs-storage *

在所有K8s节点安装NFS客户端:

Terminal window
# 在master、node1、node2上执行
yum install -y nfs-utils
# 测试挂载
mount -t nfs 192.168.100.14:/data/nfs-storage /mnt
ls /mnt
umount /mnt

8.3 部署NFS Provisioner#

创建nfs-provisioner.yaml:

# RBAC权限
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner
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: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-provisioner
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: kube-system
---
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-provisioner
namespace: kube-system
spec:
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
---
# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 设为默认
provisioner: nfs-provisioner
parameters:
archiveOnDelete: "false" # 删除PVC时不归档数据
reclaimPolicy: Delete
volumeBindingMode: Immediate
Terminal window
kubectl apply -f nfs-provisioner.yaml
# 查看Provisioner状态
kubectl get pods -n kube-system -l app=nfs-provisioner
# 查看StorageClass
kubectl get storageclass
# NAME PROVISIONER RECLAIMPOLICY
# nfs-storage (default) nfs-provisioner Delete

8.4 测试动态供给#

创建test-pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-storage
Terminal window
kubectl apply -f test-pvc.yaml
# 查看PVC(自动绑定)
kubectl get pvc test-pvc
# NAME STATUS VOLUME CAPACITY
# test-pvc Bound pvc-abc123-... 1Gi
# 查看自动创建的PV
kubectl 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: v1
kind: Pod
metadata:
name: test-nfs-pod
spec:
containers:
- name: app
image: nginx:1.20
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: test-pvc
Terminal window
kubectl 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.yaml
kubectl exec test-nfs-pod -- cat /usr/share/nginx/html/index.html

8.5 测试回收策略#

Terminal window
# 删除PVC
kubectl 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: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None # Headless Service
selector:
app: mysql
ports:
- port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
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: 5Gi
Terminal window
kubectl apply -f mysql-statefulset.yaml

9.3 验证独立存储#

Terminal window
# 查看Pod
kubectl 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 测试扩缩容#

Terminal window
# 缩容到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 <- 数据还在!

总结#

本章学习了:

  1. 存储架构

    • Volume/PV/PVC/StorageClass关系
    • 静态供给 vs 动态供给
  2. 存储类型

    • Volume:emptyDir、hostPath
    • PV:访问模式、回收策略
    • PVC:绑定规则
    • StorageClass:自动化供给
  3. 后端存储选择

    • hostPath:开发测试
    • NFS:小规模生产
    • Ceph:企业级
    • 云盘:云环境
  4. 实战经验

    • hostPath持久化(单节点)
    • NFS动态供给(多节点共享)
    • StatefulSet独立存储

生产建议:

⚠️ 存储是有状态应用的生命线

1. 能用云存储就用云存储(省心)
2. 自建存储需要专业团队运维
3. 生产环境必须:
- 定期备份
- 测试恢复流程
- 监控存储容量和性能
4. 核心数据使用高可用存储(Ceph/云盘)
5. 非核心数据可用NFS

风险提示:

⚠️ 自建存储意味着对数据负全责
⚠️ 存储故障 = 数据丢失 = 业务灾难
⚠️ 优先考虑云存储或托管存储服务

第十章:k8s调度 1.介绍什么是k8s调度,为什么重要,介绍基本概念, 了解节点选择机制 实际适用场景与优先级关系 了解容忍与污点 污点的类型与效果 Pod 的容忍方式与配置项 了解调度约束与优先级

2.介绍各种调度的实现方法,策略

3.实战 实验一:使用 nodeAffinity 实现 Pod 定向调度 为节点打标签 部署 Pod 只调度到指定节点

实验二:使用 Taint 与 Toleration 实现节点隔离 给节点添加污点 创建能容忍污点的 Pod 测试结果


06.Kubernetes 学习笔记:存储管理与数据持久化
https://dev-null-sec.github.io/posts/06-k8s学习笔记-存储管理与数据持久化/
作者
DevNull
发布于
2024-11-10
许可协议
CC BY-NC-SA 4.0