Kubernetes 存储方案与 NFS 配置
本文介绍 Kubernetes 集群中的持久化存储方案,重点讲解 NFS StorageClass 的部署配置以及动态卷供应的实现。
概述
在 Kubernetes 中,Pod 的生命周期是短暂的,当 Pod 被删除或重启时,容器内的数据会丢失。为了解决这个问题,Kubernetes 提供了持久化存储机制。本文将介绍如何通过 NFS 实现动态存储供应,为应用提供可靠的数据持久化能力。
存储基础概念
PV 与 PVC
- PV(PersistentVolume):集群管理员提供的存储资源,独立于 Pod 生命周期
- PVC(PersistentVolumeClaim):用户对存储的请求,类似于 Pod 消费 Node 资源
- StorageClass:定义存储的"类型",实现动态供应
访问模式
| 模式 | 说明 |
|---|---|
| ReadWriteOnce(RWO) | 单节点读写 |
| ReadOnlyMany(ROX) | 多节点只读 |
| ReadWriteMany(RWX) | 多节点读写 |
| ReadWriteOncePod(RWOP) | 单 Pod 读写(K8s 1.22+) |
回收策略
- Retain:保留数据,PV 不被删除
- Delete:删除 PV 和数据
- Recycle:清理数据后重新可用(已弃用)
NFS 服务端配置
安装 NFS 服务
# Ubuntu/Debian
apt-get install -y nfs-kernel-server nfs-common
# CentOS/RHEL
yum install -y nfs-utils rpcbind
配置共享目录
# 创建共享目录
mkdir -p /data/kubernetes/volumes
chmod 777 /data/kubernetes/volumes
# 配置 exports
cat >> /etc/exports <<EOF
/data/kubernetes/volumes *(rw,sync,no_root_squash,no_subtree_check)
EOF
# 启动服务
exportfs -ra
systemctl enable --now nfs-server
systemctl enable --now rpcbind
验证 NFS 服务
# 本地测试
showmount -e localhost
# 从客户端测试
showmount -e <nfs-server-ip>
部署 NFS Subdir External Provisioner
方式一:Helm 安装(推荐)
添加仓库
helm repo add nfs-subdir-external-provisioner \
https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
创建生产环境配置
# prod-values.yaml
replicaCount: 2
strategyType: Recreate
image:
repository: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner
tag: v4.0.2
pullPolicy: IfNotPresent
storageClass:
create: true
defaultClass: true # 设置为默认 StorageClass
accessModes: ReadWriteMany
reclaimPolicy: Retain # 保留数据
name: nfs-ssd-01
pathPattern: "${.PVC.namespace}/${.PVC.name}"
nfs:
server: 10.10.10.30 # NFS 服务器地址
path: /data/kubernetes/volumes
mountOptions:
- nfsvers=4.1
- rsize=1048576
- wsize=1048576
- hard
- timeo=600
- retrans=2
- noresvport
nodeSelector:
kubernetes.io/os: linux
tolerations: []
安装 Chart
# 创建命名空间
kubectl create namespace nfs
# 安装
helm upgrade --install nfs-subdir-external-provisioner \
nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
-f prod-values.yaml \
--namespace nfs \
--create-namespace
方式二:手动部署
创建 RBAC 权限
# nfs-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: nfs
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
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"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
创建 Deployment
# nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: nfs
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: nfs-ssd-01
- name: NFS_SERVER
value: 10.10.10.30
- name: NFS_PATH
value: /data/kubernetes/volumes
volumes:
- name: nfs-client-root
nfs:
server: 10.10.10.30
path: /data/kubernetes/volumes
创建 StorageClass
# nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-ssd-01
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: nfs-ssd-01
parameters:
archiveOnDelete: "true" # 删除 PVC 时归档数据
mountOptions:
- hard
- nfsvers=4.1
- nolock
- rsize=1048576
- wsize=1048576
应用配置
kubectl apply -f nfs-rbac.yaml
kubectl apply -f nfs-provisioner.yaml
kubectl apply -f nfs-storageclass.yaml
验证存储配置
查看 StorageClass
kubectl get storageclass
# 输出示例
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-ssd-01 nfs-ssd-01 Retain Immediate false 5m
创建测试 PVC
# test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
storageClassName: nfs-ssd-01
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
kubectl apply -f test-pvc.yaml
kubectl get pvc test-pvc
创建测试 Pod
# test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test-container
image: busybox:latest
command: ["/bin/sh", "-c", "touch /mnt/SUCCESS && sleep 3600"]
volumeMounts:
- name: test-volume
mountPath: /mnt
volumes:
- name: test-volume
persistentVolumeClaim:
claimName: test-pvc
kubectl apply -f test-pod.yaml
kubectl get pods test-pod
# 验证文件是否创建
kubectl exec test-pod -- ls /mnt
在应用中使用持久化存储
MySQL 有状态应用示例
# mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
storageClassName: nfs-ssd-01
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
多副本共享存储示例
# nginx-shared.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-shared
spec:
replicas: 3
selector:
matchLabels:
app: nginx-shared
template:
metadata:
labels:
app: nginx-shared
spec:
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: shared-data
mountPath: /usr/share/nginx/html
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: shared-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-pvc
spec:
storageClassName: nfs-ssd-01
accessModes:
- ReadWriteMany # 多节点读写
resources:
requests:
storage: 5Gi
存储管理最佳实践
命名空间隔离
使用 pathPattern 参数按命名空间隔离存储:
storageClass:
pathPattern: "${.PVC.namespace}/${.PVC.name}"
这将在 NFS 服务器上创建如下目录结构:
/data/kubernetes/volumes/
├── default/
│ └── mysql-pvc/
├── nginx/
│ └── nginx-pvc/
└── databases/
└── postgres-pvc/
数据备份策略
# 备份 NFS 数据
rsync -avz /data/kubernetes/volumes/ backup-server:/backup/k8s-volumes/
# 使用快照(如底层存储支持)
# ZFS/Btrfs 快照
zfs snapshot tank/k8s-volumes@$(date +%Y%m%d)
监控存储使用
# 查看 PV 使用情况
kubectl get pv -o custom-columns=NAME:.metadata.name,CAPACITY:.spec.capacity.storage,STATUS:.status.phase,CLAIM:.spec.claimRef.name
# 查看 PVC 使用情况
kubectl get pvc --all-namespaces
故障排查
常见问题
- PVC 处于 Pending 状态
# 检查 StorageClass 是否存在
kubectl get storageclass
# 查看 Provisioner 日志
kubectl logs -n nfs -l app=nfs-subdir-external-provisioner
- Pod 无法挂载卷
# 检查 NFS 服务
showmount -e <nfs-server-ip>
# 检查节点上的 NFS 客户端
apt-get install -y nfs-common
- 权限问题
# 检查 NFS 导出选项
# 确保使用 no_root_squash 或正确的用户映射
其他存储方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| NFS | 简单易用,RWX 支持 | 性能一般,单点故障 | 开发测试,小型集群 |
| Ceph RBD | 高性能,数据冗余 | 部署复杂 | 生产环境数据库 |
| CephFS | RWX 支持,高可用 | 资源占用高 | 共享存储场景 |
| Longhorn | 易用,快照备份 | 性能开销 | 中小型集群 |
| Local PV | 高性能 | 无高可用 | 有状态应用缓存 |
总结
本文介绍了 Kubernetes 中 NFS 存储的配置和使用:
- NFS 服务部署:配置 NFS 服务器和共享目录
- 动态供应:使用 nfs-subdir-external-provisioner 实现动态卷创建
- 应用集成:在 MySQL、Nginx 等应用中使用 PVC
- 最佳实践:命名空间隔离、数据备份、监控告警
NFS 是一种简单实用的存储方案,适合中小型集群和开发测试环境。对于生产环境,建议评估 Ceph、Longhorn 等更可靠的存储解决方案。